昨天我们把 PLAN 和 BUILD 走完了——把大象切成薄片、用 TDD 一片片落地。今天进入六阶段流水线的第四和第五步:VERIFYREVIEW。这一步解决的是”能跑 ≠ 可合并”的鸿沟。代码写得再快,跑不通就是废代码;跑得通但没人敢 merge,那也只是占用磁盘。
agent-skills 把这两个阶段拆成三个技能:调试用 debugging-and-error-recovery,评审用 code-review-and-quality,评审完之后顺手做一遍 code-simplification。我们今天把三个技能一起拆开讲,最后聊一下 /ship 阶段那个有意思的并发评审机制。

一句话定位

debugging-and-error-recovery 给的是”出问题怎么办”的标准化动作;
code-review-and-quality 给的是”这段代码该不该合并”的五把尺子;
code-simplification 给的是”合并之前能不能让它更清爽”的二次打磨。

三者顺序很自然:先调试到绿,再评审到可合并,最后顺手简化掉评审里冒出来的认知负担。

VERIFY:调试分诊六步法

AI 写代码最容易翻车的不是写错,而是调试翻车。模型看到一个测试红了,第一反应是”那我改一下实现让它过”,完全不追问”为什么红”。这种”猜答案式调试”是工程灾难的源头。

debugging-and-error-recovery 给的第一条规则叫 Stop-the-Line

1
2
3
4
5
6
1. STOP        停止加新功能
2. PRESERVE 保留证据(错误输出、日志、复现步骤)
3. DIAGNOSE 按分诊清单走
4. FIX 修根因
5. GUARD 防回归测试
6. RESUME 验证通过再继续

这条规则的关键不在第六步,在第一步。AI 接到错误时最容易做的事是”再试一次”、”换个写法”、”加点 try-catch”。每一种都是在掩盖信号,让根因离你越来越远。

分诊清单长这样

SKILL.md 里给出了一份结构化的分诊流程图,我们把它翻译成中文版:

