在遇到失败时应该抛出一个异常,但是该异常应该反映该方法做什么,而不是它如何做。
有时,当
程序员对因为实现的改变而导致从方法签名中增加或者删除异常感到厌烦时,他们不是通过使用一个抽象来定义特定层次可能抛出的异常类型,而只是将他们的所有方法都声明为抛出 Exception。换句话说,他们已经认定异常只是导致烦恼,并且基本上将它们关闭掉了。毋庸多言,该方法对于绝大多数可任意使用的代码来说通常不是一个好的错误处理策略。
难以理解的代码
因为许多方法都抛出一定数目的不同异常,错误处理的代码相对于实际的功能代码的比率可能会偏高,使得难以找到一个方法中实际完成功能的代码。异常是通过集中错误处理来设想减小代码的,但是一个具有三行代码和六个 catch 块(其中每个块只是记录异常或者包装并重新抛出异常)的方法看起来比较膨胀并且会使得本来简单的代码变得模糊。
异常淹没
我们都看到过这样的代码,其中捕获了一个异常,但是在 catch 块中没有代码。尽管这种编程实践很明显是不好的,但是很容易看出它是如何发生的 ?? 在原型化期间,某人通过 trycatch 块包装代码,而后来忘记返回并填充 catch 块。尽管这个错误很常见,但是这也是更好的工具可以帮助我们的地方之一 ?? 对于异常淹没的地方,通过编辑器、编译器或者静态检查工具可以容易地检测并发出警告。
极度通用的 trycatch 块是另一种形式的异常淹没,并且更加难以检测,因为这是 Java 类库中的异常类层次的结构而导致的(可疑)。让我们假定一个方法抛出四个不同类型的异常,并且调用者遇到其中任何一个异常都将捕获、记录它们,并且返回。实现该策略的一种方式是使用一个带有四个 catch 子句的 trycatch 块,其中每个异常类型一个。为了避免代码难以理解的问题,一些开发人员将重构该代码,如清单 1 所示:
清单 1. 意外地淹没 RuntimeException
try {
doSomething();
}
catch (Exception e) {
log(e);
}
尽管该代码与四个 catch 块相比更为紧凑,但是它具有一个问题 ?? 它还捕获可能由 doSomething 抛出的任何 RuntimeException 并且阻止它们进行扩散。
过多的异常包装
如果异常是在一个底层的设施中生成的,并且通过许多代码层向上扩散,在最终被处理之前它可能被捕获、包装和重新抛出若干次。当异常最终被记录的时候,栈跟踪可能有许多页,因为栈跟踪可能被复制多次,其中每个包装层一次。(在 JDK 1.4 以及后来的版本中,异常链的实现在某种程度上缓解了该问题。)
替换的方法
Bruce Eckel,Thinking in Java (请参阅 参考资料)的作者,声称在使用 Java 语言多年后,他已经得出这样的结论,认为检查型异常是一个错误 ?? 一个应该被声明为失败的试验。Eckel 提倡将所有的异常都作为非检查型的,并且提供清单 2 中的类作为将检查型异常转变为非检查型异常的一个方法,同时保留当异常从栈向上扩散时捕获特定类型的异常的能力(关于如何使用该方法的解释,请参阅他在 参考
资料 小节中的文章):
清单 2. Eckel 的异常适配器类
class ExceptionAdapter extends RuntimeException {
private final String stackTrace;
public Exception originalException;
public ExceptionAdapter(Exception e) {
super(e.toString());
originalException = e;
StringWriter sw =&