非受检异常应通过预防、精准响应和结构化表达治理,而非泛化捕获;优先校验输入与状态,必要时用自定义业务异常替代通用runtimeexception,并辅以日志监控与告警。

非受检异常(即 RuntimeException 及其子类)不该靠“捕获”来兜底,而应靠“预防+精准响应+结构化表达”来治理。它们不是运行时意外,而是代码逻辑漏洞的信号灯。
不捕获,先拦截:用校验代替 catch
空指针、参数非法、下标越界等,90% 以上源于输入或状态未校验。与其等它抛出再 try-catch,不如在入口主动拦截:
- 用
Objects.requireNonNull(obj, "user 不能为空")替代手写 if (obj == null) throw new NullPointerException() - 业务参数用
Assert.isTrue(amount.compareTo(BigDecimal.ZERO) > 0, "金额必须大于 0")提前失败 - 集合操作前调用
list.isEmpty()或list.size() > index,避免IndexOutOfBoundsException - 字符串转数字前,先用
StringUtils.isNumeric(str)或正则预判,防止NumberFormatException
真要捕获?只在有明确动作时
绝大多数业务方法里,catch (RuntimeException e) 是反模式。真正合理捕获的场景极少且具体:
- 调用不可控第三方 SDK,它内部抛
IllegalArgumentException且文档说明该情况可安全忽略或重试 - 在消息消费、定时任务等异步流程中,为防单条失败导致整个线程中断,做一层轻量级兜底并记录日志
- 全局统一入口(如 Spring 的
@RestControllerAdvice),用于格式化返回、脱敏打点、触发告警——这不是“处理异常”,而是“响应异常”
用自定义业务异常替代通用 RuntimeException
余额不足、重复提交、权限拒绝……这些不是 bug,是业务规则的自然结果。应定义语义清晰的非受检异常:
- 继承
RuntimeException,不强制上层声明,保持接口简洁 - 命名直白,如
InsufficientBalanceException、ForbiddenAccessException - 构造时传入错误码(如
"BALANCE_001")、带上下文的消息("用户 U123 余额不足,需 100.00,当前仅 12.50")、以及原始异常(cause)便于追溯 - 配合全局处理器,统一转换为标准 JSON 错误响应,前端可直接解析 errorCode 做差异化提示
日志与监控是底线保障
非受检异常虽不强制捕获,但一旦发生,必须可查、可溯、可预警:
- 全局异常处理器中,记录 ERROR 级日志,包含异常类型、完整堆栈、关键业务 ID 和脱敏后的输入参数
- 对高频出现的同一异常(如某接口每分钟 NPE 超 5 次),配置实时告警,指向具体代码行和最近一次变更
- 将异常统计接入可观测平台,按异常类型、接口路径、环境维度聚合分析,识别稳定性薄弱点











