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


当前回答

我想我读过和你一样的布鲁斯·埃克尔的采访,它总是困扰着我。事实上,这个观点是由。net和c#背后的微软天才Anders Hejlsberg(如果这确实是你在谈论的帖子的话)提出的。

http://www.artima.com/intv/handcuffs.html

虽然我是海尔斯伯格和他的作品的粉丝,但我一直认为这种观点是虚假的。基本上可以归结为:

“受控异常很糟糕,因为程序员只是滥用它们,总是捕获它们并忽略它们,这导致问题被隐藏和忽略,否则将呈现给用户”。

通过“以其他方式呈现给用户”,我的意思是如果你使用了一个运行时异常,懒惰的程序员会忽略它(而不是用空的catch块捕获它),用户会看到它。

这个论点的总结是“程序员不会正确地使用它们,而不正确地使用它们比没有它们更糟糕”。

这种说法有一定道理,事实上,我怀疑Goslings不把运算符重写放在Java中的动机也是出于类似的理由——它们让程序员感到困惑,因为它们经常被滥用。

但最终,我发现这是海尔斯伯格的一个虚假论点,可能是一个事后的论点,用来解释这种缺乏,而不是一个经过深思熟虑的决定。

我想说的是,过度使用受控异常是一件坏事,容易导致用户处理草率,但是正确使用受控异常可以让API程序员给API客户端程序员带来巨大的好处。

现在API程序员必须小心不要到处抛出检查过的异常,否则它们只会惹恼客户端程序员。非常懒惰的客户端程序员将求助于catch (Exception){},正如Hejlsberg警告的那样,所有的好处都将失去,地狱将随之而来。 但在某些情况下,没有什么可以替代良好的受控异常。

For me, the classic example is the file-open API. Every programming language in the history of languages (on file systems at least) has an API somewhere that lets you open a file. And every client programmer using this API knows that they have to deal with the case that the file they are trying to open doesn't exist. Let me rephrase that: Every client programmer using this API should know that they have to deal with this case. And there's the rub: can the API programmer help them know they should deal with it through commenting alone or can they indeed insist the client deal with it.

在C语言中,这个习语是这样的

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

其中fopen通过返回0指示失败,而C(愚蠢地)让您将0视为布尔值和…基本上,你学会了这个习语就没事了。但如果你是个新手,没有学过这个习语呢?然后,当然,你开始用

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

从艰苦中学习。

请注意,这里我们只讨论强类型语言:对于强类型语言中的API是什么有一个清晰的概念:它是供您使用的功能(方法)的大杂烩,并为每个功能(方法)使用明确定义的协议。

明确定义的协议通常由方法签名定义。 这里fopen要求你给它传递一个字符串(在C语言中是char*)。如果你给它其他东西,你会得到一个编译时错误。您没有遵循协议—您没有正确地使用API。

在一些(晦涩的)语言中,返回类型也是协议的一部分。如果你试图在某些语言中调用等同于fopen()的函数而不将其赋值给变量,你也会得到一个编译时错误(你只能用void函数来做)。

我想说的是:在静态类型语言中,API程序员鼓励客户端正确使用API,如果客户端代码出现明显错误,就会阻止它被编译。

(在像Ruby这样的动态类型语言中,你可以传递任何东西,比如float,作为文件名——它会被编译。如果你甚至不打算控制方法参数,为什么要用受控异常来麻烦用户呢?这里的参数只适用于静态类型的语言。)

那么,受控异常呢?

这里有一个可以用来打开文件的Java api。

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

看到了吗?下面是API方法的签名:

public FileInputStream(String name)
                throws FileNotFoundException

注意,FileNotFoundException是一个受控异常。

API程序员对你说: 你可以使用这个构造函数来创建一个新的FileInputStream

A)必须将文件名作为A 字符串 B)必须接受 文件可能不存在的可能性 在运行时被发现”

这就是我所关心的全部。

问题的关键在于问题所表述的“程序员无法控制的事情”。我的第一个想法是他/她指的是API程序员无法控制的东西。但事实上,受控异常在正确使用的情况下,确实应该用于客户端程序员和API程序员都无法控制的事情。我认为这是不滥用受控异常的关键。

我认为文件打开很好地说明了这一点。API程序员知道,您可能会给他们一个在调用API时不存在的文件名,并且他们将无法返回您想要的结果,而必须抛出异常。他们也知道这种情况会经常发生,客户端程序员在编写调用时可能希望文件名是正确的,但在运行时由于他们无法控制的原因也可能是错误的。

