跳转至

OpenSpec 四步法深度复盘:流程完整 ≠ 代码正确

Ch05.046 OpenSpec 四步法深度复盘:流程完整 ≠ 代码正确

📊 Level ⭐⭐ | 10.6KB | entities/openspec-四步法深度复盘-流程完整不等于代码正确.md

核心洞察

流程完整 ≠ 代码正确。 OpenSpec 四步法(propose → apply → verify → archive)保证了需求对齐的文档链路,但无法保证 AI 实际输出的代码质量。

四步法各环节短板分析

环节 设计意图 实际短板
Propose 对齐需求,一次性生成 proposal + specs + design + tasks Spec 质量无验证机制,格式错误静默忽略,来源不可靠的 Spec 导致下游全部偏离
Apply 按 tasks.md 逐条实现代码 黑盒执行,无中间检查点;错误级联放大(第 3 task 出错,后续 task 全部基于有 Bug 的代码构建)
Verify 三个维度检查(Completeness/Correctness/Coherence) 文本检查而非运行验证;verify 不阻塞 archive,发现的问题可被忽略
Archive 归档变更、合并 Delta Specs 无质量门控;有问题的变更仍可被归档并成为后续变更的基础
根本问题:四步法的质量保障停留在文档对齐层面,缺少代码层面的验证。整个链路隐含假设"文档写得好 → AI 就能实现出正确的代码",但这个假设在复杂业务逻辑、边界条件、并发处理等场景下不成立。

五项升级方案

