Java 异常处理的最佳实践

在 Java 开发中,异常处理是一个非常重要的环节。良好的异常处理实践可以提高代码的健壮性、可读性和可维护性。本文将介绍 20 个异常处理的最佳实践,帮助你在实际开发中避免常见的陷阱。

1. 尽量不要捕获 RuntimeException

阿里出品的 Java 开发手册上规定:尽量不要捕获 RuntimeException,比如 NullPointerExceptionIndexOutOfBoundsException 等,应该用预检查的方式来规避。

正例:

if (obj != null) {//...
}

反例:

try { obj.method(); 
} catch (NullPointerException e) {//...
}

2. 尽量使用 try-with-resource 来关闭资源

当需要关闭资源时,尽量不要使用 try-catch-finally,禁止在 try 块中直接关闭资源。

反例:

public void doNotCloseResourceInTry() {FileInputStream inputStream = null;try {File file = new File("./tmp.txt");inputStream = new FileInputStream(file);inputStream.close();} catch (FileNotFoundException e) {log.error(e);} catch (IOException e) {log.error(e);}
}

正例:

public void automaticallyCloseResource() {File file = new File("./tmp.txt");try (FileInputStream inputStream = new FileInputStream(file)) {} catch (FileNotFoundException e) {log.error(e);} catch (IOException e) {log.error(e);}
}

3. 不要捕获 Throwable

Throwableexceptionerror 的父类,如果在 catch 子句中捕获了 Throwable,可能会把超出程序处理能力之外的错误也捕获了。

反例:

