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


当前回答

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

其他回答

简而言之:

异常是一个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中。

这并不是反对受控异常的纯概念,但是Java用于受控异常的类层次结构是一个畸形秀。我们总是简单地称这些东西为“异常”——这是正确的,因为语言规范也这样称呼它们——但是异常在类型系统中是如何命名和表示的呢?

By the class Exception one imagines? Well no, because Exceptions are exceptions, and likewise exceptions are Exceptions, except for those exceptions that are not Exceptions, because other exceptions are actually Errors, which are the other kind of exception, a kind of extra-exceptional exception that should never happen except when it does, and which you should never catch except sometimes you have to. Except that's not all because you can also define other exceptions that are neither Exceptions nor Errors but merely Throwable exceptions.

哪些是“已检查”异常?可抛出异常是受控异常,除非它们也是错误,是未检查的异常,然后是异常,也是可抛出异常,是受控异常的主要类型,除了有一个例外,那就是if它们也是runtimeexception,因为那是另一种未检查的异常。

What are RuntimeExceptions for? Well just like the name implies, they're exceptions, like all Exceptions, and they happen at run-time, like all exceptions actually, except that RuntimeExceptions are exceptional compared to other run-time Exceptions because they aren't supposed to happen except when you make some silly error, although RuntimeExceptions are never Errors, so they're for things that are exceptionally erroneous but which aren't actually Errors. Except for RuntimeErrorException, which really is a RuntimeException for Errors. But aren't all exceptions supposed to represent erroneous circumstances anyway? Yes, all of them. Except for ThreadDeath, an exceptionally unexceptional exception, as the documentation explains that it's a "normal occurrence" and that that's why they made it a type of Error.

Anyway, since we're dividing all exceptions down the middle into Errors (which are for exceptional execution exceptions, so unchecked) and Exceptions (which are for less exceptional execution errors, so checked except when they're not), we now need two different kinds of each of several exceptions. So we need IllegalAccessError and IllegalAccessException, and InstantiationError and InstantiationException, and NoSuchFieldError and NoSuchFieldException, and NoSuchMethodError and NoSuchMethodException, and ZipError and ZipException.

只不过,即使检查了异常,也总有(相当简单的)方法可以欺骗编译器,在不检查的情况下抛出异常。如果你这样做,你可能会得到一个不确定的throwableexception,除非在其他情况下,它可能抛出一个意外的dexception,或一个未知的异常(与未知的错误无关,只针对“严重的异常”),或一个ExecutionException,或一个InvocationTargetException,或一个ExceptionInInitializerError。

哦,我们一定不能忘记Java 8时髦的新UncheckedIOException,这是一个RuntimeException异常,设计用来通过包装由I/O错误(不会引起IOError异常,尽管也存在)引起的已检查的IOException异常来抛出异常检查的概念,这些异常异常难以处理,因此您需要它们不被检查。

由于Java !

在过去的三年中,我一直在与一些开发人员一起开发相对复杂的应用程序。我们有一个代码库,它经常使用检查异常,并进行适当的错误处理,而其他一些代码库则没有。

So far, I have it found easier to work with the code base with Checked Exceptions. When I am using someone else's API, it is nice that I can see exactly what kind of error conditions I can expect when I call the code and handle them properly, either by logging, displaying or ignoring (Yes, there is valid cases for ignoring exceptions, such as a ClassLoader implementation). That gives the code I am writing an opportunity to recover. All runtime exceptions I propagate up until they are cached and handled with some generic error handling code. When I find a checked exception that I don't really want to handle at a specific level, or that I consider a programming logic error, then I wrap it into a RuntimeException and let it bubble up. Never, ever swallow an exception without a good reason (and good reasons for doing this are rather scarce)

当我使用没有检查异常的代码库时,它使我在调用函数时很难预先知道我期望什么,这可能会严重破坏一些东西。

当然,这完全取决于开发者的偏好和技能。编程和错误处理的两种方式可能同样有效(或无效),所以我不会说只有一种方法。

总而言之,我发现使用受控异常更容易,特别是在有很多开发人员的大型项目中。

我在c2.com上写的文章与原来的CheckedExceptionsAreIncompatibleWithVisitorPattern基本没有变化

总而言之:

访问者模式(Visitor Pattern)及其亲属是一类接口,其中间接调用者和接口实现都知道异常,但接口和直接调用者组成了一个不知道异常的库。

CheckedExceptions的基本假设是,所有声明的异常都可以从调用带有该声明的方法的任何点抛出。VisitorPattern揭示了这个假设是错误的。

在这种情况下,受控异常的最终结果是大量无用的代码,本质上是在运行时删除编译器的受控异常约束。

至于根本问题:

我的总体想法是,顶级处理程序需要解释异常并显示适当的错误消息。我几乎总是看到IO异常、通信异常(由于某些原因api可以区分)或任务致命错误(程序错误或备份服务器上的严重问题),所以如果我们允许对严重的服务器问题进行堆栈跟踪,这应该不会太难。