合理的异常类层次结构应以驱动处理决策为核心,统一使用runtimeexception子类表业务异常,命名需体现失败原因与决策意图(如retryable),两层继承足够,构造器须含上下文信息并实现serializable。

设计合理的异常类层次结构,核心不是堆砌类,而是让异常能驱动明确的处理决策——该重试、该告警、该返回友好提示,还是该直接中断流程。
从Throwable出发,但别碰Error
所有Java异常必须继承Throwable,这是硬性要求。但实际业务中,你几乎只用Exception分支;Error(如OutOfMemoryError、StackOverflowError)是JVM级崩溃,程序无法恢复,也不该捕获或继承它。
- 业务异常统一走RuntimeException子类——不强制上层处理,语义清晰:这是“本不该发生但发生了”的逻辑问题
- 只有极少数强契约场景才用checked exception(即继承Exception但非RuntimeException),比如自研客户端首次拉取配置失败,调用方必须决定重试、降级或告警
- 千万别让自定义异常既继承RuntimeException又声明throws——这会让调用方困惑到底要不要写try-catch
命名体现动作,不描述现象
类名是第一个决策线索。看到异常名,就该知道下一步怎么走。
- ✅ 推荐:PaymentTimeoutRetryableException(可重试)、InvalidOrderStateNonRetryableException(不可重试,应终止)
- ❌ 避免:PaymentException(不知道能否重试)、OrderError(Error后缀模糊,且未说明场景)
- 按失败原因+决策意图命名,比如加Retryable、Validation、ExternalService等前缀,比按模块分层更有效
两层足够,拒绝深度继承
不要为了“看起来有体系”硬造多级继承链,比如BaseException → OrderException → OrderCreateException → OrderCreateStockNotEnoughException。这只会增加维护成本,且catch时难以精准匹配。
- 建议结构:一个包级抽象基类(如BusinessException),封装通用字段(errorCode、traceId、context)
- 具体异常只表达“是什么错”,例如InventoryInsufficientException、CouponExpiredException,都直接继承基类
- 避免语义重复的并行类,比如BalanceNotEnoughException和InsufficientBalanceException只留一个
构造函数要带上下文,别只拼字符串
抛出异常不是打日志,而是传递可操作信息。只写new BusinessException("库存不足"),线上根本没法定位是哪个商品、哪笔订单、当前库存多少。
- 必须提供含errorCode + message + cause的全参构造器,方便包装原始异常(如SQLException)并透传链路
- 按领域提供语义化构造器,比如OrderNotFoundException(Long orderId),而不是到处拼
"订单不存在: " + id - 异常类必须实现Serializable,否则在Dubbo、RPC跨服务传递时会反序列化失败
- 别给异常加getter/setter或业务字段——异常不是DTO,状态越少越利于归因










