异常信息须含可定位上下文且脱敏,如“userid cannot be null or empty, got: ”+userid;禁拼敏感数据;自定义异常需重写构造函数并确保getmessage()返回可读文本;message表“什么错”,堆栈表“在哪错”,日志表“当时上下文”。

异常信息必须包含可定位的上下文
抛出异常时,message不能只是“操作失败”“参数错误”这种空泛描述。用户看到异常第一反应是“哪里出问题了”,所以必须带具体对象、值、状态或位置。
- ❌ 错误写法:
throw new IllegalArgumentException("参数错误"); - ✅ 正确写法:
throw new IllegalArgumentException("userId cannot be null or empty, got: " + userId); - 如果涉及集合或索引,补上长度和索引值,例如:
"index " + i + " out of bounds for array of length " + arr.length - 日志中打印异常时,也建议在
catch块里加前缀,比如"[UserService#loadById] failed to load user: " + e.getMessage()
不要在异常信息里拼接敏感数据
密码、token、身份证号、完整 SQL(含参数值)等不能直接出现在 message 中——这会导致脱敏失效,尤其在日志被集中采集或导出时风险极高。
- ❌ 危险写法:
throw new SecurityException("Invalid token: " + rawToken); - ✅ 安全写法:
throw new SecurityException("Invalid token (length=" + rawToken.length() + ")"); - 数据库异常建议只保留表名、字段名、错误码,如:
"Failed to insert into user_profile: duplicate key on email",而非拼接完整 INSERT 语句 - 若需调试,用
logger.debug("Raw token: {}", rawToken)单独记录,且确保该日志级别默认关闭
自定义异常类要重写 getMessage() 或构造函数
继承 RuntimeException 或 Exception 后,别依赖父类默认行为。很多团队封装了 BusinessException,但没重载构造逻辑,导致最终 message 仍是空或不一致。
- 务必提供至少一个接受
String的构造函数,并调用super(message) - 如果需要结构化错误码,建议额外提供
int code字段,但getMessage()仍应返回人类可读文本,而不是只返回错误码数字 - 避免在
getMessage()中做耗时操作(如远程调用、格式化大对象),它可能被频繁调用(如序列化、日志框架采样)
日志与异常堆栈的分工要清楚
异常信息(message)负责「什么错了」,堆栈负责「在哪错的」,日志负责「当时上下文是什么」。三者混用会降低排查效率。
立即学习“Java免费学习笔记(深入)”;
- ❌ 反模式:在
message里写"at com.example.UserDao.save(UserDao.java:42)"——这是堆栈的事,不是 message 的职责 - ✅ 推荐做法:异常 message 纯业务语义,堆栈保持原生,额外关键变量用
logger.warn("Save user failed. userId={}, status={}", userId, status, e)记录 - 注意某些框架(如 Spring)会自动包装异常,导致原始
message被吞掉一层,此时建议用Throwable.getCause()检查并提取真正有用的 message
message 字符串里。