public void doNotCatchThrowable() {try {} catch (Throwable t) {// 不要这样做}
}

4. 不要省略异常信息的记录

很多时候,由于疏忽大意,我们很容易捕获了异常却没有记录异常信息,导致程序上线后真的出现了问题却没有记录可查。

反例:

public void doNotIgnoreExceptions() {try {} catch (NumberFormatException e) {// 没有记录异常}
}

正例:

public void logAnException() {try {} catch (NumberFormatException e) {log.error("哦,错误竟然发生了: " + e);}
}

5. 不要记录了异常又抛出了异常

这纯属画蛇添足,并且容易造成错误信息的混乱。

反例:

try {
} catch (NumberFormatException e) {log.error(e);throw e;
}

正例:

try {
} catch (NumberFormatException e) {throw e;
}

6. 不要在 finally 块中使用 return

try 块中的 return 语句执行成功后,并不会马上返回,而是继续执行 finally 块中的语句,如果 finally 块中也存在 return 语句,那么 try 块中的 return 就将被覆盖。

反例:

private int x = 0;
public int checkReturn() {try {return ++x;} finally {return ++x;}
}

7. 抛出具体定义的检查性异常而不是 Exception

一定要避免出现下面的代码,它破坏了检查性(checked)异常的目的。声明的方法应该尽可能抛出具体的检查性异常。

反例:

public void foo() throws Exception { //错误方式
}

正例:

public void foo() throws SQLException { //正确方式
}

8. 捕获具体的子类而不是捕获 Exception 类

如果在 catch 块中捕获 Exception 类型的异常,会将所有异常都捕获,从而可能会给程序带来不必要的麻烦。

反例:

try {someMethod();
} catch (Exception e) { //错误方式LOGGER.error("method has failed", e);
}

正例:

try {// 读取数据的代码
} catch (FileNotFoundException e) {// 处理文件未找到异常的代码
} catch (IOException e) {// 处理输入输出异常的代码
}

9. 自定义异常时不要丢失堆栈跟踪

捕获异常时,不要破坏原始异常的堆栈跟踪。

反例:

catch (NoSuchMethodException e) {throw new MyServiceException("Some information: " + e.getMessage());  //错误方式
}

正例:

catch (NoSuchMethodException e) {throw new MyServiceException("Some information: " , e);  //正确方式
}

10. finally 块中不要抛出任何异常

finally 块用于定义一段代码,无论 try 块中是否出现异常,都会被执行。如果在 finally 块中抛出异常,可能会导致原始异常被掩盖。

反例:

try {someMethod();  //Throws exceptionOne
} finally {cleanUp();    //如果finally还抛出异常,那么exceptionOne将永远丢失
}

11. 不要在生产环境中使用 printStackTrace()

在生产环境中,应该使用日志系统来记录异常信息,例如 log4jslf4jlogback 等。

反例:

try {// some code
} catch (Exception e) {e.printStackTrace();
}

正例:

try {// some code
} catch (Exception e) {logger.error("An error occurred: ", e);
}

12. 对于不打算处理的异常,直接使用 try-finally,不用 catch

如果 method1 正在访问 Method 2,而 Method 2 抛出一些你不想在 Method 1 中处理的异常,但是仍然希望在发生异常时进行一些清理,可以直接在 finally 块中进行清理,不要使用 catch 块。

正例:

try {method1();  // 会调用 Method 2
} finally {cleanUp();    //do cleanup here
}

13. 记住早 throw 晚 catch 原则

“早 throw, 晚 catch” 是 Java 中的一种异常处理原则。这个原则指的是在代码中尽可能早地抛出异常,以便在异常发生时能够及时地处理异常。同时,在 catch 块中尽可能晚地捕获异常,以便在捕获异常时能够获得更多的上下文信息,从而更好地处理异常。

正例:

public static int parseInt(String str) {if (str == null || "".equals(str)) {throw new NullPointerException("字符串为空");}if (!str.matches("\\d+")) {throw new NumberFormatException("字符串不是数字");}return Integer.parseInt(str);
}

14. 只抛出和方法相关的异常

相关性对于保持代码的整洁非常重要。一种尝试读取文件的方法,如果抛出 NullPointerException,那么它不会给用户提供有价值的信息。相反,如果这种异常被包裹在自定义异常中,则会更好。NoSuchFileFoundException 则对该方法的用户更有用。

正例:

public static int divide(int a, int b) throws ArithmeticException {if (b == 0) {throw new ArithmeticException("Division by zero");}return a / b;
}

15. 切勿在代码中使用异常来进行流程控制

在代码中使用异常来进行流程控制会导致代码的可读性、可维护性和性能出现问题。

反例:

public class Demo {public static void main(String[] args) {String input = "1,2,3,a,5";String[] values = input.split(",");for (String value : values) {try {int num = Integer.parseInt(value);System.out.println(num);} catch (NumberFormatException e) {System.err.println(value + " is not a valid number");}}}
}

16. 尽早验证用户输入以在请求处理的早期捕获异常

在用户注册的业务中,如果按照这样来做:

验证用户
插入用户
验证地址
插入地址
如果出问题回滚一切

这是不正确的做法,它会使数据库在各种情况下处于不一致的状态,应该首先验证所有内容,然后再进行数据库更新。正确的做法是:

验证用户
验证地址
插入用户
插入地址
如果问题回滚一切

正例:

Connection conn = null;
try {// Connect to the databaseconn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");// Start a transactionconn.setAutoCommit(false);// Validate user inputvalidateUserInput();// Insert user datainsertUserData(conn);// Validate address inputvalidateAddressInput();// Insert address datainsertAddressData(conn);// Commit the transaction if everything is successfulconn.commit();} catch (SQLException e) {// Rollback the transaction if there is an errorif (conn != null) {try {conn.rollback();} catch (SQLException ex) {System.err.println("Error: " + ex.getMessage());}}System.err.println("Error: " + e.getMessage());
} finally {// Close the database connectionif (conn != null) {try {conn.close();} catch (SQLException e) {System.err.println("Error: " + e.getMessage());}}
}

17. 一个异常只能包含在一个日志中

不要这样做:

log.debug("Using cache sector A");
log.debug("Using retry sector B");

在单线程环境中,这样看起来没什么问题,但如果在多线程环境中,这两行紧挨着的代码中间可能会输出很多其他的内容,导致问题查起来会很难受。应该这样做:

LOGGER.debug("Using cache sector A, using retry sector B");

18. 将所有相关信息尽可能地传递给异常

有用的异常消息和堆栈跟踪非常重要,如果你的日志不能定位异常位置,那要日志有什么用呢?

正例:

LOGGER.debug("Error reading file", e);

应该尽量把 String message, Throwable cause 异常信息和堆栈都输出。

19. 终止掉被中断线程

InterruptedException 提示应该停止程序正在做的事情,比如事务超时或线程池被关闭等。

反例:

while (true) {try {Thread.sleep(100000);} catch (InterruptedException e) {} //别这样做doSomethingCool();
}

正例:

while (true) {try {Thread.sleep(100000);} catch (InterruptedException e) {break;}
}
doSomethingCool();

20. 对于重复的 try-catch,使用模板方法

类似的 catch 块是无用的,只会增加代码的重复性,针对这样的问题可以使用模板方法。

正例:

class DBUtil{public static void closeConnection(Connection conn){try{conn.close();} catch(Exception ex){//Log Exception - Cannot close connection}}
}

然后在其他地方使用:

public void dataAccessCode() {Connection conn = null;try{conn = getConnection();....} finally{DBUtil.closeConnection(conn);}
}

总结

异常处理是 Java 开发中不可或缺的一部分。通过遵循上述最佳实践,你可以编写出更加健壮、可读性更高、易于维护的代码。在实际开发中,不断积累经验,灵活运用这些原则,将有助于你更好地处理异常情况,提高代码的质量和稳定性。

思维导图

在这里插入图片描述

参考链接

Java异常处理的20个最佳实践,别再踩坑了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/465732.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统上云-流量分析和链路分析

优质博文:IT-BLOG-CN 一、流量分析 【1】流量组成: 按协议划分,流量链路可分为HTTP、SOTP、QUIC三类。 HTTPSOTPQUIC场景所有HTTP请求,无固定场景国内外APP等海外APP端链路选择DNS/CDN(当前特指Akamai)APP端保底IP列表/动态IP下…

Linux网络编程之UDP编程

UDP编程效率高,不需要差错校验,在视频点播场景应用高 基于UDP协议客户端和服务端的编程模型,和TCP模型有点类似,有些发送接收函数不同,TCP是之间调用I/O函数read0或write()进行读写操作,而UDP是用sendto()和readfrom(…

【C++动态规划 01背包】2787. 将一个数字表示成幂的和的方案数|1817

本文涉及知识点 C动态规划 C背包问题 LeetCode2787. 将一个数字表示成幂的和的方案数 给你两个 正 整数 n 和 x 。 请你返回将 n 表示成一些 互不相同 正整数的 x 次幂之和的方案数。换句话说,你需要返回互不相同整数 [n1, n2, …, nk] 的集合数目,满…

服务器开放了mongodb数据库的外网端口,但是用mongodbCompass还是无法连接。

数据库的配置文件中有个bingIp也就是绑定固定的ip才能访问数据库,默认是127.0.0.1也就是只能本地访问,所以无法连接。设置为0.0.0.0则表示所有地址都能访问。 最后再确定一下防火墙的端口是否正常开放

《Linux运维总结:基于银河麒麟V10+ARM64架构CPU部署redis 6.2.14 TLS/SSL哨兵集群》

总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:《Linux运维篇:Linux系统运维指南》 一、简介 Redis 哨兵模式是一种高可用性解决方案,它通过监控 Redis 主从架构,自动执行故障转移,从而确保服务的连续性。哨兵模式的核心组件包括哨兵(Sentine…

JDK1.5 java代码打包jar HmacSha256

文章目录 demo地址背景实现编写代码编译class文件打包 JAR 文件执行生成的 JAR 文件辅助验证方式 常见问题和解决方法常规生成jar方案maven插件idea工具 demo地址 https://github.com/xiangge-zx/HmacSha256 背景 最近接到一个需求,做一个可以用来HmacSha256加密的小工具&am…

数据结构与算法分析:专题内容——动态规划1之理论讲解(代码详解+万字长文+算法导论+力扣题)

一、前言 The future is independent of the past given the present 未来独立于过去,只基于当下。 马尔可夫链,即状态空间中经过从一个状态到另一个状态的转换的随机过程。 0 在开始之前请认真理解下面这段话: 决策类求最优解问题&#xf…

25.停车场管理系统(基于web的Java项目)

目录 1.系统的受众说明 2.相关技术与方法 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 需求分析 3.2.1 系统功能描述 3.2.2 用例图分析 4. 系统设计 4.1 系统类分析 5. 系统详细设计与实现 5.1 用户登录 5.2 系统信…

【网络】自定义协议——序列化和反序列化

> 作者:დ旧言~ > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:了解什么是序列化和分序列,并且自己能手撕网络版的计算器。 > 毒鸡汤:有些事情,总是不明白,所以我不…

.NET 8 中 Entity Framework Core 的使用

本文代码:https://download.csdn.net/download/hefeng_aspnet/89935738 概述 Entity Framework Core (EF Core) 已成为 .NET 开发中数据访问的基石工具,为开发人员提供了强大而多功能的解决方案。随着 .NET 8 和 C# 10 中引入的改进,开发人…

Docker学习—Docker的安装与使用

Docker安装 1.卸载旧版 首先如果系统中已经存在旧的Docker,则先卸载: yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine2.配置Docker的yum库 首先…

ArcGIS006:ArcMap常用操作151-200例动图演示

摘要:本文介绍了ArcMap邻域分析、栅格表面分析、水文分析、区域分析、提取分析等工具箱中的工具功能。包括计算图层间点、线、面要素间的距离、位置和角度,创建缓冲区,添加计算信息到属性表,分割面要素,编号&#xff0…

小菜家教平台(三):基于SpringBoot+Vue打造一站式学习管理系统

目录 前言 今日进度 详细过程 相关知识点 前言 昨天重构了数据库并实现了登录功能,今天继续进行开发,创作不易,请多多支持~ 今日进度 添加过滤器、实现登出功能、实现用户授权功能校验 详细过程 一、添加过滤器 自定义过滤器作用&…

SQL进阶技巧:如何计算复合增长率?

目录 0 场景描述 1 数据准备 2 问题分析 3 小结 0 场景描述 复合增长率是第N期的数据除以第一期的基准数据,然后开N-1次方再减去1得到的结果。假如2018年的产品销售额为10000,2019年的产品销售额为12500,2020年的产品销售额为15000&…

python-docx -- 读取word图片

文章目录 概念介绍形状对象读取图片自定义图形 概念介绍 从概念上来讲,word文档分为两层,一个文本层,一个绘画层; 文本层,从上到下,从左到右,流式排版,本页填满则开启新页面&#…

Python数据可视化seaborn

产品经理在做数据分析时可能需要通过可视化来分析。seaborn官网 1. relplot 散点图 https://seaborn.pydata.org/examples/scatterplot_sizes.html import pandas as pd import seaborn as sns df pd.DataFrame({x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],y: [8, 6, 7, 8, 4, 6,…

释放专利力量:Patently 如何利用向量搜索和 NLP 简化协作

作者:来自 Elastic Matt Scourfield, Andrew Crothers, Brian Lambert 组织依靠知识产权 (IP) 来推动创新、保持竞争优势并创造收入来源。对于希望将新产品推向市场的公司来说,弄清楚谁拥有哪些专利是一项必不可少的能力。搜索数百万项专利可能既困难又耗…

协议栈攻击分类(CISP-PTE笔记)

CISP-PTE笔记 协议栈攻击分类 1.协议栈自身的脆弱性 ​ 1)缺乏数据源验证机制 ​ 2)缺乏完整性验证机制 ​ 3)缺乏机密性验证机制 2.网络接口层攻击 3.网络层攻击 4.应用层攻击 网络攻击的基本模式 被动威胁(不影响通信双…

SpringBoot3集成Junit5

目录 1. 确保项目中包含相关依赖2. 配置JUnit 53. 编写测试类4、Junit5 新增特性4.1 注解4.2 断言4.3 嵌套测试4.4 总结 在Spring Boot 3中集成JUnit 5的步骤相对简单。以下是你可以按照的步骤: 1. 确保项目中包含相关依赖 首先,确保你的pom.xml文件中…

智慧城市智慧城市项目方案-大数据平台建设技术方案(原件Word)

第1章 总体说明 1.1 建设背景 1.2 建设目标 1.3 项目建设主要内容 1.4 设计原则 第2章 对项目的理解 2.1 现状分析 2.2 业务需求分析 2.3 功能需求分析 第3章 大数据平台建设方案 3.1 大数据平台总体设计 3.2 大数据平台功能设计 3.3 平台应用 第4章 政策标准保障…