昨天我们把 PLAN 和 BUILD 走完了——把大象切成薄片、用 TDD 一片片落地。今天进入六阶段流水线的第四和第五步:VERIFY 和 REVIEW。这一步解决的是”能跑 ≠ 可合并”的鸿沟。代码写得再快,跑不通就是废代码;跑得通但没人敢 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 | 1. STOP 停止加新功能 |
这条规则的关键不在第六步,在第一步。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 | git bisect start |
二分法的核心问句是:”最近一次能工作的状态是哪一版?“ 不要从代码里逐行猜,让版本控制系统帮你缩范围。
对于不是回归引入的 bug,二分法换成”按层定位”——UI 层、API 层、数据库层、构建层、外部服务,依次排错:
1 | UI/前端 → console / DOM / Network |
第三步:缩减到最小复现
把不相关的代码、配置、输入一路砍掉,直到只剩触发 bug 的最小用例。
为什么这步重要?因为复杂场景里”症状”和”根因”是混在一起的。砍掉所有无关分支,根因会自己冒出来。
第四步:修根因而不是症状
SKILL.md 给了个典型对比:
1 | 症状:用户列表有重复条目 |
判断问句是反复问”为什么”直到问不下去:
1 | 为什么列表有重复? |
问到这里就到底了。修 SQL、加唯一索引、加测试。三个动作一气呵成,不用再改 UI。
第五步:写防回归测试
bug 修完不算完。要写一个测试在没有修复时必然失败、在有修复时必然通过:
1 | it('finds tasks with special characters in title', async () => { |
这个测试存在的意义不是”再跑一遍”,而是”下次有人改坏就立刻翻车”。
第六步:端到端验证
最后跑一遍完整的场景:
1 | npm test -- --grep "specific test" # 单测 |
反合理化清单
| 借口 | 为什么是错的 |
|---|---|
| “我知道 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 | - 嵌套三层以上的 if/else |
最后两条特别值得说。**”为未来扩展而提前抽象”是工程债的最大来源。等你真的需要第三个用例时再抽,期间的复杂度都是白付的。而“在现有流程里塞新条件”** 是另一种隐蔽债务——它让现有逻辑变得”看起来通用其实特殊”,下次维护者读起来一头雾水。
架构:重构是不是只搬了复杂度
SKILL.md 里有一条非常锐利的判断:
重构要么减少认知负担,要么就是无效重构。
怎么判断?看读者需要同时 hold 住的概念数量。如果”重构后”的概念数和”重构前”一样多,那只是搬家,没简化。
1 | 坏重构:把 200 行的单体拆成 5 个 40 行的模块,每个模块都依赖 4 个其它模块 |
判断问句是”几个分支消失了”。好的重构让分支、模式、层消失;坏的重构只是把同样的分支重新分配到不同的文件里。
安全:边界校验
SKILL.md 把安全作为一个独立维度列出来,并把详细清单指向 security-and-hardening 技能。我们这里提最常翻车的三条:
- SQL 拼接(参数化查询了吗)
- 输出编码(XSS 防护了吗)
- 外部数据源当作可信数据用了
性能:N+1 是头号嫌犯
SKILL.md 列了五个常见性能反模式:
1 | - N+1 查询(列表接口每行查一次详情) |
量化比”感觉慢”有用得多。”这个 N+1 查询会让列表接口在 100 项时增加约 50ms” 比 “感觉有点慢” 更有说服力。
审批标准:让代码库整体健康度变好了就过
SKILL.md 第一段就给出了审批门槛:
批准标准:当一个改动确实让代码库整体健康度变好了,即使它不完美,也通过。完美代码不存在——目标是持续改进。
这背后有两层意思:
第一,不要因为”不是我会写的样子”而拒绝。代码评审评的是工程指标,不是风格审美。
第二,PR 不是要完美,要更健康。一段代码如果把 200 行烂代码改成 100 行稍好的代码,整体健康度提升了,就该合。不要追求一次到位。
评审流程的五步
评审不是”打开 diff 看一眼”。SKILL.md 给出了标准化流程:
1 | Step 1: 理解上下文 —— 这个改动想达成什么?spec 是什么? |
第四步的严重级别标签特别重要:
| 前缀 | 含义 | 作者动作 |
|---|---|---|
| (无前缀) | 必须改 | 合并前必须修 |
| Critical: | 阻塞合并 | 安全漏洞、数据丢失、功能失效 |
| Nit: | 可选小问题 | 作者可以忽略 |
| Optional: / Consider: | 建议 | 值得考虑但不强制 |
| FYI | 仅供参考 | 不用动 |
加了标签,作者才知道哪些必须回、哪些可选。否则所有评论都被当作必须改,评审就失去了杠杆。
第五步”验证验证过程”是个有意思的双重否定。意思是:作者说他跑过测试、构建通过——你别照单全收,自己确认一次:
1 | - 跑了哪些测试? |
改动的尺寸
PR 不是越大越好。SKILL.md 给了一个粗略的尺寸指南:
1 | ~100 行改动 → 好,一次评审能看完 |
拆法有四种:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| Stack | 小改动提交后基于它做下一个 | 顺序依赖 |
| 按文件组 | 不同审阅者关心不同文件 | 横切关注点 |
| 横向 | 先做共享代码/stub,再做消费者 | 分层架构 |
| 纵向 | 拆成更小的全栈切片 | 特性开发 |
还有一条硬规则:重构和功能改动分开提交。一个 PR 同时干两件事,评审、回滚、history 全乱了。
多模型评审
SKILL.md 给了个非常工程化的多模型评审模式:
1 | Model A 写代码 |
不同的模型有不同的盲区。用一个模型写、用另一个模型审,能抓出来单模型会漏掉的问题。这是 2026 年 AI 编码的标配工作流。
简化:清晰 > 聪明
评审过了,先别急着合并。code-simplification 技能要求再做一遍二次打磨:把评审里冒出来的”这写得有点绕”修掉。
简化的判断标准
SKILL.md 第一段就给了一个非常具体的判断问句:
新成员理解这段代码是否比原版更快?
这是一个非常实用的判断标准——它强制你跳出”作者视角”,站在读者视角再看一遍。如果改完之后读者还是要多花一秒才能理解,那就不是简化。
简化不等于少写代码
1 | // 不清晰:嵌套三元 |
五行比一行少,但五行比一行更易读。这就是简化:减少认知负担,不是减少字符数。
五个原则
SKILL.md 把简化提炼成五条原则,我们挑三条最关键的说:
1. 严格保持行为不变
简化改的是”怎么写”,不是”做什么”。输出、副作用、错误行为、边界条件必须一字不差。如果不确定改了之后行为还一不一样,就别改。
2. 跟着项目约定走
简化不是引入你的偏好。先读 CLAUDE.md,看相邻代码怎么写,命名怎么命、导入怎么排、错误怎么抛。简化打破了项目一致性,那叫 churn。
3. 警惕过度简化
1 | - 过度 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 | - 单角色容易有盲区,三视角互补 |
这是把”评审”这件事工程化的典型案例。AI 编码时代最大的红利不是”写得快”,是”审得也快”。
写在最后
VERIFY 和 REVIEW 是 AI 编码最容易被偷懒的两步。模型跑完测试就想合并,合并完就想上线。这中间省略的”停下来看一遍”恰恰是质量守门员的位置。
三个技能加在一起的核心理念很朴素:
代码能跑不等于可合并,可合并不等于可读,可读不等于健壮。
每个阶段都有自己的判断维度,跳过任何一个都在累积技术债。AI 编码时代的工程纪律没变,只是把”人忘掉的检查”自动化成”机器忘不了的检查”。
明天我们进入最后一个核心阶段——SHIP。CI/CD、可观测性、上线清单、回滚机制,看 agent-skills 是怎么把”上线”这件事也变成可重复的工程动作。
参考资料