Agent Skills 实战第五课:代码能跑还不够,用双轴 Review 查清“写得对”和“做得对”

177 阅读12分钟

在这里插入图片描述

第四课结束时,ticket 已经完成,Red -> Green 记录齐全,类型检查和完整测试套件也全部通过。

是不是可以合并了?

还不能只看绿灯。测试通过只能说明被测试的行为成立,不能自动证明代码符合仓库标准,也不能证明原始 spec 中的所有承诺都已经交付。

一份代码可能命名漂亮、结构整洁,却把“停止续订”做成了“立即终止”;也可能完全实现用户故事,却绕过项目约定的模块边界,留下很高的维护成本。

第五课要完成最后一道工程检查:使用 /code-review 对同一份 diff 进行 Standards + Spec 双轴评审,分别回答“代码是不是按项目方式写的”和“代码是不是做了规格要求的事”。

在这里插入图片描述

为什么不能只给一个“通过或不通过”

传统 review 很容易把所有发现混进同一张列表:变量名不清楚、漏了一个用户故事、重复代码、错误处理不完整、顺手加了一个 spec 没要求的扩展点。

这些问题的性质完全不同。

Standards:Is it built right?  代码是否符合仓库标准和可维护性要求
Spec:      Is it the right thing? 是否完整、准确地实现了原始规格

如果合并成一个总分,几个轻微命名问题可能冲淡一个严重需求遗漏;功能全部实现,也可能让团队忽略代码已经违背架构约定。

所以 /code-review 会让两个独立 sub-agent 并行工作,各自只看一条轴,最后把结果并排展示。它不会把两边的问题重新排序,也不会挑一个“全场最严重问题”。

第一步:先钉死 fixed point

评审不是“看看最近改了什么”,而是比较 HEAD 与一个明确的已知正确点。

fixed point 可以是:

  • main 或其他目标分支;
  • 一个 commit SHA;
  • 一个 release tag;
  • HEAD~5 这类明确提交位置。

调用时需要告诉 /code-review 从哪里开始比较,例如:

/code-review main

或者:

/code-review v2.4.0

skill 会先确认这个引用确实存在,然后固定使用三点 diff:

git diff <fixed-point>...HEAD

三个点意味着从 fixed point 与当前分支的 merge-base 开始比较,只看当前分支真正引入的改动。同时还会记录提交列表:

git log <fixed-point>..HEAD --oneline

如果 fixed point 无法解析,或者 diff 为空,评审应该立即停止。不要让两个评审 Agent 在一个错误范围上产出看似专业的意见。

课堂中常见的错误是:有人以 main 为基点,有人看本地未提交文件,有人直接打开整个 PR。范围不同,讨论自然无法对齐。

第二步:找到原始 spec

Spec 轴必须知道这次改动承诺过什么。/code-review 会按下面顺序寻找来源:

  1. 从 commit message 中查找 issue 引用,例如 #318、Closes #318 或 GitLab 引用;
  2. 使用调用 /code-review 时提供的 spec 路径;
  3. 在 docs/、specs/ 或 .scratch/ 中查找与当前分支匹配的规格;
  4. 仍然找不到时,向用户询问 spec 在哪里。

通过 issue 引用查找时,它会按照第一课的 docs/agents/issue-tracker.md 读取 tracker,而不是自行猜测 GitHub、GitLab 或本地 Markdown。

如果团队明确回答“这次没有 spec”,Spec 轴会跳过并报告 no spec available。它不能根据代码反推一个需求,再用自己猜出来的需求评审自己。

这也暴露了一个流程信号:经常找不到 spec,说明第三课的产物和第四课的 commit 没有建立可追溯关系。

第三步:找到仓库自己的代码标准

Standards 轴会读取项目里真正约束代码写法的文件,例如:

  • CODING_STANDARDS.md;
  • CONTRIBUTING.md;
  • 仓库根目录或相关模块里的工程说明;
  • 当前改动范围适用的明确架构规则。

能被 formatter、lint、type checker 自动检查的事情,不应该重复浪费 review 注意力。review 更适合判断工具难以替代的问题:命名是否表达意图、模块职责是否合理、抽象是否过早、变化是否散落。

仓库文档是最高优先级。如果项目明确认可某种写法,内置的通用经验不能把它判成违规。

没有完整标准文档,也不是“随便看一眼”

即使仓库没有 CODING_STANDARDS.md,Standards 轴仍会携带一组 Fowler code smell 基线。它们不是硬规则,而是需要工程判断的提醒。

