最近半年,我大部分时间都在跟AI编程工具打交道。一个很明显的感受是:如果只丢一句"帮我实现搜索功能"给AI,结果大概率是能跑,但改起来要命。真正把效率提上去的,不是把提示词写得花里胡哨,而是把"写代码"这件事彻底改成"写规格、审规格、让AI执行规格"。
这套东西我做了一段时间,整理下来就是一套可以复用的AI原生软件开发工作流程。核心载体是一份叫Spec Kit的规格套件——它不是某个特定软件,而是一套"先写规格、后写代码"的方法集合:包括规格文档的模板、拆解规则、任务卡片、验收标准写法,以及和主流AI编程工具配合的项目配置。
这篇文章我会把这套流程讲透:为什么要这么做、一份合格的spec长什么样、怎么把它喂给AI、怎么验证AI产出的代码、踩了哪些坑。适合正在用AI写业务代码、但经常被"看起来能用、一改就崩"折磨的开发者,也适合想在小团队里推行AI辅助开发但不知道从哪下手的技术负责人。
1. 为什么先写规格,而不是直接让AI写代码
1.1 直接上手让AI写码,翻车场景我全经历过
先说几个我真实遇到的问题。
第一个场景:我让AI实现"用户上传头像功能",对话里补充了"要压缩图片、限制格式、存到对象存储"。AI确实都做了,但它是按自己的理解做的——压缩是转成固定尺寸的缩略图,格式限制只写了jpg,存储逻辑直接写在控制器里。功能是跑通了,但和项目原有的存储服务、图片处理管线完全不兼容,后面花了两个晚上重构。
第二个场景:AI生成了一个模块,代码风格倒是没什么问题,但它自己"擅自"加了一个缓存层。我根本没提缓存,它觉得"这里应该加个缓存提升性能"。结果热点数据更新后缓存没失效,线上出现脏数据。这个已经不是代码问题,是AI在替我做架构决策。
第三个场景更常见:让AI改一个方法,它顺着上下文把周边的逻辑也"顺手优化"了一遍,diff里多出来一大堆和本次需求无关的改动。Review起来极其痛苦,而且很容易漏掉它自作主张埋进去的逻辑分支。
这三个问题的根源都一样:AI编程工具本质上是概率模型,它天然会去"猜"用户意图。需求越模糊,它的自由发挥空间越大。你给的信息越结构化、越没有歧义,它的输出才越稳定。
1.2 Spec first的本质:把"模糊意图"变成"可验证契约"
很多人以为Spec first就是把需求文档写详细一点,其实不是。关键区别在于"可验证"三个字。
传统PRD写"支持用户修改个人信息",这句话AI没法直接落地,因为不确定的范围太多:哪些字段允许改?修改后要不要审核?手机号改了要不要重新验证?邮箱和手机号能同时改吗?AI只能猜。
而Spec Kit里要求的写法是:
- 范围:用户名、头像、个性签名允许修改;手机号和邮箱不在本期范围。
- 约束:修改头像后立即生效,不需要审核。
- 验收标准:给定一个已登录用户,当用户修改用户名为空字符串时,系统拒绝保存并提示"用户名不能为空"。
- 数据定义:用户资料表增加profile字段,JSON格式,里面包含nickname、avatarUrl、bio三个属性。
到这种粒度,AI已经不需要做任何意图判断,它只需要做翻译:把规格翻译成代码。这本质上是一种"契约式开发"——spec就是你和AI之间的契约,它必须按照契约执行,而不是自由发挥。
1.3 我理解的"Spec Kit"到底是什么
Spec Kit可以理解成一个"规格工具箱",它包含四个部分:
- Spec模板:一套Markdown格式的规格文档结构,规定了某个功能模块必须有范围、数据、行为、边界、验收标准这几块。
- 拆解规则:把一个大型需求拆成多个可独立交付的小spec的指导原则(比如按用户流程拆、按数据边界拆)。
- 任务卡片:从spec里提取出来的、可直接交给AI执行的单条任务描述,包含上下文入口、变更范围、完成定义。
- 项目级规则文件:放在项目根目录的说明文件,让AI每次工作时自动加载,知道这个项目遵循"spec先行"的约定。
之后两章我会逐个展开这些东西的实际用法。
2. Spec Kit的解剖:一份好spec的结构是什么样的
2.1 一份spec必须包含的七个区块
我自己用下来,一份可以交给AI执行的spec,至少需要下面这些区块。少一个,AI就会在对应维度上自由发挥。
| 区块 | 要求 | 缺失后果 |
|---|---|---|
| 背景与目标 | 为什么要做这件事,解决谁的什么问题 | AI理解不了优先级,乱做设计取舍 |
| 范围与排除项 | 明确做什么,明确不做什么 | 最容易出现"顺手优化" |
| 数据契约 | 涉及的数据结构、字段、存储位置 | 字段命名和类型全看AI心情 |
| 行为规则 | 核心逻辑的输入输出、异常处理 | 边界条件全靠猜 |
| 验收标准 | 可自动验证的具体例子 | 无法判断"做完了" |
| 界面与交互 | 页面元素、状态流转、反馈文案 | 交互细节反复返工 |
| 依赖与影响面 | 涉及哪些模块、表、API | 改坏别处代码 |
这里的核心思想是:spec不是需求文档,是验收标准的一等公民。每个功能点都要有对应的验收标准,最好是能直接转成测试用例的。写到这个程度,AI输出的代码质量会提高一个量级。
2.2 验收标准的"Given-When-Then"写法
验收标准是Spec Kit里最容易被写废的部分。很多人写的是"用户应该能正常登录"——这种句子毫无约束力。我推荐用行为驱动开发里经典的Given-When-Then三段式:
Given:某个已登录用户 When:提交了用户名为空的修改请求 Then:系统拒绝保存,并返回错误信息"用户名不能为空",原用户名保持不变
把每一条验收标准都写成这种格式,整套spec的可机械判定程度会高很多。AI在生成代码的时候,它自己能判断哪些分支要写。你写代码Review的时候,也可以直接对着验收标准一条一条过,不需要凭空"感觉"哪里不对。
我做过的项目里,凡是我把验收标准写到位的模块,AI一次通过的概率明显提升。凡是偷懒漏掉验收标准的,几乎必出逻辑漏洞。
2.3 排除项:Spec里最反直觉但最值钱的部分
"明确不做什么"是很多人会忽视的,但实际操作里这往往是省事最多的地方。
因为AI编程工具对上下文的响应方式是非线性的,它经常会把相关功能也改掉,或者把"以后可能要做的事"也顺手实现了。写清楚排除项,相当于给AI套了一个缰绳。
我举个例子。做"订单列表筛选项"的需求时,我在spec里明确写了一条:
排除项:本次不修改订单详情页,不调整筛选组件公共逻辑,不引入新的查询参数解析库。如筛选逻辑与现有组件公共逻辑冲突,保留现有组件行为并在评审时提出。
这一条直接避免了AI"顺手"重构筛选中台组件。因为那个组件是多个页面共用的,一旦被它改了,影响面就不可控了。
2.4 信息粒度:spec写得越细越好吗
也不是。
我发现一个比较合适的粒度标准:spec描述到"AI不需要做设计决策"的程度即可。具体来说,如果AI只需要做机械翻译就能完成,spec就够了;如果AI需要做方案选型、架构判断,那规格还不够,需要继续拆。
但这个粒度很难定。我的经验是:接口签名、数据结构、行为分支、文案反馈这些必须给定;而内部实现路径、中间变量名、循环写法这些完全不用写——写了反而限制AI。
比如搜索功能,我会在spec里给的是:搜索字段、匹配规则(前缀还是包含)、结果分页方式、空结果展示。但我不会写"用Elasticsearch实现""构建倒排索引"这种过于靠拢实现的描述。除非是架构上必须指定的技术选型,否则这些应该交给AI去优化实现细节。
3. 从零搭一条AI原生的spec驱动流水线
3.1 整体链路:从想法到合入一共七步
我目前跑顺的流程是这样:
- 需求进入:把原始需求描述写进一个"需求暂存区"文档。
- 补全信息:对照Spec Kit模板,把背景、范围、数据契约、验收标准逐块补全。
- 人工评审:只看spec,不看代码。检查信息是否完整、验收标准是否可执行。
- 拆任务:把spec切成3到7天的开发单元,每个单元对应一张任务卡片。
- AI实现:把任务卡片丢给AI编程工具,限定它只改相关文件。
- 自动验证:跑测试、跑类型检查、跑lint,对照验收标准逐条验证。
- 人工Review:Review的重点从"读代码"变成"对照spec看diff"。
这套链路的核心理念是:人负责定义"做什么"和"怎么验收",AI负责"怎么做"。每一步的产物都是下一个环节的输入,职责切割非常干净。
3.2 项目规则文件:让AI每次自动进入"spec模式"
这是配套里最关键的一个落地细节。现在主流AI编程工具都支持在项目根目录放一个规则文件,AI每次工作时会自动加载。我把整个团队的开发约定写进去,开头第一段就是:
本仓库采用spec-first开发流程。任何功能开发前,相关规格说明必须存在于spec目录下。AI助手在收到任务卡片时,应首先阅读对应的spec文件,严格按照spec中的范围、数据契约和验收标准实现。凡spec未定义的内容,默认"不做"。如需扩展,先向用户提出补充spec的请求,不得自动实现。
写完之后,AI的自由发挥空间被大幅压缩。实测下来,"擅自加缓存""顺手优化周边代码"这类问题少了很多。因为规则里明确写了"凡未定义内容默认不做",它在边界模糊时会先问,而不是先写。
3.3 任务卡片长什么样
spec是对功能模块的完整描述,但对AI执行来说,一次让它实现整个模块还是太大。我把spec拆成任务卡片,每个任务卡片控制在"一次对话能完成"的体量。
我自己的任务卡片格式是这样的:
任务编号: MOD-102 关联Spec: docs/specs/user-profile.md 变更范围: 仅允许修改 src/services/profile.ts 和 src/types/profile.ts 任务描述: 按spec第3.2节实现用户资料查询接口,返回类型以spec数据契约为准。 完成定义: - 类型检查通过 - 新增的3条验收标准对应的测试用例通过 - 不修改其他文件关键点是"变更范围"和"完成定义"。前者限制AI的物理改动范围,后者给了明确的可判定标准。
3.4 分支和提交规范
AI写代码的一个大问题是它很不擅长小步提交。它经常一次性写几百行,然后丢给你一个"feat: 完成用户资料功能"的提交。这样出了问题完全没法定位。
我的解决办法是把"按验收标准分步提交"也写进项目规则里。每个验收标准跑通之后,AI必须单独提交一次,commit message里要带上验收标准的编号。
实测下来,这个约定不仅让diff变干净了,更重要的是,当某个验收标准挂了,我可以直接git revert掉对应的那次提交,不用手工清理半成品代码。
4. 从spec到可运行代码:执行层的关键细节
4.1 把spec翻译成AI任务的三个层次
"从spec到代码"这个动作,看起来只是把spec丢给AI,实际操作里有三个层次,我用不同场景分别处理。
第一层是最轻量的:从spec里抽一段行为规则,发给当前对话里的AI,让它改当前文件。这种适合几十行的小改动。我通常会把"行为规则"和"验收标准"原文粘过去,然后补充一句"只改这一个函数,其他不要动"。
第二层是中等体量:新页面、新模块这种。我会把整个spec文件路径告诉AI,让AI先读文件,然后按数据契约生成基础骨架。这个阶段先不追求功能完整,先把类型定义、接口签名、页面结构搭好。
第三层是跨模块改动:涉及多个服务、多张表、前后端联调时。我一般不会直接让AI写完整功能,而是让它分阶段执行——先改数据层,再改接口层,最后改UI层。每一层都单独完整跑通之后,才进入下一层。
分层的核心原因是:AI的上下文窗口是有限的,要求它一次处理太多跨模块信息时,它会开始"遗忘"或混淆上下文。把任务切薄,反而总完成时间更短。
4.2 让验收标准变成测试用例
这是Spec Kit方法论里我认为最重要的一环:spec里的Given-When-Then验收标准,应该被直接转成自动化测试。
我一般在任务卡片的"完成定义"里注明"将spec第X节的3条验收标准转成测试用例"。AI在拿到任务时,会先去写测试,再实现功能。因为验收标准本身写得够具体,AI转换测试用例的过程几乎不需要额外思考。
这一步做完之后,你获得的最有价值的东西不是功能代码,而是一个可回归的测试集。后面任何人改这个模块——不管是AI还是同事——只要跑一遍测试,就知道功能有没有被改坏。这比任何Code Review都可靠。
遇到没有测试框架的项目,我也会至少写一个冒烟脚本,把验收标准逐条调一遍API,输出通过/失败。这已经是底线了。
4.3 需要人工把关的三个节点
全程让AI干活不现实,有三个节点我一定会人工介入,一次都不省。
第一,spec评审时。这是整个流程里最重要的质量关卡。如果spec有歧义,后面所有环节的返工成本都会指数级放大。我甚至会在这一步故意"挑刺"——想想哪些情况AI会理解错,然后在spec里把这些歧义堵死。
第二,跨模块方案设计时。比如"订单服务新增了一个字段,需要同步到库存服务的查询逻辑"这种。即使spec写清楚了要改哪几个模块,但实现顺序、事务边界、数据一致性方案,仍然需要人来定。
第三,合入前的diff Review。注意,这里的Review重点不是逐行读代码,而是看三点:改动范围是否超出任务卡片声明的范围、是否所有验收标准对应的测试都通过了、有没有出现spec之外的新逻辑。这个Review通常只需要几分钟,比传统逐行Review高效得多。
4.4 先让AI说方案,再让AI写代码
我踩过一次印象很深的坑。
当时让AI实现一个"并发控制的分布式锁"模块。我直接把spec丢给它,它吭哧吭哧写完了,但实现方案用的是数据库悲观锁。在我们的高并发场景下,这方案根本扛不住。后来我在Spec里加了一条规则:实现复杂逻辑前,先向用户简要说明技术方案,等确认后再动手。
从这以后,"先说方案再动手"就成了任务卡片里的一行标准文案。AI会先给你一段一两百字的方案描述,包括核心实现思路、涉及的关键类、大致的时间复杂度。确认没有问题,再让它继续。这个习惯帮我避免了很多"代码没错但方案不对"的浪费。
5. 验证与迭代:spec不是写完就扔的
5.1 自动验证和技术栈配置
AI生成代码的验证,我目前跑四层:
- 类型检查:TypeScript项目就跑tsc,Python项目就跑mypy。第一道防线,拦截最基础的错误。
- Lint与格式:统一代码风格,减少diff噪音。
- 单元测试:跑任务卡片里"完成定义"指定的测试用例。
- 手工冒烟:打开本地服务,按spec里的验收标准手动过一遍交互流程。
前两层通常秒级搞定,第三层看用例数量,第四层最费时间但省不掉。曾有几次自动化全绿,但手工冒烟时发现页面按钮位置完全不对,AI把布局逻辑写拧了。
5.2 变更管理:spec要做"活的文档"
项目一迭代,spec必然要改。但很多团队的问题是:spec改了,代码没跟上;或者代码改了,spec没更新。两边漂移到最后,spec完全失去参考价值。
我的处理方式是:把"修改spec"和"修改代码"绑进同一个流程。任何需求变更进来,先改spec,再改任务卡片,然后再动代码。代码Review时,我会顺手看一眼spec目录下的变更记录有没有同步。
这套做法的好处是,三个月后你回头问"这个功能当初为什么这么设计",去翻spec,里面记录得清清楚楚。而传统直接改代码的思路,往往会丢掉这个记录。
5.3 处理AI生成的"正确但糟糕"的代码
"正确但糟糕"是我用了AI编程之后新学会的一个判断维度。功能全对、测试全过,但代码就是让人不舒服:函数一百行、状态管理绕了三层、明明是简单的场景搞了一堆抽象。
这个问题的根源,往往不是AI能力不够,而是spec里少了一个约束:"代码组织方式"。
现在我会在spec里加一个可选的"实现约束"区块,里面写诸如:单函数不超过五十行、新增依赖需要用户确认、优先复用现有工具函数而不是新写、不要在组件里直接调用API等。这些都是团队自身的技术约定,AI不知道,必须靠spec或项目规则告知。
这一招加完之后,AI产出的代码从"能用"变成了"基本符合团队风格",后续人工Review的负担大减。
6. 踩坑记录与调优经验
6.1 Spec粒度:写得太细会拖慢,写得太粗会失控
这个是整个流程里最难平衡的点。我经历过两个极端。
有一阵我要求每个spec都写到字段级,结果写文档的时间比写代码还长,团队怨声载道。后来我又试过完全放开,只有一段背景描述,让AI自由发挥,结果代码质量断崖式下跌。
现在我的标准是:做"设计决策"的地方必须写细,做"机械翻译"的地方全放开。涉及数据契约、接口签名、业务规则分支、权限边界、UI状态流转的,都属于设计决策,必须写细;而内部实现路径、临时变量、循环方式、工具函数怎么组织,都属于机械翻译,让AI自己决定。
6.2 上下文窗口不是无限的:抽丝剥茧而不是整本相赠
有人会把整个spec目录都指给AI,说"你看着办"。这会出问题:AI的上下文再大,也没有大到可以同时精确处理全部模块规格的程度。信息过载之后,它会抓不住重点,反而把各种模块的约定糅在一起。
正确做法是:每次只给它当前任务相关的spec段落。任务卡片上引用的spec路径,我会精确到章节号。如果AI需要了解相邻模块,再让它按需去读对应文件,而不是一次性把所有spec文档都喂进去。
6.3 团队协作:Spec不是一个人的事
如果只有一个人写spec,另一个人直接改代码,这套流程还是会失效。我建议的最小协作方式是:前端、后端、测试共同评审一份spec,谁发现问题当场改,改完再进开发。
这个评审通常只需要二十分钟,但它能挡掉多数跨端理解偏差。最典型的例子就是接口字段命名:后端说"userId",前端理解成"用户ID字符串",测试理解成"整数ID"。如果三方在评审spec时没有对齐数据契约,后面至少多花半天联调。
6.4 给新人的上手路径
如果你想把这套流程引入自己的项目,我的建议是:不要一上来全量推,先挑一个小模块跑通。
具体来说,选一个两周内要开发的独立小功能,按Spec Kit模板写一份spec,然后用任务卡片让AI实现。跑完一个闭环之后,你会对哪些信息该写、写多细、AI在什么情况下会跑偏,有非常具体的体感。
我大概在第三个模块跑完之后,才真正把spec写得又快又准。在那之前,每个模块都会遇到"哦原来少了这一块"的情况。这套流程本身不复杂,难的是建立"凡是会让AI产生歧义的地方,都要在spec里堵死"的条件反射。
最后说一个我自己的窍门:Spec模板里我始终保留一块叫"未定义事项"的空位。项目赶进度时,如果某个细节来不及想清楚,我就明确写上"未定义:本版本对超出三个的筛选项采用折叠展示,用户自定义排序规则暂不实现"。宁可明确说"不做",也不要让AI替我做决定。这个习惯帮我留住了很多本会被AI悄悄改掉的设计边界。