重构受检异常的关键是厘清责任、分层校验、统一语义:提取校验逻辑为独立方法,按业务场景建模异常(如InvalidUserRegistrationException),下沉校验至Controller或DTO构造,区分必须处理与仅需记录的异常。

受检异常抛出过多本身不是问题根源,而是设计信号:说明方法职责过重、校验逻辑与主流程耦合太深、契约边界不清晰。重构重点不在“少抛异常”,而在厘清责任归属、分层隔离校验、统一错误语义。
把校验逻辑从主方法中剥离出来
大量 if (...) throw new XxxException(...) 堆砌在业务方法里,会掩盖真正要执行的逻辑。应提取为独立的校验方法,返回结果或抛出统一异常:
- 定义
ValidationResult类型(含 success / errorCode / message),让校验函数返回它,主方法只做一次判断 - 或使用
void validateArgs(String a, int b, List集中式校验,内部按顺序检查并抛出首个失败对应的受检异常c) - 避免每个字段都单独写 if-throw;合并语义相近的检查,例如“非空且长度合法”“数值在有效区间内”
用受检异常封装意图,而非堆砌细节
不要为每个参数写一个专属异常类。应按业务场景建模,让异常名表达“发生了什么”,而不是“哪个字段错了”:
- ❌
UserNameTooLongException、UserEmailInvalidException、UserAgeNegativeException - ✅
InvalidUserRegistrationException(构造时传入具体原因枚举或错误码) - 异常类保留必要上下文(如用户ID、请求ID),但不暴露原始参数值,避免日志泄露敏感信息
将前置校验下沉到更早入口
如果多个方法都重复做相同校验,说明校验点太高。可前移到:
- Controller 层用 @Valid + 自定义 ConstraintValidator,由框架统一拦截并转为标准错误响应
- DTO 构造时完成基础合法性检查(如非空、格式),失败直接抛运行时异常(IllegalArgumentException),因为这是调用方传参错误,不属于需恢复的业务异常
- 服务接口契约明确要求输入已预校验,则实现类可默认信任参数,不再重复检查
区分“必须处理”和“应当记录”的异常
并非所有受检异常都该向上抛。有些是系统级约束,调用方无法恢复,强行声明只会污染接口:
- 数据库唯一键冲突 → 抛
DuplicateResourceException(自定义受检异常),上层可转为 HTTP 409 - 远程服务超时 → 抛
ServiceUnavailableException(受检),调用方可能重试或降级 - 配置缺失导致初始化失败 → 这是启动期错误,应作为
RuntimeException抛出,阻止应用启动,而非留给运行时处理










