自定义异常是为业务错误建立可识别、可捕获、可追踪的语义载体,需精准继承、清晰命名、完备构造、合理使用:继承Exception强制处理,RuntimeException用于逻辑错误;命名须规范(如InsufficientBalanceException);构造器须完整且轻量;仅当需统一拦截、类型区分或携带业务字段时才自定义。

自定义异常不是简单地“多写一个类”,而是为业务错误建立可识别、可捕获、可追踪的语义载体。核心在于:继承要准、命名要清、构造要实、使用要有据。
继承选择:Exception 还是 RuntimeException?
关键看错误是否需要强制处理:
- 继承 Exception:适用于必须被显式处理的业务异常,比如支付失败、库存不足、权限校验不通过。调用方需在方法签名中声明 throws,或用 try-catch 捕获,否则编译不通过
- 继承 RuntimeException:适用于程序逻辑错误或不可恢复的运行时问题,如参数非法(非空校验失败)、状态不一致(订单已关闭却尝试发货)。这类异常不强制捕获,便于快速暴露缺陷
- 避免直接继承 Throwable 或 BaseException —— 会绕过标准异常处理机制,导致日志丢失、监控失效、IDE 提示异常
命名规范:一眼识别用途和层级
名字不是代号,是契约:
- Java 必须以 Exception 结尾(如 InsufficientBalanceException),否则 Spring @ExceptionHandler 等框架无法识别
- Python 推荐以 Error 结尾(如 InvalidOrderError),符合 PEP 8,也与内置异常(ValueError、IOError)风格统一
- C++ 建议继承 std::runtime_error 或 std::logic_error,并保持类名具象(如 DatabaseConnectionException)
- 避免模糊词:MyError、BusinessException、FailException —— 它们不传达“什么错了”
构造器设计:支撑调试、日志与链路追踪
异常实例化必须轻量,但信息不能单薄:
- Java 必须提供全部四个标准构造器:无参、仅 message、仅 cause、message + cause;缺一不可,否则框架包装时会报 NoSuchMethodError
- Python 中 super().__init__(message) 不可省略,否则 str(e) 为空,日志只剩类名,无法定位上下文
- 所有语言都支持附加结构化字段:如订单 ID、错误码、原始异常引用(cause / __cause__),但不要塞进 message 字符串里
- 禁止在构造函数中做 I/O、网络请求、复杂计算——异常应瞬时创建
使用场景判断:别为了“自定义”而自定义
先问三个问题再动手写新类:
- 这个错误是否在多个地方重复出现,且需要统一拦截(如 API 层转 HTTP 状态码)?→ 适合自定义
- 上层是否需要根据类型做差异化响应(如重试 vs 告警 vs 降级)?→ 自定义更可控
- 是否要携带业务专属字段(租户 ID、操作人、资源路径)?→ 内置异常无法承载
- 反之,字符串为空就抛 ValueError,键不存在就抛 KeyError,无需包装——清晰比“统一”更重要










