《Effective Java》读书笔记九

《Effective Java》第十章

第六十九条:只针对异常的情况才使用异常

一、异常的正确使用场景

  1. 真正的异常情况:异常应该用于表示程序在正常执行过程中出现的意外情况,这些情况通常是不可预见的错误或异常状态。例如,文件无法打开、网络连接中断、非法的输入数据等。
  2. 无法通过正常的返回值处理:当一种情况无法通过正常的返回值来表示时,可以考虑使用异常。例如,如果一个方法在执行过程中遇到严重错误,无法返回有效的结果,那么可以抛出一个异常来通知调用者。

二、避免滥用异常的原因

  1. 性能影响:异常的处理机制相对较为昂贵,包括创建异常对象、记录堆栈信息等操作都会消耗一定的时间和资源。如果在非异常情况下频繁地使用异常,会对程序的性能产生负面影响。
  2. 可读性降低:过多的异常处理代码会使程序的逻辑变得复杂,降低代码的可读性。开发者可能需要花费更多的时间来理解异常的抛出和处理逻辑,而不是关注程序的主要业务逻辑。
  3. 可维护性问题:滥用异常可能导致异常处理代码的混乱和不一致。不同的开发者可能对异常的处理方式不同,这会增加代码的维护难度。

三、正确使用异常的方法

  1. 设计合理的异常层次结构:根据不同的异常情况,设计合理的异常类层次结构。这样可以使异常的类型更加明确,便于调用者进行针对性的处理。
  2. 提供有意义的异常信息:在抛出异常时,应该提供足够的信息,以便调用者能够了解异常的原因和上下文。这有助于快速定位和解决问题。
  3. 避免捕获过于宽泛的异常:在捕获异常时,应该尽量捕获具体的异常类型,而不是过于宽泛的异常类型(如Exception)。这样可以确保只处理预期的异常情况,避免隐藏其他潜在的问题。
  4. 考虑异常的替代方案:在一些情况下,可以考虑使用其他方式来处理错误情况,而不是总是依赖异常。例如,可以使用返回值、状态码或日志记录等方式来表示错误信息。

四、总结

只在真正的异常情况下才使用异常,避免滥用异常可以提高程序的性能、可读性和可维护性。在设计和使用异常时,要考虑异常的正确使用场景,提供有意义的异常信息,避免捕获过于宽泛的异常,并考虑异常的替代方案。这样可以使程序更加健壮和可靠。

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

一、受检异常与运行时异常的区别

  1. 受检异常(Checked Exception):在编译时必须被处理,要么被捕获,要么在方法签名中声明抛出。通常用于表示可预期的、可能在程序正常运行过程中发生的异常情况,并且这些情况可以通过合理的方式进行恢复或处理。
  2. 运行时异常(Runtime Exception):在编译时不需要被显式处理。通常用于表示编程错误或不可恢复的错误情况,这些错误一般是由于程序员的错误导致的,例如空指针引用、数组越界等。

二、对可恢复情况使用受检异常的原因

  1. 明确责任:使用受检异常可以明确地指出哪些部分的代码可能会抛出异常,以及调用者有责任处理这些异常。这有助于提高代码的可读性和可维护性,让开发者清楚地知道在哪些情况下需要进行错误处理。
  2. 促进恢复:可恢复的情况通常意味着可以采取一些措施来恢复程序的正常运行。受检异常可以促使开发者在代码中考虑这些情况,并提供相应的恢复机制,例如重试操作、请求用户输入正确的数据等。
  3. 提高健壮性:通过强制处理受检异常,可以提高程序的健壮性。即使在出现异常情况时,程序也能够以一种可控的方式进行处理,而不是突然崩溃。

三、对编程错误使用运行时异常的原因

  1. 简化代码:对于编程错误,使用运行时异常可以避免在每个可能出现错误的地方都进行显式的异常处理。这可以使代码更加简洁,专注于正常的业务逻辑,而不是被大量的异常处理代码所淹没。
  2. 快速失败:编程错误通常是不可恢复的,或者不应该被恢复。使用运行时异常可以让程序尽快失败,以便开发者能够快速发现和修复问题。
  3. 符合直觉:运行时异常通常表示程序出现了严重的问题,这些问题往往是由于程序员的错误导致的。使用运行时异常可以让开发者更容易理解问题的本质,并且在调试过程中更容易定位错误。

