上周code review的时候,发现一个同事把所有异常都用try-catch包起来,然后就ignore了。这种代码怎么能上线?
哎我跟你说,上周五晚上我们组 code review,差点没气笑了。一个同事写的服务层代码,基本上是这样子的:
try {
// 一大坨业务逻辑
} catch (Exception e) {
// ignore
}
真的就是 ignore 啊,连个 e.printStackTrace() 都没有,日志也不打。你说这种代码上线了,哪天线上出问题了,鬼才知道是啥原因。到时候领导追责,还不是背锅背到我们头上。
异常不是垃圾桶
其实很多新人有个误区,他觉得 try-catch 是个“保险箱”,能保证程序不挂掉,于是就干脆所有异常都吞掉。这就像你家厨房着火了,你把报警器电池拔掉,心想“反正没人吵闹”,那火就自动灭了吗?显然不会。异常被无脑吞掉,问题就被掩盖了,后果就是线上排查困难。
你想啊,正常写法我们至少要有日志记录:
try {
doSomething();
} catch (IOException e) {
log.error("文件读取失败", e);
throw e; // 或者转换成业务异常抛出去
}
这样即便出问题,日志里能搜到栈信息,定位快得多。可要是啥都没有,线上现象就是“用户点了按钮没反应”,那真的是查一天也查不出个结果。
你应该怎么写
我一般会跟新人说,异常处理要分三层来考虑:
能解决的就地解决比如你读文件发现路径不存在,可以给个默认值继续跑。
不能解决的往上抛交给调用者,或者全局异常处理器兜底。
日志必须打至少要知道什么时候、什么地方、什么线程出了问题。
像 Spring Boot 里面常用的 @ControllerAdvice 搭配 @ExceptionHandler,就能统一兜底。这样你在业务里不用每个方法都乱 try-catch,一旦发生异常,全局处理器能统一输出格式化的错误日志。
举个反例
你们见过这种反人类写法没?
public String getData(){
try {
return repository.find();
} catch (Exception e) {
returnnull;
}
}
看似优雅,结果调用方拿到 null,一不小心就 NullPointerException。到时候谁还记得这个 null 是你“善意”返回的?
线上排查多难受
我有次在公司楼下抽烟,小李跑过来喊我,说某个接口总是返回空数据,日志一点异常也没有。后来 debug 半天,才发现就是有人把 SQLException 给吞了,导致数据库查不出来结果,接口就返回空集合。你说这种锅,浪费多少人力排查。
更坑的是,有时候异常吞掉还会影响事务。比如你在 Spring 里这样写:
@Transactional
publicvoidsaveUser(User user){
try {
userDao.save(user);
} catch (Exception e) {
// ignore
}
}
结果 save 失败了,事务照样提交,外层完全不知道。后果就是脏数据进库,等排查到数据库层的时候,已经晚了。
怎么化解这种代码
那遇到同事写这种代码咋办?我一般建议几步:
code review 直接怼,告诉他吞异常等于埋雷。 引导用日志框架,比如 slf4j + logback,强制规范日志级别。 做静态代码检查,SonarQube 里可以配置规则,禁止空 catch。 写文档和规范,团队约定:catch 到异常必须处理或抛出,不允许无脑吞。
其实很多问题,不是技术不会,而是习惯不好。写代码不想“多此一举”,结果害死的是后面的维护人。
异常处理这东西,说难不难,但真要做到位需要点“敬畏心”。代码不是写给自己跑的,是给线上系统扛流量的。你以为少写一行日志很爽,等凌晨两点接到报警电话的时候,你就知道吞掉的不是异常,是你自己的睡眠。
兄弟们,你们组是不是也有这种喜欢乱 try-catch 的?你们都是怎么劝的?我这边有时候真想在 review 的时候直接在评论里写:“哥们,你这代码等于放了个定时炸弹。”
要不然这样,下次我给你们整理一个“异常处理最佳实践”的 demo 项目?里面把常见场景都走一遍,顺便写几个正确的 catch 样例。这样拉着新人一起改代码,比单纯说教管用多了。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html