上周我在一个内部项目里,为了把价格计算里的“折扣”改成“满减”,改了需求文档里一个英文单词,结果 AI 直接给我生成了 500 行代码——包括数据结构调整、七个关联文件的重构、四百多行测试,还有一个数据迁移的脚本。
我当时的表情大概就是:这活儿是不是干猛了?
但冷静下来之后,我发现这不是偶然。把这个经历复盘了一遍,真正值钱的不是那五百行代码,而是背后一套完整的工作方式。圈子里管这套玩意儿叫Spec Coding——先写规格,再让 AI 写实现。今天这篇文章,我就把这件事从头到尾聊透:Spec 到底怎么写、AI 为什么能在你改一个单词时帮你牵一发动全身、哪些地方容易翻车,以及我这半年踩出来的实操心得。
这篇文章适合正在用 AI 写代码的人看,特别适合那种“AI 老是听不懂人话、动不动就跑飞、改一个需求恨不得重写整个项目”的朋友。如果你还在用自然语言跟 Cursor、Claude、Copilot 瞎聊,觉得 AI 编程像抽卡,那这篇文章大概率能让你找到“稳定出货”的方法。
1. 先搞清楚一件事:Spec Coding 不是在写文档,而是在换一种编程方式
很多人一听“Spec Coding”,第一反应是:这不就是写完需求文档再让 AI 写代码吗?我早就这么干了。
真不是。你平时写的需求文档和 Spec Coding 里说的 Spec,虽然都叫“规格”,但压根是两种东西。普通需求文档是给人看的,它的目标是“让一个正常人理解业务意图”;Spec 是给 AI 看的,它的目标是“让一个逻辑上严格但缺乏常识的虚拟同事完成一次成功的变更”。这两个目标的差异,导致写法完全不同。
打个比方你就明白了。你跟一个应届生说“把价格计算改成满减”,他能理解个大概,遇到模糊的地方还会来问你。但你要跟一个过度较真、又不会主动提问的同事说同一句话,他要么什么都不做,要么直接给你整出三百个可能的误解版本。AI 编程工具就是后一种同事——它智力没问题,但你不能指望它“领会精神”。
所以 Spec Coding 的核心动作,是把“你脑子里的需求”转译成“AI 不需要猜就知道该干什么”的精确描述。它要求你回答四个问题:做什么、边界在哪、输入输出是什么、怎么算对了。
我那次改单词,改的其实是 Spec 文件里一个行为描述单词。原本写的是:
apply_discount: 根据折扣率计算实付金额改成:
apply_discount: 根据满减规则计算实付金额就这一处修改,AI 需要联动改动的东西包括:数据模型里是否还保留折扣率字段、金额计算函数的重写、前端展示文本的调整、测试用例里所有(原价−折扣价)的断言以及账务报表里的统计维度。一个单词,牵动的是整个语义网络。“折扣”是一次性比例调整,“满减”是分段条件逻辑,这俩在系统里的数据流路径完全不同。
这套工作方式真正的价值在于:你把需求转成机器可校验的规格之后,AI 不再是一个“猜”的工具,而是一个执行力极强的施工队。你给它图纸,它给你盖楼;图纸上改一个尺寸,它能自动把承重墙、管线、门窗全给你调一遍。省下的时间不光是写代码的时间,还有反复沟通确认的时间。
2. 为什么“改一个单词”能驱动 AI 写出 500 行代码
这一节是重点,咱们把这个过程拆开看。
2.1 一个词就是一条行为契约的“锚点”
在普通开发流程里,“折扣”只是一个业务术语,改就改了。但在 Spec Coding 的工作流里,一个词往往对应着一段精确的行为定义,而这段定义又牵着一堆实现细节。因为 Spec 文件本身就是分层的,顶层是意图,中层是行为契约,底层是验收标准。改顶层的一个词,底层所有依赖它的链接都要重新算。
我那次的项目是一个订单结算系统。Spec 里有一条这样写:
场景:订单价格计算 当订单包含商品 A 和商品 B 时 如果 A 属于日用品类目 则对 A 应用满减规则,满 100 减 10 输出:应付总金额 = A 商品实付 + B 商品实付我改的单词是“满减规则”前面的修饰词,但 AI 收到的信号是:满减规则的数据结构、判断条件、优惠分摊方式全部要变。于是它自动完成了下面这些事:
- 检查原来的数据结构里有没有满减相关的字段,没有就新增,并把旧字段标记为废弃;
- 重写价格计算核心函数,加入满减的判断分支;
- 刷新所有依赖这个函数的调用方,包括购物车模块、订单确认页、结算报表;
- 给新逻辑生成单元测试,并把原来断言折扣逻辑的测试用例全部更新;
- 生成一个数据修复迁移脚本,处理历史订单中需要重新计价的记录。
整个过程下来,500 行算保守的。这就是“一个词”的真实分量——在规格化的系统里,任何一个词都不是孤立的,AI 会把它当作一颗种子,沿着语义网把所有关联全部长出来。
2.2 AI 为什么能做到这件事:规格给了它“推断上下文”的路径
如果你直接跟 AI 说“把折扣改成满减”,它顶多帮你改一个函数。但如果你给它的 Spec 是一个完整的、有结构的文档,AI 就可以做图遍历式的修改——从词到定义、从定义到依赖、从依赖到实现、从实现到测试。
这个原理不复杂。AI 编程模型在处理代码时,本质上是在做一个超大规模的关联推断。你给它看的东西越多,它看到的关联路径就越多。传统自然语言聊天式编程为什么容易跑偏?因为上下文信息量不够,AI 只能靠猜。而 Spec Coding 提供了一个“语义脚手架”,AI 在脚手架内部活动,每一步都知道“这块改动是为什么服务的”。
我再打个比方。你让一个熟手改一个旧项目的报价逻辑,他不光要看函数本身,还要看调用它的地方、看存储结构、看测试,甚至要看这个函数名出现在日志里的上下文。AI 也是一样,它需要“读文档”来获得这种全局感。Spec 文件就是它用来建立全局感的那一摞文档——只是这份文档被写成了机器更容易消化的结构。
2.3 一句话总结:Spec 让 AI 从“文本接龙”变成了“合约执行者”
这一点想明白之后,看待 AI 编程的方式就完全不一样了。没有 Spec 的时候,AI 是一个生成器,你说一句它编一句,听起来挺连贯,但整体是散的;有 Spec 的时候,AI 是合约执行者,你定义行为、定义边界、定义验收标准,它负责把合约变成满足所有约束的代码。
这个转变会带来一个实际变化:你可以对 AI 的输出做系统性验证了。因为 Spec 里写了验收标准,AI 生成的测试天然就是 Spec 的映射。你改一个词,实际上是在改一组验收标准,AI 要做的,是把实现往新标准上靠拢。这个过程的工程量,当然比“改一个词”大得多——但那些工程量恰恰是让系统真正可用必须付出的代价,以前是人肉承担的,现在是 AI 承担了。
3. 实操指南:从零搭一套 Spec Coding 工作流
说了这么多理念,下面直接上干货,讲怎么落地。
3.1 第一步:把项目切片,别拿整个系统开刀
新手最容易犯的错,是试图给整个项目写一份巨无霸 Spec,写完一看两万字,AI 根本处理不了,上下文窗口一爆,立刻失忆。
正确做法是按模块切。一个业务模块一份 Spec,模块内部再按“场景”拆分。比如订单模块,可以拆成购物车、价格计算、支付回调、退款流程这几个场景。每个场景单独建一个文档,文档之间用链接互相引用。
我自己的目录结构是这样的:
specs/ cart.md pricing.md payment.md refund.md每个文档控制在 300 到 500 行,最多不超过 800 行。超过这个量,模型对后续内容的注意力会明显下降。宁可多建几个文件,也不要堆在一个文件里。
3.2 第二步:Spec 文件里只写四类东西
一份能用的 Spec,不需要华丽的描述,只需要四块内容:
- 数据定义:系统里有哪些实体、哪些字段、字段类型和约束;
- 行为规则:系统在什么条件下做什么事,输入输出是什么;
- 边界情况:空值、超限、重复提交、并发冲突之类的情形怎么处理;
- 验收标准:每一条规则怎么验证是对的,最好直接给出输入输出样例。
举个例子,价格计算模块的 Spec 我可以写成这样:
# 行为:订单价格计算 ## 数据定义 - OrderItem: sku_id, category_id, quantity, unit_price, original_amount - DiscountRule: rule_type(enum: DISCOUNT_RATE | FULL_REDUCTION), threshold, reduction, rate ## 行为规则 当订单中所有商品均属于普通类目时: 实付金额 = 商品原价合计 - 满减优惠金额 满减金额的计算方式为: 若合计金额 >= threshold,则优惠 reduction,否则优惠 0 ## 边界情况 - 订单金额为 0 时,满减不生效 - 商品类目为“特殊商品”时,不参与满减计算 - 同一订单存在多个满减规则时,取 threshold 最大的一条 ## 验收标准 - 输入:商品 A 单价 120,数量 1,类目普通,threshold=100, reduction=10 输出:实付 110 - 输入:商品 B 单价 90,数量 1,类目特殊 输出:实付 90,不参与满减你看,这里面没有一个多余的字。AI 拿到的每一句话,都是可以用来生成代码、写测试、做断言的信息。这就是它“听懂”你的关键。
3.3 第三步:用“契约优先”的顺序开发
有了 Spec 之后,开发顺序应该是:
- 先让 AI 根据 Spec 生成测试用例;
- 让 AI 根据 Spec 实现功能代码;
- 运行测试,看哪些用例没过;
- 把失败的用例反馈给 AI,让它修改代码;
- 循环到全绿为止。
这其实是测试驱动开发的一种变体,只不过把“人写测试”换成了“AI 根据 Spec 写测试”,而 Spec 本身就是人和 AI 共同维护的契约。
我实测下来,这个顺序最大的好处是:AI 不会把代码写“飞”。因为测试就是验收标准的具象化,只要测试没全绿,AI 就得一直改。改“一个单词”这件事之所以能产生那么多代码,是因为 AI 需要让所有测试适配新的行为契约——这不是靠运气,而是靠流程逼出来的。
3.4 第四步:每次改需求,先改 Spec,再动代码
这是整个工作流里最重要的一条纪律。很多人用 AI 编程,越过文档直接改代码,改完发现其他模块崩了,又回头修。Spec Coding 不一样,所有变更都从 Spec 开始。
我那次改单词,流程是这样的:
- 在 Spec 的“行为规则”里把“折扣率计算”改成“满减规则计算”;
- 同步更新“数据定义”和“验收标准”;
- 让 AI 看一下 Spec 变更记录,自动找出现有实现和 Spec 的差异点;
- 让 AI 根据新 Spec 更新代码和测试;
- 跑测试,修到全绿;
- 跑一次所有模块的联调测试,确认没有其他模块依赖旧规则。
这个流程走下来,AI 写的每一行代码都有据可循,你随时能问它“你为什么这么写”,它都会告诉你“因为 Spec 第几十几行这么定义的”。这在出问题时特别重要——回溯修改原因,几分钟就能定位,而不是靠记忆和猜。
4. 选对工具与模式:Spec 驱动 Agent 比单纯聊天稳太多
好 Spec 只是地基,工具和执行方式同样决定成败。这一节讲讲我试过的几种方案,以及各自适合什么场景。
4.1 现成的 AI 编程 IDE 够用吗
Cursor 和同类 AI 编程 IDE 是目前最容易上手的。它们支持你在多个文件里批量修改,也支持引用某个文档作为上下文。你用 Spec Coding 的工作流跟它们配合,核心操作是:把 Spec 文件拖进上下文,用 /generate 或者类似指令让它根据 Spec 生成代码。
这套方案的优势是门槛低、速度快,适合中小型模块和个人项目。缺点是:当模块间依赖关系复杂、文件很多时,IDE 的上下文管理能力不够,经常“顾头不顾腚”。我一般用它来写独立的小工具、脚本或处理单个模块内部的逻辑变更。
4.2 Agent 模式:把 Spec 当任务书用
如果你把 Cursor 换成 Claude Code、Aider 这样的 Agent 模式工具,体验会再上一级。Agent 能自己读文件、自己执行命令、自己跑测试,不需要你手动把每个文件都拖进去。它的工作模式更像一个实习生:你给它一份任务书(Spec),它自己在项目里翻资料、动手改、跑测试、汇报结果。
用 Agent 跑 Spec 驱动的开发,工作量基本在“写 Spec”和“审查代码”这两端。写 Spec 决定 AI 干活的方向,审查代码决定最终质量。中间那一大坨具体执行,AI 全包了。
实测下来,有几个配置值得注意:
- 把 Spec 文件放在项目根目录的 specs/ 文件夹下,并在 Agent 的初始指令里写清楚“所有变更必须以 specs/ 目录下的文档为准”;
- 每次变更前,先让 Agent 输出一份“变更影响清单”,列出它计划改哪些文件、为什么改,你确认后再动手;
- 跑完测试之后,让 Agent 输出测试结果摘要,别让它只丢一句“全部通过”,要让它列出每一项验收标准对应的测试用例和运行结果。
4.3 自建流水线:适合重度用户
如果你已经是 AI 编程的重度用户,可以试试自己搭一条简单的自动化流水线:用 GitHub Actions 监听 Spec 文件的变更,有改动就触发 AI Agent,自动生成代码、跑测试、提交 PR。
这个玩法省心,但前期投入成本不低,而且拼的是工程能力。我自己搭过一版,跑通之后发现一个现实问题:并不是每次 Spec 改动都会产生预期代码,Agent 偶尔会卡住。所以我现在保留了一个“人工确认”触点——Agent 生成完代码后,先把 PR 打出来,等人审,不直接合并。全自动是好,但全自动翻车的时候,你连救的机会都没有。
4.4 我的选型建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 小脚本 / 单一模块 / 快速验证 | Cursor 等 AI IDE | 上手快,上下文够用,改完即走 |
| 多模块联动 / 中等复杂度项目 | Claude Code 等 Agent | 能自主读取多文件,适合 Spec 全局驱动 |
| 有 CI/CD 基础 / 长期迭代项目 | 自建 Agent + 流水线 | 一次投入,后续需求变更的人工成本大幅降低 |
我自己现在的搭配是:小改动用 Cursor,模块级项目用 Agent,新项目起步时先自建一套流水线,后面需求变更基本都走 Spec 更新这条路。
5. 翻车实录:Spec 驱动开发常见的 7 个深坑
理想很丰满,实际操作中,坑一点都不少。我把自己的翻车经历整理成一份清单,你可以当避雷指南用。
5.1 Spec 写得太粗,AI 自由发挥到面目全非
我第一次写 Spec 的时候,只写了行为规则和数据定义,边界情况全都没写。结果 AI 在空值判断、异常处理上自由发挥,生成了大量“防御性代码”,看着稳健,实际跟业务完全不符。
后来我学乖了,每次都会专门写一节“边界情况”,把这个场景下可能出现的一切特殊输入都列出来。AI 没有常识,它不会主动想到“用户可能传空数组进来”,但它能严格执行你写出来的约束。你不写,它就默认不受约束。
5.2 Spec 写得太细,AI 被琐碎规则绑住手脚
反过来也有问题。有一回我把一个字段的取值范围写死了,AI 就严格按照那个范围生成代码,后面业务想加一个新值,AI 坚决拒绝,理由是“违反 Spec”。
解决办法是在 Spec 里区分“硬约束”和“软约束”。硬约束(比如金额不能为负)写死在验收标准里;软约束(比如当前只支持两类规则,但未来可能扩展)单独放在“设计备注”里,AI 实现时就会预留扩展点,而不是死绑现有枚举。
5.3 修改历史散落,AI 拿旧规格当依据
Spec 文件最大的敌人是版本混乱。我有一次改完 Spec 忘了保存,AI 直接根据旧文档干活,白瞎了一个多小时。后来我强制要求自己每次改动都提交 Git,并在 Spec 文件顶部放一个变更记录表,写明最近一次修改的时间和内容。
5.4 一个 Spec 里塞太多场景,AI 上下文爆炸
前面说过一个文件不超过 800 行。真超过之后,你会发现 AI 开始“忘事”——它写着后面的规则,前面定义的字段就记不清了。不是模型笨,是注意力窗口就那么大,东西太多,信息互相挤掉。
这个只能靠拆分治。我现在的标准是:一个场景一个文件,文件之间用“关联规格”字段互相引用。真到了跨文件引用的时候,AI 其实能根据链接自己跳转读取,不用非要塞在同一个文件里。
5.5 只改代码不跑测试,AI 产出“不可验证的死代码”
没有测试的 UI 自动化等于白干,没有测试的 AI 生成代码也是一样。你现在让 AI 写代码,它大概率能写出“看起来对”的代码,但这玩意儿稳不稳,全看有没有验证。
所以我坚持让 AI 先写测试。测试不通过就不算完成。这条纪律看起来很死板,但实操下来,保命。
5.6 Agent 悄悄改了规则之外的文件
这个是最惊悚的一类问题。Agent 在改代码的时候,可能为了“一致性”顺手改了其他模块的文件,改完你还不知道,上生产后才炸。
我现在对 Agent 的要求是:变更影响清单必须列全所有改动文件,并且每次改动完成后,我会用git diff --stat扫一遍,确认它没有碰规则之外的区域。发现一次“越界”,立刻把该文件从 Agent 的可写权限里拉黑。
5.7 把“生成代码”当终点,跳过人工审查
再聪明的 AI 也需要人工复审。生成代码的质量顶多到“合格工程狮入门水平”,别指望它一次产出顶级设计。我的习惯是:AI 生成完,我会重点看三处——异常处理是否有业务漏洞、数据一致性有没有被破坏、以及既有接口有没有被不兼容地改动。这三处过完,再进测试环节。
6. 从 500 行代码说起:Spec Coding 的经验价值与副作用
回到开头那个修改一个单词产生 500 行代码的案例。现在你知道了,那 500 行不是因为 AI 抽风,而是因为改一个词实际上改了一整套行为契约。这件事最大的启示是:你付出的工作重心,正在从“写代码”转移到“定义行为”。
以前一个需求过来,我要花时间想接口怎么设计、类怎么划分、函数怎么实现。现在只需要把行为定义清楚,这些事 AI 都能干。但相应的,对“定义行为”这件事本身的要求变高了——你做需求分析和规则梳理的能力,直接决定 AI 产出的上限。
还有个副作用值得提:团队协作方式会变。以前大家围绕代码评审,现在很多时候变成了围绕 Spec 讨论。代码只是具体的实现,Spec 才是共识的载体。需求方、开发、测试都盯着一份 Spec 对齐,反而比看代码扯皮要高效得多。至少在我现在的项目里,开会讨论 Spec 的时间明显超过了逐行过代码的时间。
7. 写在最后的实操心得
我最终还是想强调一点:Spec Coding 不是银弹,它不会让你变得不用思考,它只是帮你把思考转换成一种 AI 更容易消费的格式。我以前写代码,脑子里的思路是模糊的,边写边调整;现在写 Spec,必须先把思路理清楚,否则 AI 就罢工给你看。这反而倒逼我养成了“先想后做”的习惯。
如果你也想试这套工作流,我的建议是别一口吃个胖子。先找一个独立的小模块,写一份 100 行的 Spec,让 AI 按它写一版。跑通了,再加第二个模块,再试跨模块联动。等几个模块都稳定了,再考虑 Agent 和自动化的进阶配置。一步一步来,你会发现 AI 编程终于从“碰运气”变成了“按图纸施工”。
最后分享一个我一直在用的小技巧:每次让 AI 生成测试时,加一句“请同时给出测试覆盖矩阵,标注每条测试对应 Spec 的哪一条验收标准”。这句话写进 prompt 之后,测试的可追溯性一下子就出来了,出问题时人肉排查的工作量至少少一半。不信你试一次,我打包票你回不去以前那种“跑完测试不知道测了啥”的状态。