四、注意事项

  1. 不要滥用运行时异常:虽然运行时异常可以简化代码,但也不能滥用。如果某些情况实际上是可恢复的,却被错误地使用运行时异常来表示,那么可能会导致程序在出现这些情况时无法正常恢复,从而影响程序的稳定性。
  2. 合理处理受检异常:对于受检异常,开发者应该根据具体情况进行合理的处理。可以选择捕获异常并进行适当的恢复操作,或者在方法签名中声明抛出异常,让上层调用者进行处理。
  3. 提供有意义的异常信息:无论是受检异常还是运行时异常,都应该提供有意义的异常信息,以便在出现问题时能够快速定位和解决问题。

五、总结

在编写 Java 代码时,应该根据异常情况的性质来选择使用受检异常还是运行时异常。对于可恢复的情况,使用受检异常可以明确责任、促进恢复和提高程序的健壮性;对于编程错误,使用运行时异常可以简化代码、快速失败并符合直觉。同时,要注意合理使用异常,避免滥用运行时异常,并提供有意义的异常信息。

第七十一条:避免不必要地使用受检异常

一、受检异常的特点及潜在问题

  1. 强制处理:受检异常在编译时就要求开发者进行处理,要么捕获异常,要么在方法签名中声明抛出。这种强制处理机制虽然在某些情况下有助于提高代码的健壮性,但也可能导致代码变得冗长和复杂。
  2. 增加代码复杂性:处理受检异常需要添加大量的 try-catch 块或者在方法签名中声明抛出异常,这会使代码的结构变得复杂,降低代码的可读性和可维护性。
  3. 可能掩盖真正的问题:过度使用受检异常可能会导致开发者在处理异常时过于草率,只是简单地捕获异常而不进行深入的分析和处理。这样可能会掩盖真正的问题,使程序在运行时出现不可预测的错误。

二、避免不必要使用受检异常的原因

  1. 提高代码简洁性:减少不必要的受检异常可以使代码更加简洁明了,开发者可以更专注于业务逻辑的实现,而不是被繁琐的异常处理代码所困扰。
  2. 增强代码可读性:没有过多的异常处理代码干扰,代码的结构更加清晰,可读性更高。其他开发者在阅读代码时可以更容易地理解代码的意图和逻辑。
  3. 提高开发效率:避免不必要地使用受检异常可以减少开发过程中的工作量。开发者不需要花费大量的时间来编写和维护异常处理代码,从而提高开发效率。

三、判断是否必要使用受检异常的方法

  1. 可恢复性:考虑异常情况是否是可恢复的。如果异常情况可以通过合理的方式进行恢复,那么使用受检异常可能是合适的。例如,网络连接中断可以通过重试连接来恢复,这种情况下可以使用受检异常来通知调用者进行处理。
  2. 预期性:判断异常情况是否是可预期的。如果异常情况是在正常的程序运行过程中可能会出现的,并且开发者可以提前做好准备进行处理,那么使用受检异常可能是有必要的。例如,文件读取失败可能是由于文件不存在或者权限不足等原因导致的,这种情况下可以使用受检异常来通知调用者进行处理。
  3. 严重性:评估异常情况的严重性。如果异常情况会导致程序无法继续正常运行,或者会对数据的完整性和安全性造成严重影响,那么使用受检异常可能是必要的。例如,数据库连接失败可能会导致整个系统无法正常工作,这种情况下应该使用受检异常来通知调用者进行处理。

四、替代方案

  1. 运行时异常:对于不可恢复的错误或者编程错误,可以使用运行时异常来表示。运行时异常在编译时不需要进行处理,这样可以使代码更加简洁。但是,使用运行时异常时要注意提供有意义的异常信息,以便在出现问题时能够快速定位和解决问题。
  2. 返回错误码或状态:在某些情况下,可以通过返回错误码或状态来表示异常情况,而不是使用受检异常。这种方式可以使调用者更加灵活地处理异常情况,并且可以避免强制处理异常带来的复杂性。但是,使用错误码或状态时要注意确保其含义清晰明确,并且在整个代码中保持一致。

五、总结

在编写 Java 代码时,应该避免不必要地使用受检异常。要根据异常情况的可恢复性、预期性和严重性来判断是否有必要使用受检异常。如果可以使用其他方式来表示异常情况,如运行时异常、返回错误码或状态等,那么可以考虑使用这些替代方案,以提高代码的简洁性、可读性和开发效率。

第七十二条:优先使用标准的异常

