刚入行那会儿,我写 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。
而很多“莫名其妙”,其实都是当初为了“省事”埋下的。

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

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com