升级一:Propose 后加 Spec Review

  • 目标:源头把控质量,不完整的 Spec 会导致整个下游链路偏离
  • 做法:propose 生成工件后、apply 开始前,手动或用另一个 AI 实例做格式校验(#### 标题层级、ADDED/MODIFIED/REMOVED 标记)、一致性检查、边界条件审查
  • 关键:不能让同一次 propose 的 AI 自己检查自己

升级二:Apply 拆分为原子任务 + 检查点

  • 目标:错误在发生时发现,而非所有任务完成后才发现
  • 做法:apply-task-1 → check-1 → apply-task-2 → check-2 → ... → apply-task-N → check-N
  • 每个 check 点确认:当前 task 代码与描述一致、未影响已完成 task、lint 通过
  • 如果 tasks.md 拆分粒度太粗,手动拆分再逐个执行

升级三:Verify 加入运行验证

  • 目标:发现逻辑错误、竞态条件、边界情况、性能问题等文本检查发现不了的问题
  • 做法:静态检查(lint、tsc --noEmit)+ 单元测试 + 集成测试 + 人工验证点
  • 关键原则:不要只看代码对不对,要看代码能不能跑

升级四:引入 TDD 思路

  • 目标:把测试从可选项变成强制项
  • 做法:Design 阶段定义验收标准 → Tasks 中明确测试任务 → Apply 中先写测试再写实现(至少核心逻辑) → Verify 中运行测试
  • 测试不通过就不进入 archive

升级五:Archive 前设置质量门控清单

[ ] 所有 Tasks 已完成(tasks.md 无未勾选项)
[ ] Spec Review 已通过
[ ] 原子任务检查点已通过
[ ] 运行验证已通过(lint、测试、类型检查)
[ ] 无已知未修复的 Bug

深度分析

文档链路 vs 代码链路:两条平行线的错位

OpenSpec 四步法构建了一条完整的文档链路:Proposal → Specs → Design → Tasks → Code。每一环的输出都是下一环的输入,环环相扣。但这条链路的终点是代码文件,而不是正确运行的代码。 问题的本质在于:文档对齐是充分条件,不是必要条件。AI 可以精确遵循文档规范,但仍产出有 bug 的代码。这在以下场景尤为突出:

  • 复杂业务逻辑:状态机转换、事务边界、权限继承等需要深度上下文理解的能力
  • 边界条件:空指针、越界、类型转换错误往往藏在边缘路径
  • 并发处理:竞态条件、死锁、线程安全——文本审查无法暴露
  • 性能问题:时间复杂度、内存泄漏、资源未释放 这些问题的共同点是:它们不会出现在文档里,但会出现在运行时。 See also Harness Engineering

Verify 的定位偏差:暴露问题 ≠ 解决问题

Verify 被设计为"surface issues"——暴露问题,而非"block progress"——阻塞进度。这个设计选择本身并无问题,但它释放了一个信号:四步法不打算在代码质量上做强制约束。 这导致了一个矛盾:Verify 发现了问题,但 Archive 照样执行。问题被记录,却没有被修复。后续的变更基于有问题的代码继续构建,多米诺骨牌就这样倒下。 更值得思考的是:Verify 的三个维度(Completeness/Correctness/Coherence)全部是文本层面的比对。它们回答的问题是:"代码是否做了 spec 说的事",而不是 "代码做的事是否正确"。这两个问题之间,隔着一整个运行时世界。

上下文窗口压力:被低估的衰减因子

OpenSpec 官方文档明确建议在 implementation 开始前清空上下文。但在实际使用中,apply 阶段需要同时持有 Proposal + Specs + Design + Tasks 的完整信息。复杂变更产生的代码量一大,这些信息会迅速填满上下文窗口。 AI 在长会话中的"遗忘"不是 bug,是架构约束。当上下文被新信息挤占时,早期的设计决策、约束条件、边界假设会逐渐模糊。实现与原始设计的偏差就这样悄然产生——不是某一刻突然出错,而是渐进式偏离。

自 Review 的局限性:用同一把尺子量自己

"Apply 后加自 Review"是目前社区最广泛采用的改进实践。它的逻辑很直接:多一道关卡,多一层保障。但它的局限性同样明显: AI 审查自己的代码存在结构性盲区。思维模式在写代码时已经固化,回头看时同样看不到——不是因为粗心,而是因为看的角度和写时完全相同。代码审查的价值在于第二双眼睛带来的不同视角,而自 Review 恰恰缺少这个视角。 这意味着:自 Review 能发现明显的逻辑错误,但发现不了思维惯性导致的深层问题。

实践启示

质量保障必须分层叠加,不能依赖单一环节

四步法每个环节都有质量保障的设计,但每个环节的保障都存在漏洞。单独依赖任何一个环节(Propose、Verify、或自 Review)都不够。务实的做法是分层叠加:Spec Review 管源头,原子任务检查点管过程,运行验证管结果,质量门控管出口。四层防线各有分工,缺一不可。

测试必须从可选变成强制

当前四步法的测试是隐式可选的——Tasks 可以包含测试任务,也可以不包含。升级方案四(引入 TDD 思路)的核心价值不是 TDD 本身,而是把测试从可选项变成强制项。没有测试覆盖的功能变更,本质上是在赌 AI 不会出错。这个赌注在复杂场景下注定会输。

工具是死的,人是活的

OpenSpec 的工件都是 Markdown 文件,可以手动编辑。这个细节很重要。当发现 Spec 格式不对时,直接改,不要将就。当发现 Tasks 拆分粒度太粗时,手动拆,分批执行。工具的默认值不是唯一的正确用法,根据实际情况调整才是高效使用工具的正确姿势。

复杂变更必须分批处理

5+ 文件或 3+ 模块的变更,考虑拆成多个独立的 change。每个 change 只做一件事。这么做有三个好处:上下文压力更小(AI 不容易遗忘)、验证更集中(出问题容易定位)、回滚更容易(不至于一个改动卡住整批代码)。这是工程化思维在 AI 编程中的应用。

保持上下文干净是最容易被忽略的生产力

"/clear"在 apply 之前执行,看起来是多此一举的步骤,实际上是成本最低、收益最高的操作。它让 AI 重新读取所有工件文件,避免长会话中的遗忘问题。虽然会多花一点时间,但换来的实现质量提升远超时间成本。这是一个需要养成习惯的动作,而不是可选的优化。

质量门控清单是最后一道防线

Archive 前设置质量门控清单(所有 Tasks 完成 → Spec Review 通过 → 原子检查点通过 → 运行验证通过 → 无已知 Bug),本质上是在把"流程完整"变成"代码正确"的硬约束。这个清单不需要一步到位,可以根据项目实际情况逐步增加条目。关键是:它必须存在,且必须被执行。

实战建议:落地优先级

  1. 第一步:Apply 后加 Review + 手动测试(成本最低、效果最明显)
  2. 第二步:Propose 后手动检查 Spec 质量(防止源头出错)
  3. 第三步:强制在 Tasks 中包含测试任务
  4. 第四步:复杂变更时 Apply 拆分为原子任务

其他实践建议

  • 利用 OpenSpec 的 Edit 机制:工件都是 Markdown 文件,可以手动编辑;发现问题直接改,不要将就
  • 复杂变更分批处理:5+ 文件或 3+ 模块的变更拆成多个独立 change,每个只做一件事
  • 保持上下文干净:apply 之前执行 /clear,避免长会话遗忘早期设计决策