一、使用标准异常的优势

  1. 熟悉性:标准异常是 Java 语言中广泛认知的异常类型。开发人员对这些异常有一定的了解,能够快速识别出问题的大致类型,提高代码的可读性和可理解性。
  2. 一致性:使用标准异常有助于保持代码的一致性。在整个项目或团队中,如果大家都遵循使用标准异常的原则,那么代码的异常处理逻辑会更加统一,降低维护成本。
  3. 可维护性:标准异常通常经过了充分的测试和优化,具有较高的稳定性和可靠性。使用标准异常可以减少由于自定义异常处理不当而导致的潜在问题,提高代码的可维护性。
  4. 兼容性:标准异常与 Java 平台的其他部分具有良好的兼容性。在与其他库或框架进行交互时,使用标准异常可以减少兼容性问题的发生。

二、何时使用标准异常

  1. 常见错误情况:对于一些常见的错误情况,如空指针异常(NullPointerException)、数组越界异常(ArrayIndexOutOfBoundsException)等,应优先使用相应的标准异常。这些异常能够准确地反映问题的本质,并且开发人员对其处理方式也比较熟悉。
  2. 符合标准异常语义:当出现的错误情况与某个标准异常的语义相符合时,应使用该标准异常。例如,如果在输入数据不合法时,可以使用IllegalArgumentException来表示。
  3. 避免过度自定义:不要过度自定义异常类型,除非有明确的需求。在大多数情况下,使用标准异常可以满足需求,并且可以减少代码的复杂性。

三、注意事项

  1. 提供有意义的异常信息:即使使用标准异常,也应该提供有意义的异常信息,以便在出现问题时能够快速定位和解决问题。可以在抛出异常时,传入具体的错误信息或上下文信息。
  2. 不要滥用异常:虽然标准异常很方便,但也不要滥用异常。异常应该用于表示真正的异常情况,而不是作为控制流程的一种方式。过度使用异常会降低代码的性能和可读性。
  3. 考虑异常的层次结构:在某些情况下,可以考虑使用异常的层次结构来更好地组织和处理异常。例如,可以创建自定义的异常类,继承自标准异常类,以满足特定的业务需求。

四、总结

优先使用标准的异常可以提高代码的可读性、可维护性和兼容性。在选择异常类型时,应考虑常见错误情况、符合标准异常语义以及避免过度自定义。同时,要注意提供有意义的异常信息,不要滥用异常,并考虑异常的层次结构。这样可以使代码的异常处理更加规范和高效。

第七十三条:抛出与抽象对应的异常

一、重要性

  1. 保持抽象一致性:当一个方法属于某个抽象层次时,抛出的异常也应该在这个抽象层次上有意义。这样可以确保代码的抽象层次在异常处理中也得到保持,提高代码的可读性和可维护性。
  2. 便于理解和处理:如果异常与抽象对应,调用者可以更容易地理解异常发生的原因,并采取适当的措施进行处理。这有助于提高代码的健壮性和可靠性。

二、具体做法

  1. 高层次抽象方法:如果一个方法处于较高层次的抽象中,它应该抛出相对高层次的异常。这些异常通常是更通用的、与业务逻辑相关的异常,而不是具体的技术实现细节相关的异常。
    • 例如,一个业务服务层的方法不应该抛出底层数据库连接失败的具体异常,而是可以抛出一个表示业务操作失败的通用业务异常。
  2. 低层次实现方法:在低层次的实现方法中,可以抛出更具体的异常,但在向上传递的过程中,应该将这些具体异常转换为更高层次的抽象异常。
    • 比如,底层数据库访问方法可能抛出数据库特定的异常,但在服务层应该捕获这些异常并转换为业务相关的异常后再抛出。
  3. 异常转换:进行异常转换时,要确保提供足够的上下文信息,以便调用者能够理解异常的原因。可以在转换后的异常中包含原始异常的信息,以便在调试时进行更深入的分析。

三、注意事项

  1. 不要过度抽象:虽然要抛出与抽象对应的异常,但也不能过度抽象以至于异常变得模糊不清。异常应该能够准确地传达问题的本质,以便调用者能够采取有效的措施。
  2. 考虑异常的层次结构:在设计异常层次结构时,要考虑到不同层次的抽象和可能出现的异常情况。合理的异常层次结构可以使异常处理更加清晰和易于管理。
  3. 文档说明:对于抛出的异常,应该在方法的文档中进行明确的说明,包括异常的类型、发生的条件以及可能的处理方式。这有助于其他开发人员更好地理解和使用代码。

四、总结

