Java Unchecked Exceptions — The Controversy

2023-12-27 23:13:27

由于Java编程语言不需要方法来捕获或指定未检查的异常(RuntimeException、Error及其子类),因此程序员可能会试图编写只抛出未检查异常的代码,或者使其所有异常子类都继承自RuntimeException。这两种快捷方式都允许程序员编写代码,而不必费心处理编译器错误,也不必费力指定或捕获任何异常。尽管这对程序员来说似乎很方便,但它回避了捕获或指定需求的意图,并可能会给使用您的类的其他人带来问题。
为什么设计人员决定强制方法指定可以在其范围内引发的所有未捕获的检查异常?方法可以引发的任何异常都是该方法的公共编程接口的一部分。调用方法的人必须知道方法可以抛出的异常,以便他们可以决定如何处理它们。这些异常与其参数和返回值一样,也是该方法编程接口的一部分。
下一个问题可能是:“如果记录方法的API(包括它可以引发的异常)非常好,为什么不也指定运行时异常?”运行时异常表示编程问题导致的问题,因此,不能合理地期望API客户端代码从中恢复或以任何方式处理它们。这类问题包括算术异常,例如被零除;指针异常,例如试图通过空引用访问对象;以及索引异常,例如试图通过太大或太小的索引访问数组元素。

运行时异常可以发生在程序中的任何位置,在典型的程序中,它们可能非常多。必须在每个方法声明中添加运行时异常会降低程序的清晰度。因此,编译器不需要捕获或指定运行时异常(尽管可以)。
抛出RuntimeException是常见做法的一种情况是用户错误地调用方法。例如,方法可以检查其参数之一是否不正确地为null。如果参数为null,该方法可能会引发NullPointerException,这是一个未检查的异常。
一般来说,不要仅仅因为您不想为指定方法可以引发的异常而烦恼,就抛出RuntimeException或创建RuntimeException的子类。
下面是底线准则:如果可以合理地期望客户端从异常中恢复,则将其设置为检查异常。如果客户端无法执行任何操作来从异常中恢复,请将其设置为未检查的异常。

一、Exceptions的优势

既然您知道了什么是异常以及如何使用它们,现在是时候学习在程序中使用异常的好处了。

1、优势1:将错误处理代码与“常规”代码分离

异常提供了一种方法,可以将异常情况发生时要做什么的细节与程序的主逻辑分离开来。在传统编程中,错误检测、报告和处理通常会导致混乱的意大利面条代码。例如,考虑这里的伪代码方法,该方法将整个文件读入内存。

readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}

乍一看,该函数似乎足够简单,但它忽略了所有以下潜在错误:

  • 如果无法打开文件,会发生什么情况?
  • 如果无法确定文件的长度,会发生什么情况?
  • 如果无法分配足够的内存,会发生什么情况?
  • 如果读取失败会发生什么情况?
  • 如果无法关闭文件,会发生什么情况?

要处理这种情况,readFile函数必须有更多的代码来执行错误检测、报告和处理。下面是函数的外观示例。

errorCodeType readFile {
    initialize errorCode = 0;
    
    open the file;
    if (theFileIsOpen) {
        determine the length of the file;
        if (gotTheFileLength) {
            allocate that much memory;
            if (gotEnoughMemory) {
                read the file into memory;
                if (readFailed) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        close the file;
        if (theFileDidntClose && errorCode == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}

这里有太多的错误检测、报告和返回,以至于最初的七行代码都丢失在混乱中。更糟糕的是,代码的逻辑流也丢失了,因此很难判断代码是否在做正确的事情:如果函数未能分配足够的内存,文件是否真的被关闭?当您在编写方法三个月后修改它时,更难确保代码继续做正确的事情。许多程序员通过简单地忽略它来解决这个问题——当他们的程序崩溃时会报告错误。
异常使您能够编写代码的主流,并在其他地方处理异常情况。如果readFile函数使用异常而不是传统的错误管理技术,则它看起来更像下面这样。

readFile {
    try {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;
    } catch (fileOpenFailed) {
       doSomething;
    } catch (sizeDeterminationFailed) {
        doSomething;
    } catch (memoryAllocationFailed) {
        doSomething;
    } catch (readFailed) {
        doSomething;
    } catch (fileCloseFailed) {
        doSomething;
    }
}

请注意,异常不会使您节省检测、报告和处理错误的工作,但它们确实有助于您更有效地组织工作。

2、优势2:将错误向上传播到调用堆栈

异常的第二个优点是能够将错误报告传播到方法的调用堆栈中。假设readFile方法是主程序进行的一系列嵌套方法调用中的第四个方法:method1调用method2,method2调用method3,method3最终调用readFile。

method1 {
    call method2;
}

method2 {
    call method3;
}

method3 {
    call readFile;
}

还假设method1是唯一对readFile中可能发生的错误感兴趣的方法。传统的错误通知技术强制method2和method3将readFile返回的错误代码向上传播到调用堆栈,直到错误代码最终到达method1——唯一对它们感兴趣的方法。

method1 {
    errorCodeType error;
    error = call method2;
    if (error)
        doErrorProcessing;
    else
        proceed;
}

errorCodeType method2 {
    errorCodeType error;
    error = call method3;
    if (error)
        return error;
    else
        proceed;
}

errorCodeType method3 {
    errorCodeType error;
    error = call readFile;
    if (error)
        return error;
    else
        proceed;
}

文章来源:https://blog.csdn.net/leesinbad/article/details/135252665
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。