高效Java(第三版) Effective Java
1. 考虑使用静态工厂方法替代构造方法 2. 当构造方法参数过多时使用builder模式 3. 使用私有构造方法或枚类实现Singleton属性 4. 使用私有构造方法执行非实例化 5. 使用依赖注入取代硬连接资源 6. 避免创建不必要的对象 7. 消除过期的对象引用 8. 避免使用Finalizer和Cleaner机制 9. 使用try-with-resources语句替代try-finally语句 10. 重写equals方法时遵守通用约定 11. 重写equals方法时同时也要重写hashcode方法 12. 始终重写 toString 方法 13. 谨慎地重写 clone 方法 14. 考虑实现Comparable接口 15. 使类和成员的可访问性最小化 16. 在公共类中使用访问方法而不是公共属性 17. 最小化可变性 18. 组合优于继承 19. 如果使用继承则设计,并文档说明,否则不该使用 20. 接口优于抽象类 21. 为后代设计接口 22. 接口仅用来定义类型 23. 优先使用类层次而不是标签类 24. 优先考虑静态成员类 25. 将源文件限制为单个顶级类 26. 不要使用原始类型 27. 消除非检查警告 28. 列表优于数组 29. 优先考虑泛型 30. 优先使用泛型方法 31. 使用限定通配符来增加API的灵活性 32. 合理地结合泛型和可变参数 33. 优先考虑类型安全的异构容器 34. 使用枚举类型替代整型常量 35. 使用实例属性替代序数 36. 使用EnumSet替代位属性 37. 使用EnumMap替代序数索引 38. 使用接口模拟可扩展的枚举 39. 注解优于命名模式 40. 始终使用Override注解 41. 使用标记接口定义类型 42. lambda表达式优于匿名类 43. 方法引用优于lambda表达式 44. 优先使用标准的函数式接口 45. 明智审慎地使用Stream 46. 优先考虑流中无副作用的函数 47. 优先使用Collection而不是Stream来作为方法的返回类型 48. 谨慎使用流并行 49. 检查参数有效性 50. 必要时进行防御性拷贝 51. 仔细设计方法签名 52. 明智而审慎地使用重载 53. 明智而审慎地使用可变参数 54. 返回空的数组或集合不要返回null 55. 明智而审慎地返回Optional 56. 为所有已公开的API元素编写文档注释 57. 最小化局部变量的作用域 58. for-each循环优于传统for循环 59. 熟悉并使用Java类库 60. 需要精确的结果时避免使用float和double类型 61. 基本类型优于装箱的基本类型 62. 当有其他更合适的类型时就不用字符串 63. 注意字符串连接的性能 64. 通过对象的接口引用对象 65. 接口优于反射 66. 明智谨慎地使用本地方法 67. 明智谨慎地进行优化 68. 遵守普遍接受的命名约定 69. 仅在发生异常的条件下使用异常 70. 对可恢复条件使用检查异常,对编程错误使用运行时异常 71. 避免不必要地使用检查异常 72. 赞成使用标准异常 73. 抛出合乎于抽象的异常 74. 文档化每个方法抛出的所有异常 75. 在详细信息中包含失败捕获信息 76. 争取保持失败原子性 77. 同步访问共享的可变数据 78. 避免过度同步 79. EXECUTORS, TASKS, STREAMS 优于线程 80. 优先使用并发实用程序替代wait和notify 81. 线程安全文档化 82. 明智谨慎地使用延迟初始化 83. 不要依赖线程调度器 84. 其他替代方式优于Java本身序列化 85. 非常谨慎地实现SERIALIZABLE接口 86. 考虑使用自定义序列化形式 87. 防御性地编写READOBJECT方法 88. 对于实例控制,枚举类型优于READRESOLVE 89. 考虑序列化代理替代序列化实例

在详细信息中包含失败捕获信息

Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

75. 在详细信息中包含失败捕获信息

当程序由于未捕获异常而失败时,系统自动打印出异常的堆栈轨迹 。堆栈轨迹包含异常的字符串表示,这是调用其toString方法的结果。这通常包括异常的类名及其详细信息。通常,这是程序员或网站可靠性工程师在调查软件故障时所掌握的唯一信息。如果失败不容易重现,则可能很难或不可能获得更多信息。因此,异常的toString方法返回尽可能多的关于失败原因的信息是非常重要的。换句话说,异常的详细信息应该捕获失败,以便后续分析。

要捕获失败,异常的详细消息应包含导致异常的所有参数和属性的值。 例如,IndexOutOfBoundsException的详细消息应包含下限,上限和不能在边界之间的索引值。 这些信息告诉了很多关于失败的信息。 三个值中的任何一个或全部都可能是错误的。 索引可能比下限小一或等于上限(“fencepost error”),或者它可能是一个野值(wild value),太低或太高。 下限可能大于上限(严重的内部不变失败)。 这些情况中的每一种都指向一个不同的问题,如果你知道正在寻找什么样的错误,它将极大地帮助你进行诊断。

有一个与安全敏感信息有关的警告。因为堆栈轨迹可能在诊断和修复软件问题的过程中被许多人看到,所以不要包括密码、加密密钥等详细信息

虽然在异常的详细信息中包含所有相关数据非常重要,但通常不需要包含大量的不相关的信息。堆栈轨迹与文档一起分析,如果需要,再与源代码一起分析。它通常包含抛出异常的确切文件和行号,以及堆栈上所有其他方法调用的文件和行号。冗长的描述失败信息是多余的;可以通过阅读文档和源代码来收集信息。

不应将异常的详细消息与用户级错误消息混淆,后者必须能够为最终被用户理解。 与用户级错误消息不同,详细消息主要是为了程序员或网站可靠性工程师在分析故障时的原因。 因此,信息内容远比可读性重要。 用户级错误消息通常是本地化的,而异常详细消息很少被本地化。

确保异常在其详细消息中包含足够的失败捕获信息的一种方法是,在其构造方法中,而不是字符串详细消息中要求此信息。 然后可以自动生成详细消息中包括该信息。 例如,IndexOutOfBoundsException可能有一个如下所示的构造方法,而不是String构造方法:

**
 * Constructs an IndexOutOfBoundsException.
 *
 * @param lowerBound the lowest legal index value
 * @param upperBound the highest legal index value plus one
 * @param index      the actual index value
 */

public IndexOutOfBoundsException(int lowerBound, int upperBound,
                                 int index) {
    // Generate a detail message that captures the failure
    super(String.format(
            "Lower bound: %d, Upper bound: %d, Index: %d",
            lowerBound, upperBound, index));
    // Save failure information for programmatic access
    this.lowerBound = lowerBound;
    this.upperBound = upperBound;
    this.index = index;
}

从Java 9开始,IndexOutOfBoundsException最终获得了一个接受int值index参数的构造方法,但遗憾的是它删除了lowerBound和upperBound两个参数。更普遍地说,Java类库并没有大量使用这个习惯用法,但是强烈推荐使用它。它使程序员很容易抛出异常来捕获失败。事实上,它使程序员不想捕获失败都难!实际上,这个习惯用法将代码集中在异常类中生成高质量的详细信息,而不是要求该类的每个使用者都冗余地生成详细信息。

如条目 70所示,异常可能适合为其失败捕获信息(上例中的lowerBound,upperBound和index)提供访问器方法。 在检查异常上提供此类访问器方法比未检查异常更为重要,因为故障捕获信息可用于从故障中恢复。 程序员可能希望以编程方式访问未检查异常的细节,这种情况很少见(尽管也是可以想象的)。 但是,即使对于未检查异常情况,最好根据一般原则提供这些方法的访问器(条目 12,第57页)。