应为每类业务失败定义语义化异常类,如InsufficientBalanceException,继承RuntimeException,强制传入关键字段,提供getter而非重写getMessage(),关联错误码枚举,日志用占位符记录异常对象。

Java异常该不该带业务上下文
不该只抛 RuntimeException 或空泛的 IllegalArgumentException,除非你确定调用方能从堆栈里反推“用户没填手机号”还是“订单已过期”。语义化异常的本质,是把「哪里错了」和「为什么错」塞进异常类型本身,而不是靠日志拼字符串。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 为每类业务失败定义专属异常类,比如
InsufficientBalanceException、PhoneNumberNotVerifiedException,继承自RuntimeException(非检查异常更轻量) - 构造函数强制传入关键业务字段:比如
new InsufficientBalanceException(orderId, requiredAmount, availableAmount) - 避免在异常消息里拼接敏感信息(如用户身份证号),但可在字段中保留——日志脱敏由统一拦截器处理,不是异常类的责任
getMessage() 和 toString() 别手动拼接
很多人重写 getMessage() 返回类似 "余额不足:订单" + orderId + "需" + amount,这会让下游无法结构化提取字段。一旦要加监控告警或做错误码映射,就只能写正则去 parse 字符串。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 不重写
getMessage(),让它默认返回类名 + 字段 toString(JDK 14+ record 自动支持,老版本用 Lombok@Data) - 提供 getter 方法暴露结构化数据:
getOrderId()、getRequiredAmount(),让上层按需组装提示或落库 - 如果必须有可读消息(比如给前端展示),单独加一个
getFriendlyMessage()方法,不覆盖父类行为
Spring 中 @ControllerAdvice 捕获时别吞掉原始异常类型
常见错误是全局异常处理器里统一转成 ErrorResponse,但所有异常都塞进同一个 errorCode: "UNKNOWN_ERROR",前端根本分不清是参数错、权限错还是系统错。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 按异常类型分组处理:
@ExceptionHandler(InsufficientBalanceException.class)单独映射errorCode: "BALANCE_INSUFFICIENT" - 避免用
instanceof在一个 handler 里 if-else 判断十多种异常——类型多了就失控 - 把错误码定义为枚举,每个业务异常类关联一个
ErrorCode枚举实例,保证编译期校验和文档一致性
日志记录时别只打 e.printStackTrace()
堆栈里看不到 orderId 或 userId,运维查问题得翻三四个日志文件再人工关联。语义化异常的价值,一大半体现在日志可追溯性上。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用 SLF4J 的占位符记录:
log.warn("转账失败,订单{}余额不足", ex.getOrderId(), ex)—— 异常对象放最后,SLF4J 自动附加堆栈 - 在异常类里重写
toString(),只包含业务关键字段(不包含大对象或集合),避免日志刷屏 - 禁止在 catch 块里 new 一个新异常再 throw(如
throw new RuntimeException("xxx", e)),会丢失原始类型;要用throw ex或封装为带 cause 的自定义异常
最难的不是定义一堆异常类,而是让团队所有人一致地用 getter 取值、不拼字符串、不在日志里丢关键 ID。一旦松动一环,语义就退化回“请查看堆栈”。

















