写好 try-catch 的核心是明确异常意图、精准捕获可处理的具体异常、聚焦单一职责、善用 try-with-resources 清理资源、记录带上下文的完整日志,并将底层异常封装为领域异常。

写好 try-catch 不是“包住可能出错的代码”那么简单。关键在于明确异常意图、控制作用域、保留上下文,并让错误处理逻辑本身可读、可维护。
只捕获你真正能处理的异常
盲目 catch Exception 或 Throwable 会掩盖问题,干扰调试,甚至吞掉本该终止程序的严重错误(如 OutOfMemoryError)。应精准匹配业务语义中可恢复的异常类型。
- 优先捕获具体异常,比如 IOException、SQLException、IllegalArgumentException
- 避免用 Exception 捕获一切;若必须兜底,应在最外层(如 Controller 或 main 方法)且记录完整堆栈
- 不要在工具方法里静默吞掉 checked exception,除非你有明确理由并加注释说明
别让 catch 块变成“错误垃圾桶”
一个 catch 块里塞日志、重试、降级、返回默认值、再抛新异常……逻辑混杂,难以定位真实问题。每个 catch 应聚焦单一职责。
- 日志记录:用 logger.error("xxx", e) 输出带堆栈的完整信息,不要只打印 e.getMessage()
- 恢复动作:如网络超时后重试,需明确重试次数、间隔和失败后的行为
- 转换异常:将底层异常(如 SQLException)包装为领域异常(如 OrderPersistenceException),保留原始 cause
- 不建议在 catch 中 return null 或 new ArrayList() 这类“安全默认值”,除非业务明确允许且无副作用
善用 try-with-resources 和 finally 的分工
资源清理不是异常处理的附庸,而是独立的确定性行为。把 close() 放进 finally 容易出错(close 本身也可能抛异常),而 try-with-resources 是更安全、更简洁的选择。
立即学习“Java免费学习笔记(深入)”;
- 所有实现 AutoCloseable 的资源(FileInputStream、Connection、PreparedStatement 等)都应使用 try-with-resources
- finally 适合执行不可跳过的副作用操作,比如解锁、重置状态、发送监控埋点
- 避免在 finally 中修改返回值或抛出新异常——它可能覆盖 try/catch 中已准备好的结果或异常
让异常信息自带上下文,别只靠堆栈
生产环境排查时,“NullPointerException”毫无价值,“订单ID=12345 创建支付单时,支付宝签名参数 missing timestamp”才真正有用。
- 构造异常时传入业务上下文:new BusinessException("支付渠道 " + channel + " 不可用", e)
- 在日志中补充关键变量:logger.error("下单失败,用户ID={},商品SKU={},原因={}", userId, sku, e)
- 对受检异常(checked exception),考虑是否真需强制调用方处理;若只是传递性错误,可转为运行时异常并封装上下文
健壮来自克制,可读源于意图清晰。try-catch 不是容错补丁,而是业务逻辑中对“不确定性”的主动声明和有序应对。


















