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


当前回答

我不想重复所有(许多)反对受控异常的原因,我只选一个。我已经记不清写了多少次这段代码了:

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

99%的情况下我对此无能为力。最后,块进行必要的清理(或者至少它们应该这样做)。

我也记不清有多少次看到这样的场景:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

为什么?因为有人要处理这件事,而且很懒。错了吗?确定。会发生吗?绝对的。如果这是一个未检查的异常呢?应用程序就会死掉(这比吞下一个异常更好)。

然后我们有一些令人愤怒的代码,它使用异常作为一种流控制形式,就像java.text.Format所做的那样。Bzzzt。错了。用户在表单的数字字段中输入“abc”也不例外。

好吧,我想这是三个原因。

其他回答

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.

这个问题

我所看到的异常处理机制最糟糕的问题是它引入了大规模的代码复制!让我们诚实地说:在大多数项目中,在95%的时间里,开发人员真正需要做的就是以某种方式与用户沟通(在某些情况下,也需要与开发团队沟通,例如通过发送带有堆栈跟踪的电子邮件)。因此,通常在处理异常的每个地方都使用相同的代码行/块。

让我们假设我们在每个catch块中对某种类型的检查异常做简单的日志记录:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

如果这是一个常见的异常,那么在更大的代码库中甚至可能有数百个这样的try-catch块。现在让我们假设我们需要引入基于弹出对话框的异常处理,而不是控制台日志记录,或者开始额外向开发团队发送电子邮件。

等一下……我们真的要在代码中编辑这几百个位置吗?你明白我的意思:-)。

解决方案

为了解决这个问题,我们引入了异常处理程序的概念(我将进一步将其称为EH)来集中异常处理。对于每个需要处理异常的类,依赖注入框架都会注入一个异常处理程序实例。所以异常处理的典型模式现在看起来像这样:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

现在要定制我们的异常处理,我们只需要更改一个地方的代码(EH代码)。

当然,对于更复杂的情况,我们可以实现eh的几个子类,并利用DI框架提供给我们的特性。通过改变我们的DI框架配置,我们可以轻松地在全局切换EH实现,或者为有特殊异常处理需求的类提供特定的EH实现(例如使用Guice @Named注释)。

这样我们就可以区分应用程序的开发版本和发布版本中的异常处理行为。开发——记录错误并停止应用程序,用更详细的信息刺激记录错误并让应用程序继续执行)。

最后一点

最后但并非最不重要的是,似乎可以通过将异常“向上传递”直到它们到达某个顶级异常处理类来获得同样的集中化。但这会导致代码和方法签名的混乱,并引入本线程中其他人提到的维护问题。

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

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

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

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

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.

我认为这是一个很好的问题,没有任何争论。我认为第三方库(通常)应该抛出未经检查的异常。这意味着你可以隔离你对库的依赖(也就是说,你既不用重新抛出它们的异常,也不用抛出异常——这通常是不好的做法)。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创建枚举且没有该名称的枚举时,将抛出非法参数异常。这并不一定是开发者的错误!