你已经学会了如何通过提示让 AI 生成代码,而且此时多半也用这些技巧产出了些代码。接下来进入关键阶段:确保这些代码正确、安全、且可维护。
作为开发者,你不能拿到 AI 的输出就稀里糊涂地上线。你需要审查它、测试它,必要时改进它,并将其集成进你的代码库。本章聚焦于如何读懂 AI 给你的代码,如何迭代式地编辑与调试它,并如何把这段代码真正纳入你的名下,成为项目的一部分。
本章涵盖:
- 按你的原始意图来解读 AI 的代码
- “多数解”现象:为何 AI 生成的代码常像一种常见解法
- 评审代码的技巧:清晰度与潜在问题
- 当 AI 写的代码不如预期时如何调试
- 为了风格或效率对代码进行重构
- 编写测试以验证代码行为
掌握这些技能,你就能更有底气地把 AI 的贡献融入你的项目。
从意图到实现:理解 AI 的“解读”(From Intent to Implementation)
拿到 AI 的代码后,第一步应当是把它与你的意图(你给出的提示)进行对照:它是否满足了你提出的需求? 有时 AI 会略有误解,或只实现了你要求的一部分。
仔细读代码。用脑中(或纸上)单步推演:
- 追踪它在一个典型输入下会做什么。
- 如果你的提示包含多个部分(“做 X 并且做 Y”),核对 AI 是否都完成了。
- 确认 AI 没有加入你没要求的功能——有时它会“自作聪明”地加上日志或额外参数,这可能有用,也可能多余。
就像审同事的代码一样,若有不清楚的地方,标记下来。先尝试找找它存在的正当理由;如果找不到,就追问或考虑删掉。
例如,你要求写一个素数检测器,而 AI 的代码还会对每个数字打印“Checking 7…”。这可能是你提示方式的产物,或来自训练数据中的教程模式(教程常打印进度)。如果你不需要,计划移除它,或提示 AI 帮你移除。
同时,确认边界情况按你的预期处理了:你希望它处理空输入吗?如果输入可能是 None 或负数,AI 是否考虑了?
如果你的提示里存在歧义而迫使 AI 做了选择,把那些地方找出来。也许你没指定输出格式,而它选择了打印而不是返回。现在你要决定接受还是修改。
这个理解阶段至关重要,别跳过。即便你准备写测试,阅读理解仍然重要,因为测试可能覆盖不到所有问题(而且对一些显而易见的点,读更快)。
最后,考虑 AI 的假设。AI 往往倾向于“多数/常见的解读”(引出下一节)。
“多数解”问题:最常见 ≠ 最合适(The “Majority” Problem)
在大量代码上训练的模型,经常产出训练集中最常见(或最简单可行)的方案——我称之为“多数解效应”。它对一般场景是对的,但未必最适合你的具体情境。
比如,你要求一个查找算法却未给上下文,AI 可能输出线性查找,因为它直观且常见。可你其实需要二分查找,只是没强调性能关键。线性查找在中等规模下可以,但性能优先时就不行了。
类似地,AI 可能使用全局变量,因为不少入门示例都这么做;但在你的项目里,这也许是不被接受的风格。
要意识到:AI 的方案往往为通用场景做了优化。而作为人类开发者,你拥有 AI 缺乏的上下文洞见。
应对建议:
- 识别假设:如果代码假设列表已排序或输入总是有效,这假设合理吗?你指定过吗?若没有,可能应该加检查。
- 比较替代:你知道有多种做法(不同算法/结构)吗?AI 选了哪一个?这正是你想要的吗?若不是,要么重提要求,要么直接改。
- 关注边界:若 AI 代码只覆盖“常规情况”,却忽略了你在意的边界(如整数溢出),那就需要修补——训练示例可能没管这些,但你的场景很重要。
理解 AI 倾向于给出“通用解”,会让你更擅长审查它的代码。它不是魔法、也不是量身定制;它是对解法的高明猜测。量体裁衣由你来做。
可读性与结构:常见模式与隐患(Code Readability and Structure)
AI 生成的代码常有一些特征:
- 注释偏多或表述古怪(源自教程风格,教程往往“高注释”)
- 变量名习惯性地用
i/j/k之类 - 为覆盖通用情形而显得冗长
检查这些并评估是否符合你的项目风格。功能也许没问题,但需要一次可读性体检。你可能需要:
- 重命名变量,让它们更有意义,或与代码库保持一致。
- 删改注释:像
# 检查是否为素数这种在“自解释”代码上方的注释可删;但解释复杂逻辑的注释要保留或强化。 - 通过 linter/formatter(如 Python 的 Black、Go 的 gofmt)统一格式与空白、括号风格。
也要留意异常结构:本应只有一个函数,AI 却定义了多个类/函数?有时它按训练示例的拆分方式来组织。如果这显得过度,可内联(反之亦然)。代码是不是过于巧或过于直?AI 有时会给出一行流(one-liner) ,这是否契合团队偏好?不合就调整。
其他常见隐患:
- 一位偏差(off-by-one)错误
循环边界尤其要当心。可用简单样例在脑中走一遍循环。 - 未处理的异常
代码是否默认文件总能打开、输入总是正确格式?需要则补上错误处理。 - 性能陷阱
比如在大数据集里用内层循环做成员检查,其实用集合(set)更好。AI 的方案可能正确但不优。 - 库的使用
确认用的是你想要且可用的库。有时它会为一个简单求和拉进 numpy(因为见过类似示例)。若不值得引入依赖,就改成纯原生或你预计的库。 - 不一致
偶尔会出现文档字符串(docstring)与实现不符的情况(逻辑改了,注释没改)。修正之。 - 小语法问题
罕见,但在某些语言里仍可能混淆。 - 过时 API
可能调用了库的旧函数。见到不熟的调用,快速查官方文档确认与你的版本匹配。 - 占位符
若输出里有 “Your code here” 这类模板残留,记得填上。
一句话:把 AI 的代码当作实习生写完就下班的作品对待——你需要把关质量并正确集成。
调试策略:定位并修复错误(Debugging Strategies: Finding and Fixing Errors)
假设你运行了代码(或写了测试,我们稍后会讲),有些地方不工作。调试 AI 生成的代码与调试你自己的或他人的代码并无二致——唯一不同是这段代码不是你写的,你可能更不熟。但因为你已经认真读过(见图 5-1),你其实已做好准备。
六步调试法(Six-Step Approach to Debugging)
- 复现问题。
用会失败的输入运行该函数或代码,观察输出或报错。 - 定位问题源。
使用常规调试技巧(如打印日志)或调试器单步执行。若是逻辑错误(输出不对),就手动或通过打印追踪逻辑,找出与预期分叉的位置。 - 对照提示与代码。
有时 bug 仅仅是没有完全实现需求——比如你要求排序但并未正确排序。这可能意味着 AI 的逻辑有缺陷,或边界情况(如空列表)未被处理。 - 借助 AI 来调试。
你可以把有问题的代码喂回给 AI,并说明:“这段代码在 X 场景下结果不对,能帮忙找 bug 吗?”它常会像代码评审那样分析并指出问题。例如,它也许会发现循环本应遍历到len(arr),却只到len(arr)-1。要注意不要盲目信任——就像请同事协助调试一样看待它。 - 修复代码。
你可以选择手动修,或提示 AI 生成修正版。若修复一目了然,直接改;若不明显,可以这样提示:“上述函数在输入 X 上失败(期望 Y,实际 Z)。请修正。”AI 可能会据此调整代码。 - 再次测试。
确认问题已解决,且没有引入新的问题。
我推荐测试驱动式调试:尽可能为关键函数编写一些测试(本章后面测试部分会详述)。任何失败的测试都会直接指明问题所在;除最简单的函数外,这通常比手动检查更快。
最后,调试时别只问 what,也要问 why。试着理解 AI 为何出错:是否因为提示里该点不清楚?这会影响你下次如何提示,或者提醒你今后总要针对该方面做二次检查。比如,如果你发现 AI 不特别说明就不处理空输入,你就会开始在提示中总是写明,并在审查时重点关注。
为可维护性而重构:让 AI 代码成为“你的代码”
(Refactoring for Maintainability: Making AI Code Your Code)
当代码在功能上正确后,考虑重构,使其符合项目规范并便于未来维护。AI 的工作是尽快给你代码;你的工作是把它打磨好。
以下是另一套六步重构流程:
-
对齐风格规范。
用代码格式化器或 linter 处理一遍,修复如“变量名应小写”“行过长”等警告。这能迅速让代码看起来像你们代码库的风格。多数 AI 工具在风格上还行,但通常还需微调。 -
改进命名与结构。
若 AI 在类中起了_helper1、_helper2之类的函数名,而你偏好语义化命名,就重命名。若它拆出一堆仅被使用一次的小函数,且并未增加清晰度,可考虑内联(反之亦然)。 -
移除不必要部分。
例如输出里可能带了你没要求的main区块或测试代码——不需要就删。反过来,如果它把所有逻辑都塞进单个函数,而你希望为清晰度拆分成若干小块,就现在拆开。 -
补充文档。
若这段代码将作为库或模块提供给他人使用,请按规范添加docstring/注释。AI 可能写了一些注释,但你要确保符合项目标准(比如你们要求在 docstring 中记录参数与返回值)。 -
必要时优化。
代码虽能跑,但是否够高效?若它会在紧凑循环或大数据上频繁调用,检查其复杂度。AI 可能没有采用最优方案(“多数解”往往是简单循环而非更优解)。若有性能顾虑,改用更好的算法。你可以再次让 AI 参与:“请把这段代码优化快一些,比如把查找从 list 改用 set。”
当然,作为开发者,你通常知道自己想要的模式,也可直接实现。 -
必要时简化。
有时 AI 的代码会过度冗长。例如,本可用单个条件表达式的地方写了多层if-else。虽说“显式”未必是坏事,但适度化繁为简能提高可读性且不失清晰。
重构的目标,是让后来者看到这段代码时看不出“AI 写的”痕迹——它就应该是好代码。这往往意味着加入一些人类的小细节,让代码更干净。
重构后要验证没有被你改坏——下面就转向测试。
测试的重要性:单元、集成与端到端
(The Importance of Testing: Unit, Integration, and End to End)
测试一直重要,对 AI 生成代码则格外重要,原因有二:其一,你不是从零写的,需要更强把握它在各种情况下能工作;其二,若你之后继续提示 AI 修改或引入更多 AI 代码,测试能帮助你确保既有功能不被破坏。下面快速回顾常见测试类型:
-
单元测试(Unit tests)
为你从 AI 得到的每个函数/模块编写测试,尤其覆盖边界情况。以“素数”示例:测试素数、合数、1(边界)、0或负数(定义预期行为)、大素数等。若全部通过,基本可信。
你甚至可以让 AI 生成这些测试:“为上述函数编写覆盖边界情况的 PyTest 单元测试。”
它通常做得还不错。但仍要亲自审查,确保有效且到位。 -
集成测试(Integration tests)
若 AI 代码会与代码库其他部分交互(如访问数据库),写测试在上下文中调用它:是否确实写入了应有的数据?若它输出会被其他函数消费,就把它们串起来测试。 -
端到端测试(End-to-end tests)
若该代码是更大流程的一部分,就从头跑到尾。例如它位于某个Web 路由中,就在测试环境里对该路由发请求,检查输出格式、错误处理等是否可靠。
需要的测试强度,取决于代码的重要性与复杂度。但即便是简单脚本里的少量断言或一次手工跑通,也好过没有。记住:测试不仅能发现 bug,还能锁定行为。当你(或 AI)之后再改动时,测试能防止回归。
测试也是归属感的好工具:当你通过测试发现并修复了问题,就能对这段代码建立信心。此时,说“这段代码是你的”完全合理——就像代码库里的其他任何代码:你理解它、信任它,并有测试为其护航。
关于 AI 与测试的小提示
一些 AI 编码工具开始集成测试建议。例如 CodeWhisperer 偶尔会在代码后建议一个assert。把这类建议当作起点,不要假设它面面俱到。动动脑筋想些有创意的边界用例——这正是人类直觉仍然最有价值的地方。
摘要与下一步(Summary and Next Steps)
我们已经走过了生成—理解—调试—重构这条闭环。这个循环的耗时可能很短(小函数在几分钟内完成),也可能更长(复杂模块需要数小时到数天,并伴随间歇性的 AI 协助)。
需要明确的是:你(开发者)对最终代码负责。AI 只是加速创作的工具,一旦出问题,它不会替你背锅。另有许可证/版权风险:一些 AI 提供商提示,当输出超过一定长度时,统计上更可能包含被复制的材料。虽然这种情况很少见,且厂商已经大幅缓解,但就像你查看 Stack Overflow 的答案时会留意授权或署名一样,仍应快速检查——尤其当输出体量大、或“好得出奇”时。比如,你让它“实现快速排序”,AI 返回 20 行干净代码——这通常没问题,属于常识性实现。但若你要求的是冷门内容,而它给了你一大段代码,不妨截取其中独特的字符串上网搜索,判断是否逐字来自某处。近期已有记录在案的案例表明,一些 AI 系统会复现期刊文章等受版权保护文本。作为负责任的代码所有者,当 AI 生成内容超出通用模式或与特定来源高度吻合时,应核验其来源。
最后,把代码集成进你的项目:纳入版本控制,必要时在提交信息里注明“使用了 AI 辅助”。这并非硬性要求,但有些团队希望保留这类痕迹以便追溯。
随着需求变化,你很可能会不断修改这段 AI 生成的代码。像对待其他代码一样对待它:别想着“这是 AI 的代码,我再让 AI 改”。你当然可以继续让 AI 协助,但也可以直接手改——选对你来说最高效且最可维护的方式即可。
通过认真审查与测试,AI 生成的代码就会成为你项目里“再普通不过”的一部分。此时,第 10 行是 AI 写的还是你写的,已无关紧要——重要的是它是否满足项目的需求与标准。
遵循上述做法,你既能利用 AI 编码的速度优势,又能保障质量;既能避免盲目信任的陷阱,又能把它正确地融入专业的开发工作流。
接下来,第 6 章将讨论 AI 如何从根本上改变原型阶段。我会介绍利用 AI 助手把从想法到可用原型的周期从数天缩短到数小时的实用技巧;讨论具体的 AI 原型工具(包括 Vercel v0 与 screenshot-to-code 工具),以及在 AI 引导下进行迭代打磨的策略。
我还会探讨从 AI 生成原型过渡到生产就绪代码这一关键过程:当 AI 成为开发工作流的核心组成时,会出现哪些机会与挑战。通过真实案例,我将展示开发者如何在快速验证想法的同时保持代码质量,并规避从概念到落地过快推进时常见的坑。