Home
avatar

.Sam

【AI Native开发-09】测试全绿之后,代码评审还应该拦什么?

30 秒看懂这篇

测试全绿只说明已执行的断言通过了。它不会自动告诉你:是否改了 Plan 禁止触碰的文件、是否引入了新依赖、是否把关键逻辑 mock 成了空转。

flowchart TD
    A["一份待合入 Diff"] --> B["第一层:范围<br/>文件与 Plan 授权一致?"]
    B --> C["第二层:基本真实性<br/>依赖、导入、构建、调试残留"]
    C --> D["第三层:高风险逻辑<br/>权限、数据、并发、不可逆操作"]
    D --> E["第四层:测试有效性<br/>行为与副作用真的被断言了吗?"]
    E --> F{"风险分级放行"}
    F --> G["低风险:自动检查 + 快速评审"]
    F --> H["高风险:人工逐行审 + 明确批准"]

本篇给出 Diff 四层评审法、变更风险分级和 CI 控制规则。核心不是放弃代码质量,而是先确保改动忠于契约,再把有限的人眼投入高风险实现。

一个典型场景

删除功能的分支有 6 个文件、约 300 行改动,定向测试通过。合入前执行生产构建却失败:一处是 Plan 禁止修改的 article-loader.ts 被改了函数签名,另一处是删除接口 import 了不存在的模块。定向测试因 mock 没有覆盖真实导入与生产构建,没能发现。

这个案例的合理交付顺序是:定向测试通过 → 全量静态检查与生产构建失败 → 阻止合入 → 退回 Implement。不应写成“已经上线后才发现构建失败”;CI 的价值正是让这类问题无法越过合入闸门。

核心问题: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 自检 + CI 把关;仅在预先批准时可自动合入):
  - 纯展示、文案、样式调整
  - 文档、注释、类型补充
  - 不涉及逻辑和数据的重构
  → 测试 / 构建通过 + Diff 范围核对;自动合入不得触碰受保护路径

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

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

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

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

# .github/CODEOWNERS 示例;替换为真实且有写权限的用户或团队
/src/lib/auth/**           @org/auth-maintainers
/src/lib/article-loader.*  @org/content-maintainers
/keystatic.config.ts       @org/content-maintainers
/package.json              @org/platform-maintainers

CODEOWNERS 本身负责分配所有者,不会单独形成强制闸门。GitHub 上还需要在分支保护或 ruleset 中启用 Require review from Code Owners;其他平台可用相应的保护分支与 CI 审批规则实现同等约束。

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;陪跑测试回测试设计或自动化补强。P0 必须人逐行评审,P0 / P1 由人放行;P2 只有在预先批准的自动化策略、受保护路径和 CI 条件全部满足时才可自动合入。反复出现的越界应沉淀为 CI 与 pitfalls 规则。

结语

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


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

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