2026-07-18-简谈SDD(规格驱动开发)
关于规格驱动开发(SDD)
规格驱动开发(SDD)是一种以“规范文档”为核心的工程方法论。它和普通的 AI Coding 最大的区别在于普通 AI Coding 更多是在“帮开发者写一段代码”,而 SDD 更接近于“让 Coding Agent 基于一份完整的规格说明,主动完成一项开发任务”。
SDD的核心理念可以概括为:
- 规范第一,代码第二:规范不再是代码的附属文档,而是开发的起点和核心
- 意图驱动:开发活动应以“业务目标和用户需求”为基础
- 可执行规格:规格文档可以自动生成代码、API合约和测试用例
- 持续精炼:规格文档在整个生命周期中不断完善
也就是说,SDD 的核心是先把需求、技术方案、边界条件、验收标准等内容先写清楚,再让 Coding Agent 基于这些上层信息去理解需求、拆分任务、修改代码,最终交付一个可以编译通过、主体功能可用的版本。与传统开发相比,SDD实现了权力反转:过去是“代码为王”,现在是“代码服务规格”。规格足够精确时,它可以直接生成计划与实现,减少意图与落地之间的鸿沟。
为什么推荐 SDD 工作模式
当前大部分软件开发,本质上仍然是一种流水线模式。对很多研发来说,每天的工作流程大致是:从产品、设计或上游研发那里获取需求文档,理解需求之后进入开发阶段,最后再交付一个可以测试的版本给 QA。这个过程会不断循环,构成了日常研发工作的基本节奏。在这种模式下,研发真正追求的是两件事:
第一,更快地处理需求。 第二,同时处理更多事情。
而 SDD 工作模式刚好能够提升这两点。其方法论主要有调研先行(在“古法编程”里也有这个基本要求hhh)和并行开发(开一堆shell,发挥 SDD 工作模式并行处理能力的优势)
简单来说的话,SDD实践的话我觉得要做到两点:
- 先调研,先增加自己的 Context,再写 Spec,再指挥 Agent 开发。
- 主需求 + 优化需求,或者 大需求 + 小需求的拆分,让主需求正在由 Agent 实现的时候,研发可以同时让另一个 Agent 做一些相对独立的事情
SDD 主流程图

