异常处理定义健壮性边界:需区分可恢复与不可恢复场景,优先用try-with-resources和@ControllerAdvice,自定义异常须含errorCode、状态码及重试标识,禁用空catch和高频路径抛异常。

异常处理不是让程序“看起来不崩溃”,而是决定程序在出错时该继续做什么、不该做什么——它直接定义了健壮性的边界。
Java中checked异常强制捕获,反而常导致错误掩盖
像 IOException、SQLException 这类 checked 异常,编译器逼你写 try-catch 或声明 throws,但很多人只写个空 catch 或打个 e.printStackTrace() 就完事。这等于把故障信号直接丢进黑洞。
- 真正该做的是:区分可恢复场景(如重试网络请求)和不可恢复场景(如配置文件缺失),前者用
try-catch-retry,后者应尽早throw new RuntimeException("config missing", e) - 不要在 service 层吞掉
SQLException后抛出泛化Exception,下游无法判断是连接超时还是主键冲突 - Spring 项目里,更推荐用
@ControllerAdvice统一处理全局异常,但需保留原始异常类型和关键字段(如 SQL 状态码、HTTP 状态建议值)
finally块里资源关闭不等于安全,AutoCloseable才是底线
手写 finally 关闭 InputStream 或 Connection 很容易漏掉 null 判断,或在 close() 本身抛异常时覆盖主异常。
- 优先用 try-with-resources:
try (FileInputStream fis = new FileInputStream("a.txt")) { ... },JVM 保证close()被调用且异常不压制主流程 - 自定义资源类必须实现
AutoCloseable,并在close()中避免抛受检异常(否则无法用于 try-with-resources) - 注意 JDBC 的
ResultSet、Statement、Connection都实现了AutoCloseable,但嵌套顺序很重要:先开的后关,否则可能触发 “connection closed” 错误
自定义异常不是起个新类名,而是传递决策上下文
健壮性差的系统往往堆砌一堆 UserNotFoundException、UserInvalidException,但调用方根本不知道该重试、跳过、还是告警。
立即学习“Java免费学习笔记(深入)”;
- 每个自定义异常应包含明确的
errorCode(如USER_NOT_FOUND = "40401")、httpStatus、是否可重试(isRetryable()) - 避免继承
RuntimeException就万事大吉;若业务逻辑依赖异常分支(如风控拒绝时走补偿流程),就该用 unchecked 异常,但必须文档化行为契约 - 日志记录时,别只打
e.toString()—— 要输出e.getClass().getSimpleName()+e.getMessage()+ 关键业务 ID(如orderNo),否则线上查问题等于盲人摸象
最常被忽略的一点:异常处理的成本不在 catch 块里,而在异常对象创建时的栈快照。高频路径(如循环内校验)绝不能靠抛异常来控制流程,哪怕只是 IllegalArgumentException —— 这种地方用 if-return 才是真健壮。










