应将异常留给具备业务上下文和恢复能力的层级捕获,工具层只抛出异常;对可预期错误用受检异常强制处理;避免防御性捕获掩盖缺陷;转换异常时须保留cause并补充上下文。
避免过早捕获异常,核心是把异常留给能真正处理它的地方——不是越早捕获越安全,而是越靠近业务上下文、越具备恢复能力的位置捕获才越合理。
只在能做决策的层级捕获
低层工具方法(如 DAO 查询、文件读取)通常不具备业务语义,不该自行“消化”异常。比如数据库查询失败,DAO 层只需抛出 SQLException 或自定义 DataAccessException;是否重试、降级、提示用户,应由 Service 或 Controller 层根据当前订单状态、用户权限等上下文判断。
- 反例:在工具类中 catch (IOException) 后返回 null 或默认值,上层无法区分“查无数据”和“磁盘故障”
- 正例:工具方法声明 throws IOException,调用方根据场景决定是记录日志后继续、切换备用源,还是向用户展示友好提示
用受检异常明确责任边界
对调用者可预期、可恢复的错误(如网络超时、配置缺失),使用受检异常强制其思考“怎么应对”。这比让所有人盲目 catch (Exception) 更能守住业务逻辑完整性。
- 例如:定义 ConfigLoadException extends Exception,要求所有加载配置的方法必须声明 throws;调用方就必须处理——要么提供默认配置,要么中断启动流程并告警
- 若用 RuntimeException 包裹后悄悄吞掉,系统可能带着错误配置运行数小时才发现异常行为
警惕“防御性捕获”掩盖真实问题
为防止空指针或格式错误而提前 try-catch,常导致逻辑跳过关键校验,让错误数据进入后续流程。这类异常本质是代码缺陷,应通过前置检查修复,而非靠捕获兜底。
- 不要 catch (NumberFormatException) 解析字符串,而应在解析前用正则或 StringUtils.isNumeric() 过滤
- 不要 catch (NullPointerException) 调用对象方法,而应先 if (obj != null) 或用 Optional 封装
- 真正需要捕获的,是那些不可控的外部依赖失败(如远程接口超时、第三方 SDK 抛异常)
用异常包装传递上下文,不丢失原始意图
当不得不在中间层转换异常时(如将底层 IOException 转为业务层 OrderCreationFailureException),务必保留 cause,并补充业务上下文信息,确保上层仍能追溯根源并做出准确响应。
立即学习“Java免费学习笔记(深入)”;
- 正确写法:throw new OrderCreationFailureException("创建订单失败,关联用户ID:" + userId, e);
- 错误写法:throw new OrderCreationFailureException("创建失败"); —— 堆栈断层,排查时只能看到业务异常,找不到 IO 错误源头


