Smell在 diff 中通常表现为
Mysterious Name名字无法说明变量、函数或类型的真实含义
Duplicated Code多个 hunk 重复相同逻辑结构
Feature Envy一个方法频繁读取另一个对象的数据
Data Clumps同一组参数或字段总是一起出现
Primitive Obsession用 string、number 代替稳定的领域概念
Repeated Switches多处重复按同一种类型进行条件分支
Shotgun Surgery一个逻辑变化迫使许多文件到处修改
Divergent Change一个模块因为多个无关原因被同时修改
Speculative Generality为 spec 没有提出的未来需求增加抽象和扩展点
Message Chains调用方依赖过长的对象导航链
Middle Man新增的类或函数主要只是把调用转发出去
Refused Bequest子类或实现者拒绝了大部分继承契约

这里有两个必须守住的边界:

第一,仓库标准覆盖 smell 基线。项目有意采用某种结构时,不要拿通用模式压过本地决定。

第二,smell 永远是 judgement call。报告应该写“可能存在 Speculative Generality”,并引用具体 hunk 和影响,而不是写成“违反第九条,必须修改”。

Standards 轴到底检查什么

Standards sub-agent 会拿到完整 diff、提交列表、标准文件和全部 smell 基线。它需要分别报告:

  • 违反已记录仓库标准的地方,并引用标准文件和具体规则;
  • diff 中可能出现的 smell,说明属于哪一种并引用代码片段;
  • 哪些是硬性标准违反,哪些只是需要团队权衡的判断。

以停止续订为例,可能出现这样的发现:

Hard violation:Billing 模块标准要求公开 service 返回 Result,
但新接口直接抛出通用 Error。引用 CONTRIBUTING.md 的错误处理规则。

Judgement call:新增 CancellationStrategyFactory,
目前只支持一个策略,可能属于 Speculative Generality。

第一条有项目文档依据,可以明确判定。第二条需要结合后续方向和维护成本讨论,不能机械要求删除。

Spec 轴到底检查什么

Spec sub-agent 不评价变量名,也不判断代码够不够优雅。它只对照原始规格检查三类问题:

  1. spec 要求但 diff 没有实现,或者只实现了一部分;
  2. diff 新增了 spec 没要求的行为,形成 scope creep;
  3. 看起来实现了要求,但行为方式明显错误。

每条 finding 都要引用 spec 中对应的原句,避免“我觉得需求应该这样”的主观评审。

例如 spec 写着:

重复提交停止续订请求必须保持幂等,不产生额外状态事件。

如果代码每次调用都会追加一条 RenewalStopped,Spec 轴应该报告“要求实现但行为错误”,并引用上面这条规格。

又比如 diff 增加了“立即终止并按比例退款”,而 Out of Scope 明确写着第一版不处理退款,这就是 scope creep。即使代码写得很好,也应该从当前 ticket 移除或另开规格。

为什么两个 sub-agent 要并行、隔离上下文

如果同一个评审 Agent 先看到 spec 严重遗漏,再去看代码标准,它很容易把注意力全部放在需求问题上;反过来也一样。

/code-review 把两个轴放进独立上下文:

同一份 diff
   ├── Standards Agent:只看项目标准与 code smells
   └── Spec Agent:只看原始承诺与实现行为

这样可以减少相互污染,也保证两份报告各自有完整注意力。最终聚合阶段只做轻微整理,不把两边 finding 合成一张重新排序的列表。

最终报告应该长什么样

一份合格输出至少有两个独立章节:

## Standards

- [Hard violation] ...
- [Judgement call: Speculative Generality] ...

## Spec

- [Missing] ...
- [Scope creep] ...
- [Incorrect behaviour] ...

结尾只总结每条轴各有多少发现,以及各自最严重的问题:

Standards:2 项,最严重的是错误处理违反模块标准。
Spec:1 项,最严重的是重复请求没有保持幂等。

不要再给一个“综合评分 76 分”,也不要说“总体最大问题是……”。两个轴故意分开,就是为了避免其中一边遮住另一边。

同一段代码可能在两条轴上同时出现

这不是重复报告,而是两个不同理由。

例如 Agent 为未来可能出现的退款、冻结、暂停订阅设计了一个复杂 CancellationStrategy 体系。

Standards 轴可能把它标为 Speculative Generality:当前只有一个真实策略,抽象带来了额外维护成本。

Spec 轴可能把它标为 scope creep:本次规格只要求停止续订,退款和暂停明确不在范围内。

一个是设计风险,一个是交付范围偏移。修复动作可能相同,但判断依据不同,必须分别保留。

