我删掉了项目里 80% 的 try-catch,系统反而更稳了

刚入行那会儿,我写 Java 代码有个"强迫症":
每个方法都包一层 try-catch,生怕程序崩。

复制代码

public User getUserById(Long id) { try { return userMapper.selectById(id); } catch (Exception e) { log.error("查询用户失败", e); return null; } }

看起来很负责?其实这是在给系统埋雷。

直到去年一个线上事故,我才彻底醒悟:乱用 try-catch,比不处理异常更危险。


事故现场:一个 null 引发的数据错乱

我们有个订单导出功能,逻辑大概是:

  1. 根据订单 ID 查用户;
  2. 拼装 Excel 行数据;
  3. 写入文件。

因为 getUserById 在异常时返回 null,而下游代码没判空,直接调用了 user.getName() ------ 结果 NPE,整个导出任务中断。

更要命的是,因为异常被"吃掉"了,日志里只有一行模糊的 "查询用户失败",根本不知道是哪个订单、什么参数导致的。运维翻了半小时日志才定位到问题。

客户那边,几百个商户的对账单全卡住,差点终止合作。


为什么"处处 try-catch"是个坏习惯?

1. 掩盖真实问题

异常是系统的"警报器"。你把它 catch 了又不做有效处理(比如返回 null 或空字符串),等于把警报关了,但火还在烧。

2. 破坏调用链的语义

上游调用者以为方法执行成功了(毕竟没抛异常),结果拿到一个 null,后续逻辑全乱套。这比直接抛异常更难排查。

3. 让监控和告警失效

如果你用 APM(比如 SkyWalking、Arthas)或 ELK 做异常监控,被吞掉的异常根本不会上报。等你发现时,可能已经影响成千上万用户。


那到底该怎么处理异常?

我的原则就一条:只在你能真正"处理"异常的地方 catch 它。

什么叫"能处理"?举几个例子:

  • 重试:网络超时,可以再试一次;
  • 降级:查缓存失败,走数据库兜底;
  • 用户友好提示:比如"手机号格式错误",而不是"500 Internal Error"。

除此之外,别 catch,让它往上抛!

✅ 正确姿势 1:让 Controller 统一兜底

Spring Boot 里,用 @ControllerAdvice 全局处理异常,既干净又可控:

复制代码

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) { return ResponseEntity.status(404).body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleUnexpected(Exception e) { log.error("系统异常", e); // 这里才该打 error 日志! return ResponseEntity.status(500).body(new ErrorResponse("服务暂时不可用")); } }

这样,Service 层完全不用写 try-catch,专注业务逻辑。出问题时,日志清晰、响应规范、监控也能抓到。

✅ 正确姿势 2:明确声明 checked exception(如果真有必要)

比如读取配置文件失败,你可以自定义一个 ConfigLoadException,并在方法签名 throws 出去。调用方必须面对它,而不是假装没事发生。

✅ 正确姿势 3:记录上下文,别只 log.error(e)

如果真要在中间层记录日志(比如重要业务入口),一定要带上关键参数

复制代码

try { processOrder(orderId); } catch (Exception e) { log.error("处理订单失败, orderId={}", orderId, e); // 👈 关键! throw e; // 别吞掉,继续抛 }

这样排查时,直接搜 orderId 就能找到完整链路。


特别提醒:别用 Exception 接所有异常!

见过太多人这么写:

复制代码

catch (Exception e) { ... }

这会把 NullPointerExceptionIllegalArgumentException 这类本该快速失败的编程错误也吞掉。

正确的做法是:

  • 能处理的特定异常,单独 catch;
  • 不能处理的,让它们自然抛出;
  • 如果非要兜底,至少用 RuntimeException,别用 Exception

总结:少即是多

删掉那些无意义的 try-catch 后,我们的代码:

  • 更简洁(Service 层清爽多了);
  • 更可靠(异常不再静默消失);
  • 更好排查(日志+监控联动,5 分钟定位问题)。

作为不想打工的码农,我们接项目、做外包,最怕的就是半夜被叫起来修"莫名其妙"的 bug。

而很多"莫名其妙",其实都是当初为了"省事"埋下的。

真正的健壮,不是不让程序出错,而是让错误暴露得足够快、足够清楚。

相关推荐
卓怡学长4 小时前
w176基于SpringBoot的医院管理系统
java·spring boot·spring·maven·intellij-idea
大模型码小白4 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
吴声子夜歌5 小时前
Java面试——Spring原理及应用(一)
java·spring·面试
weixin_399380696 小时前
TongWeb7.0.4.9M11一键安装脚本(by sy)
java·spring
叫我Paul就好10 小时前
Spring 为何没有在 Java之外的地方存在?
java·后端·spring
爱码猿12 小时前
SpringAop通过el表达式动态获取方法入参的值
spring boot·spring·aop
东小西13 小时前
【SAA实战】第 2 篇:模型与消息——ReactAgent 怎么挑模型、怎么传消息
java·后端·spring
就叫飞六吧15 小时前
Spring 动态注册与移除 Bean 科普
java·后端·spring
晓晓_za89866816 小时前
多租户 GEO 优化系统源码架构:权限隔离与数据分离实现
java·tcp/ip·spring·缓存·微服务·架构
choumou_M16 小时前
MySQL_1:数据库悲观锁
后端·spring·spring cloud