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


当前回答

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.

其他回答

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

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

我最初同意你的观点,因为我一直支持受控异常,并开始思考为什么我不喜欢在. net中没有受控异常。但后来我意识到我并不喜欢受控异常。

回答您的问题,是的,我喜欢我的程序显示堆栈跟踪,最好是非常难看的跟踪。我希望应用程序爆发成一堆您希望看到的最糟糕的错误消息。

原因是,如果出现这种情况,我必须修复它,而且必须马上修复。我想马上知道有什么问题。

您实际处理了多少次异常?我说的不是捕获异常——我说的是处理异常?这样写太简单了:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

我知道你可能会说这是一种糟糕的实践,“答案”是做一些异常(让我猜猜,记录它?),但在现实世界(tm)中,大多数程序员就是不这样做。

所以,是的,我不想捕捉异常,如果我没有必要这样做,我希望我的程序在我搞砸的时候爆炸。默默的失败是最糟糕的结果。

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

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

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

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

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

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

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

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

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

下面是反对受控异常的一个论点(来自joelonsoftware.com):

The reasoning is that I consider exceptions to be no better than "goto's", considered harmful since the 1960s, in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto's: They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn't reveal potential bugs. They create too many possible exit points for a function. To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don't catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn't think about.