Home
avatar

.Sam

【AI Native开发-09】AI Native 模式下的代码评审与变更控制

摘要

代码写完、测试变绿,不等于可以合入。在 AI Native 开发里,代码评审(Code Review)的对象变了:以前审的是”同事写的代码”,现在审的是”AI 在一份 Plan 约束下产出的改动”。这带来两个新问题——改动量更大(AI 一分钟能写人一小时的量),以及越界更隐蔽(AI 会在”合法文件”里夹带计划外逻辑)。

核心论点:

  1. 评审的单位从”代码行”变成”变更与契约的一致性”。 首要问题不是”这行代码优不优雅”,而是”这份 Diff 是否忠实地实现了 Plan,有没有越界、有没有夹带、有没有偏离 Spec”。
  2. 变更必须分级。 不是所有改动都值得同样的审查强度。可逆的、低影响的改动可以快速放行;不可逆的、触及权限和钱的改动必须强制人眼逐行看。
  3. 变更控制靠机制,不靠自觉。 文件所有权、Diff 白名单、高风险路径强制审批——把”不许越界”从口号变成 CI 能拦住的硬规则。

本篇给出 AI 产出 Diff 的评审清单、变更风险分级模型、越界检测方法,以及一套可以落到 CI 的变更控制规则。

一个典型场景

删除文章功能做完了,分支上 6 个文件、约 300 行改动。测试全绿。评审时你重点看了删除接口 delete.ts,逻辑没问题,权限校验、状态判断都在,于是合了。

上线后出现两个问题:

  1. 生产构建突然失败——排查发现 AI 在 article-loader.ts(Plan 的”不碰清单”里明列的文件)里改了一个函数签名,它觉得”新接口需要一个加载方式”。这个改动在本地 dev 没报错,生产构建时类型检查才炸。
  2. 更隐蔽:AI 在删除接口里”贴心地”加了一行——删除成功后调用一个 notifyFollowers() 函数(项目里根本没有这个函数,它连函数带调用一起造了),还在文件顶部 import 了一个不存在的模块。本地测试因为走了 mock 没暴露,构建时才报模块找不到。

这两个问题的共同点:它们都不在”核心逻辑”里,藏在评审容易跳过的角落——一个在”不该动的文件”里,一个在”看起来合理的顺手添加”里。评审只盯着主逻辑看,就会漏。

核心问题:AI 时代的 Code Review 变了什么

传统 Code Review 的隐含假设是:改动量小、作者理解每一行、评审者也大致理解。AI 打破了这三个假设:

  • 改动量暴涨:AI 可以一次产出几百行,评审者逐行看的负担急剧上升;
  • “作者”不完全理解改动:人是 AI 的协作者,但人未必逐行读过 AI 写的每一行——这意味着评审不能依赖”作者自己把关过了”;
  • 越界动机更强:AI 倾向于产出”完整”的方案,会主动补齐它认为缺失的部分(造函数、加 import、顺手改别处),这些补齐全无 Plan 授权。

所以评审的重心必须转移:

传统评审重心:代码对不对、优不优雅、有没有 bug
AI 时代评审重心:
  1. 这份 Diff 是否 = Plan 授权的改动?(一致性 / 越界)
  2. 高风险逻辑是否正确、是否有人眼确认?(权限、不可逆操作)
  3. 有没有 AI 特有的痕迹?(幻觉 import、编造 API、空实现、吞错误)
  4. 测试是否真的验证了行为,而不是陪跑?(断言有效性)

顺序很重要:先查”该不该有这些改动”,再查”改动对不对”。 越界的改动再优雅也是事故,因为它绕过了 Plan 评审。

Diff 评审清单

评审一份 AI 产出的分支,按这个顺序过:

【第一层:范围一致性】(先看,最重要)
□ git status / diff --stat 里的文件,是否都在 Plan 的
  "新增 / 修改"清单内?
□ Plan 的"不碰清单"里的文件,是否一个都没动?
□ 有没有 Plan 之外的新文件、新依赖(package.json)?
□ 改动行数和这个步骤的规模匹配吗?
  (一个"加删除按钮"的步骤改了 800 行,必有夹带)

【第二层:AI 特有痕迹】
□ 有没有 import / require 一个项目里不存在的模块?
□ 有没有调用一个不存在的函数 / API?
   (grep 确认被调用的函数真的有定义)
