如何优雅处理异常?处理异常的原则

前言

在我们日常工作中,经常会遇到一些异常,比如:NullPointerException、NumberFormatException、ClassCastException等等。

那么问题来了,我们该如何处理异常,让代码变得更优雅呢?

图片

1 不要忽略异常

不知道你有没有遇到过下面这段代码:

反例:

Long id = null;
try {id = Long.parseLong(keyword);
} catch(NumberFormatException e) {//忽略异常
}

用户输入的参数,使用Long.parseLong方法转换成Long类型的过程中,如果出现了异常,则使用try/catch直接忽略了异常。并且也没有打印任何日志。如果后面线上代码出现了问题,有点不太好排查问题。建议大家不要忽略异常,在后续的工作中,可能会带来很多麻烦。

正例:

Long id = null;
try {id = Long.parseLong(keyword);
} catch(NumberFormatException e) {log.info(String.format("keyword:{} 转换成Long类型失败,原因:{}",keyword , e))
}

后面如果数据转换出现问题,从日志中我们一眼就可以查到具体原因了。

2 使用全局异常处理器

有些经常喜欢在Service代码中捕获异常。不管是普通异常Exception,还是运行时异常RuntimeException,都使用try/catch把它们捕获。

反例:

try {checkParam(param);
} catch (BusinessException e) {return ApiResultUtil.error(1,"参数错误");
}

在每个Controller类中都捕获异常。在UserController、MenuController、RoleController、JobController等等,都有上面的这段代码。显然这种做法会造成大量重复的代码。我们在Controller、Service等业务代码中,尽可能少捕获异常。这种业务异常处理,应该交给拦截器统一处理。在SpringBoot中可以使用@RestControllerAdvice注解,定义一个全局的异常处理handler,然后使用@ExceptionHandler注解在方法上处理异常。

