处理 Java 异常时应避免的 7 个常见错误
admin
2024-03-29 02:54:06
0

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

以下是在处理应用程序中的异常时应避免的几个错误。

错误 1:指定一个 java.lang.Exception 或 java.lang.Throwable

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

public void doNotSpecifyException() throws Exception {doSomething();
}public void doSomething() throws NumberFormatException, IllegalArgumentException {// do something
}

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

!> 我们的方法的调用者获得的唯一信息是可能出了问题。 但我们不会分享有关可能发生的异常事件类型的任何信息。 将此信息隐藏在一个不明确的 throws 子句后面。

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

使用特定类

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

public void specifySpecificExceptions() throws NumberFormatException, IllegalArgumentException {doSomething();
}

错误 2:捕获不特定的异常

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

!> 这提供了几个好处。 它允许我们以不同的方式处理每个异常类,并防止我们捕获没有预料到的异常。

%> 但请记住 ,处理异常类或其超类之一的第一个 catch 块将捕获它。 所以,一定要先上最具体的类。 否则,我们的 IDE 将显示错误或警告消息,告诉我们无法访问代码块。

try {doSomething();
} catch (NumberFormatException e) {// handle the NumberFormatExceptionlog.error(e);
} catch (IllegalArgumentException e) {// handle the IllegalArgumentExceptionlog.error(e);
}

错误 3:记录并抛出异常

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

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

处理时记录下来

因此,最好只在处理异常时记录异常。 就像在下面的代码片段中一样。 doSomething 方法抛出异常。 doMore 方法只是指定它,因为开发人员没有足够的信息来处理它。 然后它在 doEvenMore 方法中得到处理,该方法还写入一条日志消息。

public void doEvenMore() {try {doMore();} catch (NumberFormatException e) {// handle the NumberFormatException} catch (IllegalArgumentException e) {// handle the IllegalArgumentException}
}public void doMore() throws NumberFormatException, IllegalArgumentException {doSomething();
}public void doSomething() throws NumberFormatException, IllegalArgumentException {// do something
}

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

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

  1. 它们基本上像 Go To 语句一样工作,因为它们取消代码块的执行并跳转到处理异常的第一个 catch 块。 这使得代码很难阅读。
  2. 它们不如 Java 的通用控制结构高效。 顾名思义,我们应该只将它们用于异常事件,并且 JVM 不会像其他代码那样优化它们。

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

错误 5:删除异常的原始原因

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

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

try {doSomething();
} catch (NumberFormatException e) {throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}

错误 6:泛化异常

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

public void doNotGeneralizeException() throws Exception {try {doSomething();} catch (NumberFormatException e) {throw new Exception(e);} catch (IllegalArgumentException e) {throw new Exception(e);}
}

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

try {doNotGeneralizeException();
} catch (Exception e) {if (e.getCause() instanceof NumberFormatException) {log.error("NumberFormatException: " + e);} else if (e.getCause() instanceof IllegalArgumentException) {log.error("IllegalArgumentException: " + e);} else {log.error("Unexpected exception: " + e);}
}

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

具体并保留原因

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

try {doSomething();
} catch (NumberFormatException e) {throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}

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

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

public void persistCustomer(Customer c) throws MyPersistenceException {// persist a Customer
}public void manageCustomer(Customer c) throws MyBusinessException {// manage a Customertry {persistCustomer(c);} catch (MyPersistenceException e) {throw new MyBusinessException(e, e.getCode()); }
}public void createCustomer(Customer c) throws MyApiException {// create a Customertry {manageCustomer(c);} catch (MyBusinessException e) {throw new MyApiException(e, e.getCode()); }
}

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

确保添加信息

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

!> 因此 ,请注意我们引入的自定义异常类的数量。我们应该经常问自己新的异常类是否提供了任何附加信息或其他好处。在大多数情况下,我们不需要超过一层的自定义异常即可实现。

public void persistCustomer(Customer c) {// persist a Customer
}public void manageCustomer(Customer c) throws MyBusinessException {// manage a Customerthrow new MyBusinessException(e, e.getCode()); 
}public void createCustomer(Customer c) throws MyBusinessException {// create a CustomermanageCustomer(c);
}

有关 Java 异常的更多信息

如大家所见,在处理 Java 异常时应尽量避免一些常见错误。 这有助于我们避免常见错误并实施易于维护和在生产中监控的应用程序。

如果这份常见错误快速列表有用,大家还应该看看我们的最佳实践这篇文章。 它为大家提供了一个建议列表,大多数软件开发团队都使用这些建议来实施他们的异常处理并避免出现本文中描述的问题。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...