多年来,我一直无法得到以下问题的一个像样的答案:为什么一些开发人员如此反对受控异常?我有过无数次的对话,在博客上读过一些东西,读过Bruce Eckel说的话(我看到的第一个站出来反对他们的人)。

我目前正在编写一些新代码,并非常注意如何处理异常。我试图了解那些“我们不喜欢受控异常”的人的观点,但我仍然看不出来。

我的每一次谈话都以同样的问题结束。让我把它建立起来:

一般来说(从Java的设计方式来看),

Error is for things that should never be caught (VM has a peanut allergy and someone dropped a jar of peanuts on it) RuntimeException is for things that the programmer did wrong (programmer walked off the end of an array) Exception (except RuntimeException) is for things that are out of the programmer's control (disk fills up while writing to the file system, file handle limit for the process has been reached and you cannot open any more files) Throwable is simply the parent of all of the exception types.

我听到的一个常见的说法是,如果发生了异常,那么所有开发人员要做的就是退出程序。

我听到的另一个常见论点是受控异常使得重构代码更加困难。

对于“我要做的就是退出”的参数,我说即使你退出了,你也需要显示一个合理的错误消息。如果你只是在处理错误上押注,那么当程序在没有明确说明原因的情况下退出时,你的用户不会太高兴。

对于“它使重构变得困难”的人群来说,这表明没有选择适当的抽象级别。与其声明方法抛出IOException,不如将IOException转换为更适合当前情况的异常。

我对用catch(Exception)(或在某些情况下用catch(Throwable)包装Main没有问题,以确保程序可以优雅地退出-但我总是捕获我需要的特定异常。这样做至少可以显示适当的错误消息。

人们从来不会回答的问题是:

如果抛出RuntimeException 子类代替异常 子类,你怎么知道 你应该去抓?

如果答案是catch Exception,那么您也在以与系统异常相同的方式处理程序员错误。在我看来这是不对的。

如果你捕获Throwable,那么你是在以同样的方式处理系统异常和虚拟机错误(等等)。在我看来这是不对的。

如果答案是您只捕获您知道抛出的异常,那么您如何知道抛出了哪些异常呢?当程序员X抛出一个新的异常而忘记捕获它时会发生什么?这对我来说似乎很危险。

我认为显示堆栈跟踪的程序是错误的。不喜欢受控异常的人不会有这种感觉吗?

所以,如果你不喜欢受控异常,你能解释一下为什么不,并回答没有得到回答的问题吗?

我不是在寻求什么时候使用这两种模型的建议,我想知道的是为什么人们从RuntimeException扩展,因为他们不喜欢从Exception扩展,和/或为什么他们捕获一个异常然后重新抛出一个RuntimeException,而不是将抛出添加到他们的方法中。我想了解不喜欢受控异常的动机。


当前回答

试图解决这个尚未解决的问题:

如果抛出RuntimeException子类而不是Exception子类,那么您如何知道应该捕获什么?

恕我直言,这个问题包含似是而非的推理。仅仅因为API告诉你它抛出了什么并不意味着你在所有情况下都以相同的方式处理它。 换句话说,您需要捕获的异常取决于您使用抛出异常的组件的上下文。

例如:

如果我正在为数据库编写连接测试程序,或者编写检查用户输入XPath的有效性的程序,那么我可能希望捕获并报告操作抛出的所有已检查和未检查的异常。

然而,如果我正在编写一个处理引擎,我可能会以与NPE相同的方式处理XPathException (checked):我将让它运行到工作线程的顶部,跳过该批处理的其余部分,记录问题(或将其发送给支持部门进行诊断),并为用户联系支持人员留下反馈。

其他回答

这篇文章是我读过的关于Java异常处理的最好的文章。

它更倾向于未检查的异常,而不是已检查的异常,但这个选择的解释非常透彻,并基于强有力的论据。

我不想在这里引用太多的文章内容(最好是整体阅读),但它涵盖了这个线程中未检查异常倡导者的大部分论点。尤其是这个论点(似乎很受欢迎):

以异常在API层底部某处抛出的情况为例,因为没有人知道这个错误甚至可能发生,即使它是一种非常合理的错误类型,当调用代码抛出它时(例如FileNotFoundException而不是VogonsTrashingEarthExcept…)在这种情况下,我们是否处理它并不重要,因为没有任何东西可以处理它)。

作者“回应”:

It is absolutely incorrect to assume that all runtime exceptions should not be caught and allowed to propagate to the very "top" of the application. (...) For every exceptional condition that is required to be handled distinctly - by the system/business requirements - programmers must decide where to catch it and what to do once the condition is caught. This must be done strictly according to the actual needs of the application, not based on a compiler alert. All other errors must be allowed to freely propagate to the topmost handler where they would be logged and a graceful (perhaps, termination) action will be taken.

主要的思想或文章是:

当涉及到软件中的错误处理时,唯一安全且正确的假设是,存在的每个子程序或模块都可能发生故障!

因此,如果“没有人知道这个错误甚至可能发生”,那么这个项目就有问题了。像作者建议的那样,这种异常至少应该由最通用的异常处理程序来处理(例如,处理所有没有更特定的处理程序处理的异常)。

很遗憾,似乎没有多少人发现这篇伟大的文章:-(。我衷心建议每一个犹豫哪种方法更好的人花点时间阅读它。

Anders在软件工程电台的第97集中谈到了受控异常的陷阱,以及他为什么把它们排除在c#之外。

正如人们已经说过的,Java字节码中不存在受控异常。它们只是一种编译器机制,与其他语法检查没有什么不同。我看到很多受控异常,就像我看到编译器抱怨一个冗余的条件:if(true) {a;b;}。这很有帮助,但我可能是故意这么做的,所以我忽略你的警告。

事实是,如果你强制执行受控异常,你将无法强迫每个程序员“做正确的事情”,而其他人现在都是附带损害,他们只是因为你制定的规则而讨厌你。

修复坏程序!不要试图修改语言来阻止它们!对于大多数人来说,“对异常做一些事情”实际上只是告诉用户它。我也可以告诉用户一个未检查的异常,所以不要让您的已检查异常类出现在我的API中。

没有人提到的一件重要的事情是它如何干扰接口和lambda表达式。

假设您定义了MyAppException扩展异常。它是由应用程序抛出的所有异常继承的顶级异常。在某些地方,您不想对特定的异常做出反应,您希望调用者解决它,因此您声明throws MyAppException。

一切看起来都很好,直到你想使用别人的界面。显然它们没有声明抛出MyAppException的意图,所以编译器甚至不允许你调用在那里声明抛出MyAppException的方法。这对于java.util.function来说尤其痛苦。

但是,如果您的异常扩展了RuntimeException,那么接口就不会有问题。如果愿意,可以在JavaDoc中提到异常。但除此之外,它只是无声地穿过任何东西。当然,这意味着它可以终止你的申请。但是在很多企业软件中都有异常处理层,未检查的异常可以省去很多麻烦。

尽管阅读了整页,我仍然找不到一个反对受控异常的合理论点。相反,大多数人都在谈论糟糕的API设计,无论是在一些Java类中还是在他们自己的类中。

这个功能唯一令人讨厌的地方就是原型设计。这可以通过向语言中添加一些机制来解决(例如,一些@supresscheckedexceptions注释)。但是对于常规编程,我认为受控异常是一件好事。