多年来,我一直无法得到以下问题的一个像样的答案:为什么一些开发人员如此反对受控异常?我有过无数次的对话,在博客上读过一些东西,读过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中提到异常。但除此之外,它只是无声地穿过任何东西。当然,这意味着它可以终止你的申请。但是在很多企业软件中都有异常处理层,未检查的异常可以省去很多麻烦。

其他回答

《有效的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

受控异常的一个问题是,如果一个接口的一个实现使用了它,异常通常会附加到该接口的方法上。

受控异常的另一个问题是它们容易被滥用。java.sql就是一个完美的例子。连接的close()方法。它可以抛出一个SQLException,即使您已经显式地声明您已经完成了连接。close()可能传达了哪些您所关心的信息?

通常,当我关闭()一个连接*时,它看起来像这样:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

另外,不要让我开始各种解析方法和NumberFormatException... .NET的TryParse,它不抛出异常,使用起来要容易得多,以至于不得不回到Java(我工作的地方同时使用Java和c#)。

*作为额外的注释,PooledConnection的connection .close()甚至不关闭连接,但您仍然必须捕获SQLException,因为它是一个已检查异常。

异常类

当谈到异常时,我总是会参考Eric Lippert的恼人异常博客文章。他将例外情况分为以下几类:

Fatal - These exceptions are not your fault: you cannot prevent then, and you cannot sensibly handle them. For example, OutOfMemoryError or ThreadAbortException. Boneheaded - These exceptions are your fault: you should have prevented them, and they represent bugs in your code. For example, ArrayIndexOutOfBoundsException, NullPointerException or any IllegalArgumentException. Vexing - These exceptions are not exceptional, not your fault, you cannot prevent them, but you'll have to deal with them. They are often the result of an unfortunate design decision, such as throwing NumberFormatException from Integer.parseInt instead of providing an Integer.tryParseInt method that returns a boolean false on parse failure. Exogenous - These exceptions are usually exceptional, not your fault, you cannot (reasonably) prevent them, but you must handle them. For example, FileNotFoundException.

API用户:

不能处理致命或愚蠢的异常。 应该处理令人烦恼的异常,但它们不应该出现在理想的API中。 必须处理外生异常。

已检查的异常

API用户必须处理特定的异常,这是调用方和被调用方之间的方法契约的一部分。契约指定了被调用方期望的参数的数量和类型,调用方期望的返回值类型,以及调用方期望处理的异常。

由于API中不应该存在令人烦恼的异常,因此只有这些外生异常必须作为方法契约的一部分进行检查。相对较少的异常是外生的,因此任何API都应该有相对较少的受控异常。

受控异常是必须处理的异常。处理异常就像吞下它一样简单。在那里!异常被处理。时期。如果开发者想这样处理,没问题。但他不能忽视这个例外,已经得到了警告。

API的问题

但是任何检查了令人烦恼和致命异常的API(例如JCL)都会给API用户带来不必要的压力。必须处理这样的异常,但是要么异常太常见,以至于一开始就不应该是异常,要么在处理它时什么都做不了。这导致Java开发人员讨厌受控异常。

此外,许多api没有适当的异常类层次结构,导致各种非外生异常原因都由单个受控异常类表示(例如IOException)。这也导致Java开发人员讨厌受控异常。

结论

外生异常指的是那些不是您的错、无法预防、并且应该处理的异常。这些构成了所有可能引发的异常的一个小子集。api应该只检查外生异常,所有其他异常都不检查。这将使API更好,给API用户带来更少的压力,从而减少捕获所有、吞咽或重新抛出未经检查的异常的需要。

所以不要讨厌Java和它的受控异常。相反,讨厌那些过度使用受控异常的api。

良好的证明Checked Exception是不需要的:

A lot of framework that does some work for Java. Like Spring that wraps JDBC exception to unchecked exceptions, throwing messages to the log Lot of languages that came after java, even on top on java platform - they do not use them Checked exceptions, it is kind prediction about how the client would use the code that throws an exception. But a developer who writes this code would never know about the system and business that client of code is working in. As an example Interfcace methods that force to throw checked exception. There are 100 implementation over the system, 50 or even 90 of implementations do not throw this exception, but the client still must to catch this exception if he user reference to that interface. Those 50 or 90 implementations tend to handle those exceptions inside themself, putting exception to the log (and this is good behavior for them). What we should do with that? I would better have some background logic that would do all that job - sending message to the log. And If I, as a client of code, would feel I need handle the exception - I will do it. I may forget about it, right - but if I use TDD, all my steps are covered and I know what I want. Another example when I'm working with I/O in java, it forces me to check all exception, if file does not exists? what I should do with that? If it does not exists, the system would not go to the next step. The client of this method, would not get expected content from that file - he can handle Runtime Exception, otherwise I should first check Checked Exception, put a message to log, then throw exception up out form the method. No...no - I would better do it automatically with RuntimeEception, that does it / lits up automatically. There is no any sense to handle it manually - I would be happy I saw an error message in the log (AOP can help with that.. something that fixes java). If, eventually, I deice that system should shows pop-up message to the end user - I will show it, not a problem.

我很高兴java能让我选择使用什么,当使用核心库时,比如I/O。Like提供了相同类的两个副本——一个用RuntimeEception包装。然后我们可以比较人们会使用什么。但是现在,很多人会选择java或其他语言之上的框架。比如Scala, JRuby等等。许多人相信SUN是对的。

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