Effective-java-读书笔记之异常

第69条 只针对异常的情况才使用异常

异常应该只用于异常的情况下, 永远不应该用于正常的控制流.

缺点: 代码难看, 性能降低, 隐藏真正的错误, 有bug, 难以维护.

良好设计的API不应该强迫它的客户端为了正常的控制流而使用异常.

如果类具有状态相关(state-dependent)的方法, 往往也应该有个状态测试(state-testing)方法.

举例: Iterator接口的next()方法状态相关, 相应的测试方法是hasNext().

状态测试方法不适用的情形: 并发未同步可能会产生状态不一致; 检车工作重复执行了动作操作会有性能问题.

另一种状态测试的做法: 让状态相关的方法返回一个可识别的值, 比如null或者是空的optional的值.

(对next()不适用, 因为null是next()方法的合法返回值.)

第70条 对可恢复的情况使用受检异常, 对编程错误使用运行时异常

Java提供三种可抛出结构(throwable):

  • 受检异常(checked exception).
  • 运行时异常(run-time exception).
  • 错误(error).

决定使用受检异常或是非受检异常时, 主要的原则: 如果期望调用者能够适当地恢复, 对于这种情况就应该使用受检的异常.

通过抛出受检的异常, 强迫调用者在一个catch子句中处理该异常, 或者将它传播出去.

每个受检异常都是对API用户的一个潜在指示: 与异常相关联的条件是这个方法的一种可能的结果.

用运行时异常(runtime exception)来表明编程错误. 大多数运行时异常都表示前提违例, 例如数组越界访问.

如果无法决定到底应该用受检(checked exception)还是非受检异常(runtime exception), (无法确定是否可能恢复), 就用runtime exception.

虽然Java语言规范没有要求, 但是按照惯例, 错误(error)往往被JVM保留用于表示资源不足, 约束失败或者其他使程序无法继续执行的条件.

因此, 基于这个惯例, 最好不要实现Error的新的子类, 你实现的所有未受检的抛出结构都应该是RuntimeException的子类(直接或间接).

不要定义任何既不是checked exception又不是runtime exception的异常.

第71条 避免不必要地使用受检的异常

受检的异常强迫程序员处理异常的情况, 大大增强了可靠性.

但是过分使用受检的异常会使API使用起来非常不方便.

如果方法抛出一个或多个受检的异常, 调用该方法的代码就必须在一个或多个catch块中处理这些异常, 或者它必须声明抛出这些异常.

这两种方式都会对API的使用者造成负担. Java 8开始, 这种负担加重, 因为抛出受检异常的方法不能直接在流中使用.

如果正确地使用API并不能阻止这种异常条件的产生, 并且一旦产生异常, 使用API的程序员可以立即采取有用的动作, 这种负担就被认为是正当的.

-> 这两个条件都满足受检异常才是正当的.

消除受检异常最简单的方法就是返回一个空的optional(异常情况下的缺省值). 这种方法的缺点就是不能提供更多的信息.

"把受检的异常变成未受检的异常"的一种方法是, 把这个抛出异常的方法分成两个方法, 其中第一个方法返回一个boolean, 表明是否该抛出异常. -> 状态测试方法.

第72条 优先使用标准的异常

使用标准异常的好处: API好用; 可读性; 更少的异常类节省了类加载的时间和空间.

常用的异常:

  • IllegalArgumentException
  • IllegalStateException
  • NullPointerException
  • IndexOutOfBoundsException
  • ConcurrentModificationException
  • UnsupportedOperationException

不要直接使用Exception, RuntimeException, Throwable, Error, 把这些类看作抽象的.

如果一个异常符合你的需求就使用它, 抛出这个异常时的条件要和异常文档中标注的一致, 而不仅仅是名字.

如果想要继承一个标准异常, 增加更多细节, feel free.

异常都是可序列化的, 没有什么好的理由不要写自己的异常.

一些情况下的异常选择: 如果没有任何参数值可以work, 抛IllegalStateException, 否则抛IllegalArgumentException.

第73条 抛出与抽象相对应的异常

异常转译(exception translation): 更高层的实现应该捕获底层的异常, 同时抛出可以按照高层抽象进行解释的异常.

异常链(exception chaining): 如果低层的异常对于调试导致高层异常的问题非常有帮助, 使用异常链将低层的异常(原因cause)传到高层异常.

大多数标准的异常都有支持链的构造器, 对于没有的, 可以用Throwable.initCause()设置原因.

异常链不仅方法提供访问cause, 还集成了cause的stack trace到高层异常中.

虽然异常转换优于无脑的异常传播, 但是也不应该被过度使用.

  • 可能的情形下, 最好的方法是能避免底层的异常, 确保底层方法成功. (有时候可以通过检查高层传入底层的参数实现.)
  • 如果底层异常的确不可避免, 让高层默默解决这些异常, 从而使高级别方法的调用者与低层问题隔离. 加log供之后研究.

第74条 每个方法抛出的异常都要有文档

始终要单独地声明受检的异常, 并且利用Javadoc的@throws标记, 准确地记录下抛出每个异常的条件.

虽然Java并不要求方法声明它可能会抛出的未受检异常, 但是仔细地为未受检异常建立文档是非常明智的, 因为它们有效描述了方法的前提条件.

使用Javadoc的@throws标签来标记方法抛出的每个异常, 但是不要对非受检异常使用throws关键字.

这样对于方法的使用者来说可以很好地区分两类异常: 在文档中有@throws, 在方法声明中没有throws子句的就是非受检异常.

但是要标记所有的非受检异常只是一种理想情况, 现实生活中很难达到.

如果一个异常被一个类中的很多方法基于同样的理由抛出, 可以在类的文档注释中说明这个异常.

第75条 在细节消息中包含能捕获失败的信息

程序由于未被捕获的异常失败的时候, 会打印该异常的堆栈轨迹, 包含该异常的toString()结果: 通常包含类名和细节消息(detail message).

异常的细节信息应该包含对该异常有贡献的参数和域的值.

但是要注意不要包含敏感信息, 如密码, 加密秘钥等.

为了确保在异常的细节消息中包含足够的信息, 一种办法是在异常的构造器中引入这些信息, 然后只要把它们放到消息描述中, 就可以自动产生细节信息.

第76条 努力使失败保持原子性

失败原子性(failure atomic): 失败的方法调用应该使对象保持在被调用之前的状态.

实现这种效果的途径:

  • 设计一个不可变的对象.
  • 在执行操作之前检查参数的有效性, 在对象的状态被修改之前抛出适当的异常. -> 让可能会失败的计算部分都在对象状态被修改之前发生.
  • 在对象的一份临时拷贝上执行操作, 当操作完成后再用临时拷贝中的结果代替对象的内容.
  • 编写一段恢复代码, 使对象回滚.

第77条 不要忽略异常

空的catch块会使异常达不到应有的目的. -> 至少应该有个说明吧.

如果你选择忽视一个异常, catch块应该包含一个注释, 解释为什么这么做是合理的, 而且catch括号中的异常变量应该被命名为ignored.

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
Effective-java-读书笔记之异常
如果类具有状态相关(state-dependent)的方法, 往往也应该有个状态测试(state-testing)方法.
<<上一篇
下一篇>>