同一作者,同一个应用想法("给老人开发一款 macOS 计算器"),分别用三种规格驱动开发(Spec-Driven Development)工具完成。本文以应用产物为基准,对比三个工具实际交付了什么、为什么不同。
先把两个可量化的维度摆在同一坐标系里:流程投入(规格/流程文档总行数)与产物产出(Swift 代码行数、测试数)。
第一眼结论:流程投入与产物规模不成正比。Spec Kit 的流程文档是 OpenSpec 的 3.3 倍,但交付的代码只多 22%;matt-skills 流程文档仅为 Spec Kit 的一半,交付的代码却是 1.8 倍。流程重量买的是"别的属性"——可验证性、可追溯性,而不是更多功能。
把三个应用按功能点逐条核对(✓ = 有,✗ = 明确不做,◐ = 有但形态不同):
| 功能点 | OpenSpec | matt-skills | Spec Kit |
|---|---|---|---|
| 四则运算 + 连续运算 | ✓ | ✓ | ✓ |
| 退格(逐位删除) | ✗ 只有一键清除 | ✓ | ✓ |
| 百分比 %("100+10%=110"语义) | ✗ | ✓ | ✗ |
| 正负号 +/- | ✗ | ✓ | ✗ |
| 重复等号(再按 = 重复上次运算) | ✗ | ✓ | ✗ |
| 计算历史(抽屉回看) | ✗ | ✓ 50 条 | ✗ |
| 设置页 | ✗ | ✓ 语音/语速/配色 | ✗ |
| 中文语音播报 | ◐ 按需点"读出来"按钮 | ✓ 每键播报 + 慢/标准两档 | ◐ 可开关,默认关,重启保持 |
| 中文读法文字显示 | ✓ 常驻屏幕("十二万三千四百五十六") | ✗ 仅用于播报 | ◐ 用于播报文案 |
| 数字分组显示 | 四位分节(贴合"万":12 3456) | 千分位(1,234,567) | ✗ 无分节 |
| 窗口行为 | ◐ 固定尺寸不可缩放 | ◐ 可调 + 最小尺寸下限 | ◐ 最小 480×640,内容随窗口缩放 |
| 主题 | ◐ 跟随系统深浅色 | ◐ 深色默认,可切浅色高对比 | ◐ 高对比常量色(对比度 ≥7:1) |
| 本地化资源文件 | ✗ | ✗ | ✓ Localizable.strings |
| UI 测试 | ✗ | ✗ | ✓ 关键路径冒烟 |
| 应用图标 | ✗ | ✓ 自制 + 生成脚本 | ✗ |
矩阵一目了然地分出三类产品:
| 维度 | OpenSpec | matt-skills | Spec Kit |
|---|---|---|---|
| 数值实现 | Decimal,design.md 明确"禁止 Double 参与运算路径" |
Double,配"数值规范文本"约束(≤10 位有效数字、智能去尾零) |
Decimal,research.md 论证拒绝 Double;8 位小数四舍五入 |
| 测试框架与风格 | XCTest,41 个,集中在纯逻辑(中文读法 15 + 引擎 13) | Swift Testing,76 个,中文测试名(连续按数字键_依次拼接显示),"只测外部行为、不触碰内部状态" |
XCTest + UI 测试;含独有的"设计常量合规断言"(字号 ≥24pt、按钮 ≥44pt、对比度) |
| 错误处理 | 除以零 → "不能除以零",任意键恢复 | 同上 + 输入超 12 位拒绝并提示 | 同上 + 溢出(极大数)→ "数字太大了" |
| 模块边界 | Core 层不 import SwiftUI,可脱离 UI 测试 | 引擎为纯 Swift 值类型状态机;播报拆出 SpeechScript / Formatter / Adapter / Announcer 四层 | Engine 目录不 import SwiftUI/AppKit;语音经协议替身隔离测试 |
| 完成度 | 任务 6.1(逐条人工验收)未勾选 | 14 个 issue 全部完成,留 1 项"实际播报效果手工验证" | 38 个任务完成 37,剩 1 项 |
| 流程产物 | OpenSpec | matt-skills | Spec Kit |
|---|---|---|---|
| 核心工件 | 1 个 change:proposal(为什么)+ design(怎么做,含备选与放弃理由)+ tasks + 3 份能力规格 | CONTEXT.md 术语表(权威定义)+ spec.md(36 条用户故事 + 显式测试决策)+ 14 个带依赖与状态的 issue | constitution 章程(5 原则,2 条不可妥协)+ spec(FR 编号需求 / SC 编号成功标准)+ research 决策 + API/UI 契约 + 任务清单 |
| 独有概念 | Non-goals 段落;change 归档后 specs/ 保留为长期真相源 | 测试接缝(test seams);"好测试 = 只测外部行为";术语表优先于 spec | Constitution Check 合规门;契约验收线(字号 ≥48pt、对比度 ≥7:1、按钮 ≥64pt) |
| 流程重量 | 最轻:约 6 个文件,304 行 | 居中:503 行,但工件类型最多元 | 最重:1005 行,阶段门最多 |
Non-goals 段落逼你在动手前砍功能;design.md 风险段把测试资源集中到最易错的纯逻辑。产物是克制的 MVP,差异化来自"敢不做"。
技能集不约束范围,范围控制全靠 CONTEXT.md 术语共识。忠实还原实体计算器全部语义,行为测试最彻底,但功能面最大。
章程 + 契约把质量写成可机械核查的验收线。用户感知不到的地方(本地化、常量、UI 测试)投入最多,交接完整度最高。
0.1 + 0.2 = 0.3 的精度问题,是三个项目都绕不开的同一道坎,但它的解决方案"登记"在完全不同的工件里:
| 项目 | 精度决策的位置 | 最终方案 |
|---|---|---|
| OpenSpec | design.md 第 2 号决策(含备选与放弃理由) | Decimal 全程参与运算 |
| Spec Kit | research.md R1(含备选与拒绝理由) | |
| matt-skills | CONTEXT.md 术语表条目"数值规范文本"(领域概念,非技术决策) | Double 运算 + 展示层 10 位有效数字规范 |
三个工具的"严肃性"都恰好体现在各自最强调的工件里:OpenSpec 的决策记录、Spec Kit 的研究阶段、matt 的术语表。讽刺的是,最强调工程纪律的 matt 版是唯一用 Double 的 —— 因为它的纪律集中在行为测试而非架构决策,精度被当成"展示口径"问题交给领域层处理,而非"运算正确性"问题。
| 工具 | 范围控制机制 | 结果 |
|---|---|---|
| OpenSpec | proposal 的 Non-goals 段落(一次性) | 砍掉 %、+/-、退格、历史、设置页 → 功能面最窄 |
| Spec Kit | 章程原则 II"简洁至上" + FR 编号排除(长期有效) | 同上,且规则可继承到下一版 |
| matt | CONTEXT.md 术语共识(砍掉了 M+/M− 记忆键) | %、历史、设置全部保留 → 功能面最宽 |
关键差异在于机制的可继承性:OpenSpec 的 Non-goals 随 change 归档而失效,Spec Kit 的章程是持久约束,matt 的共识在术语表里天然持久但约束力最弱(它只是"定义",不是"门禁")。
回到第一组数据:Spec Kit 用 3.3 倍的流程文档没有换来更多功能(功能面反而比 matt 版窄),买到的是三样东西 —— 可追溯性(FR→SC→契约→任务互相编号引用)、可机械核查性(合规测试实际断言了契约条款)、交接完整度(新人或新 AI 会话从 plan.md 即可完整接手,甚至预声明了"引擎文件被多个故事共享,不跨故事并行")。这些属性在单人快速迭代中价值有限,在团队协作与长期维护中价值倍增。
三个项目完成于 2026-09-10 → 09-21 → 10-07,同一作者对同一个应用想法,写出的三份规格本质上就是三个不同的产品定义。工具不只规定"怎么做"的流程,还通过各自的文档结构(Non-goals 段落 / 用户故事 + FR-SC / 术语表)预设了思考问题的框架,从而反向塑造了需求本身。
| 场景 | 建议 | 理由 |
|---|---|---|
| 个人项目、快速迭代、想尽快收敛范围 | OpenSpec | 流程最轻,Non-goals 机制天然防镀金,归档后规格即长期真相源 |
| 重视测试质量与知识沉淀、范围判断有自信 | matt-skills | 行为测试哲学最彻底,术语表/ADR 沉淀最好;但范围控制全靠人,易膨胀 |
| 团队协作、需要审计合规、长期维护 | Spec Kit | 可追溯、可核查、可交接;代价是流程负担最重 |
一句话总结:OpenSpec 帮你决定不做什么,matt-skills 帮你把做的测到最彻底,Spec Kit 帮你证明做的是对的。