信任但要验证:学习捕捉AI遗漏的内容
我们常说AI“理解”代码,但它们并不像人类那样真正理解你的问题或代码库。它们只是在模仿之前见过的文本和代码模式,旨在生成看起来正确且合理的答案。虽然通常正确——这就是为什么氛围编码(反复将提示输出反馈给AI而不阅读生成的代码)效果很好——但并不保证正确。由于LLM的工作方式和我们提示方式的限制,这些解决方案很少考虑整体架构、长期策略,甚至常常忽略良好的代码设计原则。
我发现的应对这些风险最有效的原则完全借用了另一个领域:信任但要验证。虽然这个短语从国际关系到系统管理都被使用过,但它完美捕捉了我们与AI生成代码所需的关系。我们足够信任AI,将其输出作为起点,但在提交之前验证一切。
信任但验证是有效方法的基石:信任AI作为起点,但要验证设计是否支持变更、可测试性和清晰度。这意味着应用与任何代码相同的批判性审查模式:检查假设、理解代码的实际作用,并确保它符合你的设计和标准。
验证AI生成的代码意味着阅读它、运行它,有时甚至逐行调试。问问自己,这段代码在几个月后是否仍然对你——或其他人——有意义。在实践中,这可能意味着即使对AI生成的代码也要进行快速设计审查,当耦合或重复开始出现时进行重构,并刻意检查命名以使变量和函数读起来清晰。这些额外步骤帮助你保持批判性思维,避免将早期错误锁定在代码库中,变得难以修复。
验证还意味着采取具体步骤检查你的假设和AI的输出——比如为代码生成单元测试,正如我们之前讨论的。AI可以提供帮助,但默认情况下并不可靠。它不知道你的问题、你的领域或你团队的背景,除非你在提示中明确说明并仔细审查输出以确保你传达得当且AI理解正确。
AI也可以帮助验证:它可以建议重构、指出重复逻辑,或帮助将混乱的代码提取为更清晰的抽象。但需要你指导它进行这些更改,这意味着你必须先发现它们——这对在多个项目中见过这些问题的经验丰富的开发人员来说要容易得多。
除了直接审查代码外,还有几种技术可以帮助验证。它们基于这样的理念:AI根据其工作的上下文生成代码,但它无法像人类开发人员那样告诉你为什么做出特定选择。当代码不工作时,通常是因为AI根据其训练数据中的模式填补了空白,但这些模式实际上与你的实际问题不匹配。以下技术旨在帮助揭示这些隐藏的假设,突出选项,以便你能够为自己的代码做出决定,而不是留给AI。
要求AI解释它刚刚生成的代码。接着询问它为什么做出特定的设计选择。这种解释不同于人类作者带你了解他们的意图;它是AI解释自己的输出。但这种视角仍然有价值,就像有第二位审查者描述他们在代码中看到的内容。如果AI犯了错误,它的解释可能会重复这个错误,因为它仍然基于相同的上下文工作。但这种一致性实际上可以帮助揭示你可能仅通过阅读代码无法捕捉的假设或误解。
尝试生成多个解决方案。要求AI生成两到三个替代方案,迫使它改变方法,这通常揭示不同的假设或权衡。一个版本可能更简洁;另一个更符合惯例;第三个更明确。即使没有一个完美,将选项并列比较有助于你比较模式并决定最适合代码库的内容。比较替代方案是保持批判性思维并控制代码库的有效方式。
使用AI作为自己的批评者。在AI生成代码后,要求它审查该代码的问题或改进之处。这可能有效,因为它迫使AI将代码作为新任务处理;上下文转换更可能揭示AI第一次未检测到的边缘情况或设计问题。由于这种转换,你可能会得到矛盾或挑剔的反馈,但这也可能有用——它揭示了AI从训练中提取矛盾模式的地方(或者更准确地说,是从训练中提取矛盾模式的地方)。将这些批评视为你自己判断的提示,而不是盲目应用的修复。同样,这是一种通过突出你可能在浏览生成代码时跳过的问题来保持批判性思维的技术。
这些验证步骤可能感觉会减慢你的速度,但它们实际上是对速度的投资。在五分钟的审查后捕捉设计问题比在六个月后当它遍布代码库时调试要快得多。目标是通过添加战略检查点来超越简单的氛围编码,在这些检查点你从生成模式切换到评估模式。
AI在极短时间内生成大量代码的能力是一把双刃剑。这种速度很诱人,但如果你不小心,你可能会直接通过氛围编码陷入经典的反模式(参见“构建AI抵抗的技术债务:当速度创造长期痛苦”)。在我自己的编码中,我见过AI沿着这条路径迈出清晰的步骤,创建过于结构化的解决方案,如果我允许它们不受控制,将直接导致过于复杂、高度耦合和分层的设计。我发现了它们,因为我花了数十年编写代码和团队工作,所以早期识别了模式并纠正了它们——就像我在与团队成员的代码审查中做了数百次那样。这意味着要足够放慢速度来思考设计,这是“信任但验证”心态的关键部分,涉及仔细审查变更,以避免构建后来无法解开的层层复杂性。
为AI生成的代码编写良好单元测试的难度也是一个强烈的信号。如果测试对AI来说难以生成,那就是停下来思考的信号。将单元测试添加到你的氛围编码循环中创建一个检查点——一个暂停、质疑输出并切换回批判性思维的理由。这种技术借鉴了测试驱动开发:使用测试不仅是为了以后捕捉错误,还为了揭示设计何时过于复杂或不清晰。
当你要求AI帮助为生成的代码编写单元测试时,首先让它生成测试计划。注意麻烦的迹象:大量模拟、复杂设置、过多依赖——尤其是需要修改代码的其他部分。这些信号表明设计过于耦合或不清晰。当你看到这些信号时,停止氛围编码并阅读代码。要求AI解释它。在调试器中运行它。保持批判性思维模式,直到你对设计满意为止。
还有其他明确的信号表明这些风险正在悄悄出现,它们告诉您何时停止信任并开始验证:
- 重复循环:开发人员循环使用相同AI提示的轻微变体而没有取得有意义的进展,因为他们避免退后一步重新思考问题(参见“理解重复循环:当AI陷入困境时”)。
- 几乎可用的AI生成代码:感觉足够接近信任但隐藏了细微、难以诊断的错误的代码,这些错误稍后在生产或维护中出现。
- 需要“散弹枪手术”的代码变更:要求AI进行小更改需要它在代码库的多个不相关部分创建级联编辑——这表明一个日益增长且难以管理的相互依赖网络,即散弹枪手术代码味道。
- 脆弱的单元测试:过于复杂、紧密耦合或依赖过多模拟才能使AI生成的代码通过的测试。
- 调试挫败感:不断在其他地方破坏的小修复,揭示了潜在的设计缺陷。
- 对输出的过度自信:因为AI交付了看起来完成的东西而跳过审查和设计步骤。
所有这些都是退出氛围编码循环、应用批判性思维并有意识地使用AI重构代码以实现简单性的信号。