EffectiveJava第九章:异常

版权声明:本文为博主原创文章,转载请注明出处 https://github.com/baiiu https://blog.csdn.net/u014099894/article/details/64544564

充分发挥异常的有点,可以提高程序的可读性、可靠性和可维护性。
如果使用不当,它们也会带来负面影响。

57. 只针对异常的情况才使用异常

  • 异常应该只用于异常的情况下,它们永远不应该用于正常的控制流。

  • 乱用异常是应该的,不要在有更好的方案下选择使用异常包裹代码

    • 异常机制的设计初衷是用于不正常的情形,所以很少会有JVM的实现对它们进行优化,使得与显示的测试一样快速
    • 把代码放在try-catch块中反而阻止了现代JVM实现本来可能要执行的某些特定优化
    • 把数组进行遍历的标准模式并不会导致冗余的检查,有些现代JVM实现会将它们优化掉
  • 乱用异常不仅模糊了代码的意图,降低了它的性能,而且还不能保证正常的工作。如果出现了不相关的Bug,这种写法会掩盖该Bug,极大的增加了调试的复杂性。

  • 设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常。
    提供状态测试方法、可识别的返回中这两种做法,可以帮助你设计更好的API,使外界不用使用异常。

总之,异常是为了在异常情况下使用而设计的,不要将它们用于控制流,也不要编写迫使它们这么做的API。

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

什么时候适合使用哪种异常

  • 如果期望调用者能够适当的恢复,对于这种异常就应该使用受检的异常。

    • 通过抛出受检异常,强迫调用者在catch语句中处理该异常,或者throws出去进行处理。
    • API的设计者让API用户面对受检的异常,以此强制用户从这个异常条件中恢复。
  • 用运行时异常来表明编程错误

    • 如果程序抛出未受检的异常或错误,往往就属于不可恢复的情形,如果程序并未捕获,那么将导致线程终止,并出现适当的错误消息。
    • Error往往被JVM保留,所以你实现的所有未受检异常都应该是RuntimeException的子类。

总之,对于可恢复的情况,使用受检的异常;对于程序错误,则使用运行时异常。
异常也是一个对象,可以在它上面定义任何方法以提供所需要的信息。

59. 避免不必要的使用受检的异常

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

60. 优先使用标准的异常

  • 重用现有异常的好处:

    • 使你的API更加易于学习和使用
    • 因为熟悉,可读性会更好
    • 异常类越少,在内存印迹就越小,装载这些类的时间开销也越小
  • 几乎所有的错误方法都可以被归结为非法参数或非法状态,所有这两个异常被经常使用,但也有少数应用于特定条件下的非法参数(NullPointException)和非法状态(IndexOutOfException)。

    • IllegalArgumentException
    • IllegalStateException
  • ConcurrentModificationException:
    如果一个对象被设计为专用于单线程或者与外部同步机制配合使用,一旦发现它正在或已经被并发的修改,就应该抛出这个异常。

  • UnsupportedOperationException:
    如果对象不支持所请求的操作,就应该抛出这个异常。
    比如接口的某个实现类不想提高该方法,就可以抛出该异常。

一定要保证抛出的异常的条件与该异常的文档中描述的条件一致,这种重用必须建立在语义的基础上,而不是建立在名称的基础上。

61. 抛出与抽象相对应的异常

如果方法抛出的异常与它所执行的任务没有明显的联系,这种情形将会使人不知所措。
当方法传递由底层抛出的异常时,常常发生这种情况,就需要进行异常转译

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

  • 异常链(exception chaining)
    如果底层的异常对于调试导致高层异常的问题非常有帮助,使用异常链就很合适。底层的异常原因被传导到高层的异常。
    对于没有提供支持链(构造方法传入异常原因)的异常类,可以使用Trowable.initCause()方法设置原因,并使用getCause()来获取该原因。

try {
} catch (LowerLevelException e){
    throw new HigherLevelException(e);//传导,new HigherLevelException().initCause(e);
}
  • 不要滥用异常转译。
    如有可能,在调用底层方法时尽量避免其抛出异常,确保它们会成功。

62. 每个方法抛出的异常都要有文档

  • 始终要单独声明受检异常,并利用Javadoc的@throws标记,准确记录下抛出该异常的条件。

  • 如果一个类中许多方法出于同样的原因抛出同一个异常,就可以在该类的文档注释中对这个异常建立文档,而不是为每个方法单独建立文档。

63. 在细节消息中包含能捕获失败的消息

比如IndexOutOfException,应该尽可能的说明index、length等。

64. 努力使失败保持原子性

失败的方法调用应该使对象保持在被调用之前的状态,具有这种属性的方法被称为具有失败原子性(failure atomic)

有几种途径可以做到这种效果:

  1. 设计一个不可变的对象
    失败了永远也不会影响到已有的对象。

  2. 在执行操作之前检查参数的有效性
    这可以使得在对象的状态被修改之前,先抛出适当的异常。

  3. 编写一段恢复代码
    由它来拦截操作过程中发生的失败,使对象回滚到操作开始之前的状态上。

  4. 在对象的一份临时拷贝上执行操作,当操作完成后再用临时拷贝中的结果代替对象的内容

总之,作为方法规范的一部分,产生的任何异常都应该让对象保持在该方法调用之前的状态;如果违反这条规则,API文档就应该清楚的指明对象将会处于什么样的状态。

65. 不要忽略异常

try {
} catch (Exception e){
  //...不作为
}

至少,catch块也应该包含一条说明,解释为什么可以忽略这个异常。

这条建议同样适用于受检异常和未受检异常。

正确的处理异常能够彻底挽回失败,避免可能的失败。

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/64544564