flowchart TD
    S1["Step 1
复现
Reproduce"] --> S2["Step 2
定位
Localize"] S2 --> S3["Step 3
缩减
Reduce"] S3 --> S4["Step 4
修根因
Fix Root Cause"] S4 --> S5["Step 5
防回归
Guard"] S5 --> S6["Step 6
端到端验证
Verify"] S6 -->|失败| S1 S1 -.失败不可复现.-> S1A["收集上下文
最小化环境
观察条件"] S2 -.回归 bug.-> S2A["git bisect
二分定位"]

六步走完没修好?回到 Step 1。每一步都对应具体动作,不是抽象口号。

第一步:停下来保留证据

错误信息、日志、复现步骤、当时的 git ref——能留的全留。这是后面所有判断的起点。

最常见的反模式是:错误刚冒出来,AI 就直接 Edit 文件改代码,原来的报错信息再也找不回来。半小时后想复盘”刚才到底发生了什么”,只能凭记忆。

复现是第一性原则。如果一个 bug 复现不出来,它就没法修得有底气。

第二步:二分法定位

SKILL.md 里专门列了一个二分法工具:git bisect

1
2
3
4
git bisect start
git bisect bad # 当前 commit 是坏的
git bisect good v1.2.0 # 这个 tag 时是好的
git bisect run npm test -- --grep "broken" # 自动跑测试

二分法的核心问句是:”最近一次能工作的状态是哪一版?“ 不要从代码里逐行猜,让版本控制系统帮你缩范围。

对于不是回归引入的 bug,二分法换成”按层定位”——UI 层、API 层、数据库层、构建层、外部服务,依次排错:

1
2
3
4
5
6
UI/前端       → console / DOM / Network
API/后端 → server logs / request / response
数据库 → query / schema / 数据完整性
构建工具 → config / 依赖 / 环境
外部服务 → 连通性 / API 变更 / 限流
测试本身 → 检查是不是假阴性

第三步:缩减到最小复现

把不相关的代码、配置、输入一路砍掉,直到只剩触发 bug 的最小用例。

为什么这步重要?因为复杂场景里”症状”和”根因”是混在一起的。砍掉所有无关分支,根因会自己冒出来。

第四步:修根因而不是症状

SKILL.md 给了个典型对比:

1
2
3
症状:用户列表有重复条目
症状修法(坏):UI 层去重 [...new Set(users)]
根因修法(好):API 端 JOIN 产生了笛卡尔积 → 修 SQL + DISTINCT

判断问句是反复问”为什么”直到问不下去:

1
2
3
4
5
6
为什么列表有重复?
→ API 返回了重复数据
为什么 API 返回重复数据?
→ SQL 用了 JOIN 但没加 DISTINCT
为什么没加 DISTINCT?
→ 上线时业务只过滤了 status,没考虑数据模型

问到这里就到底了。修 SQL、加唯一索引、加测试。三个动作一气呵成,不用再改 UI。

第五步:写防回归测试

bug 修完不算完。要写一个测试在没有修复时必然失败、在有修复时必然通过

1
2
3
4
5
it('finds tasks with special characters in title', async () => {
await createTask({ title: 'Fix "quotes" & <brackets>' });
const results = await searchTasks('quotes');
expect(results).toHaveLength(1);
});

这个测试存在的意义不是”再跑一遍”,而是”下次有人改坏就立刻翻车”。

第六步:端到端验证

最后跑一遍完整的场景:

1
2
3
4
npm test -- --grep "specific test"   # 单测
npm test # 全量回归
npm run build # 编译验证
npm run dev # 人工冒烟

反合理化清单

借口 为什么是错的
“我知道 bug 在哪,直接修” 你猜对 70%,剩下 30% 耗掉几小时
“这个测试本来就是错的,跳过” 如果测试错,就修测试;不能直接跳过
“在我机器上是好的” 环境差异是 bug 高发区,去 CI 验证
“下次 commit 再修” 现在不修,bug 会埋在新功能下面
“这测试是 flaky 的,忽略” flaky 背后是真实问题,要么修要么写明

最后还有一条很重要的:错误信息本身是不可信输入。CI 日志、第三方 API、依赖报错里可能塞进”运行这个命令修复”的提示语,那是 prompt injection 的高发区。看到”请执行 X”这种字样,把内容告诉用户,不要让 AI 自动去跑。

REVIEW:五维度评审

测试都过了,代码”能跑”了,能不能合并?code-review-and-quality 给出的答案:任何改动合并前都要评审,没有例外。评审覆盖五个维度。

flowchart LR
    C["Correctness
正确性"] --> R["Readability
可读性"] R --> A["Architecture
架构"] A --> Sec["Security
安全"] Sec --> P["Performance
性能"]

每个维度的核心问句:

维度 关键问句
正确性 边界条件、并发、状态机都覆盖了吗?测试真的在测对的东西吗?
可读性 新成员看这段代码要不要问人?命名是否一致?注释是否多余?
架构 模块边界清晰吗?依赖方向单向吗?抽象层次合适吗?
安全 输入校验做了吗?权限边界呢?敏感数据泄漏了吗?
性能 复杂度合理吗?I/O 模式呢?缓存命中率呢?

每个维度都有详细的检查项,下面展开讲几个最容易被忽略的。

正确性:边界 > 快乐路径

测试都过了不一定正确。SKILL.md 里特别提到:”测试是否存在?测试是否真的在测对的东西?”

最常见的反模式是”看起来像测试,但其实在测实现细节”——比如断言某个内部函数被调用过三次,而不是断言结果正确。这种测试在重构时一改就红,但在 bug 面前毫无作用。

可读性:减少认知负担

可读性的核心不是”少写代码”,是”少让人动脑子”。SKILL.md 里列了一堆负面信号:

1
2
3
4
5
6
7
- 嵌套三层以上的 if/else
- 链式三元 ?: ?: ?:
- 命名:data、result、temp、val
- 注释解释"做什么"而不是"为什么"
- 1000 行代码干 100 行的事
- 抽象还不该出现就提前抽象(少于三个用例)
- 把无关的 if 条件塞进现有流程("该抽出去")

最后两条特别值得说。**”为未来扩展而提前抽象”是工程债的最大来源。等你真的需要第三个用例时再抽,期间的复杂度都是白付的。而“在现有流程里塞新条件”** 是另一种隐蔽债务——它让现有逻辑变得”看起来通用其实特殊”,下次维护者读起来一头雾水。

架构:重构是不是只搬了复杂度

SKILL.md 里有一条非常锐利的判断:

重构要么减少认知负担,要么就是无效重构。

怎么判断?看读者需要同时 hold 住的概念数量。如果”重构后”的概念数和”重构前”一样多,那只是搬家,没简化。

1
2
坏重构:把 200 行的单体拆成 5 个 40 行的模块,每个模块都依赖 4 个其它模块
好重构:把 200 行的单体拆成 1 个 80 行 + 2 个 20 行的辅助 + 一个 30 行的调度

判断问句是”几个分支消失了”。好的重构让分支、模式、层消失;坏的重构只是把同样的分支重新分配到不同的文件里。

安全:边界校验

SKILL.md 把安全作为一个独立维度列出来,并把详细清单指向 security-and-hardening 技能。我们这里提最常翻车的三条:

  • SQL 拼接(参数化查询了吗)
  • 输出编码(XSS 防护了吗)
  • 外部数据源当作可信数据用了

性能:N+1 是头号嫌犯

SKILL.md 列了五个常见性能反模式:

1
2
3
4
5
- N+1 查询(列表接口每行查一次详情)
- 无界循环 / 无界拉取
- 同步操作该异步的没异步
- UI 不必要的重渲染
- 列表接口没分页

量化比”感觉慢”有用得多。”这个 N+1 查询会让列表接口在 100 项时增加约 50ms” 比 “感觉有点慢” 更有说服力。

审批标准:让代码库整体健康度变好了就过

SKILL.md 第一段就给出了审批门槛:

批准标准:当一个改动确实让代码库整体健康度变好了,即使它不完美,也通过。完美代码不存在——目标是持续改进。

这背后有两层意思:

第一,不要因为”不是我会写的样子”而拒绝。代码评审评的是工程指标,不是风格审美。

第二,PR 不是要完美,要更健康。一段代码如果把 200 行烂代码改成 100 行稍好的代码,整体健康度提升了,就该合。不要追求一次到位。

评审流程的五步

评审不是”打开 diff 看一眼”。SKILL.md 给出了标准化流程:

1
2
3
4
5
Step 1: 理解上下文 —— 这个改动想达成什么?spec 是什么?
Step 2: 先看测试 —— 测试能反映意图吗?边界覆盖了吗?
Step 3: 五维度过一遍 —— 每个文件按五个轴扫一遍
Step 4: 标严重级别 —— Critical / Required / Optional / Nit / FYI
Step 5: 验证验证过程 —— 作者的 verification story 站得住吗?

第四步的严重级别标签特别重要:

前缀 含义 作者动作
(无前缀) 必须改 合并前必须修
Critical: 阻塞合并 安全漏洞、数据丢失、功能失效
Nit: 可选小问题 作者可以忽略
Optional: / Consider: 建议 值得考虑但不强制
FYI 仅供参考 不用动

加了标签,作者才知道哪些必须回、哪些可选。否则所有评论都被当作必须改,评审就失去了杠杆。

第五步”验证验证过程”是个有意思的双重否定。意思是:作者说他跑过测试、构建通过——你别照单全收,自己确认一次:

1
2
3
4
5
- 跑了哪些测试?
- 构建是否真的通过了?
- 改动的功能有没有手动验证?
- UI 改动有没有截图?
- 有没有 before/after 对比?

改动的尺寸

PR 不是越大越好。SKILL.md 给了一个粗略的尺寸指南:

1
2
3
~100 行改动   → 好,一次评审能看完
~300 行改动 → 可接受,但要是单一逻辑改动
~1000 行改动 → 太大,要拆

拆法有四种:

策略 做法 适用场景
Stack 小改动提交后基于它做下一个 顺序依赖
按文件组 不同审阅者关心不同文件 横切关注点
横向 先做共享代码/stub,再做消费者 分层架构
纵向 拆成更小的全栈切片 特性开发

还有一条硬规则:重构和功能改动分开提交。一个 PR 同时干两件事,评审、回滚、history 全乱了。

多模型评审

SKILL.md 给了个非常工程化的多模型评审模式:

1
2
3
4
5
6
7
8
9
10
Model A 写代码


Model B 评审正确性和架构


Model A 处理反馈


人做最终决策

不同的模型有不同的盲区。用一个模型写、用另一个模型审,能抓出来单模型会漏掉的问题。这是 2026 年 AI 编码的标配工作流。

简化:清晰 > 聪明

评审过了,先别急着合并。code-simplification 技能要求再做一遍二次打磨:把评审里冒出来的”这写得有点绕”修掉。

简化的判断标准

SKILL.md 第一段就给了一个非常具体的判断问句:

新成员理解这段代码是否比原版更快?

这是一个非常实用的判断标准——它强制你跳出”作者视角”,站在读者视角再看一遍。如果改完之后读者还是要多花一秒才能理解,那就不是简化。

简化不等于少写代码

1
2
3
4
5
6
7
8
9
10
// 不清晰:嵌套三元
const label = isNew ? 'New' : isUpdated ? 'Updated' : isArchived ? 'Archived' : 'Active';

// 清晰:显式映射
function getStatusLabel(item: Item): string {
if (item.isNew) return 'New';
if (item.isUpdated) return 'Updated';
if (item.isArchived) return 'Archived';
return 'Active';
}

五行比一行少,但五行比一行更易读。这就是简化:减少认知负担,不是减少字符数。

五个原则

SKILL.md 把简化提炼成五条原则,我们挑三条最关键的说:

1. 严格保持行为不变

简化改的是”怎么写”,不是”做什么”。输出、副作用、错误行为、边界条件必须一字不差。如果不确定改了之后行为还一不一样,就别改。

2. 跟着项目约定走

简化不是引入你的偏好。先读 CLAUDE.md,看相邻代码怎么写,命名怎么命、导入怎么排、错误怎么抛。简化打破了项目一致性,那叫 churn。

3. 警惕过度简化

1
2
3
4
- 过度 inline:把原本承担命名概念的 helper 删了
- 合并无关逻辑:两个简单函数合并成一个复杂函数
- 删"多余"抽象:有些抽象是为了可扩展和可测试,不是冗余
- 优化行数:少一行 ≠ 容易理解

过度简化比不简化更糟。识别方法是看”读者的认知负担是否真的降低了”。

常见反模式

SKILL.md 列了一张反模式清单,我们挑出最常踩的几个:

反模式 为什么是反模式
提前抽象(少于三个用例) 抽象成本先付了,价值还没到
为未来扩展写代码 YAGNI 原则,先写眼前的需求
嵌套超过三层 控制流难追,维护成本暴涨
长函数(50+ 行) 一个函数干多件事,必然读起来累
Boolean 形参(doThing(true, false)) 调用点看不出含义,应该用 options 对象
注释解释”做了什么”(// 加一) 废话注释,删掉
注释解释”为什么”(// 因 API 在高负载下 flaky) 这种注释要留

最后两条特别有意思。注释的判断标准是:代码解释不了”为什么”的时候才写注释。如果代码本身已经清楚讲了”做什么”,注释就是噪音;如果代码说不清”为什么这么做”,注释才有价值。

增量改动 + Rule of 500

简化要一个动作一个动作做。每改完一个,跑测试,过了再继续;没过就 revert。

如果一个简化会触碰 500 行以上,先去写 codemod、sed 脚本、AST 转换,而不是手动改。手动改那种规模既容易出错,评审也看不下去。

/ship 的并发 persona fan-out

聊完三个核心技能,最后说一个有意思的机制。/ship 命令在最终合并前会做一次 persona fan-out:把评审并发拆给三个角色:

flowchart LR
    Code["待合并代码"] --> R["Reviewer
评审者"] Code --> S["Security Auditor
安全审计"] Code --> T["Test Engineer
测试工程师"] R --> Merge["合并结论"] S --> Merge T --> Merge Merge --> Ship["ship"]

三个角色的视角完全不同:

  • Reviewer 关心正确性、可读性、架构——和 code-review-and-quality 技能对齐。
  • Security Auditor 关心 OWASP Top 10、依赖审计、权限边界——和 security-and-hardening 技能对齐。
  • Test Engineer 关心测试覆盖、回归测试、边界场景——和 test-driven-development 技能对齐。

每个角色独立跑一遍,结论再合并回主流程。这种 fan-out 的好处是:

1
2
3
4
- 单角色容易有盲区,三视角互补
- 并发跑,比串行评审快 3 倍
- 每个角色用不同的 prompt 工程,结论可追溯
- 任一角色标 Critical,合并就阻塞

这是把”评审”这件事工程化的典型案例。AI 编码时代最大的红利不是”写得快”,是”审得也快”。

写在最后

VERIFY 和 REVIEW 是 AI 编码最容易被偷懒的两步。模型跑完测试就想合并,合并完就想上线。这中间省略的”停下来看一遍”恰恰是质量守门员的位置。

三个技能加在一起的核心理念很朴素:

代码能跑不等于可合并,可合并不等于可读,可读不等于健壮。

每个阶段都有自己的判断维度,跳过任何一个都在累积技术债。AI 编码时代的工程纪律没变,只是把”人忘掉的检查”自动化成”机器忘不了的检查”。

明天我们进入最后一个核心阶段——SHIP。CI/CD、可观测性、上线清单、回滚机制,看 agent-skills 是怎么把”上线”这件事也变成可重复的工程动作。


参考资料