有人把异常处理写成 try-except 大帽子,不管什么毛病都扣一个 Exception。线上炸了,日志里只有一行 something went wrong。你翻半天代码,血压上来了,却不知道该骂谁。
我们团队以前也这样。后来设计了一套分层异常体系。核心就一条:每个异常精准对应一个责任人。报错一出来,看一眼异常类型就知道打谁的电话。
先搭三个基础层。框架层异常,这是基础设施自己出的问题,比如数据库连不上,缓存吃太多内存。这个异常只能由运维和基础设施组扛。他们得半夜爬起来重启集群。
业务逻辑异常,这是最怕的一类。用户下单发现余额不够,库存扣超了,传的参数有明显错误。这类异常必须扔到业务所有者的脸上。谁写的规则谁负责。
数据层异常,底层存储出的毛病。比如唯一键冲突,数据格式不匹配。谁写的数据库访问代码,谁就认领。数据对不上,别推给网络问题。
每个异常类继承自超类 BaseAppException。超类里强制三个字段:code, message, owner。owner 就是负责人的花名或者工号。我们直接在异常初始化时就把人怼进去。
class BaseAppException(Exception): def __init__(self, code, message, owner):
self.code = code
self.message = message
self.owner = owner
super().__init__(f'[{code}] {message} 找 {owner}')
业务逻辑异常就长这样。
class OrderException(BaseAppException): def __init__(self, order_id, reason):
code = 40001
message = f'订单 {order_id} 失败,原因 {reason}'
owner = '张三'
super().__init__(code, message, owner)
线上出错了,错误日志里直接打印 找 张三。值班的看到这条日志,不用翻代码,不用猜谁负责。拿起手机就拨张三电话。张三不可能说这不是我的事,因为异常类型和 owner 是他自己写进去的。
我们在框架层也做了处理。全局捕获异常时,判断一下是不是 BaseAppException。如果是,就直接把 owner 和错误码发送到告警系统。企微群艾特对应的人。不是 BaseAppException 的统统归为框架层下属,直接通知基础设施组。
还有一点,异常不要随便吞掉。很多同学 except Exception: 然后写一句日志就过去了。这不对。我们约定的规矩是:捕获了就重新抛,除非你能给出明确的重试策略。不能扔个空 except 当无事发生。
架构师老王一开始觉得这套东西太重。后来线上出了个库存异常,日志一出来,owner 写的是“小李”。老王朝小李吼了一声,小李抬头说“我的锅”。三分钟定位修复。老王说,这就是分层异常体系的价值。不用开会,不用讨论,直接找到人。
现在每个新人入职,第一件事就是给自己的异常配上 owner。代码还没合进去呢,owner 先写对了。这比什么代码规范都管用。
异常不再是事故,而是线索。谁写的代码,谁签字。谁出的事,谁接电话。你不想凌晨接电话,就老老实实把异常分类弄明白。
分层异常体系不复杂。就三个基础层,每层一个超类。加个 owner 字段。代码里别偷懒,每个异常都要有主人。这样做半年了,线上问题处理速度提升了四倍。没人能甩锅,因为异常已经把锅端到他脸上了。