必须强制异常链传递,即每次抛新异常时显式传入原始异常作为cause,并确保自定义异常提供且正确调用super(msg, cause)构造器,日志记录也须传异常对象而非字符串。
杜绝直接丢弃原始异常,核心是让“不传 cause”这件事在写代码时就变得困难甚至不可行——不是靠提醒,而是靠机制和习惯共同拦截。
强制用带 cause 的构造器包装
每次捕获异常后要抛新异常,必须显式把原始异常作为 cause 传入。这是保留堆栈的唯一可靠方式。
- ✅ 正确:throw new BusinessException("库存扣减失败", e); —— 构造器接收 Throwable cause,链自动建立
- ❌ 错误:throw new BusinessException("库存扣减失败: " + e.getMessage()); —— 堆栈全丢,只剩一句话
- ⚠️ 危险:throw new RuntimeException("包装异常"); —— 类型泛、无上下文、无法追溯源头
自定义异常必须提供 cause 构造器
如果团队定义了业务异常(如 OrderException、AuthException),每个都要有接收 Throwable cause 的构造方法,并调用 super(message, cause)。
- 缺这个构造器,下游一包装就断链;写了但没调用 super,同样失效
- 建议模板:public XxxException(String msg, Throwable cause) { super(msg, cause); }
- CI 中可用 SpotBugs 规则 RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE 检查构造器是否正确转发 cause
日志记录必须传异常对象,不能只打字符串
logger.error() 第二个参数必须是异常实例,而不是 getMessage() 或 toString() 拼接的结果。
- ✅ 正确:logger.error("支付回调验签失败", e); —— 完整堆栈进日志
- ❌ 错误:logger.error("支付回调验签失败: " + e.getMessage()); —— 日志里看不到哪一行出的问题
- 别在 catch 块里只 log 一句就 return 或静默结束——这等于把异常“藏起来”
用工具卡住错误写法
靠人盯容易漏,靠工具才能守住底线。
- IntelliJ 开启 inspection:“Throwing exception without cause” 设为 error 级别
- Maven/Gradle 集成 ErrorProne,启用 check “MissingCause” 规则,CI 失败即阻断
- 阿里巴巴 Java 规约插件会标出所有 new XxxException("msg") 且未传 cause 的调用点
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











