本文目录导读:
- 前置条件检查(Preconditions)
- 后置条件检查(Postconditions)
- 内部状态不变性检查(Invariants)
- 分支/条件覆盖测试(Dead Code Detection)
- 调试和测试辅助(尤其是单元测试)
- ⚠️ 重要:什么时候不该用assert?
- 总结一句话:
assert(断言)是一种用于调试和测试的机制,用来检查程序内部不应该出现的情况,它的核心作用是验证假设。
断言用来检查“绝对不应该发生”的错误,而不是“可能会发生”的错误(比如网络超时、密码错误)。
以下是assert的主要使用场景,以及对应的例子(以Python为例,但逻辑适用于大多数语言如Java、C、JavaScript等):
前置条件检查(Preconditions)
在函数开始执行时,断言传入的参数必须满足某些条件,否则说明调用方存在逻辑错误。
def divide(a, b):
# 断方:分母绝对不能为0
assert b != 0, "除数不能为0!"
return a / b
后置条件检查(Postconditions)
在函数返回前,断言结果必须符合预期,这通常用于验证算法或复杂计算的正确性。
def square_root(x):
# 假设x >= 0
result = x ** 0.5
# 后置条件:平方后的值应该非常接近原值(允许浮点误差)
assert abs(result * result - x) < 1e-6, f"计算错误:sqrt({x}) != {result}"
return result
内部状态不变性检查(Invariants)
在循环或复杂逻辑的某个阶段,断言“系统必须处于某种稳定状态”,如果断言失败,说明代码逻辑有bug。
def process_list(data):
# 要求传入的data非空
assert data is not None and len(data) > 0, "列表不能为空"
# 在中间处理中,检查某些状态的正确性
sorted_data = sorted(data)
for i in range(len(sorted_data) - 1):
# 断言:排序后的列表,后一个元素必须大于等于前一个
assert sorted_data[i] <= sorted_data[i+1], f"排序算法在索引{i}处失败"
return sorted_data
分支/条件覆盖测试(Dead Code Detection)
当代码中存在理论上永不可能到达的分支时,用断言来捕获“如果万一真到了这里,那一定是出bug了”。
def handle_response(status_code):
if status_code == 200:
return "OK"
elif status_code == 404:
return "Not Found"
else:
# 我们预期只处理200和404,如果来了别的值,说明逻辑有误
assert False, f"意外的状态码:{status_code}"
调试和测试辅助(尤其是单元测试)
在开发阶段,通过断言验证代码逻辑,消灭潜在的bug,断言失败会立即暴露问题,比等到运行时崩溃更容易定位。
def test_database_insert():
db = Database()
user_id = db.insert("Alice")
# 断言:插入后必须返回一个有效的ID(非负数)
assert user_id >= 0, "数据库插入返回了无效ID"
⚠️ 重要:什么时候不该用assert?
断言不是用来处理可以预见的外部错误(异常处理才是它该干的)。生产环境中断言通常会被禁用(例如Python使用 -O 参数运行时可关闭断言;Java中assert语句默认关闭)。
| 场景 | 应该用断言吗? | 应该用什么? |
|---|---|---|
| 用户输入了空密码 | if + 抛出ValueError或返回错误提示 |
|
| 网络请求超时 | try...except 捕获异常 |
|
| 配置文件不存在 | try...except FileNotFoundError |
|
| 自己写的排序函数里,中间变量应该是递增的 | 用assert |
|
| 传入的列表参数必须是已排序状态(如果调用方承诺了) | 用assert |
|
某个switch/case分支理论上不应进入 |
用assert False |
总结一句话:
- 断言 = 帮作者抓bug,通常开发时启用,生产环境关闭。
- 异常 = 帮用户处理可预见的错误,无论开发/生产都应保留。