面向对象系统应构建层级化、语义清晰的领域异常体系,统一继承自domainerror基类,禁止裸except,按业务模块分层派生异常,跨层调用需保留异常链,dao层自动转换db异常,测试须验证异常类型与因果链,apm工具需支持__cause__渲染。

大型面向对象系统里,异常不该是零散的 except Exception 堆砌,而应是一套有层级、可追溯、语义清晰的体系。核心原则就一条:**让调用方只关心“发生了什么业务问题”,而不是“底层哪个模块崩了”。**
定义领域异常基类并约束继承链
所有业务异常必须继承自一个统一的基类(比如 DomainError),它本身继承 Exception,但禁止直接实例化。
- 基类可预设通用字段,如
error_code、user_friendly_message,避免每个子类重复定义 - 强制要求子类重写
__init__,传入必要上下文(如失败的订单 ID、用户 ID),而非仅靠字符串拼接 - 禁止在项目中出现
except Exception或裸except:—— 它们会吞掉DomainError之外的致命错误(如MemoryError、KeyboardInterrupt)
按模块/边界分层派生具体异常
异常类型要反映系统职责划分,而不是技术栈。比如:
-
PaymentError下再分InsufficientBalanceError、PaymentGatewayTimeoutError -
UserError下再分UserNotFoundError、InvalidUserStateError - 数据库层抛出的
psycopg2.OperationalError永远不该透出到服务层 —— 必须被转换为某个StorageError子类
关键点:同一层模块只 raise 自己包下的异常;跨层调用时,用 raise ... from original_exc 保留原始 traceback,但绝不暴露底层异常名。
在基类或工具函数中封装 raise_from 逻辑
手动写 raise NewError(...) from exc 容易漏、容易错,尤其在多个地方要转换同一类底层异常时。
- 抽一个
raise_as(exc: BaseException, new_error_cls: type, **kwargs)工具函数,内部统一处理from和上下文注入 - 在 DAO 基类的
_execute_query方法里统一拦截 DB 异常,自动转成StorageError子类 - 避免在 service 方法里写两层 try-except:外层捕获 DB 异常、内层再 raise 领域异常 —— 这会让堆栈变深且难以调试
测试时断言异常链而非仅消息字符串
验证异常是否“优雅”,不能只检查 str(e) 是否含关键词。真正重要的是调用链是否完整:
- 用
assert isinstance(exc, UserNotFoundError)确认类型 - 用
assert exc.__cause__ is not None和assert isinstance(exc.__cause__, psycopg2.OperationalError)验证原始异常未丢失 - API 测试中检查返回的 error code 和 message 字段是否来自领域异常,而非底层库的默认字符串
最容易被忽略的是:异常链在日志聚合系统(如 Sentry)里默认不展开显示。上线前必须确认 APM 工具能正确解析 __cause__ 并渲染为可折叠的嵌套 stack trace —— 否则设计再好也白搭。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











