异常处理是系统的安全气囊。
平时没有存在感,但碰撞发生的瞬间,马上弹出,在崩溃边缘托住一切。
许多项目初期为求速度,拆掉气囊。于是 Controller 里 try-catch 泛滥,前端报错五花八门,代码的混乱,本质是协作防线的失守。
构建异常处理框架,不为优雅,是为活下去。
告别裸奔
没有框架的保护,每一行代码都在裸奔。
开发者陷入防御性编程的焦虑中。为了防止崩溃,在每个方法里小心翼翼地包裹 try-catch。
这种焦虑,带来了三个恶果:
-
冗余:样板代码掩盖了逻辑,噪音淹没了信号。
-
混乱:返回格式随心所欲,协作成本极高。
-
泄露:堆栈裸奔,等于把家底亮给黑客。
我们需要一个漏斗。
不管业务逻辑抛出什么牛鬼蛇神,经过这个漏斗,流出来的必须是标准、干净的 JSON。
这就是全局异常处理的核心价值:把混乱留在内部,把秩序留给外界。
立法:错误码
治理混乱,先立法。
错误码,就是协议。是后端与前端、与用户之间,白纸黑字的契约。
拒绝 HTTP 状态码
直接用 HTTP 状态码(400/500),是在偷懒。
库存不足、余额不足、活动未开始,都是 400? 前端怎么展示?用户怎么理解?
混用状态码,其实就是在逃避定义的责任。
三段式结构
好的错误码,像车牌号,具备定位能力。
建议组合:类型 + 服务 + 场景。
-
Type(谁的错):
-
A:用户错了(参数填反)。 -
B:系统错了(逻辑校验)。 -
C:第三方错了(依赖挂掉)。
-
-
Service(哪里的错):
01用户中心,02订单中心。 -
Scenario(具体的错):
004具体死因。
枚举管理
不要在代码里写死字符串。用枚举(Enum)来管理这些法律条款。
执法:全局捕获
有了法律,还需要执法者。
在 Spring Boot 中,@ControllerAdvice就是那个铁面无私的法官。
分层治理
处理器的核心逻辑,在于分层。我们要区分可预见和不可预见。
-
业务异常(BizException):这是我们主动抛出的。
-
处理:记录 INFO 日志。
-
响应:返回对应的业务错误码。
-
态度:这是用户的问题,与系统无关。
-
-
系统异常(Exception):这是意料之外的 Bug(空指针、SQL 报错)。
-
处理:记录 ERROR 日志,打印完整堆栈。
-
响应:返回统一的
SYSTEM_ERROR,掩盖内部细节。 -
态度:这是系统的耻辱,必须立刻修复。
-
@RestControllerAdvice
@Slf4j
publicclass GlobalExceptionHandler {
// 1. 抓业务异常 -> 这种是通知
@ExceptionHandler(BizException.class)
public Result<Void> handleBizException(BizException e) {
log.info("业务阻断: code={}, msg={}", e.getErrorCode().getCode(), e.getMessage());
return Result.error(e.getErrorCode());
}
// 2. 抓系统异常 -> 这种是事故
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统崩溃", e); // 必须留案底
return Result.error(ErrorCode.SYSTEM_ERROR); // 给用户留面子
}
}
进阶:让死因说话
搭建好框架只是及格。优秀的异常处理,能让排查效率提升十倍。
TraceId:案发现场
用户报错了,只回一句系统繁忙?开发盲人摸象,病急乱投医。
必须在响应里,带上 traceId。
public static <T> Result<T> error(ErrorCode errorCode) {
// ...
result.setTraceId(MDC.get("traceId")); // 留下线索
return result;
}
前端截图,后端搜 ID。秒级还原案发现场。
差异化报警
不是什么报错都得报警。
-
业务异常:不报。密码输错三次,是用户的事,不是系统的事。
-
系统异常:必报。数据库挂了,半夜也得爬起来,这是命。
写在最后
异常处理的本质,是治理不确定性。
跑通逻辑,是及格。 兜住意外,是本事。
衡量系统的标准,不是顺境时的速度,是逆境时的生存能力。
把混乱锁在黑盒里,把确定性交付给世界。
1121

被折叠的 条评论
为什么被折叠?



