目录
- 仅在发生异常的条件下使用异常
- 对可恢复条件使用已检查异常,对编程错误使用运行时异常
- 避免不必要地使用检查异常
- 赞成使用标准异常
- 抛出合乎于抽象的异常
- 文档化每个方法抛出的所有异常
- 在详细信息中包含失败捕获信息
- 争取保持失败原子性
- 不要忽略异常
异常
仅在确有异常条件下使用异常
- 一个设计良好的 API 不能迫使其客户端为一般的控制流程使用异常。只有在某些不可预知的条件下才能调用具有「状态依赖」方法的类,通常应该有一个单独的「状态测试」方法,表明是否适合调用「状态依赖」方法。例如,Iterator 接口具有「状态依赖」的 next 方法和对应的「状态测试」方法 hasNext。这使得传统 for 循环(在 for-each 循环内部也使用了 hasNext 方法)在集合上进行迭代成为标准习惯用。
- 如果要在没有外部同步的情况下并发地访问对象,或者受制于外部条件的状态转换,则必须使用 Optional 或可识别的返回值,因为对象的状态可能在调用「状态测试」方法与「状态依赖」方法的间隔中发生变化。
- 如果一个单独的「状态测试」方法重复「状态依赖」方法的工作,从性能问题考虑,可能要求使用 Optional 或可识别的返回值
对可恢复情况使用 checked 异常,对编程错误使用运行时异常
- 把错误Error保留给 JVM 使用,以指示:资源不足、不可恢复故障或其他导致无法继续执行的条件。
考虑到这种约定被大众认可,所以最好不要实现任何新的 Error 子类。 - 因为 未checked 异常通常表示可恢复的条件,所以这类异常来说,设计能够提供信息的方法来帮助调用者从异常条件中恢复尤为重要。
例如,假设当使用礼品卡购物由于资金不足而失败时,抛出一个 未checked 异常。该异常应提供一个访问器方法来查询差额。这将使调用者能够将金额传递给购物者。 - 自定义的所有 unchecked 可抛出项都应该继承 RuntimeException,平常就是用的未受检异常
- checked 异常必须捕获,未checked 异常没强制要求
避免不必要地使用 checked 异常
- 如果一个方法抛出 checked 异常,调用它的代码必须在一个或多个 catch 块中处理它们;或者通过声明抛出,让它们向外传播。如果
(1)正确使用 API 也不能防止异常情况
(2)使用 API 的程序员在遇到异常时可以采取一些有用的操作
那么这种负担是合理的。 - 消除 checked 异常的最简单方法是返回所需结果类型的 Optional 对象(55)这种技术的缺点是,该方法不能返回任何详细说明其无法执行所需计算的附加信息
鼓励复用标准异常
- 常见标准异常见参考文献
抛出能用抽象解释的异常
- 异常转换:高层应该捕获低层异常,并确保抛出的异常可以用高层抽象解释
// 异常转换
try {
... // 使用较低层次的抽象来完成我们的任务
} catch (LowerLevelException e) {
throw new HigherLevelException(...);
}
- 链式异常:低层异常(作为原因)传递给高层异常,高层异常提供一个访问器方法(Throwable 的 getCause 方法)来检索低层异常
// Exception Chaining
try {
... // Use lower-level abstraction to do our bidding
}
catch (LowerLevelException cause) {
throw new HigherLevelException(cause);
}
为每个方法记录会抛出的所有异常
- rt
异常详细消息中应包含捕获失败的信息
- 异常的详细消息应该包含导致异常的所有参数和字段的值
不应包含密码、加密密钥等详细信息.
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
// 生成捕获故障的详细信息
super(String.format("Lower bound: %d, Upper bound: %d, Index: %d",lowerBound, upperBound, index));
// 为编程访问保存故障信息s
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.index = index;
}
尽力保证故障原子性
一般来说,一个失败的方法调用应该使对象处于调用之前的状态:
- 最简单的方法是设计不可变对象
- 而对于操作可变对象的方法,实现故障原子性的最常见方法是在执行操作之前检查参数的有效性
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
- 上述例子中如果取消了初始大小检查,当该方法试图从空堆栈中弹出元素时,仍然会抛出异常。但是,这会使 size 字段处于不一致的(负值)状态,导致以后该对象的任何方法调用都会失败。
此外,pop 方法抛出的 ArrayIndexOutOfBoundsException 也不适于高层抽象解释
- 以对象的临时副本执行操作,并在操作完成后用临时副本替换对象的内容
- 例如,一些排序函数在排序之前将其输入 list 复制到数组中,以降低访问排序内循环中的元素的成本。这样做是为了提高性能,但是作为一个额外的好处,它确保如果排序失败,输入 list 将保持不变
- 编写恢复代码,拦截在操作过程中发生的故障,并使对象回滚到操作开始之前的状
不要忽略异常
- rt
网友评论