
规范驱动开发SDD最近在技术圈讨论度很高Spec-kit 这个工程化工具也频繁出现在我的信息流里。做后端十几年我见过太多“文档写一套、代码做一套”的项目最后文档变成考古资料代码靠老人传帮带。所以第一次看到 SDD 这个概念被做成可落地的工具时我花了不少时间折腾 Spec-kit手头一个用户中心模块正好拿来当试验田。这篇文章把我对 SDD 的理解、Spec-kit 的实操过程、踩过的坑、以及我本人对“规范作为工程资产”这件事的思考都写下来给同样被代码和文档脱节问题折磨的团队一个参考。1. SDD 到底是什么为什么在这个时间点突然火起来1.1 先写规范再写实现SDD 的全称是 Specification-Driven Development规范驱动开发。它的核心逻辑朴素得不像一个“新方法论”任何一次业务变更第一步不是写代码而是先把“这次需求到底要做什么、边界在哪、数据长什么样、失败怎么处理”这些问题用结构化形式写明白。规范落定之后后续的代码实现、接口文档、测试用例、数据库模型设计全部以这份规范为基准展开。这和传统写需求文档有本质区别。传统需求文档是给人类读的散文一段话描述一个行为不同读者会产出不同理解。而 SDD 里的规范是机器可读的结构化资产一个业务规则对应一条明确条目字段、类型、约束、状态流转都定义清楚。开发照着规范写代码测试照着规范写用例review 也照着规范核对实现三方有了同一把“尺子”。我参与过的一个支付模块重构就是典型例子。需求文档两百多页里面关于“退款”的定义散落在四五个章节不同开发对“退款后订单状态到底怎么流转”的理解至少有三个版本。代码写出来后光是对齐这个认知分歧就开了四次评审会。如果当时把“退款流程”抽成结构化规范每个状态转移一步一约束这种分歧根本不会发生。1.2 为什么传统流程管不住复杂项目传统瀑布流的问题大家都清楚需求文档和代码是两套体系文档不更新代码必然偏离。到了敏捷开发虽然原则里写明“可工作的软件优先于详尽的文档”但落地实践中文档往往更没人写了——冲刺排得满满当当谁愿意停下来更新那个没人看的 Word 文档而这两年 AI 编程助手普及之后这个问题变得更尖锐。AI 可以一分钟生成几百行代码效率很高但如果没有明确的规范约束给到生成上下文AI 产出的代码在风格、边界处理、错误处理上可能各有各的“理解”。我以前带过的一个项目三个人用同一个 AI 助手生成代码两周后代码库里出现了三种不同风格的错误处理方式有人返回 null有人抛异常有人用 Result 对象。代码能跑但维护成本肉眼可见地飙升。SDD 在这个时间点火起来不是因为它是银弹而是因为它恰好补上了 AI 时代最缺的那块拼图给 AI 生成行为提供可执行的约束边界。没有规范时AI 是自由发挥有规范时AI 是照着施工图施工。这是效率工具之外的另一层价值。1.3 SDD 和 TDD、BDD、DDD 的边界在哪里方法论的边界如果不理清楚团队落地时会很混乱。我简单画一条线TDD测试驱动开发先写测试再写实现关注的是代码行为的自动化验证。它处理的是“实现是否正确”。BDD行为驱动开发用 Given/When/Then 句式描述业务行为关注的是业务场景的完整覆盖。它处理的是“场景是否明确”。DDD领域驱动设计聚焦业务建模和领域语言关注的是系统结构与业务认知的一致性。它处理的是“模型是否准确”。SDD规范驱动开发先用结构化语言定义系统的契约和行为约束再让代码、测试、文档围绕这个契约生成与执行。它处理的是“实现是否遵循约定”。它们不是互斥关系更像一条递进链路DDD 定领域模型SDD 定行为契约BDD 定场景描述TDD 验证行为正确性。Spec-kit 这类工具精准卡位在 SDD 这一段把规范从“概念”变成“可执行的工程基线”。2. Spec-kit 解决了什么问题规范从文档变成可执行资产2.1 传统规范的痛点写归写、做归做做工程这么久我深知“规范”这个词在多数团队里已经有点变味了。你问任何项目经理他们都会说“我们有规范文档”但细看实际情况通常是这样规范写在 Confluence 上写完靠大家自觉阅读代码 review 的时候没有任何机制自动检查实现是否符合规范条目接口定义、数据库表结构、测试用例、前端类型定义各写各的同步全靠人工在群里吆喝。最典型的一个痛点是“规范写了等于没写”。我见过某团队的数据字典文档写得非常完整每个字段都标注了类型、长度、默认值、是否可空。但代码里照样出现 varchar(255) 存手机号、时间用字符串存储的“野生风格操作”因为没有机制阻止这种偏离文档变成了装饰品。2.2 Spec-kit 的定位把规范变成工作流的一等公民Spec-kit 解决的核心问题用一句话概括是让规范直接参与工程的构建与校验过程而不是躺在文档系统里当参考。它把规范文件作为整个开发工作流的一份输入从这份输入里自动生成多种下游产物同时反过来校验代码变更是否违背规范约束。我实际用下来的感受是它改变了开发节奏。以前写一个用户注册接口流程是“查需求文档 → 问后端这个字段叫啥 → 问前端这个返回结构怎么定 → 写代码 → 联调时发现字段对不上 → 改文档”。用 Spec-kit 之后流程变成了“改规范 → 自动生成接口定义和字段类型 → 前后端直接消费生成结果 → 写代码 → 自动校验一致”。看起来只是把顺序调整了一下但实际省下了大量沟通成本因为你沟通的对象从“人”变成了“规范”。这种定位上的差异很微妙但很关键。传统工具里规范最多是个参考物Spec-kit 这类工具则把规范提升为工作流中不可跳过的一环谁跳过谁就在 CI 阶段收到报错。2.3 核心能力拆解规范解析、双向追踪、变更影响分析要把“规范驱动开发”这个思路变成工程事实工具层面离不开三个核心能力这也是 Spec-kit 内部设计的三个关键模块规范解析器读取结构化的规范文件如 YAML/JSON转换为内部的规范模型。它要处理字段类型映射、约束解析、模块依赖关系等细节是整个工具的地基。代码生成器基于规范模型生成下游产物。我用到的主要是 TypeScript 接口、OpenAPI 文档、Mock 数据、数据库迁移脚本骨架和测试用例骨架。这部分直接决定团队的“上手体感”——生成的东西好不好用团队就愿不愿意用。双向追踪与校验器通过规范文件中的元数据标识比如每个字段的 spec 来源标记与源码建立映射实现两个方向的追踪从规范查实现这个字段在哪些地方被引用和从代码查规范这个类到底对应哪条规范条目。同时提供check命令在代码变更时验证实现是否偏离规范。另外还有一项很有用的能力是变更影响分析。规范文件里任何一处字段定义发生变化工具能通过追踪关系自动列出受影响的服务、文件、测试用例。这个能力在多人协作时价值极大——以前改一个字段类型全靠吼“用了这个接口的人都注意一下”现在直接看工具生成的 impact list 就行。能力解决的问题传统做法Spec-kit 做法规范解析规范无法被机器读取Word 文档人工阅读结构化 YAML 由解析器加载代码生成下游产物各自为政手写接口/数据库/文档从规范统一生成双向追踪代码与规范脱节靠人肉记忆或注释元数据映射自动追踪变更影响分析改一处、坏一片开会同步或碰运气自动输出影响范围清单3. 实操落地用 Spec-kit 搭建规范驱动的工作流3.1 环境准备与安装我以 Node.js 生态为例Spec-kit 目前对这套生态支持最好。安装很简单一个 npm 命令就能搞定。npm install -g spec-kit如果不想全局安装也可以作为项目依赖引入npm install --save-dev spec-kit安装完先确认版本spec-kit --version我本地验证时用的是 1.4.2 版本不同版本命令可能略有差异但核心命令接口大同小异。项目根目录下执行初始化命令工具会自动生成一套标准的工程结构spec-kit init初始化后项目结构大致如下project-root/ ├── spec/ │ ├── user.yaml │ └── order.yaml ├── src/ │ ├── generated/ │ └── handlers/ ├── spec-kit.config.json └── package.json如果你已经有存量项目不建议直接 init 覆盖现有结构。我当时是手动创建了spec/目录然后在spec-kit.config.json里配置生成目标的输出路径这样可以把生成产物指到已有代码的旁边不影响正在跑的服务。提示第一次使用别急着配全所有功能。先把“规范解析 接口代码生成”跑通让团队看到可用的生成结果再逐步把校验、追踪、CI 门禁这些环节加进来。3.2 编写第一份可执行规范Spec-kit 默认支持 YAML 和 JSON 两种规范格式。我更推荐 YAML原因很简单JSON 写复杂嵌套结构时括号和逗号的噪音太大尤其当你定义了十几个字段、四五个状态流转的契约时YAML 的缩进层级读起来直观得多。用注册用户这个业务来演示。在spec/目录下新建user.yamlversion: 1.0 module: user entities: - name: User description: 系统用户实体 fields: - name: userId type: uuid constraints: [required, immutable] - name: email type: string constraints: [required, unique, email] - name: passwordHash type: string constraints: [required, minLength: 64] - name: status type: enum values: [active, inactive, banned] default: active - name: createdAt type: datetime constraints: [required] contracts: - name: registerUser description: 新用户注册 request: email: { type: string, constraints: [required, email] } password: { type: string, constraints: [required, minLength: 8] } response: userId: { type: uuid } status: { type: enum, values: [active] } errors: - code: EMAIL_EXISTS description: 邮箱已被注册 - code: INVALID_PASSWORD description: 密码不满足安全策略每个部分都有它的作用。entities定义领域实体后续生成的数据库迁移、TypeScript interface 都以这里为基础。contracts定义接口契约包括请求参数、成功响应格式、错误码列表这是前后端联调的唯一依据。version字段用来追踪规范本身的迭代历史人员发生变动时团队能从版本记录里看出业务定义是怎么演变过来的。第一次写完规范文件后立刻跑一遍解析验证spec-kit validate spec/user.yaml这个命令只检查语法和结构正确性比如字段重复定义、类型不在白名单、enum 值缺失等。我当时写完就遇到一个报错constraints: [required, email]这种写法里把email当成自定义约束但内建约束名列表里没有这一个后来发现正确写法是用format: email来声明。这类细节看文档最快但实际跑一遍记住更牢。3.3 配置代码生成与自动校验规范文件写好后核心配置集中在spec-kit.config.json里。我贴一个典型的配置文件{ specDir: spec, generators: { typescript: { output: src/generated/types, options: { useEnums: true, strictNullChecks: true } }, openapi: { output: docs/openapi.json }, mock: { output: src/generated/mock, options: { locale: zh-CN } }, db: { dialect: postgresql, output: src/generated/migrations } }, validation: { enforceOnCommit: true, ignorePatterns: [src/legacy/**] } }生成器是 Spec-kit 的灵魂。我实际用得最多的是 typescript 和 openapi 两个。执行生成命令spec-kit generate跑完后在src/generated/types/user.ts里会看到由规范里User实体生成的 TypeScript 类型定义export type UserStatus active | inactive | banned; export interface User { userId: string; email: string; passwordHash: string; status: UserStatus; createdAt: string; } export interface RegisterUserRequest { email: string; password: string; } export interface RegisterUserResponse { userId: string; status: active; }这层代码不需要任何人去手写维护改规范重新生成即可。团队里前后端联调的时候直接把生成产物作为沟通基础谁都不许在代码里手改生成的 interface——一旦改了下次 generate 的时候必然冲突这是设计上的强制约束。校验命令同样简单spec-kit check它会扫描src/下的代码根据文件头部的元数据注释判断这个文件对应哪条规范然后验证字段引用、类型使用、API 路径是否与规范一致。比如手写代码里出现了User类型不存在的字段check 就会报错。3.4 与 CI/CD 集成让规范成为门禁如果只在本地跑 check靠个人自觉SDD 的约束力就大打折扣。真正让规范从“建议”变成“纪律”的是把校验动作嵌进 CI 流水线。我在项目的 GitHub Actions 工作流里加了这样一个 jobname: spec-check on: [push, pull_request] jobs: spec: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm ci - run: npx spec-kit validate --all - run: npx spec-kit generate --check # 检查生成产物是否与规范一致 - run: npx spec-kit check # 检查代码实现是否偏离规范三个命令分别处理三件事规范文件本身是否合法、生成的产物是否最新、手写代码是否偏离规范。任何一个不过就直接把 PR 堵在门外。这个设计有个好处它把“更新规范”和“更新代码”捆绑成一个原子操作——如果你的 PR 里有代码变更而没有对应的规范变更check 就会提示你缺少规范条目。本地 Git 钩子也可以加一道快速反馈。我习惯在 pre-commit 阶段只跑spec-kit validate --all因为 generate 和 check 在 CI 里跑就够本地跑多了反而打断开发节奏。3.5 团队协作规范几条铁律工具只是底座团队协作的约定同样重要。我们试行时立了三条规矩改代码先改规范。任何涉及字段、接口、状态流转的变更必须先更新 spec 文件再动手写实现代码。评审时第一看 spec 变更第二才看代码变更。禁止手改生成产物。所有src/generated/目录下的文件都不允许人工修改改动只能通过更新规范和重新 generate 完成。违反这个约束的人会收到代码 review 里最不留情面的 comment。每条规范必须有 owner。每个模块的 spec 文件声明一个 owner变更时 owner 必须在场参与评审。这样避免规范文件变成“人人都能改、人人都没责任”的死文档。这三条规矩执行了一个多月最大的变化是新人接手一个模块时不再需要追着老员工问“这个字段啥意思”直接读 spec 文件配合生成代码看效率提升非常明显。4. 常见问题与排错实录4.1 规范文件写对了但生成不出来这是我遇到频率最高的一类问题。表现是spec-kit validate通过但spec-kit generate时某个模块的输出是空的或者报错说找不到实体。排查步骤按顺序来确认配置文件里的specDir指向的目录与spec/实际路径一致。我遇到过大小写不一致的问题在 macOS 上开发没感觉CI 切到 Linux 就出问题。确认规范文件里module字段与generators下的模块过滤配置不冲突。如果配置了includeModules: [user]而 YAML 里写的是module: users那结果必然为空。确认实体定义里至少有一个字段带有required约束。我测试时发现一个场景如果某个实体所有字段都没有约束生成器会认为这个实体没有被正确建模主动跳过并给出警告。这类问题大多不是工具 bug而是配置与规范写法之间的隐式约定没对齐。建议遇到空输出时先跑spec-kit debug --generators它会打印出每个生成器实际读取到哪些实体定位哪一步断了。4.2 规范变更后旧代码校验失败这是 SDD 工作流里最“正确”的一种失败有人改了规范但存量代码没跟上。它不意味着出故障了而是意味着规范与实现之间的漂移被工具抓到了。处理策略看变更类型。如果是新增字段代码侧往往是安全兼容的我会先让团队在代码里补上新字段的默认值处理再合并规范变更。如果是删除字段或修改类型属于破坏性变更分三步走在规范里给旧字段标deprecated: true保留一段时间。代码侧把对这个字段的引用逐步清掉。某次迭代过去后再在规范里彻底移除该字段同时删除代码里的残留引用。这套“先标记后删除”的流程和修线上问题时的灰度发布异曲同工目的是不让规范变更成为一次爆炸性事件。我给团队的建议是破坏性规范变更要单独发起 PR不要混在业务代码里否则单看代码完全看不出这个变更的意图。4.3 多模块仓库怎么处理依赖顺序中大型项目里模块之间往往有依赖关系比如order模块的契约引用了user模块的实体。如果user规范还没解析就跑去生成order就会报“引用的实体不存在”。Spec-kit 里的解决方案是显式声明依赖。在order.yaml顶部加一段version: 1.0 module: order dependsOn: - user生成器会自动按拓扑序处理模块先跑user再跑order。如果模块之间存在循环依赖工具会直接报错这是合理的设计——规范层面的循环引用往往意味着领域模型没切干净该拆解的还是得拆解。我在一个订单 支付 退款三个模块的仓库里实测声明依赖关系后生成时间多了不到两秒但生成过程从“靠运气”变成了“确定性”尤其当三个模块互相引用契约时顺序稳定带来的收益非常大。4.4 团队不接受 SDD 怎么办工具好不等于团队愿意用。强制推行 SDD 最常见的反弹是——“写规范的时间不如多写两行代码”。我的处理方式是不争论、先试点。选一个稳定的业务模块比如用户中心作为试点限定一个月时间把规范文件、生成代码、校验流程全部配置好。试点期间不开大会只做一件事每次需求变更先改 spec再改代码。一个月后看两个指标联调阶段的字段不匹配问题数量、新人上手理解业务的时间。试点模块的数据通常都会明显好于非试点模块到这一步团队内部的推动力就自己出现了。还有一个软性技巧把“规范变更”和“代码变更”在 PR 模板里绑定。我在 PR 模板里加了一个必填项“本次变更对应的 spec 条目”填不出来的人说明没有按 SDD 流程操作。这一栏的存在让流程约束变得看得见摸得着。4.5 与 AI 助手的协作模式有人担心 SDD 会增加工作量但配合 AI 编程助手使用时SDD 反而能显著提升 AI 生成代码的可靠性。我自己常用的模式有两种第一种是把规范文件直接作为上下文输入给 AI。用 Cursor 或 Copilot 的时候spec/user.yaml文件被明确引用后生成代码时 AI 会参考字段约束和契约定义生成的代码在结构上与规范保持一致的命中率明显提高。这本质上是在给大模型提供一个“施工图”让它不要自由发挥。第二种是把spec-kit check作为 AI 生成代码的自动门禁。AI 生成代码后不直接合并先跑一遍校验不通过就让 AI 根据报错信息自行修正。我用这个闭环处理过几个需求AI 生成的代码从“能跑的版本”变成了“符合契约的版本”review 成本大幅下降。注意AI 生成代码时如果引用了规范文件请确认引用的是当前最新版本。我遇到过 AI 根据缓存中的旧规范生成代码导致 check 全挂的情况。建议在大模型对话中每次重新引用或粘贴规范内容别依赖模型记忆。5. 我对 Spec-kit 深入使用后的个人心得5.1 规范即文档让 Spec 成为团队的单一事实来源以前团队的知识库有三个地方描述同一个接口需求文档、接口文档、代码注释。三处描述经常不一致谁对谁错只能靠猜。Spec-kit 落地后我强制要求所有下游文档都从规范生成或者以规范为准手动维护的文档一律作废。这个做法落地后最直观的成果是团队里关于“某一个字段到底存什么格式”的争执消失了答案就在 spec 文件里谁有疑问直接去看。从“写文档给人看”到“写规范给机器执行”这是一次思维方式的转换。人看的文档会被忽视机器执行的规范不会被忽视因为 CI 会拿着它卡你。这个心智转变比选哪个工具更重要。5.2 对 AI 生成代码的质量控制LLM 时代的护栏现在 AI 辅助编码已经是大势所趋但 AI 写出来的代码质量充满不确定性。Spec-kit 的价值在于它给 AI 生成行为提供了一道“护栏”规范先行约束 AI 自主生成 自动化校验形成闭环。规范在生成前就限定了字段和类型校验在生成后拦截不匹配的部分AI 在反馈中迭代修正。我实测的感受是没有护栏时让 AI 写业务代码大约有五成概率要返工修类型和边界问题有规范约束后返工率明显降低因为大多数类型错误在 check 阶段就被拦住了。作为一个对代码质量有执念的老工程师这个改进让我对 AI 协作的态度从“警惕”变成了“可控地使用”。5.3 什么项目适合 SDD、什么项目不适合SDD 不是银弹我的判断标准是看项目的稳定性和协作复杂度。适合上 SDD 的项目中大型业务系统多人协作领域模型相对稳定需要长期维护有对外 API 契约。比如电商系统的订单、支付、用户模块这类模块的生命周期以年计算规范带来的约束收益会随时间累积越来越大。不适合上 SDD 的项目快速原型、探索性项目、一次性脚本。这类项目需求本身就随时在变规范制定成本大于收益强制套 SDD 反而拖慢验证速度。如果你正被“代码和文档脱节”这个问题折磨我个人建议是不用急着全面铺开先挑一个稳定的模块试用 Spec-kit 或同类工具跑通 规范 — 生成 — 校验 这条最小闭环再根据团队体感决定是否推广。如果只让我留一条经验那就是——别把规范当成一个文档任务要把它当成一个工程资产。规范写清楚之后代码反而简单了因为大多数“这个需求到底什么意思”的争论在写规范的阶段就已经被解决掉了。