因此,API明确了这一点:在某些情况下,当你打电话给我时,这个文件不存在,你最好处理它。

有了反案例,这一点就更清楚了。假设我在写一个表API。我有一个包含这个方法的API的表模型:

public RowData getRowData(int row) 

现在,作为一个API程序员,我知道会有这样的情况:一些客户端会为行传递一个负值,或者在表外传递一个行值。所以我可能会抛出一个受控异常,并迫使客户端处理它:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(当然,我不会称之为“检查过”。)

这是对受控异常的错误使用。客户端代码将充满获取行数据的调用,每个调用都必须使用try/catch,用于什么?它们是否会向用户报告查找了错误的行?可能不会,因为无论我的表格视图周围的UI是什么,它都不应该让用户进入一个非法行被请求的状态。所以这是客户端程序员的错误。

API程序员仍然可以预测客户端将编写这样的错误,并且应该用IllegalArgumentException这样的运行时异常来处理它。

对于getRowData中的受控异常,这显然会导致Hejlsberg的懒惰程序员只是添加空捕获。当这种情况发生时,即使对测试人员或客户端开发人员调试,非法行值也不会很明显,相反,它们会导致难以查明来源的直接错误。阿里安火箭发射后会爆炸。

好了,问题来了:我说的是检查异常FileNotFoundException不仅是一个好东西,而且是API程序员工具箱中的一个基本工具,它可以以对客户端程序员最有用的方式定义API。但是CheckedInvalidRowNumberException非常不方便,会导致糟糕的编程,应该避免使用。但是如何区分呢?

我想这并不是一门精确的科学我想这在一定程度上支持了海尔斯伯格的观点。但我不喜欢把孩子和洗澡水一起倒掉,所以请允许我在这里提取一些规则来区分好的受控异常和坏的异常:

Out of client's control or Closed vs Open: Checked exceptions should only be used where the error case is out of control of both the API and the client programmer. This has to do with how open or closed the system is. In a constrained UI where the client programmer has control, say, over all of the buttons, keyboard commands etc that add and delete rows from the table view (a closed system), it is a client programming bug if it attempts to fetch data from a nonexistent row. In a file-based operating system where any number of users/applications can add and delete files (an open system), it is conceivable that the file the client is requesting has been deleted without their knowledge so they should be expected to deal with it. Ubiquity: Checked exceptions should not be used on an API call that is made frequently by the client. By frequently I mean from a lot of places in the client code - not frequently in time. So a client code doesn't tend to try to open the same file a lot, but my table view gets RowData all over the place from different methods. In particular, I'm going to be writing a lot of code like if (model.getRowData().getCell(0).isEmpty())

而且,每次都必须在尝试/捕获中结束,这将是痛苦的。

Informing the User: Checked exceptions should be used in cases where you can imagine a useful error message being presented to the end user. This is the "and what will you do when it happens?" question I raised above. It also relates to item 1. Since you can predict that something outside of your client-API system might cause the file to not be there, you can reasonably tell the user about it: "Error: could not find the file 'goodluckfindingthisfile'" Since your illegal row number was caused by an internal bug and through no fault of the user, there's really no useful information you can give them. If your app doesn't let runtime exceptions fall through to the console it will probably end up giving them some ugly message like: "Internal error occured: IllegalArgumentException in ...." In short, if you don't think your client programmer can explain your exception in a way that helps the user, then you should probably not be using a checked exception.

这就是我的规则。有些刻意,毫无疑问会有例外(如果你愿意,请帮助我完善它们)。但我的主要论点是,在像FileNotFoundException这样的情况下,checked异常是API契约中与参数类型一样重要和有用的一部分。因此,我们不应该仅仅因为它被滥用就放弃它。

抱歉,我不是故意说这么长时间的。最后我想提两点建议:

答:API程序员:谨慎使用受控异常以保持它们的有用性。如果有疑问,请使用未检查的异常。

B:客户端程序员:养成在开发早期创建封装异常(谷歌)的习惯。JDK 1.4及后续版本在RuntimeException中为此提供了一个构造函数,但您也可以轻松地创建自己的构造函数。下面是构造函数:

public RuntimeException(Throwable cause)

然后养成这样的习惯:当您不得不处理受控异常时,您感到懒惰(或者您认为API程序员在第一时间使用受控异常时过于热心),不要只是吞下异常,包装它并重新抛出它。

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