例如:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 统一处理异常** @param e 异常* @return API请求响应实体*/@ExceptionHandler(Exception.class)public ApiResult handleException(Exception e) {if (e instanceof BusinessException) {BusinessException businessException = (BusinessException) e;log.info("请求出现业务异常:", e);return ApiResultUtil.error(businessException.getCode(), businessException.getMessage());} log.error("请求出现系统异常:", e);return ApiResultUtil.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误,请联系系统管理员!");}}

有了这个全局的异常处理器,之前我们在Controller或者Service中的try/catch代码可以去掉。

如果在接口中出现异常,全局的异常处理器会帮我们封装结果,返回给用户。

3 尽可能捕获具体异常

在你的业务逻辑方法中,有可能需要去处理多种不同的异常。你可能你会觉得比较麻烦,而直接捕获Exception。

反例:

try {doSomething();
} catch(Exception e) {log.error("doSomething处理失败,原因:",e);
}

这样捕获异常太笼统了。其实doSomething方法中,会抛出FileNotFoundException和IOException。这种情况我们最好捕获具体的异常,然后分别做处理。

正例:

try {doSomething();
} catch(FileNotFoundException e) {log.error("doSomething处理失败,文件找不到,原因:",e);
} catch(IOException e) {log.error("doSomething处理失败,IO出现了异常,原因:",e);
}

这样如果后面出现了上面的异常,我们就非常方便知道是什么原因了。

4 在finally中关闭IO流

我们在使用IO流的时候,用完了之后,一般需要及时关闭,否则会浪费系统资源。我们需要在try/catch中处理IO流,因为可能会出现IO异常。

反例:

try {File file = new File("/tmp/1.txt");FileInputStream fis = new FileInputStream(file);byte[] data = new byte[(int) file.length()];fis.read(data);for (byte b : data) {System.out.println(b);}fis.close();
} catch (IOException e) {log.error("读取文件失败,原因:",e)
}

上面的代码直接在try的代码块中关闭fis。假如在调用fis.read方法时,出现了IO异常,则可能会直接抛异常,进入catch代码块中,而此时fis.close方法没办法执行,也就是说这种情况下,无法正确关闭IO流。

正例:

FileInputStream fis = null;
try {File file = new File("/tmp/1.txt");fis = new FileInputStream(file);byte[] data = new byte[(int) file.length()];fis.read(data);for (byte b : data) {System.out.println(b);} 
} catch (IOException e) {log.error("读取文件失败,原因:",e)
} finally {if(fis != null) {try {fis.close();fis = null;} catch (IOException e) {log.error("读取文件后关闭IO流失败,原因:",e)}}
}

在finally代码块中关闭IO流。但要先判断fis不为空,否则在执行fis.close()方法时,可能会出现NullPointerException异常。需要注意的地方时,在调用fis.close()方法时,也可能会抛异常,我们还需要进行try/catch处理。

5 多用try-catch-resource

前面在finally代码块中关闭IO流,还是觉得有点麻烦。因此在JDK7之后,出现了一种新的语法糖try-with-resource。上面的代码可以改造成这样的:

File file = new File("/tmp/1.txt");
try (FileInputStream fis = new FileInputStream(file)) {byte[] data = new byte[(int) file.length()];fis.read(data);for (byte b : data) {System.out.println(b);}
} catch (IOException e) {e.printStackTrace();log.error("读取文件失败,原因:",e)
}

try括号里头的FileInputStream实现了一个AutoCloseable接口,所以无论这段代码是正常执行完,还是有异常往外抛,还是内部代码块发生异常被截获,最终都会自动关闭IO流。我们尽量多用try-catch-resource的语法关闭IO流,可以少写一些finally中的代码。而且在finally代码块中关闭IO流,有顺序的问题,如果有多种IO,关闭的顺序不对,可能会导致部分IO关闭失败。而try-catch-resource就没有这个问题。

6 不在finally中return

我们在某个方法中,可能会有返回数据。

反例:

public int divide(int dividend, int divisor) {try {return dividend / divisor;} catch (ArithmeticException e) {// 异常处理} finally {return -1;}
}

上面的这个例子中,我们在finally代码块中返回了数据-1。这样最后在divide方法返回时,会将dividend / divisor的值覆盖成-1,导致正常的结果也不对。我们尽量不要在finally代码块中返回数据。

正解:

public int divide(int dividend, int divisor) {try {return dividend / divisor;} catch (ArithmeticException e) {// 异常处理return -1;}
}

如果dividend / divisor出现了异常,则在catch代码块中返回-1。

7 少用e.printStackTrace()

我们在本地开发中,喜欢使用e.printStackTrace()方法,将异常的堆栈跟踪信息输出到标准错误流中。

反例:

try {doSomething();
} catch(IOException e) {e.printStackTrace();
}

这种方式在本地确实容易定位问题。但如果代码部署到了生产环境,可能会带来下面的问题:

  1. 可能会暴露敏感信息,如文件路径、用户名、密码等。

  2. 可能会影响程序的性能和稳定性。

正解:

try {doSomething();
} catch(IOException e) {log.error("doSomething处理失败,原因:",e);
}

我们要将异常信息记录到日志中,而不是保留给用户。

8 异常打印详细一点

我们在捕获了异常之后,需要把异常的相关信息记录到日志当中。

反例:

try {double b = 1/0;
} catch(ArithmeticException e) {log.error("处理失败,原因:",e.getMessage());
}

这个例子中使用e.getMessage()方法返回异常信息。但执行结果为:

doSomething处理失败,原因:

这种情况异常信息根本没有打印出来。我们应该把异常信息和堆栈都打印出来。

正例:

try {double b = 1/0;
} catch(ArithmeticException e) {log.error("处理失败,原因:",e);
}

执行结果:

doSomething处理失败,原因:
java.lang.ArithmeticException: / by zeroat cn.net.susan.service.Test.main(Test.java:16)

将具体的异常,出现问题的代码和具体行数都打印出来。

9 别捕获了异常又马上抛出

有时候,我们为了记录日志,可能会对异常进行捕获,然后又抛出。

反例:

try {doSomething();
} catch(ArithmeticException e) {log.error("doSomething处理失败,原因:",e)throw e;
}

在调用doSomething方法时,如果出现了ArithmeticException异常,则先使用catch捕获,记录到日志中,然后使用throw关键抛出这个异常。这个骚操作纯属是为了记录日志。但最后发现日志记录两次。因为在后续的处理中,可能会将这个ArithmeticException异常又记录一次。这样就会导致日志重复记录了。

10 优先使用标准异常

在Java中已经定义了许多比较常用的标准异常,比如下面这张图中列出的这些异常:

图片

反例:

public void checkValue(int value) {if (value < 0) {throw new MyIllegalArgumentException("值不能为负");}
}

自定义了一个异常表示参数错误。其实,我们可以直接复用已有的标准异常。

正例:

public void checkValue(int value) {if (value < 0) {throw new IllegalArgumentException("值不能为负");}
}

11 对异常进行文档说明

我们在写代码的过程中,有一个好习惯是给方法、参数和返回值,增加文档说明。

反例:

/*  *  处理用户数据*  @param value 用户输入参数*  @return 值 */
public int doSomething(String value) throws BusinessException {//业务逻辑return 1;
}

这个doSomething方法,把方法、参数、返回值都加了文档说明,但异常没有加。

正解:

/*  *  处理用户数据*  @param value 用户输入参数*  @return 值*  @throws BusinessException 业务异常*/
public int doSomething(String value) throws BusinessException {//业务逻辑return 1;
}

抛出的异常,也需要增加文档说明。

12 别用异常控制程序的流程

我们有时候,在程序中使用异常来控制了程序的流程,这种做法其实是不对的。

反例:

Long id = null;
try {id = Long.parseLong(idStr);
} catch(NumberFormatException e) {id = 1001;
}

如果用户输入的idStr是Long类型,则将它转换成Long,然后赋值给id,否则id给默认值1001。每次都需要try/catch还是比较影响系统性能的。

正例:

Long id = checkValueType(idStr) ? Long.parseLong(idStr) : 1001;

我们增加了一个checkValueType方法,判断idStr的值,如果是Long类型,则直接转换成Long,否则给默认值1001。

13 自定义异常

如果标准异常无法满足我们的业务需求,我们可以自定义异常。

例如:

/*** 业务异常** @author rice* @date 2024/11/5*/
@AllArgsConstructor
@Data
public class BusinessException extends RuntimeException {public static final long serialVersionUID = -6735897190745766939L;/*** 异常码*/private int code;/*** 具体异常信息*/private String message;public BusinessException() {super();}public BusinessException(String message) {this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();this.message = message;}
}

对于这种自定义的业务异常,我们可以增加code和message这两个字段,code表示异常码,而message表示具体的异常信息。BusinessException继承了RuntimeException运行时异常,后面处理起来更加灵活。提供了多种构造方法。定义了一个序列化ID(serialVersionUID)。

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

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

相关文章

DBAPI连接阿里云 maxcompute 报错

使用正确的驱动包 访问以下链接寻找驱动包 https://github.com/aliyun/aliyun-odps-jdbc/releases/tag/v3.4.3 注意要使用odps-jdbc-3.4.3-jar-with-dependencies.jar &#xff0c;这个是完整的jar包 不要使用odps-jdbc-3.4.3.jar&#xff0c;这个不是完整的&#xff0c;它还…

2024最新Python安装教程+Pycharm安装教程【附安装包】

Python安装 1.首先下载好Python安装包 获取方式&#xff1a;点击这里&#xff08;扫描神秘②薇码免下载&#xff09;完全免费&#xff01;&#xff01;&#xff01; 2.打开安装包&#xff0c;先勾选最下面两个选项&#xff0c;再选择第二个自定义安装 3.这里默认全选&#xff…

【数据库】elasticsearch

1、架构 es会为每个索引创建一定数量的主分片和副本分片。 分片&#xff08;Shard&#xff09;&#xff1a; 将索引数据分割成多个部分&#xff0c;每个部分都是一个独立的索引。 主要目的是实现数据的分布式存储和并行处理&#xff0c;从而提高系统的扩展性和性能。 在创建索…

JAVA基础:数组 (习题笔记)

一&#xff0c;编码题 1&#xff0c;数组查找操作&#xff1a;定义一个长度为10 的一维字符串数组&#xff0c;在每一个元素存放一个单词&#xff1b;然后运行时从命令行输入一个单词&#xff0c;程序判断数组是否包含有这个单词&#xff0c;包含这个单词就打印出“Yes”&…

【学习】使用webpack搭建react项目

前言 在日常工作中&#xff0c;我大多是在已有的项目基础上进行开发&#xff0c;而非从头构建项目。因此&#xff0c;我期望通过本次学习能填补我在项目初始化阶段知识的空白&#xff0c;与大家共同进步。在此过程中&#xff0c;我欢迎并感激任何指正或建议&#xff0c;无论是…

什么是人工智能体?

人工智能体&#xff08;AI Agent&#xff09;是指能够感知环境、做出决策并采取行动以实现特定目标的自主实体。以下是对人工智能体的具体介绍&#xff1a; 定义与核心概念 智能体的定义&#xff1a;智能体&#xff0c;英文名为Agent&#xff0c;是指具有智能的实体&#xff0…

【初阶数据结构篇】链式结构二叉树(续)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

二叉树 最大深度(递归)

给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;root [1,null,2] 输出…

python机器人Agent编程——实现一个本地大模型和爬虫结合的手机号归属地天气查询Agent

目录 一、前言二、准备工作三、Agent结构四、python模块实现4.1 实现手机号归属地查询工具4.2实现天气查询工具4.3定义创建Agent主体4.4创建聊天界面 五、小结PS.扩展阅读ps1.六自由度机器人相关文章资源ps2.四轴机器相关文章资源ps3.移动小车相关文章资源ps3.wifi小车控制相关…

python安装selenium,geckodriver,chromedriver

安装浏览器 找到浏览器的版本号 chrome 版本 130.0.6723.92&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; firfox 116.0.3 (64 位)&#xff0c;但是后面运行的时候又自动更新到了 127.0.0.8923 安装selenium > pip install selenium > pip show …

基于SSM+uniapp的营养食谱系统+LW参考示例

1.项目介绍 功能模块&#xff1a;用户管理、年龄类型管理、阶段食谱管理、体质类型管理、季节食谱管理、职业食谱管理等系统角色&#xff1a;管理员、普通用户技术栈&#xff1a;SSM&#xff0c;uniapp&#xff0c; Vue等测试环境&#xff1a;idea2024&#xff0c;HbuilderX&a…

python常用的第三方库下载方法

方法一&#xff1a;打开pycharm-打开项目-点击左侧图标查看已下载的第三方库-没有下载搜索后点击install即可直接安装--安装成功后会显示在installed列表 方法二&#xff1a;打开dos窗口输入命令“pip install requests“后按回车键&#xff0c;看到successfully既安装成功&…

vue项目安装组件失败解决方法

1.vue项目 npm install 失败 删除node_modules文件夹、package-lock.json 关掉安装对话框 重新打开对话框 npm install

qt QComboBox详解

QComboBox是一个下拉选择框控件&#xff0c;用于从多个选项中选择一个。通过掌握QComboBox 的用法&#xff0c;你将能够在 Qt 项目中轻松添加和管理组合框组件&#xff0c;实现复杂的数据选择和交互功能。 重要方法 addItem(const QString &text)&#xff1a;将一个项目添…

window 利用Putty免密登录远程服务器

1 在本地电脑用putty-gen生成密钥 参考1 参考2 2 服务器端操作 将公钥上传至Linux服务器。 复制上述公钥到服务器端的authorized_keys文件 mkdir ~/.ssh vi ~/.ssh/authorized_keys在vi编辑器中&#xff0c;按下ShiftInsert键或者右键选择粘贴&#xff0c;即可将剪贴板中的文…

JAVA基础:多重循环、方法、递归 (习题笔记)

一&#xff0c;编码题 1.打印九九乘法表 import java.util.*;public class PanTi {public static void main(String[] args) {Scanner input new Scanner(System.in);for (int i 0; i < 9; i) {//i控制行数/* System.out.println("。\t。\t。\t。\t。\t。\t。\t。\…

微服务系列二:跨微服务请求优化,注册中心+OpenFeign

目录 前言 一、纯 RestTemplate 方案存在的缺陷 二、注册中心模式介绍 三、注册中心技术&#xff1a;Nacos 3.1 Docker部署Nacos 3.2 服务注册 3.3 服务发现 四、代码优化&#xff1a;OpenFeign工具 4.1 OpenFeign快速入门 4.2 连接池的必要性 4.3 抽取服务、最佳实…

国产数据库之Vastbase海量数据库 G100

海量数据库Vastbase是基于openGauss内核开发的企业级关系型数据库。其语法和Oracle数据库很像&#xff0c;基本是从Oracle数据库迁移到海量数据库&#xff0c;以下简单介绍入门的使用 1、建库操作 地址&#xff1a;x.x.x.x root/Qa2021 安装路径&#xff1a;/home/vastbase 创…

爬虫学习4

from threading import Thread#创建任务 def func(name):for i in range(100):print(name,i)if __name__ __main__:#创建线程t1 Thread(targetfunc,args("1"))t2 Thread(targetfunc, args("2"))t1.start()t2.start()print("我是诛仙剑")from …

不要只知道deepl翻译,这里有10个专业好用的翻译工具等着你。

deepl翻译的优点还是有很多的&#xff0c;比如翻译的准确性很高&#xff0c;支持翻译的语言有很多&#xff0c;并且支持翻译文件和文本。但是现在翻译工具那么多&#xff0c;大家需要翻译的场景也有很多&#xff0c;怎么能只拥有一个翻译工具呢。所以在这里我帮助大家寻找了一波…