□ 有没有空 try/catch、catch 后什么都不做的错误吞掉?
□ 有没有写死的测试数据、mock 值残留在生产代码里?
□ 有没有返回假数据 / 空数组 / "TODO 实现"的占位逻辑?
□ 注释里有没有"假设""大概应该是"这类不确定措辞?

【第三层:高风险逻辑】(必须逐行人眼)
□ 权限校验:服务端是否真的校验了,还是只靠前端隐藏按钮?
□ 不可逆操作:删除 / 覆盖 / 外发,有没有确认 + 缓解?
□ 状态判断:用的是真实字段(frontmatter 的 hide 布尔)还是幻觉字段
  (status === 'published',本项目根本没有 status 字段)?
□ 并发 / 幂等:重复调用、并发调用安全吗?
□ 输入校验:外部传入的 id、参数有没有校验和边界处理?

【第四层:测试有效性】
□ 测试断言的是行为 / 副作用,还是只断言"没报错"?
□ 权限失败的用例,有没有断言"副作用没发生"
  (文件还在 / 数据没变),而不只是状态码?
□ 用空实现测试法推演过吗?
  (把被测逻辑换成空实现,测试应该变红——不变红说明测试没在测)
□ 有没有被改宽松 / 删掉的断言?(对比 Plan 阶段预期)

第一层和第二层是 AI 时代新增的重点,也是传统评审最容易跳过的。养成一个动作:合入前跑一次 git diff main --stat,对着 Plan 的文件清单逐行核对。 这一步两分钟,能拦住绝大多数越界事故。

变更风险分级

不是所有改动都值得逐行审。按风险分级,把有限的人眼注意力花在刀刃上。四个维度(第 00 篇风险分级的细化应用):

维度低风险高风险
可逆性可随时回滚(纯前端文案、样式)不可逆(删除、支付、外发、数据覆盖)
影响面单页面、单函数全局、多模块、被多处依赖
数据敏感性不碰用户数据触及权限、隐私、资金
检测延迟错了立刻能看到错了要到生产 / 特定条件才暴露

据此把变更分成三级:

P0(强制逐行人眼评审 + 人工确认合入):
  - 权限 / 认证 / 授权逻辑
  - 不可逆操作(删除、支付、发消息、数据删除)
  - 全局共享模块、被多处依赖的核心函数
  - 数据迁移、schema 变更
  → 不允许 AI 自动合入,必须人逐行看 Diff 并签字

P1(人评审重点逻辑,测试充分可较快放行):
  - 新业务功能、新接口
  - 修改现有业务逻辑
  - 涉及状态流转、并发
  → 人审范围一致性 + 高风险逻辑,测试必须覆盖异常路径

P2(可较宽松,AI 自检 + 测试把关即可):
  - 纯展示、文案、样式调整
  - 文档、注释、类型补充
  - 不涉及逻辑和数据的重构
  → 测试通过 + Diff 范围核对即可

关键原则:风险等级由”改动本身最危险的那部分”决定,不由改动大小决定。 一个三行的删除权限逻辑改动是 P0,一个三百行的纯样式页面是 P2。AI 特别需要这个分级——它倾向于认为”改得少就简单”,但权限判断里一行错就是越权。

越界检测:把”不许越界”变成硬规则

靠人每次记得核对清单不可靠,要把变更控制落到机制上:

1. 文件所有权 / 路径保护(CODEOWNERS 或 CI 脚本)

