多年来,我一直无法得到以下问题的一个像样的答案:为什么一些开发人员如此反对受控异常?我有过无数次的对话,在博客上读过一些东西,读过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,而不是将抛出添加到他们的方法中。我想了解不喜欢受控异常的动机。
受控异常最初的形式是试图处理意外事件,而不是失败。值得称赞的目标是突出特定的可预测点(无法连接,文件未找到等)并确保开发者能够处理这些问题。
最初的概念中从未包括的是强制宣布大量系统性和不可恢复的故障。这些失败不能被声明为受控异常。
在代码中失败通常是可能的,EJB、web和Swing/AWT容器已经通过提供最外层的“失败请求”异常处理程序来满足这一点。最基本的正确策略是回滚事务并返回错误。
关键的一点是,运行时异常和受控异常在功能上是等价的。检查异常所能做的处理或恢复是运行时异常所不能做的。
反对“受控”异常的最大争论是大多数异常无法修复。简单的事实是,我们并不拥有发生故障的代码/子系统。我们看不到实现,我们不负责它,也不能修复它。
如果我们的应用程序不是一个数据库..我们不应该试图修复DB。这违反了封装原则。
特别有问题的是JDBC (SQLException)和用于EJB的RMI (RemoteException)。而不是根据最初的“受控异常”概念确定可修复的偶发事件,这些强制普遍存在的系统可靠性问题,实际上是不可修复的,被广泛宣布。
Java设计中的另一个严重缺陷是异常处理应该正确地放在尽可能高的“业务”或“请求”级别。这里的原则是“早扔晚接”。受控异常做的很少,但会阻碍这一点。
我们在Java中有一个明显的问题,即需要数千个不做任何事情的try-catch块,其中很大一部分(40%以上)被错误编码。这些方法几乎都没有实现任何真正的处理或可靠性,但会增加大量的编码开销。
最后,“受控异常”与FP函数式编程几乎不兼容。
他们对“立即处理”的坚持与“延迟捕获”异常处理最佳实践和任何抽象循环/或控制流的FP结构都不一致。
许多人谈论“处理”受控异常,但他们是在胡说八道。在失败后继续使用null、不完整或不正确的数据来假装成功没有处理任何事情。这是最低级的工程/可靠性渎职。
干净利落的失败,是处理异常最基本的正确策略。回滚事务、记录错误和向用户报告“失败”响应是良好的实践——最重要的是,防止不正确的业务数据提交到数据库。
在业务、子系统或请求级别上,异常处理的其他策略是“重试”、“重新连接”或“跳过”。所有这些都是通用的可靠性策略,并且在运行时异常时工作得更好。
最后,失败比使用不正确的数据运行要好得多。继续将导致次要错误,远离原始原因,更难调试;或者最终将导致提交错误的数据。有人会因此被解雇。
看到的:
——http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/
在过去的三年中,我一直在与一些开发人员一起开发相对复杂的应用程序。我们有一个代码库,它经常使用检查异常,并进行适当的错误处理,而其他一些代码库则没有。
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)
当我使用没有检查异常的代码库时,它使我在调用函数时很难预先知道我期望什么,这可能会严重破坏一些东西。
当然,这完全取决于开发者的偏好和技能。编程和错误处理的两种方式可能同样有效(或无效),所以我不会说只有一种方法。
总而言之,我发现使用受控异常更容易,特别是在有很多开发人员的大型项目中。
好吧,这不是关于显示堆栈跟踪或无声崩溃。它是关于在层与层之间沟通错误的能力。
The problem with checked exceptions is they encourage people to swallow important details (namely, the exception class). If you choose not to swallow that detail, then you have to keep adding throws declarations across your whole app. This means 1) that a new exception type will affect lots of function signatures, and 2) you can miss a specific instance of the exception you actually -want- to catch (say you open a secondary file for a function that writes data to a file. The secondary file is optional, so you can ignore its errors, but because the signature throws IOException, it's easy to overlook this).
实际上,我现在在一个应用程序中处理这种情况。我们将异常重新打包为AppSpecificException。这使得签名非常干净,我们不必担心在签名中爆炸。
当然,现在我们需要在更高的级别上专门化错误处理,实现重试逻辑等等。所有的东西都是AppSpecificException,所以我们不能说“如果一个IOException被抛出,重试”或“如果ClassNotFound被抛出,完全中止”。我们没有可靠的方法来获得真正的异常,因为当它们在我们的代码和第三方代码之间传递时,东西会一次又一次地重新打包。
这就是为什么我非常喜欢python中的异常处理。你只能捕获你想要和/或能处理的东西。其他所有东西都冒出来了,就好像你自己重新扔了它一样(不管怎样你已经这么做了)。
我一次又一次地发现,在我提到的整个项目中,异常处理分为3类:
Catch and handle a specific exception. This is to implement retry logic, for example.
Catch and rethrow other exceptions. All that happens here is usually logging, and its usually a trite message like "Unable to open $filename". These are errors you can't do anything about; only a higher levels knows enough to handle it.
Catch everything and display an error message. This is usually at the very root of a dispatcher, and all it does it make sure it can communicate the error to the caller via a non-Exception mechanism (popup dialogue, marshaling an RPC Error object, etc).