如果正确地使用 API 并不能阻止这种异常条件的产生,并且一旦产生异常,使用 API 的程序员可以立即采取有用的动作,这种负担就被认为是正当的。除非这两个条件都成立,否则更适合于使用未受检异常。作为一个石蕊测试(石蕊测试是指简单而具有决定性的测试),你可以试着问自己:程序员将如何处理该异常。下面的做法是最好的吗?
“把受检异常变成未受检异常”的一种方法是,把这个抛出异常的方法分成两个方法,其中第一个方法返回一个 boolean 值,表明是否应该抛出异常。这种 API 重构,把下面的调用序列:
```java
/* Invocation with checked exception */
try {
obj.action( args );
} catch ( TheCheckedException e ) {
... /* Handle exceptional condition */
}
```
重构为:
```java
/* Invocation with state-testing method and unchecked exception */
if ( obj.actionPermitted( args ) ) {
obj.action( args );
} else {
... /* Handle exceptional condition */
}
```
这种重构并非总是恰当的,但是,凡是在恰当的地方,它都会使 API 用起来更加舒服。虽然后者的调用序列没有前者漂亮,但是这样得到的 API 更加灵活。如果程序员知道调用将会成功,或者不介意由于调用失败而导致的线程终止,这种重构还允许以下这个更为简单的调用形式:
```java
obj.action(args);
```
如果你怀疑这个简单的调用序列是否符合要求,这个 API 重构可能就是恰当的。这样重构之后的 API 在本质上等同于第 69 条中的“状态测试方法”,并且同样的告诫依然适用:如果对象将在缺少外部同步的情况下被并发访问,或者可被外界改变状态,这种重构就是不恰当的,因为在 actionPermitted 和 action 这两个调用的时间间隔之中,对象的状态有可能会发生变化。如果单独的 actionPermitted 方法必须重复 action 方法的工作,出于性能的考虑,这种 API 重构就不值得去做。
总而言之,在谨慎使用的前提之下,受检异常可以提升程序的可读性;如果过度使用,将会使 API 使用起来非常痛苦。如果调用者无法恢复失败,就应该抛出未受检异常。如果可以恢复,并且想要迫使调用者处理异常的条件,首选应该返回一个 optional 值。当且仅当万一失败时,这些无法提供足够的信息,才应该抛出受检异常。