# 高风险路径,改动必须人工审批
/src/lib/auth/**        @人工owner
/src/lib/article-loader.*  @人工owner   # 被多处依赖
/keystatic.config.ts    @人工owner
/package.json           @人工owner      # 新增依赖必须审

CI 里加一道检查:如果 AI 的分支动了受保护路径,且没有人工审批记录,直接拦下。

2. Diff 白名单校验

把 Plan 授权的文件清单作为输入,CI 校验实际改动文件是否超出:

# 思路:Plan 里声明允许改的文件,CI 比对实际 diff
allowed_files = plan["files_to_add"] + plan["files_to_change"]
actual_changed = git diff --name-only main...HEAD

unauthorized = actual_changed - allowed_files - 自动生成文件
if unauthorized:
    fail(f"发现 Plan 未授权的改动: {unauthorized}")

这道检查把”越界”从”评审时靠人眼发现”变成”CI 自动拦截”。AI 改了”不碰清单”里的文件,CI 直接红,根本到不了人评审那一步。

3. 新增依赖告警

package.json / pnpm-lock.yaml 的任何变更都应触发人工确认。AI 有时为了一个小功能引入一个重依赖,甚至 import 一个不存在的包(幻觉依赖)。

4. 生产代码里的调试残留扫描

CI 扫描:console.log / debugger / 写死的 localhost /
        TODO / FIXME / 注释掉的代码块
出现在生产代码里则警告或拦截。

变更可追溯

每个合入的变更都应该能回答三个问题:

这个改动是为什么?   → 关联的 Spec / Plan / issue
它改了什么?         → Diff(在授权范围内)
它被验证过吗?       → 关联的测试 + Verify 报告

落到实践:

  • 分支名 / commit 关联需求编号(如 feature/article-delete-REQ001);
  • PR 描述里贴 Plan 链接和测试结果;
  • 高风险变更在 PR 描述里显式列出”风险点和缓解措施”。

这样三个月后回头看任何一处代码,都能追溯到当初的意图和验证证据——这在 AI 高频产出的代码库里尤其重要,否则代码库会迅速堆积”没人知道为什么在这”的 AI 生成逻辑。

代码评审反模式

反模式一:全绿就合。 测试通过 ≠ 改动安全。测试可能是陪跑(断言无效)、可能漏掉越界文件、可能没覆盖高风险路径。测试是必要条件不是充分条件。

反模式二:只看主逻辑。 人的注意力天然集中在”这个功能怎么实现的”,于是跳过 import 区、跳过错误处理、跳过”顺手改的别的文件”。AI 的幻觉和越界恰恰藏在这些角落。对策:用清单强制覆盖第一层(范围)和第二层(AI 痕迹)。

反模式三:信任”作者已检查”。 传统评审里作者是人、会自我把关;AI 时代”作者”是 AI + 人,人可能根本没逐行看过。不能假设改动已经被理解过——评审者可能是第一个完整读这份 Diff 的人类。

反模式四:对 AI 产出过度宽容或过度苛刻。 两个极端都有害:宽容到”AI 写的大概对”就合,会放进幻觉代码;苛刻到要求每行都像资深工程师手写、反复让 AI 重写,会拖慢速度还不如自己写。正确姿态:对范围和高风险逻辑零容忍,对风格和非关键实现保持宽容。

失败回退

  • 评审发现越界改动 → 退回 Implement,撤回计划外改动;越界严重、Diff 无法清理 → 整体回滚分支重做;
  • 发现幻觉 import / 不存在的 API → 退回 Implement 修正,并检查为什么测试没拦住(测试是否 mock 过度);
  • 发现高风险逻辑错误(权限、状态判断)→ 退回 Implement,同时追问这是 Spec 缺口还是实现错误;
  • 发现测试是陪跑(空实现测试仍通过)→ 回到测试设计 / 自动化用例补强断言;
  • 反复出现同类越界 → 把对应规则加进 CI 变更控制pitfalls.md,让机制去防,而不是每次靠人记。

人审重点

  • P0 变更必须人逐行看,这是不可委托的——权限、不可逆操作、核心共享模块;
  • 范围一致性核对可以人做也可以 CI 做,但放行决策在人;
  • AI 可以先出一份”自评审意见”(它能机械地查 import 是否存在、是否有 console.log、文件是否超范围),人在此基础上专注判断业务逻辑和风险;
  • 最终合入决策永远在人,尤其是高风险变更。

结语

AI 让”写代码”变便宜了,于是”判断哪些改动该存在”变成了最贵的动作。代码评审的主角不再是代码本身,而是变更与契约的一致性——这份 Diff 是不是我们在 Plan 里授权的那一份。


到这里,“怎么把代码做出来”这条线(Spec → Plan → 评审 → Implement → 代码评审)就完整了。但软件正确与否,最终要靠验证说话。从下一篇开始进入测试与验证篇:首先是测试的源头——怎么把一份 Spec 系统性地变成结构化的文本测试用例,让”测什么”不再凭经验,而是每条需求都有对应的用例追踪。

AI Native SDD 代码评审 变更控制 软件工程