把它放在IDE的一个小代码模板中,当你觉得懒的时候就可以使用它。这样,如果您真的需要处理检查异常,那么在运行时看到问题后,您将被迫返回并处理它。因为,相信我(和安德斯·海尔斯伯格),你永远不会回到你的TODO

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}

其他回答

《有效的Java异常》一文很好地解释了什么时候使用未检查的异常,什么时候使用已检查的异常。以下是那篇文章中的一些引语,以突出其要点:

应急预案: 一种期望的条件,要求方法提供可根据该方法的预期目的来表达的替代响应。方法的调用者期望这些类型的条件,并有一个策略来处理它们。 错: 一种计划外的情况,阻止方法实现其预期目的,如果不参考方法的内部实现,则无法描述该情况。

(SO不支持表格,所以你可能想从原始页面阅读以下内容…)

Contingency Is considered to be: A part of the design Is expected to happen: Regularly but rarely Who cares about it: The upstream code that invokes the method Examples: Alternative return modes Best Mapping: A checked exception Fault Is considered to be: A nasty surprise Is expected to happen: Never Who cares about it: The people who need to fix the problem Examples: Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers Best Mapping: An unchecked exception

我知道这是一个老问题,但我花了一段时间与受控异常搏斗,我有一些东西要补充。请原谅我的长度!

我对受控异常的主要不满是它们破坏了多态性。让它们很好地与多态接口一起工作是不可能的。

以Java List界面为例。我们有常见的内存实现,比如ArrayList和LinkedList。我们还有骨架类AbstractList,这使得设计新的列表类型变得很容易。对于只读列表,我们只需要实现两个方法:size()和get(int index)。

这个例子类WidgetList从一个文件中读取一些固定大小的Widget类型的对象(没有显示出来):

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

By exposing the Widgets using the familiar List interface, you can retrieve items (list.get(123)) or iterate a list (for (Widget w : list) ...) without needing to know about WidgetList itself. One can pass this list to any standard methods that use generic lists, or wrap it in a Collections.synchronizedList. Code that uses it need neither know nor care whether the "Widgets" are made up on the spot, come from an array, or are read from a file, or a database, or from across the network, or from a future subspace relay. It will still work correctly because the List interface is correctly implemented.

但事实并非如此。上面的类不能编译,因为文件访问方法可能会抛出一个IOException,一个你必须“捕获或指定”的受控异常。您不能将其指定为抛出——编译器不会允许您这样做,因为这会违反List接口的约定。而且WidgetList本身也没有处理异常的有用方法(我将在后面详细说明)。

显然,唯一要做的事情是捕获并重新抛出已检查异常作为一些未检查的异常:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

(编辑:Java 8为这种情况添加了UncheckedIOException类:用于跨多态方法边界捕获和重新抛出ioexception。有点证明了我的观点!))

So checked exceptions simply don't work in cases like this. You can't throw them. Ditto for a clever Map backed by a database, or an implementation of java.util.Random connected to a quantum entropy source via a COM port. As soon as you try to do anything novel with the implementation of a polymorphic interface, the concept of checked exceptions fails. But checked exceptions are so insidious that they still won't leave you in peace, because you still have to catch and rethrow any from lower-level methods, cluttering the code and cluttering the stack trace.

我发现,无处不在的Runnable接口如果调用了抛出受控异常的东西,就经常会退到这个角落。它不能按原样抛出异常,所以它所能做的就是通过捕获并作为RuntimeException重新抛出而使代码变得混乱。

实际上,如果使用hack,可以抛出未声明的受控异常。JVM在运行时并不关心受控异常规则,因此我们只需要欺骗编译器。最简单的方法就是滥用泛型。这是我的方法(类名显示,因为(在Java 8之前)它是通用方法调用语法中必需的):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

华友世纪!使用此方法,我们可以在堆栈的任何深度抛出检查异常,而无需声明它,无需将其包装在RuntimeException中,也不会使堆栈跟踪变得混乱!再次使用"WidgetList"的例子:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

不幸的是,对受控异常的最后一种侮辱是,如果编译器认为无法抛出受控异常,则拒绝允许您捕获该异常。(未检查的异常没有此规则。)要捕获偷偷抛出的异常,我们必须这样做:

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

这有点尴尬,但从好的方面来看,它仍然比提取包装在RuntimeException中的受控异常的代码稍微简单一些。

高兴的是,扔t;语句在这里是合法的,尽管检查了t的类型,这要归功于Java 7中添加的关于重新抛出捕获的异常的规则。


