处理Java异常时应避免的7个常见错误

1539914032(1).jpg

处理异常是最常见但不一定是最简单的任务之一。它仍然是经验丰富的团队中经常讨论的主题之一,您应该了解一些最佳实践和常见错误。

在处理应用程序中的异常时,您应该避免以下几点。

错误1:指定java.lang.Exceptionjava.lang.Throwable

正如我在之前的一篇文章中所解释的那样,您需要指定或处理已检查的异常。但是,检查的异常不是您可以指定的唯一异常。您可以在throws子句中使用java.lang.Throwable的任何子类。因此,您可以在throws子句中使用java.lang.Exception,而不是指定由以下代码段引发的两个不同的异常。

 

但这并不意味着你应该这样做。指定ExceptionThrowable使得在调用方法时几乎不可能正确处理它们。

您的方法调用者获得的唯一信息是可能出错的地方。但是,您不会共享有关可能发生的异常事件类型的任何信息。您将此信息隐藏在非特定的throws子句后面。

当您的应用程序随时间变化时,情况会更糟。非特定throws子句隐藏了调用者必须预期和处理的异常的所有更改。这可能会导致您需要通过测试用例而不是编译器错误找到的几个意外错误。

使用特定的类

因此,即使必须使用多个异常类,也要更好地指定最具体的异常类。这告诉调用者您的方法需要处理哪些异常事件。它还允许您在方法引发其他异常时更新throws子句。因此,如果您更改了throws子句,您的客户就会知道更改,甚至会出错。这比仅在运行特定测试用例时显示的异常更容易查找和处理。

 

错误2:捕获非特异性异常

此错误的严重性取决于您正在实施的软件组件的类型以及捕获异常的位置。在Java SE应用程序的main方法中捕获java.lang.Exception可能没问题。但是,如果您正在实现库或者正在处理应用程序的更深层,那么您应该更喜欢捕获特定的异常。

这提供了几个好处。它允许您以不同方式处理每个异常类,并且它可以防止您捕获您不期望的异常。

但请记住,处理异常类或其中一个超类的第一个catch块将捕获它。因此,请务必先抓住最具体的课程。否则,您的IDE将显示错误或警告消息,告知您无法访问的代码块

 





错误3:记录并抛出异常

这是处理Java异常时最常见的错误之一。记录抛出异常的位置然后将其重新抛出给可以实现特定于用例的处理的调用者似乎是合乎逻辑的。但是你不应该出于以下三个原因:

  1. 您没有足够的有关您的方法的调用者想要实现的用例的信息。该异常可能是预期行为的一部分,由客户端处理。在这种情况下,可能不需要记录它。这只会向您的日志文件添加错误的错误消息,需要您的运营团队进行过滤。
  2. 日志消息不提供任何尚未成为异常本身的信息。其消息和堆栈跟踪应提供有关异常事件的所有相关信息。消息描述了它,堆栈跟踪包含有关它发生的类,方法和行的详细信息。
  3. 当您在捕获它的每个catch块中记录它时,您可能会多次记录同一个异常。这会破坏监视工具中的统计信息,并使您的操作和开发团队更难阅读日志文件。
处理它时记录它

因此,最好只在处理异常记录异常。就像在下面的代码片段中一样。该DoSomething的方法抛出异常。该doMore法只是规定了它,因为开发商没有足够的信息来处理它。然后在doEvenMore方法中处理它,该方法也写入日志消息。

 

错误4:使用异常来控制流量

使用异常来控制应用程序的流程被认为是一种反模式,主要有两个原因:

  1. 它们基本上像Go To语句一样工作,因为它们取消了代码块的执行并跳转到处理异常的第一个catch块。这使代码很难阅读。
  2. 它们不如Java的通用控制结构有效。正如其名称所示,您应该仅将它们用于异常事件,并且JVM不会以与其他代码相同的方式优化它们。

因此,最好使用适当的条件来破坏循环或if-else-statements来决定应该执行哪些代码块。





错误5:删除异常的原因

有时您可能希望将异常包装在另一个异常中。也许您的团队决定使用自定义业务异常,错误代码和统一处理。只要你不删除原因,这种方法没有任何问题。

实例化新异常时,应始终将捕获的异常设置为其原因。否则,您将丢失描述导致异常的异常事件的消息和堆栈跟踪。该异常类及其所有子类提供了几个构造方法,其接受原始异常作为参数,并将其设置为的原因。

 

错误6:概括例外情况

概括异常时,捕获特定的异常,如NumberFormatException,并抛出非特定的java.lang.Exception。这与我在这篇文章中描述的第一个错误相似甚至更糟。它不仅隐藏了有关API上特定错误情况的信息,而且还使其难以访问。

 

正如您在下面的代码片段中所看到的,即使您知道方法可能抛出哪些异常,也不能简单地捕获它们。您需要捕获通用的Exception类,然后检查其原因的类型。这段代码不仅实现起来很麻烦,而且难以阅读。如果将此方法与错误5结合使用会更糟。这会删除有关异常事件的所有信息。

 

那么,更好的方法是什么?

要具体并保持原因

这很容易回答。您抛出的异常应始终尽可能具体。如果您包装异常,您还应该将原始异常设置为原因,这样您就不会丢失堆栈跟踪和描述异常事件的其他信息。

 

错误7:添加不必要的异常转换

正如我之前解释的那样,只要将原始异常设置为原因,将异常包装到自定义异常中会很有用。但是一些架构师过度使用它并为每个架构层引入了一个自定义异常类。因此,它们在持久层中捕获异常并将其包装到MyPersistenceException中。业务层在MyBusinessException中捕获并包装它,并且这一直持续到它到达API层或被处理。

 

很容易看出这些额外的异常类没有提供任何好处。他们只是引入了包装异常的其他层。虽然将礼物包装在很多彩色纸中可能很有趣,但它在软件开发中并不是一个好方法。




确保添加信息

当您需要找到导致异常的问题时,只需考虑需要处理异常的代码或您自己。您首先需要挖掘几层异常以找到原始原因。直到今天,我从未见过使用此方法的应用程序,并为每个异常层添加了有用的信息。它们要么概括错误消息和代码,要么提供冗余信息。

因此,请注意您引入的自定义异常类的数量。您应该始终问自己,新的异常类是否提供任何其他信息或其他好处。在大多数情况下,您不需要多层自定义异常即可实现此目的。

 

有关Java异常的更多信息

正如您所看到的,在处理Java异常时,您应该尝试避免几个常见错误。这有助于您避免常见错误并实现维护。