本文翻译自 Toward automated verification of unreviewed AI-generated code,原载于 Hacker News。
我一直在思考一个问题:要在生产环境中使用未经人工审查的 AI 生成代码,需要满足什么条件?
为了探索这个问题,我做了一个实验,这个实验彻底改变了我的思维方式——从”我必须 always 审查(review) AI 生成的代码”转变为”我必须 always 验证(verify) AI 生成的代码”。
这里需要区分两个概念:
- 审查(review):逐行阅读代码
- 验证(verify):通过任何方式确认代码是正确的——可以是人工审查,可以是机器可执行的约束检查,也可以两者结合
实验设计
我让一个编码 agent 生成一个简化版 FizzBuzz 问题的解决方案,然后让它根据以下几个预定义的约束进行迭代检查:
1. 属性测试(Property-based Testing)
代码必须通过属性测试。这会将解决方案空间限制在满足需求的范围内,包括验证不会抛出异常、延迟足够低等测试。
与传统的”特定输入对应特定输出”的测试方式不同,属性测试会针对更广泛的数值范围运行。比如,下面的属性测试(使用 Hypothesis 框架)会用 100 个半随机的 3 和 5 的公倍数来运行 fizzbuzz 函数,并且会优先选择”有趣”的边界情况,比如零或极大数:
@given(n=st.integers(min_value=1).map(lambda n: n * 3 * 5))
def test_returns_fizzbuzz_for_multiples_of_3_and_5(n: int) -> None:
assert fizzbuzz(n) == "FizzBuzz"
相比之下:
def test_returns_fizzbuzz_for_multiples_of_3_and_5(n: int) -> None:
assert fizzbuzz(15) == "FizzBuzz"
assert fizzbuzz(30) == "FizzBuzz"
属性测试让我们更有信心某个”属性”在整个系统中都成立,代价是速度较慢、非确定性、以及更高的复杂度。
2. 变异测试(Mutation Testing)
变异测试工具(如 mutmut)会对你的代码做微小的修改——比如交换运算符或调整常量——然后重新运行测试套件。如果测试失败,说明这个”变异体”被”杀死”了(好事);如果测试通过,说明变异体”存活”了(坏事)。
举个例子,考虑以下代码:
def double(n: int):
print(f"DEBUG n={n}")
return n * 2
def test_doubles_input():
assert double(3) == 6
如果把 print(f"DEBUG n={n}") 变异为 print(None),测试仍然通过,说明变异体存活了。修复方法是要么移除这个副作用,要么为它添加测试。
通常变异测试用于扩展测试套件。但如果我们假设测试本身是正确的,我们就可以反过来用它来限制代码——确保代码只满足需求,没有多余的行为。
3. 无副作用
代码不能有任何副作用。
4. 类型检查和代码检查
由于我使用的是 Python,我还强制执行类型检查(type-checking)和代码检查(linting)。当然,不同的编程语言可能不需要这些检查。
为什么这些检查足够?
这些检查似乎足以让我在不看代码的情况下信任生成的代码。
理论上,仍然存在”无效但能通过测试”的程序空间,但这个空间非常小,而且很难意外落入。
有人可能会担心生成的代码难以维护。但我开始认为,在这种场景下,可维护性和可读性其实并不重要。我们应该把 AI 生成的输出当作编译后的代码(compiled code)来对待——你不需要阅读编译后的机器码或字节码,你只需要验证它的行为是正确的。
现实考量
目前,设置这些约束的开销仍然超过了直接阅读代码的成本。但这建立了一个基准线,随着 agent 和工具的不断改进,这个基准可以逐步被蚕食和优化。
如果你想亲自尝试,可以查看 fizzbuzz-without-human-review 这个仓库,它用 Python 实现了上述所有检查。
我的思考
这篇文章提出了一个很有价值的视角转变:从”审查代码”到”验证代码”。
在 AI 辅助编程越来越普及的今天,如果我们每次都要逐行审查 AI 生成的代码,效率提升会非常有限。作者提出的这套验证框架——属性测试 + 变异测试 + 无副作用 + 静态检查——本质上是在用形式化方法来保证代码质量。
这个思路让我想到了几个值得深入的方向:
-
测试驱动开发的升级版:传统的 TDD 是先写测试再写代码,而这里更进一步——测试不仅是验证工具,更是约束条件,定义了代码的合法边界。
-
像对待编译产物一样对待 AI 代码:这个类比很精准。我们从来不会去”审查”编译器生成的汇编代码,因为我们信任编译器的正确性。如果 AI 编程工具能够提供类似级别的”验证保证”,那我们对 AI 生成代码的信任度就可以大幅提升。
-
工程实践的前景:虽然目前设置这些约束的成本还比较高,但随着工具链的成熟,这很可能会成为 AI 辅助编程的标准实践。想象一下,未来的 IDE 可能会自动为 AI 生成的代码配置这些验证框架。
对于国内开发者来说,这是一个值得提前布局的方向。如果你正在使用 Cursor、Copilot 或其他 AI 编程工具,不妨尝试在你的项目中引入属性测试和变异测试,为未来的”无审查开发”做准备。
核心要点
- 从”审查”代码转变为”验证”代码——关注正确性而非可读性
- 四层验证框架:属性测试、变异测试、无副作用、静态检查
- 将 AI 生成代码视为”编译产物”,而非需要人工阅读的源码
- 目前设置成本较高,但随着工具进步会越来越实用