Review 发现以后,先分类再行动

团队拿到报告后,不要让 Agent 立刻一股脑修复。先给每条 finding 指定处理方式:

  • Fix now:当前 ticket 必须修复的硬性标准或规格问题;
  • Clarify spec:规格本身存在歧义,需要回到需求负责人确认;
  • Follow-up ticket:真实存在但不属于当前范围的改进;
  • Accept:团队接受的权衡,并在必要时记录原因;
  • False positive:引用证据关闭误报。

Spec finding 通常优先阻止交付,因为它意味着承诺没有完成或做错。Standards finding 是否阻止合并,要看它是仓库硬规则还是 judgement call。仍然不要把两边合成一个统一严重度排行榜。

用第四课的 diff 做一次完整演练

假设第四课分支是 feature/stop-renewal,目标分支是 main,commit message 引用了 #318。

课堂操作可以这样开始:

/code-review main

Agent 应该依次完成:

1. 确认 main 可以解析
2. 确认 git diff main...HEAD 非空
3. 记录 main..HEAD 的 commit 列表
4. 从 commit message 找到 #318
5. 根据 issue-tracker.md 获取 spec
6. 查找仓库标准文件
7. 并行运行 Standards 和 Spec 评审
8. 分轴汇总 findings

任何一步缺失,都可能让评审失去依据。例如没有 fixed point,diff 范围不可信;没有 spec,不能声称需求全部实现;没有引用标准,Standards 容易退化成个人代码偏好。

团队使用约定应该写什么

培训最后不要只总结“这几个命令很好用”。要把团队自己的触发条件写具体。

可以从下面这份模板开始:

## Agent Skills 团队约定

### 必须先 /grill-with-docs 的场景

- 出现未定义或多义的业务术语;
- 改动涉及状态机、权限或多个领域上下文;
- 存在难回滚且有真实取舍的架构决定。

### 必须先 /to-spec 的场景

- 用户可见行为发生变化;
- 改动跨越多个模块或需要明确 out of scope;
- 需要提前确认 testing seam。

### 必须拆 /to-tickets 的场景

- 工作无法在一个 fresh context 中完成;
- 存在可以并行的纵向交付路径;
- 工单之间存在必须显式记录的 blocking edges。

### 测试约定

- 测试 seam 在写测试前确认;
- 测试公开行为,不测试私有方法和内部调用次数;
- 每个行为必须先出现有效 Red;
- expected 来自 spec、固定示例或其他独立真值。

### Review 约定

- 默认 fixed point 是目标分支 merge-base;
- Standards 与 Spec 分开报告,不给综合评分;
- 无 spec 时必须明确跳过 Spec 轴;
- code smell 是 judgement call,仓库文档优先。

这份约定应该进入 AGENTS.md、CLAUDE.md 或团队已有的工程手册,并在项目工作方式变化时更新。

常见误区

没有指定 fixed point 就开始 review。 先停下来明确比较点。范围不可信,finding 也不可信。

使用两点 diff。 skill 要求 git diff <fixed-point>...HEAD,通过 merge-base 隔离当前分支引入的变化。

找不到 spec,就按代码猜需求。 Spec 轴应该跳过并明确说明,没有资格虚构验收标准。

Standards 只有个人偏好。 每条硬性问题引用仓库标准;通用 smell 必须标记为 judgement call。

formatter 能发现的问题又报告一遍。 跳过工具已经强制执行的事项,把注意力留给语义和设计。

把两个轴合成一个严重度列表。 保持分开。代码标准和规格符合度的修复路径不同。

一看到 finding 就全部自动修改。 先分类,确认哪些属于当前 ticket,哪些需要补规格或另开 follow-up。

只做一次 review。 Fix now 完成后,使用同一 fixed point 再跑一次,防止修复引入新的需求偏移。

第五课真正完成的是一条可重复的工程闭环

到这里,五节课串成了一条完整链路:

setup
  -> grill-with-docs
    -> to-spec
      -> to-tickets
        -> implement + tdd
          -> code-review

第一课让 Agent 知道项目规则,第二课不让猜测进入需求,第三课把共识切成可交付工作,第四课让每一步实现都有失败和通过的证据,第五课再把代码标准与规格符合度分开验收。

真正的收益不是团队多记住五个斜杠命令,而是建立了一套 AI 也必须遵守的工程反馈系统。Agent 可以写得很快,但它不能跳过上下文、决定、测试和评审,也不能用“代码能跑”代替“工作已经正确交付”。