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


当前回答

In my opinion the checked exceptions are a very fine concept. Unfortunately the most programmers who have worked together we me have another opinion so that the projects have a lot of wrong used exception handling. I have seen that the most programmers create one (only one) exception class, a subclass of RuntimeException. That contains a message, sometimes a multi language key. I have no chance to argument against this. I have the impression that I talk to a wall when I explain them what anti patterns are, what a contracts of a methods are... I'm a little bit disappointed.

但是今天,对所有事情都有一个通用运行时异常的概念显然是一种反模式。他们使用它来检查用户输入。该异常被抛出,以便用户对话框可以从中产生错误消息。但并不是每个方法的调用者都是对话!通过抛出运行时异常,方法的契约被更改但没有声明,因为它不是一个受控异常。

希望他们今天学到了一些东西,并将在另一个地方进行检查(这是有用和必要的)。只使用受控异常不能解决问题,但受控异常会向程序员发出信号,表明他实现了错误的东西。

其他回答

试图解决这个尚未解决的问题:

如果抛出RuntimeException子类而不是Exception子类,那么您如何知道应该捕获什么?

恕我直言,这个问题包含似是而非的推理。仅仅因为API告诉你它抛出了什么并不意味着你在所有情况下都以相同的方式处理它。 换句话说,您需要捕获的异常取决于您使用抛出异常的组件的上下文。

例如:

如果我正在为数据库编写连接测试程序,或者编写检查用户输入XPath的有效性的程序,那么我可能希望捕获并报告操作抛出的所有已检查和未检查的异常。

然而,如果我正在编写一个处理引擎,我可能会以与NPE相同的方式处理XPathException (checked):我将让它运行到工作线程的顶部,跳过该批处理的其余部分,记录问题(或将其发送给支持部门进行诊断),并为用户联系支持人员留下反馈。

SNR

首先,检查异常降低了代码的“信噪比”。Anders Hejlsberg也谈到了命令式编程和声明式编程,这是一个类似的概念。不管怎样,考虑下面的代码片段:

在Java中从非UI线程更新UI:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

在c#中从非UI线程更新UI:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

这对我来说清楚多了。当您开始在Swing中做越来越多的UI工作时,检查异常开始变得非常烦人和无用。

监狱打破

即使要实现最基本的实现,比如Java的List接口,作为契约式设计工具的受控异常也行不通。考虑一个由数据库、文件系统或任何其他抛出受控异常的实现支持的列表。唯一可能的实现是捕获已检查异常,并将其作为未检查异常重新抛出:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

现在你要问所有这些代码的意义是什么?被检查的异常只是增加了噪音,异常被捕获但没有处理,契约式设计(就被检查的异常而言)已经失效。

结论

捕获异常与处理异常是不同的。 受控异常会给代码添加噪音。 没有它们,异常处理在c#中也能很好地工作。

我之前在博客上写过。

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”变量……这样生活就简单多了,不是吗?

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

总而言之:

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

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

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

至于根本问题:

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

我认为这是一个很好的问题,没有任何争论。我认为第三方库(通常)应该抛出未经检查的异常。这意味着你可以隔离你对库的依赖(也就是说,你既不用重新抛出它们的异常,也不用抛出异常——这通常是不好的做法)。Spring的DAO层就是一个很好的例子。

On the other hand, exceptions from the core Java API should in general be checked if they could ever be handled. Take FileNotFoundException or (my favourite) InterruptedException. These conditions should almost always be handled specifically (i.e. your reaction to an InterruptedException is not the same as your reaction to an IllegalArgumentException). The fact that your exceptions are checked forces developers to think about whether a condition is handle-able or not. (That said, I've rarely seen InterruptedException handled properly!)

还有一件事——RuntimeException并不总是“开发人员出错的地方”。当您尝试使用valueOf创建枚举且没有该名称的枚举时,将抛出非法参数异常。这并不一定是开发者的错误!