Spec Kit 框架: SDD 的实践工具
Spec Kit 是 GitHub 官方开源的 SDD 工具包,把 AI 辅助编程组织成一套可控的结构化流程。它做三件事:与 Copilot、Claude、Gemini 等主流 Agent 集成;把自然语言需求逐层解析成规格、方案和实现计划;内置一套"宪法式"架构原则约束整个开发流程。
安装与初始化
# 安装 CLIuv tool install specify-cli --from git+https://github.com/github/spec-kit.git@vX.Y.Z
# 初始化项目specify init my-project --integration copilotcd my-project升级相关命令:
specify self check # 只读检查是否有新版specify self upgrade --dry-run # 预览升级动作specify self upgrade # 就地升级到最新稳定版specify self upgrade --tag vX.Y.Z[suffix] # 锁定到指定发布标签specify self upgrade 会像 pip install -U 一样直接执行,底层对 uv tool 安装走 uv tool install --force,因此锁定 dev、alpha/beta/rc 等标签同样有效;uvx 临时运行和源码检出会被自动识别,只给建议不实际安装。需要限时可设 SPECIFY_UPGRADE_TIMEOUT_SECS。
完整工作流
Spec Kit 把开发拆成七个前后衔接的步骤,每一步对应一个斜杠命令(大多数助手用 /speckit.*;Codex CLI 技能模式用 $speckit-*;Copilot CLI 用 /agents 选择助手)。
| 步骤 | 命令 | 作用 | 产物 |
|---|---|---|---|
| 1 | /speckit.constitution | 确立代码质量、测试、体验、性能等全局准则 | constitution.md |
| 2 | /speckit.specify | 只描述"做什么"和"为什么",不谈技术栈 | spec.md |
| 3 | /speckit.clarify | 自动追问不确定点,把返工前置为提问 | 更新 spec.md |
| 4 | /speckit.plan | 给定技术栈,生成架构与实施计划 | plan.md / data-model.md / contracts/ |
| 5 | /speckit.tasks | 拆成带依赖关系的可执行任务清单 | tasks.md |
| 6 | /speckit.analyze | 实现前校验规格/计划/任务是否一致 | 冲突报告 |
| 7 | /speckit.implement | 按 TDD 节奏逐个执行任务、生成代码 | 源码 |
一个典型的照片管理应用,四条核心命令大致长这样:
/speckit.constitution Create principles focused on code quality, testing standards, UX consistency, and performance/speckit.specify Build an app to organize photos into albums grouped by date, reorderable by drag-and-drop, no nested albums, tile preview inside each album./speckit.plan Use Vite with minimal libraries, vanilla HTML/CSS/JS, images stay local, metadata in a local SQLite DB./speckit.tasks/speckit.implement各阶段产物示例
constitution.md——把不可协商的规则固化下来,Agent 生成代码时自动遵守:
### 代码质量
- 每个 PR 必须通过自动化 lint / 格式化,合并前强制检查- 公共 API 保持最小化,附带示例和输入/输出文档- 复杂或跨文件的架构决策必须在 PR 里附简短设计说明
### 测试标准
- 鼓励测试优先;每个新功能至少含单元测试 + 跨组件集成测试- CI 跑单元/集成/冒烟测试,失败不得合并- 不稳定测试立即标记、隔离、修复,不得长期忽略spec.md——把需求规格化为带优先级和验收场景的用户故事:
### 用户故事 1:按日期创建和查看相册 (P1)
作为用户,我希望照片自动按日期分组,以便按日/月浏览。验收场景:
1. 给定 2025-11-06 和 2025-11-07 的照片,打开应用应看到两个对应日期的相册2. 无时间戳的照片归入"未知日期"相册,并支持手动指定plan.md——产出项目结构与分阶段实施计划:
specs/1-organize-photos/├── plan.md research.md spec.md├── data-model.md quickstart.md├── contracts/api.md└── tasks.md (由 /speckit.tasks 生成)
## Phase 2:实施计划
- [ ] T004 用 sql.js 实现数据库层 + 文件持久化垫片 [性能]- [ ] T010 主相册视图,支持拖放排序和键盘备用方案 [体验][测试]- [ ] T011 相册视图,平铺网格 + 虚拟化 + 增量加载 [性能][测试]配套的 contracts/api.md 定义本地 IPC 接口(渲染进程 ↔ 主进程),data-model.md 定义实体结构,例如 Photo 含 id / source_path / timestamp / checksum 等字段,并在 (checksum, user_id) 上加唯一约束去重。
tasks.md——按阶段拆分、标注依赖和并行标记 [P]:
## 阶段 1:设置
- [x] T001 初始化项目骨架(package.json、Vite、Electron 入口)- [x] T002 CI 骨架 + lint/test 步骤 [代码质量]- [x] T004 Vitest / Playwright 配置 [测试]
## 阶段 2:基础
- [ ] T007 [P] 用 sql.js 实现数据库模式和迁移表,提供 initDb()最后 /speckit.implement 按任务顺序逐个执行,严格走 TDD,把规格真正落成代码。
monorepo中的 SDD 实践
到了 monorepo——尤其是那种多运行时、web 和其他端共用一堆 packages 的仓库——第一个绕不开的问题就是
目前个人比较推荐的一种分法是按信息的归属维度来切,全局 spec 集中存放,包级 README 就近归档。
项目中spec文档描述的东西会有两种区分,「一个功能 / 一次架构决策」或者「一个包的内部约定」。横跨多个包的功能规格,以及架构层面的决策,维度是「功能」和「架构」,不属于任何单一的包,集中放在根目录的 specs/ 下。一个跨端分享功能会同时牵动 apps/web、packages/features、packages/platform,没法「属于」其中哪一个,那就放在在中立的顶层。
而单个包的内部契约,天然属于那个包,就近放进包内的 README.md。比如 packages/platform/README.md 写清楚 PlatformAPI 的接口约定,packages/core/README.md 说明 store 的实体缓存规则。这些都可以作为包的说明,跟着包走,改包的人一眼就能看到,不用跑到十万八千里外的根目录去翻。
两者之间用链接互相指过去就行:根目录的 specs/features/share-page/design.md 里链到 apps/web 和 packages/features 的具体位置;包的 README 再反向链回相关的 spec。这样无论你是从功能视角还是从包视角进来,都能顺着链接摸到另一头。
落到目录上大概是这样:
repo/├── specs/│ ├── features/│ │ └── share-page/│ │ ├── requirements.md # 要做什么、验收标准│ │ ├── design.md # 怎么做、涉及哪些包、关键接口│ │ └── tasks.md # 可勾选的实现清单│ └── adr/│ ├── 0001-ssr-only-in-web.md│ ├── 0002-platform-adapter.md│ └── 0003-pnpm-turborepo.md├── apps/│ └── web/README.md└── packages/ ├── platform/README.md # PlatformAPI 接口约定 └── core/README.md # store 实体缓存规则关于治理 spec 腐化
Spec会和代码悄悄漂移,比如代码改了三版,spec 还停在第一版,时间一长大家就都不信它了,于是彻底沦为摆设。 防止这个问题有一些解决方案:
第一,把 spec 拆成三段,对应功能的生命周期。 这里可以直接借鉴 Kiro / spec-kit 的 requirements → design → tasks 三件套
第二,在 spec 里显式记下「影响的包」。 每个 feature 的 design.md 顶部放一张「Affected packages」表,列清楚它落在哪几个 apps/* 和 packages/* 上。这张表是弥合「功能维度」和「包维度」错位的关键,能从一份 spec 反查出要动哪些代码,也能从一个 PR 反过来对照该改哪几份 spec。
## Affected packages
| 包 / 应用 | 改动性质 || ----------------- | -------------------------- || apps/web | 新增分享页路由与 SSR || packages/features | 分享逻辑主体 || packages/platform | 新增分享相关的 PlatformAPI |第三,架构决策用 ADR,而且只增不改。 前面几轮定下来的那些决策——「SSR 只在 web 端」「platform adapter 负责吸收环境差异」「用 pnpm + Turborepo」——每一条都写成一份带编号、带日期的 ADR,结构固定为「背景 / 决策 / 后果」三段。ADR 的铁律是不修改历史:决策变了不是去改旧文件,而是新增一条,并标明它 supersede(取代)了哪一条旧的。这样决策的演进是可追溯的,新人进来翻 ADR 就能看懂「为什么当初这么定、后来又为什么改了」,而不是面对一堆没有来由的约定。
更新和维护:让「改 spec」变成流程的一部分
monorepo 里的 SDD 能不能长期跑下去取决于有没有一套让 spec 跟着代码一起变的机制,机制立起来之后,日常怎么让它活着,靠的是把维护动作嵌进已有的开发流程里,而不是额外开一摊。
可以在 PR 模板里加一行强制勾选:「本次改动是否涉及 spec / ADR?若是,已同步更新并贴出链接。」搭配前面那张 Affected packages 表,review 的人很容易判断这个 PR 该不该带上 spec 改动。改功能顺手改 spec,这件事只要进了 checklist,就不容易被漏掉。
再往前一步,可以让 spec-kit 的 /speckit.analyze 在实现前先跑一遍,校验 spec、plan、tasks 三者之间有没有互相矛盾——发现冲突时,规矩是先改文档再改代码,而不是闷头把代码写完让文档去追,在动手之前把漂移掐掉。
下面是可以接在文章后面的一节,把你列的几点局限整理成博客风格的连贯叙述。我在保留你原意和例子的基础上,把逻辑串得更顺了一些,也补了一两句收束。
SDD 工作模式的局限
SDD不是银弹,也有不擅长的场景,开发者也要知道它的边界在哪。
不适合多需求相互耦合、且上下文没打通的场景
SDD 的天花板很大程度上取决于 Context 的组织程度。
比如 A 需求里的某个需求点,其实在 B 需求中也应该生效。但产品没提,需求文档里也没写,可 QA 认为要实现,产品被问到时也觉得要实现。而如果只按正常的需求开发流程走,「不生效」才是最符合文档的结果。
这本质上是上下文没打通的问题,顺带也暴露了产品团队提需混乱。在传统开发里,这类隐性信息往往靠研发之间口头同步、产品临时补充、项目群里的一句话,或者某个老员工的经验判断来兜住。但在 SDD 模式下,只要这些信息没有显式写进 Spec,Agent 大概率不会主动发现——它没法替你去读那些散落在会议纪要和聊天记录里的"潜规则"。
所以当多个需求之间存在交叉影响,而上下文又分散在不同文档、不同人、不同会议里时,SDD 的效果会明显下降。除非团队有一个部门级的 Context 仓库,能把所有需求决策、产品变更、技术方案和历史背景统一沉淀下来,再有一个 Agent 在开发前自动复核所有相关上下文——否则,单个需求级别的 SDD 很难稳定处理这种跨需求耦合。
不适合多团队协作的超大需求"一次性写完"
加入业务要新增一个模块,这种需求通常不是某个研发改几个文件就能搞定的,它往往横跨多个团队、多个模块、多个阶段,本身就是一个大型专项。这种情况下一次 Spec、一次 Agent 执行,肯定不能把整个需求端到端做完。
推荐的做法是先把超大需求拆成多个阶段,再在每个阶段内部用 SDD。比如第一阶段做技术调研和整体方案,第二阶段做 Demo 验证,第三阶段拆客户端基础能力,第四阶段接服务端协议,第五阶段完善体验和异常处理,第六阶段进测试和灰度。SDD 在每个阶段内部依然好用,但它不负责替你决定这些阶段该怎么切。
尤其是超大需求的前期,真正难的地方往往不是写代码,而是达成共识。光是整体技术方案就可能要讨论一周甚至更久,涉及模块边界、团队分工、风险评估、上线节奏、兜底策略——这些都不是 Coding Agent 一把梭能定下来的,得靠人拉齐。
不适合频繁变化、缺乏稳定输入的产品需求
如果产品需求本身就飘忽不定,文档想一出是一出、小巧思特别多,今天改交互、明天改路径、后天又推翻核心逻辑,那不管你用什么工作模式,研发效率都会被拖垮。
而 SDD 在这种情况下甚至可能更吃亏——因为它的前置成本(写 Spec、定 plan、拆 tasks)是押注在"需求相对稳定"这个前提上的。需求一天变三回,开发者认真写的 Spec 就跟着一天报废三回。当产品团队还不是 AI First 的工作方式时,这个矛盾会格外明显。
SDD 把质量押在了"输入"上。需求清晰、上下文完整、边界稳定——满足这些前提时,才能把人从逐行盯代码里解放出来(古法编程也这样其实);可一旦前提不成立,再好的流程也救不回来。所以说 SDD 比起一套"写代码的方法",更像一面镜子,照出的是团队在 Context 沉淀和需求管理上的水平。
写在最后
回头看会发现一件挺有意思的事
原因也不难理解。过去这些规范之所以常常被当成"最佳实践"束之高阁,是因为人可以偷懒:需求没写清,靠脑子记;文档没更新,靠口头问;边界没定义,靠经验蒙。人有容错、有默契、有临场发挥,烂一点的流程也能靠人硬扛过去。但 Agent 不吃这一套——你喂给它多少清晰的 Context,它就还你多少确定的结果;你含糊,它就跟着含糊。于是那些原本"最好要做"的软件工程动作,在 SDD 里变成了"不做就跑不动"的硬约束。AI 没有淘汰这些方法论,而是把它们从可选项变成了必选项。
所以真正的变化不在于工具,而在于对开发者的要求。当写代码这件事本身越来越多地交给 Agent,人的价值就上移到了"想清楚"这一层——怎么把模糊的需求拆成精确的规格,怎么在动手前识别出跨需求的耦合和风险,怎么判断一个技术方案的边界和兜底,怎么让整个团队的上下文沉淀得足够 Agent 能读懂。这些恰恰是最难自动化、也最需要经验和判断的部分。换句话说,AI 接管了"体力活",却把对"脑力活"的门槛抬高了。
这也是为什么在这样一个看似"人人都能靠 AI 写代码"的时代,持续学习反而比以往任何时候都不能停。今天要学的不只是某个新框架或某个 Agent 的用法——那些迭代得太快,追不过来也不必追(只要学得慢就不用学,毕竟很多概念活不过两个月纯营销);更该沉下心去补的,是那些经久不衰的基本功:工程方法论、系统设计的取舍、对复杂度的敬畏,以及把一件事想透彻、说清楚的能力。工具会一茬一茬地换,但这些底层的东西,才能让开发者在现在浪潮里始终站得住,走得远(除非ai迭代到不需要任何harness了)。
总之软件工程好啊,软件工程得学.jpg

分享到社交平台
将本文分享给你的朋友们
Zhongye