在软件开发实践中,AI 生成代码的可信度问题始终是工程师面临的挑战。传统观点认为,未经人工审查的 AI 生成代码不应进入生产环境,但这种做法在 AI 辅助编程日益普及的今天显得愈发不可扩展。Peter Lavigne 在其最新实验中提出了一种新范式:将「审查」与「验证」视为两个独立概念,通过自动化约束替代人工逐行阅读,从而实现未审查 AI 生成代码的生产级可信度。
审查与验证的概念分离
理解这一方法论的核心在于澄清两个容易被混淆的术语。审查(Review)指人工逐行阅读代码以确认其正确性,这是一种高度依赖人类认知资源的活动。验证(Verification)则是通过任何机器可执行的检查手段来确认代码正确性,这些手段可以包括测试、静态分析、形式化方法等。关键洞察是:我们不必同时依赖审查和验证,而是可以在充分验证的前提下跳过人工审查。
这一理念的实践意义在于:当自动化验证手段足够强大时,代码的可信度不再依赖于人类是否「看过」它,而是依赖于是否满足预定义的技术约束。这与传统的代码审查流程形成了鲜明对比 —— 后者将人工阅读视为必要的前置步骤,而前者将其视为可选的增强手段。
自动化验证栈的构建
Lavigne 的实验使用一个简化的 FizzBuzz 问题作为测试用例,构造了一套多层验证约束体系。这套体系并未依赖复杂的机器学习模型或形式化证明,而是使用了软件工程中已成熟的测试工具链。
属性测试(Property-Based Testing) 是第一层约束。传统单元测试使用特定的输入输出对验证代码行为,而属性测试则通过运行大量随机生成的输入来验证代码是否满足某种「属性」。以 Python 的 Hypothesis 库为例,测试可能生成数百个 3 和 5 的公倍数来验证 FizzBuzz 输出是否始终正确。这种方法的优势在于它能覆盖人工测试难以想到的边界情况 —— 零、负数、极大整数等。虽然属性测试的执行速度较慢且具有非确定性,但它提供了传统测试无法企及的覆盖广度。
突变测试(Mutation Testing) 构成第二层约束。这类工具会小幅度修改代码 —— 例如将加法改为减法、将常量 15 改为 14—— 然后重新运行测试套件。如果测试失败,变异体被「杀死」,说明测试套件足够敏感;如果测试通过,变异体「存活」下来,暗示测试存在盲点。Lavigne 巧妙地反转了突变测试的常规用途:与其用它来增强测试套件,不如用它来约束代码空间。如果假设测试本身是正确的,那么能够通过所有测试的代码变种必然满足测试所表达的规范,从而缩小了「代码看起来对但实际不对」的风险窗口。
无副作用约束 是第三层关键要求。纯函数式代码更容易推理和验证,因为其输出完全由输入决定,不存在隐藏的状态变更。这一约束不仅简化了验证过程,还使代码的数学性质更加清晰。在 FizzBuzz 场景中,函数被要求不得包含打印语句、不得修改全局状态、不得产生任何可观察的副作用。
静态检查 作为补充层包括类型检查和代码 linting。对于 Python 项目,这意味着启用严格的类型注解检查和 pylint 或 ruff 的全部规则。虽然这些检查不直接验证功能正确性,但它们消除了大量常见的编程错误 —— 类型不匹配、未使用的变量、导入错误等。
可维护性的重新定位
一个常见的担忧是:未经人工审查的 AI 生成代码是否可维护?Lavigne 的观点具有启发性:当验证约束足够强时,代码的可维护性重要性显著降低。我们应该将经过充分验证的 AI 生成代码视为「编译产物」而非「源代码」。这意味着如果需求发生变化,我们可以重新生成代码并重新验证,而不是试图理解并修改现有代码。
这种思路与传统的「代码即资产」观念形成对比。在 AI 辅助编程时代,代码的「可再生性」可能比「可读性」更有价值 —— 只要能够在短时间内重新生成并验证符合要求的代码,原代码的具体实现细节就不再那么关键。
工程实践参数与落地建议
对于希望在项目中尝试这一方法的团队,以下是经过提炼的关键参数和实施要点。
属性测试的输入空间设计应覆盖三个维度:正常输入、边界条件和对抗性输入。对于数值函数,边界条件包括零、负数、极大值;对于字符串函数,考虑空字符串、超长字符串和特殊字符;对冲数组操作时,测试空数组、单元素数组和极端长度数组。一个实用的原则是每个属性至少运行 100 次随机生成,并在 CI 中设置超时阈值防止测试无限运行。
突变测试的存活率阈值建议控制在 5% 以下。大多数突变测试工具会报告「被杀死的变异体百分比」,这个指标越接近 100% 意味着测试套件越健壮。如果存活率超过 5%,说明测试可能存在未被覆盖的代码路径或逻辑分支。需要注意的是,变异体的选择应当与项目特点匹配 —— 对于业务逻辑密集的代码,可能需要配置更多的条件判断变异;对于数值计算密集的代码,算术运算符变异更为关键。
纯函数约束的自动化检查可以通过静态分析工具实现。例如 Python 的 pylint-sandbox 或专门的函数式编程 linter 插件检测函数是否包含文件系统操作、网络请求、随机数生成等副作用调用。对于必须包含副作用的场景(如写入日志),建议将其分离为独立的副作用函数,纯函数通过依赖注入接收这些操作而非直接执行。
CI 流水线集成应遵循渐进式检查顺序:先运行快速的静态检查和 linting(通常在数秒内完成),再运行单元测试(分钟级别),最后运行属性测试和突变测试(可能需要数分钟)。这种分层策略可以在开发早期捕获明显问题,避免浪费计算资源等待完整的验证结果。
适用边界与风险提示
这种方法并非万能解决方案。对于安全关键系统、受监管的代码库或涉及复杂业务逻辑的组件,仍然建议在自动化验证基础上叠加人工审查。实验表明,当问题域足够清晰、规范足够精确时,自动化验证的效果最佳;反之,当需求本身存在模糊性或需要领域专业知识判断时,自动化手段难以完全覆盖。
另一个实际考量是初始投入成本。构建完整的验证约束体系需要一定的工具配置和测试编写经验,这在前几次项目中可能显得「不划算」。但一旦建立基线,后续项目的复用成本将显著降低,而且自动化验证的一致性和可重复性远优于人工审查。
资料来源:Peter Lavigne, "Toward automated verification of unreviewed AI-generated code", peterlavigne.com, 2026-03-16.