When checked exceptions meet polymorphism, the opposite case is also a problem: when a method is spec'd as potentially throwing a checked exception, but an overridden implementation doesn't. For example, the abstract class OutputStream's write methods all specify throws IOException. ByteArrayOutputStream is a subclass that writes to an in-memory array instead of a true I/O source. Its overridden write methods cannot cause IOExceptions, so they have no throws clause, and you can call them without worrying about the catch-or-specify requirement.

但并非总是如此。假设Widget有一个保存到流的方法:

public void writeTo(OutputStream out) throws IOException;

声明这个方法接受普通的OutputStream是正确的做法,因此它可以多态地用于各种输出:文件、数据库、网络等等。以及内存数组。然而,对于内存中的数组,有一个虚假的要求来处理一个实际上不会发生的异常:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

像往常一样,受控异常会成为阻碍。如果变量声明为具有更多开放式异常需求的基类型,则必须为这些异常添加处理程序,即使您知道它们不会出现在应用程序中。

但是等等,受控异常实际上非常烦人,它们甚至不允许您做相反的事情!想象一下,您当前捕获了OutputStream上的write调用抛出的任何IOException,但您想将变量的声明类型更改为ByteArrayOutputStream,编译器将斥责您试图捕获它说不能抛出的检查异常。

That rule causes some absurd problems. For example, one of the three write methods of OutputStream is not overridden by ByteArrayOutputStream. Specifically, write(byte[] data) is a convenience method that writes the full array by calling write(byte[] data, int offset, int length) with an offset of 0 and the length of the array. ByteArrayOutputStream overrides the three-argument method but inherits the one-argument convenience method as-is. The inherited method does exactly the right thing, but it includes an unwanted throws clause. That was perhaps an oversight in the design of ByteArrayOutputStream, but they can never fix it because it would break source compatibility with any code that does catch the exception -- the exception that has never, is never, and never will be thrown!

That rule is annoying during editing and debugging too. E.g., sometimes I'll comment out a method call temporarily, and if it could have thrown a checked exception, the compiler will now complain about the existence of the local try and catch blocks. So I have to comment those out too, and now when editing the code within, the IDE will indent to the wrong level because the { and } are commented out. Gah! It's a small complaint but it seems like the only thing checked exceptions ever do is cause trouble.


我快做完了。我对受控异常的最后一个不满是,在大多数调用站点上,没有什么有用的东西可以用它们来做。理想情况下,当出现问题时,我们应该有一个称职的特定于应用程序的处理程序,可以通知用户问题和/或适当地结束或重试操作。只有堆栈中较高的处理程序才能做到这一点,因为它是唯一知道总体目标的处理程序。

相反,我们得到了下面的习语,这是一种关闭编译器的猖獗方式:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

在GUI或自动化程序中,打印的消息将不会被看到。更糟糕的是,它在异常之后继续执行其余代码。异常实际上不是一个错误吗?那就别印出来。否则,马上就会出现其他异常,此时原始异常对象将消失。这个习惯用法不比BASIC的On Error Resume Next或PHP的error_reporting(0);更好。

调用某种类型的记录器类也好不到哪里去:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

这就像e.p printstacktrace()一样懒惰;仍然在不确定的状态下继续编写代码。另外,特定日志系统或其他处理程序的选择是特定于应用程序的,因此这会影响代码重用。

但是等等!有一种简单而通用的方法可以找到特定于应用程序的处理程序。它位于调用堆栈的更高位置(或者它被设置为线程的未捕获异常处理程序)。因此,在大多数情况下,您所需要做的就是将异常抛出到堆栈的更高位置。例如,投掷;受控异常只会碍事。

我确信,在设计语言时,受控异常听起来是个好主意,但在实践中,我发现它们都很麻烦,而且没有任何好处。

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

这个问题

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

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

最后一点

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

在过去的三年中,我一直在与一些开发人员一起开发相对复杂的应用程序。我们有一个代码库,它经常使用检查异常,并进行适当的错误处理,而其他一些代码库则没有。

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)

当我使用没有检查异常的代码库时,它使我在调用函数时很难预先知道我期望什么,这可能会严重破坏一些东西。

当然,这完全取决于开发者的偏好和技能。编程和错误处理的两种方式可能同样有效(或无效),所以我不会说只有一种方法。

总而言之,我发现使用受控异常更容易,特别是在有很多开发人员的大型项目中。