Java异常处理应精准而非冗余:try块仅含易失败操作,catch需具体、带上下文日志并明确动作,finally仅限资源释放,分层收口由统一处理器响应。
Java中try-catch不是写得越多越安全,而是写得越准、越少、越有迹可循才越可靠。统一风格的核心目标不是统一格式,而是统一责任——让异常在哪发生、被谁处理、如何记录、是否传播,一目了然。
try块只放“真可能出错”的代码
一个try里塞进参数校验、日志打印、DTO转换、业务判断,等于把异常信号埋进噪音里。真正该放进try的,只有那些JVM或底层API明确会抛受检异常、或实际运行中易失败的操作:
- 文件读写(FileInputStream、Files.readAllLines)
- 数据库操作(JdbcTemplate.query、Connection.createStatement)
- 远程调用(RestTemplate.getForObject、FeignClient接口调用)
- JSON序列化/反序列化(ObjectMapper.readValue)
- 数值或时间格式解析(Integer.parseInt、LocalDateTime.parse)
其他逻辑——比如if (obj == null) throw new IllegalArgumentException()、Objects.requireNonNull()、日志打点——全部移出try。这既缩小了异常监控范围,也避免掩盖真实问题边界。
catch必须具体、带日志、有动作
禁止出现catch (Exception e) { }或e.printStackTrace()这类“假处理”。每个catch块至少完成三件事:
立即学习“Java免费学习笔记(深入)”;
-
按继承顺序精准捕获:FileNotFoundException → IOException → Exception;子类在前,否则编译不通过且逻辑失效
-
日志必须带原始异常和业务上下文:用log.error("订单[{}]支付回调解析失败", orderId, e),而非log.error("解析失败")
-
明确后续动作:返回降级值需注释说明原因;重抛必须包装(throw new ServiceException("支付回调异常", e));静默兜底仅限极少数已评审场景
finally只做资源释放,不用它写业务逻辑
finally的唯一正当用途是释放资源——关流、断连、解锁。现代Java优先使用try-with-resources替代手动finally,语义更清晰、关闭更可靠:
- 所有实现AutoCloseable的资源(InputStream、Connection、Scanner等)都应声明在try括号内
- 若必须用finally,禁止在里面写日志、调用服务、修改状态、return或throw
- 特别警惕finally里return会覆盖try/catch的返回值,这是隐蔽的逻辑陷阱
分层收口,非必要不分散写try-catch
Service层一般不主动捕获并吞异常,而是让受检异常向上抛(如throws IOException),由统一异常处理器集中响应:
- @RestControllerAdvice处理Controller层异常:业务异常转友好提示,系统异常记全栈,受检异常映射HTTP状态码
- Service方法签名显式声明受检异常,避免用catch (Exception e)一锅端,掩盖底层真实问题
- 工具类或底层封装(如JSON工具、文件工具)可提供带日志+重抛的标准模板,但不替代上层决策