多年来,我一直无法得到以下问题的一个像样的答案:为什么一些开发人员如此反对受控异常?我有过无数次的对话,在博客上读过一些东西,读过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,而不是将抛出添加到他们的方法中。我想了解不喜欢受控异常的动机。


当前回答

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

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

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

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

其他回答

我们已经看到了c#首席架构师的一些参考。

下面是Java人员关于何时使用受控异常的另一种观点。他承认了其他人提到的许多负面因素: 有效的异常

《有效的Java异常》一文很好地解释了什么时候使用未检查的异常,什么时候使用已检查的异常。以下是那篇文章中的一些引语,以突出其要点:

应急预案: 一种期望的条件,要求方法提供可根据该方法的预期目的来表达的替代响应。方法的调用者期望这些类型的条件,并有一个策略来处理它们。 错: 一种计划外的情况,阻止方法实现其预期目的,如果不参考方法的内部实现,则无法描述该情况。

(SO不支持表格,所以你可能想从原始页面阅读以下内容…)

Contingency Is considered to be: A part of the design Is expected to happen: Regularly but rarely Who cares about it: The upstream code that invokes the method Examples: Alternative return modes Best Mapping: A checked exception Fault Is considered to be: A nasty surprise Is expected to happen: Never Who cares about it: The people who need to fix the problem Examples: Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers Best Mapping: An unchecked exception

简而言之:

异常是一个API设计问题。——不多不少。

检查异常的参数:

为了理解受控异常为什么不是好事,让我们把问题转过来问:受控异常什么时候或为什么有吸引力,也就是说,为什么你希望编译器强制声明异常?

答案是显而易见的:有时您需要捕获异常,而这只有在被调用的代码为您感兴趣的错误提供了特定的异常类时才有可能。

因此,受控异常的理由是,编译器强迫程序员声明抛出了哪些异常,希望程序员随后也会记录特定的异常类和导致异常的错误。

但在现实中,往往是一个包装。acme只抛出AcmeException而不抛出特定的子类。然后调用者需要处理、声明或重新发出acmeexception信号,但仍然不能确定是发生了AcmeFileNotFoundError还是发生了AcmePermissionDeniedError。

因此,如果您只对AcmeFileNotFoundError感兴趣,解决方案是向ACME程序员提交一个特性请求,并告诉他们实现、声明和记录AcmeException的子类。

所以为什么要麻烦呢?

因此,即使使用受控异常,编译器也不能强迫程序员抛出有用的异常。这仍然只是API质量的问题。

因此,没有受控异常的语言通常情况不会更糟。程序员可能倾向于抛出一般Error类的非特定实例,而不是AcmeException,但如果他们完全关心API质量,他们终究会学会引入AcmeFileNotFoundError。

总的来说,异常的规范和文档与普通方法的规范和文档没有太大的区别。这些也是一个API设计问题,如果程序员忘记实现或导出一个有用的特性,那么API就需要改进,以便您可以有效地使用它。

如果遵循这条推理线,很明显,在Java等语言中非常常见的声明、捕获和重新抛出异常的“麻烦”通常没有什么价值。

同样值得注意的是,Java VM没有检查过的异常——只有Java编译器检查它们,并且在运行时,类文件的异常声明更改后是兼容的。Java虚拟机的安全性并不是通过受控异常来提高的,而是通过编码风格来提高的。

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

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

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

Ok... Checked exceptions are not ideal and have some caveat but they do serve a purpose. When creating an API there are specific cases of failures that are contractual of this API. When in the context of a strongly statically typed language such as Java if one does not use checked exceptions then one must rely on ad-hoc documentation and convention to convey the possibility of error. Doing so removes all benefit that the compiler can bring in handling error and you are left completely to the good will of programmers.

因此,一个人删除了Checked异常,比如在c#中所做的,那么如何以编程和结构的方式传达错误的可能性呢?如何通知客户端代码,这样那样的错误可能发生,必须处理?

在处理受控异常时,我听到了各种可怕的事情,它们被滥用了,这是肯定的,但未受控异常也是如此。我说,等几年,当api被堆叠在很多层的时候,你会乞求某种结构化的方法来传达失败。

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

Many have argued that not being able to load the file was almost always the end of the world for the process and it must die a horrible and painful death. So yeah.. sure ... ok.. you build an API for something and it loads file at some point... I as the user of said API can only respond... "Who the hell are you to decide when my program should crash !" Sure Given the choice where exceptions are gobbled up and leave no trace or the EletroFlabbingChunkFluxManifoldChuggingException with a stack trace deeper than the Marianna trench I would take the latter without a cinch of hesitation, but does this mean that it is the desirable way to deal with exception ? Can we not be somewhere in the middle, where the exception would be recast and wrapped each time it traversed into a new level of abstraction so that it actually means something ?

最后,我看到的大多数争论都是“我不想处理异常,许多人不想处理异常。受控异常迫使我去处理它们,因此我讨厌受控异常。”完全消除这种机制并将其降级到地狱的深渊是愚蠢的,缺乏判断力和远见。

如果我们消除了受控异常,我们也可以消除函数的返回类型,并且总是返回一个“anytype”变量……这样生活就简单多了,不是吗?