3、断言的最佳实践:什么时候用断言?什么时候不该用?
说实话,我见过不少团队在断言的使用上走了极端。要么满屏都是 assert,要么一个 assert 都不敢用。这两种做法,我都踩过坑。
今天我就把这些年总结的经验摊开来讲。什么时候该用断言,什么时候千万别用,咱们一次性说清楚。
3.1 核心原则:断言是「内部契约」,不是「用户输入校验」
先记住一句话:断言是用来检查「不应该发生」的情况,而不是「可能发生」的情况。
什么意思呢?我举个例子你就明白了。
# 错误用法:用断言检查用户输入
def divide(a, b):
assert b != 0, "除数不能为零!"
return a / b
# 正确用法:用条件判断处理用户输入
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零!")
return a / b
你想想看,用户输入 0 是「可能发生」的事。这不是 bug,是正常业务场景。断言一旦被禁用(比如 python -O 运行时),这个检查就失效了。到时候用户输入 0,程序直接崩溃,那可就出大事了。
3.2 什么时候该用断言?
我个人习惯把断言用在三个场景里。这些场景都有一个共同点:如果断言失败,说明代码里有 bug,而不是外部环境出了问题。
3.2.1 前置条件检查(Preconditions)
函数内部对参数做「合理性」检查。注意,不是「合法性」检查。
def calculate_discount(price, rate):
# 前置条件:rate 必须在 0 到 1 之间
# 如果调用者传了 1.5,那是调用者的 bug
assert 0 <= rate <= 1, f"折扣率 {rate} 超出范围"
# 前置条件:price 必须是正数
assert price > 0, f"价格 {price} 不合法"
return price * rate
这里的关键是:rate 和 price 的来源是内部代码。如果传错了,说明上游逻辑有 bug。断言能帮你快速定位问题。
3.2.2 后置条件检查(Postconditions)
函数执行完后,检查结果是否符合预期。这个习惯我是在做金融系统时养成的。
def process_order(order):
# ... 复杂的订单处理逻辑 ...
result = do_something(order)
# 后置条件:处理后的订单状态不能是 "pending"
assert result.status != "pending", f"订单 {order.id} 处理异常"
return result
后置条件检查特别适合那些「理论上不可能出错」的逻辑。一旦出错,说明你的算法或逻辑有漏洞。
3.2.3 不变式检查(Invariants)
这个稍微抽象一点。不变式指的是「在整个过程中必须一直成立的条件」。
class BankAccount:
def __init__(self, balance):
self.balance = balance
# 不变式:余额不能为负
assert self.balance >= 0
def withdraw(self, amount):
old_balance = self.balance
self.balance -= amount
# 不变式:提款后余额不能为负
assert self.balance >= 0, f"余额不足!当前余额 {old_balance},提款 {amount}"
return self.balance
3.3 什么时候不该用断言?
这部分更重要。我列了几个「禁区」,你最好记下来。
| 场景 | 为什么不能用断言 | 应该用什么 |
|---|---|---|
| 用户输入校验 | 断言可能被禁用,校验会失效 | if + raise ValueError |
| 文件/网络操作 | 外部环境不可控,断言失败会直接崩溃 | try/except 异常处理 |
| 数据库操作 | 数据一致性需要事务保证,不是断言能解决的 | 事务 + 回滚机制 |
| 性能关键路径 | 断言有性能开销,且无法被优化 | 日志记录 + 监控告警 |
| 第三方接口调用 | 接口返回结果不可预测 | 状态码检查 + 重试机制 |
说白了,凡是「外部输入」或「环境因素」导致的问题,都不该用断言。断言只负责抓「内部 bug」。
3.4 实战中的断言策略
我一般会按「风险等级」来分层使用断言。这里画个图,你一看就明白了。
这个金字塔模型是我在实际项目中慢慢总结出来的。顶层是「必须用」的,底层是「不用」的。中间两层看情况灵活处理。
3.5 几个实用建议
最后,分享几个我个人的小习惯。不一定适合所有人,但你可以参考一下。
- 断言信息要写清楚:别只写个 "assert x",要带上错误信息。比如
assert x > 0, f"x 的值为 {x},期望大于 0"。这样出问题时一眼就能看出问题在哪。 - 不要用断言做数据清理:断言失败就崩溃了,没有机会做清理工作。如果你需要在失败时释放资源,用 try/finally。
- 测试代码里多用断言:单元测试是断言的最佳舞台。pytest 的 assert 就是专门干这个的。
- 生产环境慎用断言:如果一定要在生产环境用,确保你的部署脚本不会加 -O 参数。我一般会在 CI/CD 流程里检查这个。
核心总结:
- 断言是「内部契约」,不是「外部校验」
- 用断言抓「内部 bug」,用异常处理「外部异常」
- 高风险逻辑必须用断言,外部交互不用
- 断言信息要详细,方便快速定位问题
嗯,关于断言的最佳实践,差不多就这些了。记住一句话:断言是开发者的朋友,但不是万能工具。用对了地方,它能帮你省下大量调试时间;用错了地方,它可能给你惹来大麻烦。
可可精品资料库