“抛出与抽象对应的异常”是一种良好的编程实践,可以提高代码的抽象层次一致性、可读性和可维护性。通过在不同层次的抽象中抛出合适的异常,并进行适当的异常转换,可以使代码更加健壮和可靠。同时,要注意不要过度抽象和合理设计异常层次结构,并在文档中对异常进行说明。

第七十四条:每个方法抛出的所有异常都要建立文档

一、建立文档的重要性

  1. 提高代码可读性:明确记录每个方法可能抛出的异常,使其他开发人员在阅读代码时能够快速了解该方法可能出现的错误情况,从而更好地理解方法的行为和使用方式。
  2. 便于错误处理:开发人员在调用方法时,可以根据文档中记录的异常信息进行有针对性的错误处理。这样可以提高代码的健壮性,避免因未处理异常而导致的程序崩溃。
  3. 促进团队协作:在团队开发中,良好的文档可以确保团队成员对方法的异常情况有一致的理解,减少沟通成本和误解,提高开发效率。

二、文档内容要点

  1. 异常类型:明确列出方法可能抛出的所有异常类型。可以使用 Java 文档注释中的@throws标签来记录异常信息。
  2. 异常原因:简要说明异常发生的原因,帮助开发人员理解在什么情况下会抛出该异常。例如,是由于输入参数不合法、资源不可用还是其他特定的情况导致的异常。
  3. 处理建议:如果可能,可以提供一些处理异常的建议或最佳实践。这可以帮助开发人员在遇到异常时采取正确的措施,提高问题解决的效率。

三、建立文档的方法

  1. 使用文档注释:在方法的声明上方使用 Java 的文档注释格式,详细记录方法抛出的异常信息。确保文档注释的准确性和完整性,及时更新文档以反映方法的实际行为变化。
  2. 保持一致性:在整个项目中保持异常文档的一致性风格。遵循统一的文档规范,使不同方法的异常文档易于阅读和理解。
  3. 审查和维护:定期审查代码中的异常文档,确保其与实际的方法实现保持一致。在方法的行为发生变化或新增异常情况时,及时更新文档。

四、注意事项

  1. 不要遗漏异常:确保记录所有可能抛出的异常,包括受检异常和运行时异常。即使某些异常在特定情况下不太可能发生,也应该进行记录,以提供全面的信息。
  2. 准确描述异常:文档中的异常描述应该准确反映实际情况,避免模糊或不准确的描述。如果可能,可以提供具体的示例来说明异常的发生场景。
  3. 考虑异常的层次结构:如果方法调用其他方法,并且这些被调用方法可能抛出异常,要考虑在文档中记录这些间接抛出的潜在异常情况。

五、总结

为每个方法抛出的所有异常建立文档是一种良好的编程习惯,可以提高代码的可读性、可维护性和健壮性。通过明确记录异常类型、原因和处理建议,可以帮助开发人员更好地理解和处理方法可能出现的错误情况,促进团队协作和项目的顺利进行。

第七十五条:在细节消息中包含失败-捕获信息

一、重要性

  1. 有助于调试:当异常发生时,详细的失败-捕获信息可以为开发者提供关键线索,帮助快速定位问题的根源。这在复杂的系统中尤其重要,能够减少调试时间和成本。
  2. 提高可理解性:清晰的失败信息使其他开发者或维护人员更容易理解异常发生的情况,即使他们不熟悉具体的代码实现。这有助于提高代码的可维护性。
  3. 增强错误报告:在向用户报告错误时,包含有意义的失败-捕获信息可以让用户更好地理解问题,提供更准确的反馈,有助于问题的解决。

二、具体做法

  1. 记录关键信息:在异常的细节消息中,应包含与失败相关的关键信息,如输入参数的值、执行的关键操作、涉及的资源等。这些信息可以帮助开发者更好地理解异常发生的上下文。
  2. 捕获异常时记录:当捕获异常时,不要仅仅重新抛出异常而不添加任何有用的信息。可以在捕获点添加一些额外的上下文信息,然后再重新抛出异常,使异常的细节消息更加丰富。
  3. 使用有意义的消息格式:构造异常消息时,使用清晰、有意义的格式,以便于阅读和理解。可以采用描述性的语言,说明异常发生的情况和原因。

三、注意事项

  1. 避免敏感信息:在记录失败-捕获信息时,要注意避免包含敏感信息,如用户密码、机密数据等。确保只记录与问题解决相关的必要信息。
  2. 不要过度冗长:虽然要提供详细信息,但也不要使异常消息过于冗长。保持消息简洁明了,突出关键信息,避免过多的无关细节。
  3. 考虑性能影响:记录详细的失败信息可能会有一定的性能开销。在性能敏感的代码中,要权衡记录信息的必要性和性能影响。

