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

2023-10-29

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

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

错误 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 NumberFormatException
	log.error(e);
} catch (IllegalArgumentException e) {
	// handle the IllegalArgumentException
	log.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 Customer
	try {
		persistCustomer(c);
	} catch (MyPersistenceException e) {
		throw new MyBusinessException(e, e.getCode()); 
	}
}

public void createCustomer(Customer c) throws MyApiException {
	// create a Customer
	try {
		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 Customer
	
	throw new MyBusinessException(e, e.getCode()); 
}

public void createCustomer(Customer c) throws MyBusinessException {
	// create a Customer
	manageCustomer(c);
}

有关 Java 异常的更多信息

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

处理 Java 异常时应避免的 7 个常见错误 的相关文章

  • Gradle 构建错误:无法从 https://repo1.maven.org/maven2/io/fabric/tools/gradle/maven-metadata.xml 加载 Maven 元数据

    我在 Android studio 中遇到 gradle 构建错误 如下所示 Error A problem occurred configuring project MyApp Could not resolve all dependen
  • 如何将本机库链接到 IntelliJ 中的 jar?

    我正在尝试在 IntelliJ 中设置 OpenCV 但是我一直在弄清楚如何告诉 IntelliJ 在哪里可以找到本机库位置 在 Eclipse 中 添加 jar 后 您可以在 Build Config 屏幕中设置 Native 库的位置
  • 在 Java 中克隆对象 [3 个问题]

    这样做会调用Asub的clone方法吗 或者Asub深度克隆是否正确 如果没有的话 有没有办法通过这种方法对Asub进行深度克隆呢 abstract class Top extends TopMost protected Object cl
  • 不同帐户上的 Spring Boot、JmsListener 和 SQS 队列

    我正在尝试开发一个 Spring Boot 1 5 应用程序 该应用程序需要侦听来自两个不同 AWS 帐户的 SQS 队列 是否可以使用 JmsListener 注解创建监听器 我已检查权限是否正确 我可以使用 getQueueUrl 获取
  • Mockito:如何通过模拟测试我的服务?

    我是模拟测试新手 我想测试我的服务方法CorrectionService correctPerson Long personId 实现尚未编写 但这就是它将执行的操作 CorrectionService将调用一个方法AddressDAO这将
  • 如何通过 javaconfig 使用 SchedulerFactoryBean.schedulerContextAsMap

    我使用 Spring 4 0 并将项目从 xml 移至 java config 除了访问 Service scheduleService 带注释的类来自QuartzJobBean executeInternal 我必须让它工作的 xml 位
  • 如何在java中将一个数组列表替换为另一个不同大小的数组列表

    我有两个大小不同的数组列表 如何从此替换 ArrayList
  • 如何获取之前的URL?

    我需要调用我的网络应用程序的 URL 例如 如果有一个从 stackoverflow com 到我的网站 foo com 的链接 我需要 Web 应用程序 托管 bean 中的 stackoverflow 链接 感谢所有帮助 谢谢 并不总是
  • jQuery AJAX 调用 Java 方法

    使用 jQuery AJAX 我们可以调用特定的 JAVA 方法 例如从 Action 类 该 Java 方法返回的数据将用于填充一些 HTML 代码 请告诉我是否可以使用 jQuery 轻松完成此操作 就像在 DWR 中一样 此外 对于
  • Java 公历日历更改时区

    我正在尝试设置 HOUR OF DAY 字段并更改 GregorianCalendar 日期对象的时区 GregorianCalendar date new GregorianCalendar TimeZone getTimeZone GM
  • 从最终实体获取根证书和中间证书

    作为密码学的菜鸟 我每天都会偶然发现一些简单的事情 今天只是那些日子之一 我想用 bouncy castle 库验证 java 中的 smime 消息 我想我几乎已经弄清楚了 但此时的问题是 PKIXparameters 对象的构建 假设我
  • 没有 Spring 的自定义 Prometheus 指标

    我需要为 Web 应用程序提供自定义指标 问题是我不能使用 Spring 但我必须使用 jax rs 端点 要求非常简单 想象一下 您有一个包含键值对的映射 其中键是指标名称 值是一个简单的整数 它是一个计数器 代码会是这样的 public
  • 将流转换为 IntStream

    我有一种感觉 我在这里错过了一些东西 我发现自己做了以下事情 private static int getHighestValue Map
  • 内部类的构造函数引用在运行时失败并出现VerifyError

    我正在使用 lambda 为内部类构造函数创建供应商ctx gt new SpectatorSwitcher ctx IntelliJ建议我将其更改为SpectatorSwitcher new反而 SpectatorSwitcher 是我正
  • 尝试将 Web 服务部署到 TomEE 时出现“找不到...的 appInfo”

    我有一个非常简单的项目 用于培训目的 它是一个 RESTful Web 服务 我使用 js css 和 html 创建了一个客户端 我正在尝试将该服务部署到 TomEE 这是我尝试部署时遇到的错误 我在这里做错了什么 刚刚遇到这个问题 我曾
  • 为什么 Java 8 不允许非公共默认方法?

    让我们举个例子 public interface Testerface default public String example return Hello public class Tester implements Testerface
  • Eclipse 选项卡宽度不变

    我浏览了一些与此相关的帖子 但它们似乎并不能帮助我解决我的问题 我有一个项目 其中 java 文件以 2 个空格的宽度缩进 我想将所有内容更改为 4 空格宽度 我尝试了 正确的缩进 选项 但当我将几行修改为 4 空格缩进时 它只是将所有内容
  • 使用 AsyncTask 传递值

    我一直在努力解决这个问题 但我已经到了不知道该怎么办的地步 我想做的是使用一个类下载文件并将其解析为字符串 然后将该字符串发送到另一个类来解析 JSON 内容 所有部件都可以单独工作 并且我已经单独测试了所有部件 我只是不知道如何将值发送到
  • 使用反射覆盖最终静态字段是否有限制?

    在我的一些单元测试中 我在最终静态字段上的反射中遇到了奇怪的行为 下面是说明我的问题的示例 我有一个基本的 Singleton 类 其中包含一个 Integer public class BasicHolder private static
  • 双枢轴快速排序和快速排序有什么区别?

    我以前从未见过双枢轴快速排序 是快速排序的升级版吗 双枢轴快速排序和快速排序有什么区别 我在 Java 文档中找到了这个 排序算法是双枢轴快速排序 作者 弗拉基米尔 雅罗斯拉夫斯基 乔恩 本特利和约书亚 布洛赫 这个算法 在许多数据集上提供

随机推荐

  • 网盘系统

    作者主页 编程千纸鹤 作者简介 Java 前端 Pythone开发多年 做过高程 项目经理 架构师 主要内容 Java项目开发 毕业设计开发 面试技术整理 最新技术分享 收藏点赞不迷路 关注作者有好处 文末获得源码 项目编号 BS XX 1
  • CTFhub_SSRF靶场教程

    CTFhub SSRF 题目 1 Bypass 1 1 URL Bypass 请求的URL中必须包含http notfound ctfhub com 来尝试利用URL的一些特殊地方绕过这个限制吧 1 利用 绕过限制url https www
  • http post 方法传递参数的2种方式

    try HttpPost httpPost new HttpPost url StringEntity stringEntity new StringEntity param param参数 可以为 key1 value1 key2 val
  • 什么是人力资源360度评估法?

    1 360度评估法的定义 360度评估法 360 Feedback 又称 360度考核法 或 全方位考核法 属于人力资源中绩效考核方法之一 是指由员工自己 上司 直接部属 同仁同事甚至顾客或家人等从全方位 各个角度来评估人员的方法 而且 为
  • java注解与反射的基本使用(这一篇就够了!)

    一 注解 Annotation 1 什么是注解 相信大家对注解应该并不陌生 在现在信息飞速发展的年代 各种优秀的框架或许都离不开注解的使用 像我们在实现接口一个方法时 也会有 Override注解 注解说白了就是对程序做出解释 与我们在方法
  • 【three.js】世界坐标系和设备坐标系

    three js 坐标系转换 简述 屏幕坐标转世界坐标 世界坐标转屏幕坐标 第三方 CSS2DRenderer 第三方 CSS3DRenderer 简述 物体的坐标转换过程大致为 局部坐标 gt 世界坐标 gt 观察空间坐标 gt 裁剪空间
  • vue项目流程demo示例

    前言 自己写从头做一个vue项目 没什么技术 主要是温顾流程 gt 没写完 只写到创建完项目 目录 1 准备工作 node 淘宝镜像 yarn vite IDE集成开发工具 2 创建项目 初始化项目 文件目录 3 全局设置 主题设置 全局变
  • python3,使用sys.setdefaultencoding('utf-8'),编译时报错

    借鉴博客 http blog csdn net fly910905 article details 74922378 正常情况下 我们在使用python做页面开发时 防止中文出现乱码问题 python2 情况下会使用 如下语句 import
  • ‘mkvirtualenv‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

    问题 mkvirtualenv 不是内部或外部命令 也不是可运行的程序 或批处理文件 或者 mkvirtualenv 无法将 mkvirtualenv 项识别为 cmdlet 函数 脚本文件或可运行程序的名称 请检查名称的拼写 如果包括路径
  • 华为HCIP云计算考证心得

    华为认证是社会认证中一种 现在很多公司也比较看重这个的 有的公司还会根据你考取证书的等级高低有工资加成 透露下我之前所在的公司 是华为的合作伙伴 就是华为代理公司把 持有HCIP证书可有每月有500的加成 IE的话1000 当然不同的公司也
  • php实现抽奖

    不啰嗦 直接上代码
  • Unity 轻量级对象池管理器(上)

    参考 https www jianshu com p 144181beab79 完整代码请跳转至 Unity 轻量级对象池管理器 下 一 前言 很多时候 你都要考虑一个问题 就是到底是牺牲时间换空间 还是牺牲空间换时间 特别是在资源紧缺的那
  • 前后端分离项目打包上传服务器

    前后端分离项目打包上传服务器 项目环境 前端项目打包 npm run build 后端项目打包 xshell和xftp 项目环境 本次项目前端使用的是vue 后端使用spring boot 分别使用的编辑器是vs code和ide 前端项目
  • Failed to load resource: the server responded with a status of 500 ()

    文章目录 前提 解决思路 心得 前提 1 最近在写SSM博客项目 基于jsp编写的 跟着视频敲 直接运行已经给好的资料 在登录验证的时候 发现页面加载半天 如下图 2 于是就盲目的去比对是不是自己复制的代码有问题 然后就一行行的比对 这犯了
  • Linux中磁盘分区的具体步骤

    磁盘分区是在Linux操作系统中重要的任务之一 通过分区可以让我们更好地管理磁盘空间 并更好地使用磁盘来存储数据和程序 在这篇博客里 我将向大家介绍如何在Linux中进行磁盘分区的实战步骤 步骤一 打开终端 首先 我们需要打开终端 在终端中
  • Linux系统巡检项及详细巡检方法与解决方案

    一 背景 1 开发脚本实现OS配置参数巡检 2 推动监控系统发布OS巡检插件 3 利用监控数据形成报表 二 巡检项整改方案解析 1 检查僵尸进程 此项检查项是检查主机系统是否存在D状态的进程 D是一种不可中断的sleep 如果发现D状态并且
  • [React Hooks 翻译] 3-8 State Hook

    示例 等价的class组件 使用class实现一个计数器 你可能会这么写 class Example extends React Component constructor props super props this state coun
  • 微信公众号 几种移动端UI框架介绍

    微信公众号 几种移动端UI框架介绍 微信公众号开发 主要是移动端网页的页面开发 在这里推荐3个移动端UI框架 WeUI SUI和Mint UI 1 WeUI 1 1 WeUI WeUI是微信官方设计团队为微信 Web 开发量身设计 可以令用
  • 从RecyclerView、NestedScrollView源码分析嵌套滑动异常

    一 显示不全 自动滚动异常 NestedScrollView嵌套RecyclerView时 有2个问题 1 RecyclerView数据加载完成后 会自动滚动到第一个itemView的位置上 导致RecyclerView上面的布局不显示 2
  • 处理 Java 异常时应避免的 7 个常见错误

    处理异常是最常见但不一定是最简单的任务之一 它仍然是经验丰富的团队中经常讨论的话题之一 并且我们应该了解一些最佳实践和常见错误 以下是在处理应用程序中的异常时应避免的几个错误 错误 1 指定一个 java lang Exception 或