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


当前回答

我读了很多关于异常处理的书,即使(大多数时候)我不能真的说我对受控异常的存在感到高兴或悲伤,这是我的看法:在低级代码(IO,网络,OS等)中受控异常,在高级api /应用程序级别中未受控异常。

即使在它们之间没有那么容易划清界限,我发现在同一屋檐下集成几个api /库而不始终包装大量的检查异常是非常烦人/困难的,但另一方面,有时强制捕获一些异常并提供一个在当前上下文中更有意义的不同异常是有用/更好的。

The project I'm working on takes lots of libraries and integrates them under the same API, API which is completely based on unchecked exceptions.This frameworks provides a high-level API which in the beginning was full of checked exceptions and had only several unchecked exceptions(Initialization Exception, ConfigurationException, etc) and I must say was not very friendly. Most of the time you had to catch or re-throw exceptions which you don't know how to handle, or you don't even care(not to be confused with you should ignore exceptions), especially on the client side where a single click could throw 10 possible (checked) exceptions.

The current version(3rd one) uses only unchecked exceptions, and it has a global exception handler which is responsible to handle anything uncaught. The API provides a way to register exception handlers, which will decide if an exception is considered an error(most of the time this is the case) which means log & notify somebody, or it can mean something else - like this exception, AbortException, which means break the current execution thread and don't log any error 'cause it is desired not to. Of course, in order to work out all custom thread must handle the run() method with a try {...} catch(all).

公共无效运行(){

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

如果您使用WorkerService来安排作业(Runnable, Callable, Worker),这是不必要的,它会为您处理一切。

当然,这只是我的个人观点,它可能不是正确的,但对我来说这是一个很好的方法。我将在发布项目后看看我认为对我有好处的东西,对其他人也有好处……:)

其他回答

良好的证明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是对的。

Artima发表了一篇对。net架构师之一Anders Hejlsberg的采访,其中尖锐地讨论了反对受控异常的论点。品性差的人:

The throws clause, at least the way it's implemented in Java, doesn't necessarily force you to handle the exceptions, but if you don't handle them, it forces you to acknowledge precisely which exceptions might pass through. It requires you to either catch declared exceptions or put them in your own throws clause. To work around this requirement, people do ridiculous things. For example, they decorate every method with, "throws Exception." That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn't help anybody.

的确,受控异常一方面增加了程序的健壮性和正确性(你被迫对接口做出正确的声明——方法抛出的异常基本上是一种特殊的返回类型)。另一方面,你面临的问题是,由于异常“冒出来”,当你改变一个方法抛出的异常时,你经常需要改变大量的方法(所有的调用者,以及调用者的调用者,等等)。

Java中的受控异常不能解决后一个问题;c#和VB。我们把孩子和洗澡水一起倒掉。

OOPSLA 2005论文(或相关技术报告)中描述了一种折衷的好方法。

简而言之,它允许你说:方法g(x)像f(x)一样抛出,这意味着g抛出f抛出的所有异常。瞧,没有级联更改问题的受控异常。

虽然这是一篇学术论文,但我还是鼓励你阅读(部分)它,因为它很好地解释了受控异常的优点和缺点。

Robert C. Martin在他的著作《Clean Code》中也不推荐使用受控异常,他认为受控异常违反了开闭原则:

什么价格?受控异常的价格是Open/Closed Principle1侵犯。如果从方法抛出checked异常 在您的代码中,并且catch高于三级,您必须声明 实例之间的每个方法签名中的异常 抓住。这意味着在软件的低级别上的更改可以 在许多更高级别上强制更改签名。变更的模块 必须重建和重新部署,即使他们不关心 改变了。

程序员需要知道一个方法可能抛出的所有异常,以便正确地使用它。因此,仅仅用一些异常来打击他并不一定能帮助一个粗心的程序员避免错误。

微小的好处被繁重的成本所抵消(特别是在较大、不太灵活的代码库中,不断修改接口签名是不切实际的)。

Static analysis can be nice, but truly reliable static analysis often inflexibly demands strict work from the programmer. There is a cost-benefit calculation, and the bar needs to be set high for a check that leads to a compile time error. It would be more helpful if the IDE took on the role of communicating which exceptions a method may throw (including which are unavoidable). Although perhaps it would not be as reliable without forced exception declarations, most exceptions would still be declared in documentation, and the reliability of an IDE warning is not so crucial.