四、总结

在异常的细节消息中包含失败-捕获信息是一种良好的编程实践。它可以帮助开发者快速定位和解决问题,提高代码的可维护性和可理解性。在记录信息时,要注意避免敏感信息、保持简洁明了,并考虑性能影响。

第七十六条:努力使失败保持原子性

一、重要性

  1. 数据一致性:确保在出现失败情况时,系统的状态不会处于一种不一致或部分更新的状态。这对于维护数据的完整性和正确性至关重要。
  2. 可恢复性:使失败具有原子性有助于更容易地进行错误恢复。可以明确知道哪些操作已经完成,哪些需要回滚,从而采取适当的恢复措施。
  3. 可靠性:提高系统的可靠性,用户可以信任系统在出现问题时不会留下不可预测的状态,减少潜在的错误和故障。

二、实现方法

  1. 事务处理:在涉及多个操作的场景中,使用事务来确保所有操作要么全部成功,要么全部失败。如果在事务过程中出现异常,能够自动回滚到事务开始前的状态。
  2. 检查点和回滚机制:在执行一系列操作时,可以设置检查点。如果发生失败,可以回滚到最近的检查点,重新开始执行未完成的操作。
  3. 单一职责原则:设计方法和模块时,遵循单一职责原则,使每个操作尽可能独立和原子。这样,当一个操作失败时,不会影响其他无关的操作。
  4. 异常处理:在代码中合理地处理异常,确保在出现异常时能够及时清理资源、回滚部分操作或采取其他适当的措施,以保持系统的原子性。

三、注意事项

  1. 性能考虑:实现失败的原子性可能会带来一定的性能开销,特别是在频繁进行事务处理或回滚操作的情况下。需要在性能和数据一致性之间进行权衡。
  2. 复杂性增加:努力使失败保持原子性可能会使代码变得更加复杂。需要仔细设计和测试,确保原子性的实现不会引入新的错误或问题。
  3. 资源管理:在进行回滚或清理资源时,要确保正确地管理资源,避免资源泄漏或其他资源相关的问题。

四、总结

努力使失败保持原子性是提高软件系统可靠性和数据一致性的重要原则。通过使用事务处理、设置检查点、遵循单一职责原则和合理处理异常等方法,可以实现失败的原子性。但同时要注意性能、复杂性和资源管理等方面的问题,以确保系统的稳定和高效运行。

第七十七条:不要忽略异常

一、忽略异常的危害

  1. 隐藏问题:忽略异常可能会导致潜在的问题被掩盖,使得错误无法被及时发现和处理。这可能会在后续的程序运行中引发更严重的故障,甚至导致系统崩溃。
  2. 数据不一致:如果异常发生在对数据进行操作的过程中,忽略异常可能会导致数据处于不一致的状态。这会影响系统的正确性和可靠性。
  3. 难以调试:当异常被忽略时,开发人员很难确定问题的根源。没有异常信息的提示,调试过程会变得更加困难和耗时。

二、正确处理异常的方法

  1. 捕获并处理:在可能发生异常的代码段中,使用 try-catch 块捕获异常,并根据具体情况进行适当的处理。可以采取记录日志、通知用户、进行重试等措施。
  2. 向上传播:如果无法在当前位置处理异常,可以将异常向上传播给调用者,让更上层的代码来决定如何处理。这样可以确保异常得到适当的关注和处理。
  3. 声明抛出:如果一个方法可能抛出异常,并且无法在方法内部处理,可以在方法签名中声明抛出该异常,让调用者知道需要处理这个异常。

三、注意事项

  1. 避免空 catch 块:不要使用空的 catch 块来忽略异常。空 catch 块会使异常完全被忽略,无法进行任何处理或记录。
  2. 考虑异常的类型:不同类型的异常可能需要不同的处理方式。要根据异常的具体类型和含义来进行合理的处理。
  3. 记录异常信息:即使无法完全处理异常,也应该记录异常的相关信息,以便在后续的调试和维护中能够了解问题的发生情况。

四、总结

在编写 Java 代码时,不要忽略异常。异常是程序运行过程中出现问题的重要指示,应该被认真对待和处理。通过捕获、向上传播或声明抛出异常,可以确保异常得到适当的关注和处理,提高程序的稳定性和可靠性。同时,要避免使用空 catch 块,并根据异常的类型进行合理的处理和记录异常信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值