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


当前回答

这个问题

我所看到的异常处理机制最糟糕的问题是它引入了大规模的代码复制!让我们诚实地说:在大多数项目中,在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注释)。

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

最后一点

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

其他回答

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

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

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

例如:

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

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

Anders在软件工程电台的第97集中谈到了受控异常的陷阱,以及他为什么把它们排除在c#之外。

这并不是反对受控异常的纯概念,但是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 !

没有人提到的一件重要的事情是它如何干扰接口和lambda表达式。

假设您定义了MyAppException扩展异常。它是由应用程序抛出的所有异常继承的顶级异常。在某些地方,您不想对特定的异常做出反应,您希望调用者解决它,因此您声明throws MyAppException。

一切看起来都很好,直到你想使用别人的界面。显然它们没有声明抛出MyAppException的意图,所以编译器甚至不允许你调用在那里声明抛出MyAppException的方法。这对于java.util.function来说尤其痛苦。

但是,如果您的异常扩展了RuntimeException,那么接口就不会有问题。如果愿意,可以在JavaDoc中提到异常。但除此之外,它只是无声地穿过任何东西。当然,这意味着它可以终止你的申请。但是在很多企业软件中都有异常处理层,未检查的异常可以省去很多麻烦。

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

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

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

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

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

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

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