news 2026/9/8 19:47:26

AI编程时代如何用SDD文档驱动开发避免代码失控

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI编程时代如何用SDD文档驱动开发避免代码失控

1. 为什么我在 AI 编程时代重新捡起“写文档”

最近大半年,我几乎每天都在用各种 AI 编程工具写代码、改代码、查 bug。说实话,效率提升确实明显,但踩的坑也一点不少:让 AI 生成一个功能模块,它洋洋洒洒给你几百行代码,跑起来没问题,可一旦要加需求、换逻辑,整个代码就像一团被猫玩过的毛线。改一处,崩三处,最后只能推倒重来。

后来我复盘了一下,问题不在 AI,在我自己。我太依赖“对话式编程”了——想到什么就让 AI 写什么,写着写着需求就飘了,边界就模糊了,代码自然就失控了。直到我认真去了解 SDD,也就是 Spec-Driven Development,文档驱动的开发方法论,才算把 AI 这头猛兽拴上了缰绳。

SDD 的核心思路就一句话:**先写清楚“要做什么”和“怎么算做完”,再让 AI 动手写代码。**一切以文档为准,文档先行,代码殿后。这个理念其实在传统软件工程里不算新鲜,但在 AI 编程时代,它的价值被彻底放大了。因为 AI 不知道你的业务上下文,不懂你心里没说出口的隐藏需求,它只会根据你的 prompt 给一个“看起来对”的答案。只有你把需求、约束、边界、验收标准都写清楚,AI 才能给出真正可用的代码。

这篇文章我不会跟你扯太多学院派理论,就结合我这几个月的实战经验,聊聊 SDD 到底是什么、怎么落地、能解决哪些具体问题。如果你是做应用开发、用 AI 辅助编码、或者正在带技术团队的,这篇文章应该能帮你在“AI 写代码”这件事上少走很多弯路。

2. SDD 的概念拆解:它不是什么新东西,只是被 AI 逼成了刚需

2.1 SDD 的基本含义:规格先于实现

简单讲,SDD 就是把“写代码”这件事拆成两步:第一步,用自然语言或结构化文档把软件要做什么、做到什么程度描述清楚;第二步,再基于这份规格说明去实现代码。在传统开发里,这就是需求分析和概要设计;但 SDD 更强调“规格文档作为唯一事实来源”,代码只是规格的一种具体实现。

在 AI 编程的语境下,这个“规格文档”直接就变成了 AI 的输入。你给 AI 的 prompt 越规范、越完整,AI 生成的代码就越贴合预期。反之,如果你上来就说“帮我写个用户登录模块”,AI 大概率会给你一个能跑但到处是坑的实现:没有错误处理、没有并发控制、没有参数校验、硬编码了一堆业务常量。你用的时候才发现各种问题,再回头去补,来回沟通的成本比你自己写还高。

2.2 从 Vibe Coding 到 SDD:两种 AI 编程姿势的对比

最近网上很流行一个词叫 Vibe Coding,意思是跟着感觉编程——开着 AI 聊天窗口,想到哪写到哪,让 AI 不断生成、修改代码。这种方式在写 demo、做原型、跑通小工具的时候特别爽,但它有明显的天花板:代码库一旦超过几千行,或者涉及多个模块协作,这种“随缘编程法”就会原形毕露。

我做了一个简单的对比,SDD 和 Vibe Coding 的核心差异如下表:

维度Vibe CodingSDD
起点一句话需求一份完整的规格文档
迭代方式对话式反复修改文档版本化驱动
代码边界模糊,容易蔓延清晰,受规格约束
适用场景原型、工具、一次性脚本正式项目、多人协作、长期维护
可测试性低,改起来全靠手感高,验收标准明确
在 AI 时代的风险AI 幻觉被无限放大AI 幻觉被控制在范围内

注意,我不是说 Vibe Coding 一无是处,它确实是 AI 时代很自然的入门方式。但你如果想正经做一个产品、一个可持续维护的系统,SDD 几乎是唯一靠谱的选择。

2.3 Thoughtworks 的三级分类框架:SDD 也不是一刀切

我最早看到 SDD 是被 Thoughtworks 的一位工程师专家 Birgitta Böckeler 的文章启发的。她提出了一个三级分类框架,我实际操作下来觉得非常实用:

  • 第一级:需求规格(Requirements Spec)。用自然语言描述用户故事、业务规则和验收标准。这一级解决“做什么”的问题,主要面向产品经理、业务分析师。
  • 第二级:技术规格(Technical Spec)。面对开发人员,描述系统架构、模块划分、接口定义、数据结构、异常处理策略。这一级解决“怎么做”的问题。
  • 第三级:实例规格(Executable Spec)。用可执行的测试用例表达规格,让“文档”可以直接被机器验证。这一级解决“怎么证明做完了”的问题。

我在实战中把这三层直接映射到了 AI 编程的 input 设计上:第一层给 AI 提供业务上下文,第二层给 AI 规定技术边界,第三层作为 AI 生成代码后的自动校验工具。三层缺一不可,光有需求没有技术约束,AI 会产出风格完全不同、无法跟现有代码融合的实现;光有技术没有测试,你无法验证 AI 写的代码到底对没对。

3. 文档先行到底写什么:从业务需求到执行规格的一步步拆解

很多朋友看到“写文档”这三个字就开始头疼,脑子里浮现的是几十页没人看的 Word。别急,SDD 里的文档不是让你写那种“面子工程”,而是写真正能驱动开发的“操作手册”。我把我在实践中用得最顺的一套文档结构分享出来,你按这个框架填充,基本就能覆盖大部分项目场景。

3.1 业务需求层:用户故事 + 验收条件

这一层是给 AI 提供“为什么做”和“做什么”的上下文。我一般用下面的模板来写,每条用户故事尽量控制在一个自然段内:

作为【某类用户】,我希望【执行某操作】,以便【达成某价值】。
验收标准:

  1. 当【前置条件】时,系统应该【行为 A】;
  2. 当【异常情况】时,系统应该【行为 B】;
  3. 完成后,【某个可观察的状态】应该变为【预期结果】。

举个例子。我在做一个企业内部的知识库系统时,有一条需求是这样写的:

作为知识库管理员,我希望在导入文档时自动检测重复文件,以便节省存储空间并避免内容混乱。
验收标准:

  1. 当上传文件的内容哈希与库中已有文件完全一致时,系统应该拒绝导入并提示“该文件已存在”;
  2. 当上传文件内容相同但文件名不同时,系统应该询问用户是否覆盖;
  3. 文件导入成功后,应该在文档列表中展示最新的版本号。

这段描述我直接丢给 AI,它生成的代码基本覆盖了主要逻辑分支。如果我只说“实现一个知识库,支持文档上传”,AI 大概率不会想到去处理重复文件这种细节。

3.2 技术规格层:架构约束 + 接口定义

技术规格是约束 AI 不要“放飞自我”的紧箍咒。没有人喜欢返工,而 AI 特别喜欢在不经意间引入你技术栈之外的依赖、或者改变你已有的编码风格。所以技术规格必须写清楚:

  • 语言和框架版本。比如“使用 Python 3.11 + FastAPI,禁止引入 Django”。
  • 项目目录结构。比如“业务逻辑放在services/,路由处理器放在api/,数据模型放在models/”。
  • 接口定义。请求方法、路径、入参、出参、错误码。
  • 数据存储方案。用哪个数据库、表结构怎么设计、有没有缓存层。
  • 编码风格。命名规范、注释要求、异常处理模式。

拿接口定义来说,我习惯用 OpenAPI 风格去描述,但不用写得太正式,关键字段对齐就行。比如:

POST /api/v1/documents/import 入参: file: binary,文件内容 override: boolean,可选,是否覆盖同名重复文件 出参: 200: { "document_id": "xxx", "version": 2, "message": "导入成功" } 409: { "error": "duplicate_content", "message": "该文件已存在" } 422: { "error": "invalid_file_type", "message": "不支持的文件格式" }

这段规格 AI 一看就懂,生成代码时就会主动处理这些分支,不会只写一个“快乐路径”了事。

3.3 执行规格层:可跑的测试就是最好的文档

第三层,也是我强烈推荐你重点投入的一层:把验收条件转成自动化测试。这一层有双重作用:一是给 AI 当“约束条件”,二是给开发者当“安全网”。

我在实际操作中,会把每个用户故事的验收标准直接翻译成测试代码。比如上面的文档导入案例,我可能先用 Pytest 写好三个测试用例:

def test_duplicate_content_rejected(client, db): # 先导入一个文件 client.post("/api/v1/documents/import", files={"file": ("a.txt", b"hello", "text/plain")}) # 再导入内容相同但文件名不同的文件 resp = client.post("/api/v1/documents/import", files={"file": ("b.txt", b"hello", "text/plain")}) assert resp.status_code == 409 assert resp.json()["error"] == "duplicate_content"

这些测试写好后,我把它们连同技术规格一起丢给 AI,让它在不修改测试的前提下实现功能。这样 AI 生成的代码对不对,不用靠人肉 review,直接跑测试就知道。这个过程其实就是把第三级“可执行规格”用到了实战里。

4. 完整实操:一次用 SDD 驱动 AI 开发的全过程记录

这一章我拿一个真实的小项目来走一遍完整流程,你跟着做一遍,基本就能掌握 SDD + AI 的节奏了。项目背景是我给团队做的内部工具:一个简单的工时记录 API,支持团队成员登记每日工时、查看周报。需求不大,但涉及联表查询、权限校验、聚合统计,足够说明问题了。

4.1 第一步:先写业务需求,不急着打开编辑器

我打开一个空白 Markdown 文件,开始写这个项目的业务需求。这里有个心得:先别管技术方案,就把业务理清楚。我通常会和需求方一起列关键流程,然后写成用户故事。

工时记录系统的故事拆解:

  • 作为团队成员,我希望每天登记我的工时,以便后续统计工作量。
  • 作为团队成员,我希望看到自己本周的工时总和,以便确认是否满勤。
  • 作为团队经理,我希望查看任意成员的一周工时明细,以便评估分配是否合理。

验收标准我挑两条写细一点:

故事 A:登记工时

  1. 当选择日期为工作日(周一至周五)时,系统应该允许登记 1 到 12 小时的工时;
  2. 当日期为周末时,系统应该拒绝登记并提示“周末无需登记工时”;
  3. 当已存在同一天同一项目的登记记录时,系统应该返回 409 冲突错误;
  4. 团队成员只能登记自己的工时,不能替他人登记。

故事 B:查看周报

  1. 当查询本周的工时时,系统应该返回周一至周日七天的数据,无记录的天返回 0;
  2. 团队经理可以查询任意成员的周报,普通成员只能查询自己的;
  3. 接口返回数据中应包含总工时时长和项目维度的小计。

这一步大概花了我 30 分钟。看似“啥也没干”,实际上项目的大框架已经在脑子里成形了,后续所有环节都可以围绕这份文档展开。

4.2 第二步:确定技术约束,写技术规格

接下来定义技术方案。因为项目不大,我选择了 FastAPI + SQLite + SQLAlchemy。技术规格文档里我明确写了项目结构:

time-tracker/ ├── app/ │ ├── main.py # 应用入口 │ ├── models.py # SQLAlchemy 模型 │ ├── schemas.py # Pydantic 请求/响应模型 │ ├── services/ # 业务逻辑 │ ├── repositories/ # 数据访问层 │ └── api/ # 路由层 ├── tests/ │ ├── conftest.py │ ├── test_entries.py │ └── test_reports.py

接口方面定义了三个核心端点:

POST /api/v1/entries # 登记工时 GET /api/v1/entries?date=2025-06-09&limit=20 # 查询个人记录 GET /api/v1/reports/weekly?user_id=xxx&week=2025-W24 # 周报

并且约定了统一响应格式:

{ "status": "ok" | "error", "data": {...} | null, "message": "错误描述(可选)" }

这些信息越具体,AI 生成的代码就越“像你团队的人写的”。

4.3 第三步:写测试用例,让 AI 有据可循

接着,我先把测试用例写好。这一步很多人不习惯,因为传统开发里测试总是排在功能后面。但在 SDD 流程里,我强烈建议先写测试,甚至可以让 AI 帮你生成测试骨架——反正测试本身就是规格的一部分。

# tests/test_entries.py from fastapi.testclient import TestClient from app.main import app client = TestClient(app) def test_create_entry_success(): resp = client.post("/api/v1/entries", json={ "user_id": 1, "date": "2025-06-09", "hours": 8, "project": "platform" }) assert resp.status_code == 200 assert resp.json()["status"] == "ok" data = resp.json()["data"] assert data["user_id"] == 1 assert data["date"] == "2025-06-09" assert data["hours"] == 8 def test_create_entry_duplicate_returns_409(): payload = { "user_id": 1, "date": "2025-06-10", "hours": 7, "project": "platform" } client.post("/api/v1/entries", json=payload) resp = client.post("/api/v1/entries", json=payload) assert resp.status_code == 409 def test_create_entry_weekend_rejected(): resp = client.post("/api/v1/entries", json={ "user_id": 1, "date": "2025-06-14", # 周六 "hours": 4, "project": "platform" }) assert resp.status_code == 422 assert "周末" in resp.json()["message"]

写测试时有一个坑要提醒:不要只测“正常路径”。AI 特别擅长应付正常情况,但业务真正翻车全在异常分支上。所以我每个关键用户故事都至少配一个异常测试。

4.4 第四步:把规格文档喂给 AI,定向生成代码

当业务需求、技术约束、测试用例三份文档齐了,就可以把它们打包交给 AI 了。我一般会这样组织 prompt:

请根据以下规格实现一个工时记录 API。 【技术约束】 - Python 3.11 + FastAPI + SQLAlchemy 2.0 + SQLite - 必须使用 app/services、app/repositories、app/api 三层结构 - 统一响应格式见技术规格文档 - 不要修改 tests/ 目录下的测试文件 【业务需求】 (粘贴 3.1 的用户故事和验收标准) 【接口定义】 (粘贴 3.2 的接口清单) 【测试用例】 (粘贴 4.3 的测试代码) 请生成完整的项目文件,确保所有测试通过。

这里有一个非常关键的技巧:让 AI 先不要一次生成全部代码,而是先让它给出实现方案,你确认后再让它动手。我踩过多次坑,AI 一道 mission 就直接生成十几个文件,结果数据库模型设计不合理,全部返工。现在我会先问它“你打算怎么建模?user 和 entry 什么关系?重复检测用什么策略?”等方案通过了,再让它写。

4.5 第五步:跑测试,验证,迭代

AI 生成完代码后,我直接在项目目录跑:

pip install -r requirements.txt pytest -v

第一次跑通常不会全绿。遇到失败用例时,我不会盲目让 AI 修,而是把失败信息、预期结果、实际结果三段贴回去,让它定位问题。比如有次测试失败是因为周末校验逻辑写在了 service 层,但前端传参时日期格式不对导致判断失效。这种情况下如果不给上下文,AI 就只是盲目地“把断言改成通过”,把测试逻辑都给你改了。所以我的 prompt 一定是:

测试 test_create_entry_weekend_rejected 失败: 期望状态码 422,实际返回 200。 接口返回数据:{"status": "ok", "data": {...}} 我行周末校验的方式是判断 date.isoweekday(),但似乎没有被执行。 请检查代码层级,找到问题并修复,不要修改测试。

这样 AI 才会老老实实去修业务代码,而不是偷偷改成测试能过的假实现。

4.6 第六步:补充边界场景,做一轮强化评审

主流程跑通、测试全绿之后,整个项目其实只完成了 70%。剩余 30% 是 AI 很容易忽略的边界场景和安全性事项。我会加一轮强化评审,专门检查:

  • 超长输入、空值、异常类型。比如 hours 传了个负数、date 传了 "abc"、project 传了 10000 字。
  • 权限校验是否真的生效。比如普通用户能不能通过改 user_id 查别人的周报。
  • 性能问题。比如没有为 user_id + date 建索引,导致数据量大时查询变慢。
  • 数据一致性。比如重复检测在高并发下会不会失效。

这些问题我平时写代码可能也会遗漏,但现在我可以把“你自己 review 一下刚才的代码,重点检查这几个方面”作为 prompt 发给 AI,省去了人工逐行审阅的时间。但请注意,AI 的 review 只能作为辅助参考,关键逻辑的最终判断还得靠你自己。

5. 围绕 SDD 的工具选型与配置建议

工具选得好,SDD 落地轻松一半。这里我给不同角色的建议:

5.1 文档管理:我用的三件套

  • Markdown + Git:首选。Markdown 维护成本低,Git 负责版本管理,文档变更历史一目了然。与代码同仓库,尤其适合文档与代码强关联的项目。
  • OpenAPI(Swagger):如果项目涉及大量前后端接口对接,直接用 OpenAPI 描述接口,Swagger UI 自带可调试的文档页面,开发提效非常明显。
  • Cucumber / Gherkin(行为驱动):如果你的团队重视可执行规格,可以把用户故事写成 Given-When-Then 格式,工具直接生成测试模板。不过这个上手门槛稍高,不是所有团队都适合。

5.2 AI 编程工具怎么配合 SDD 使用

我用的是 Claude 和 Github Copilot 这类工具,配合 SDD 的姿势是:

  • 在项目根目录放一个AGENTS.mdCLAUDE.md文件,把项目结构、编码规范、常用命令写进去。这样 AI 每次读取上下文时都会先看到这份文档,不用我反复提醒。
  • 对复杂模块,我会先让 AI 根据我写的规格生成代码,再让另一个 AI 模型做代码评审,把两者结果对照权衡。不同模型的侧重点不太一样,交叉验证能发现很多单模型忽略的问题。

下面是我常用的一个AGENTS.md模板:

# 项目:time-tracker ## 技术栈 - Python 3.11 + FastAPI + SQLAlchemy 2.0 + SQLite ## 目录结构 - app/api: 请求入口,只做参数校验和响应封装 - app/services: 业务逻辑 - app/repositories: 数据访问 - tests: 测试目录 ## 编码规范 - 禁止使用 requests,统一用 httpx - 所有接口必须返回 {status, data, message} 格式 - 日期统一用 ISO 8601 字符串 - 异常处理统一用 app 自定义异常类,禁止裸抛 ## 常用命令 - 启动服务: uvicorn app.main:app --reload - 跑测试: pytest -v

有了这个文件,AI 每次生成的代码风格都会相对统一,我可省心不少。

5.3 测试框架:不同类型项目怎么配置

  • Python 项目:pytest + httpx(FastAPI 的 TestClient 底层就是 httpx)+ 内存版 SQLite 跑测试,速度快,接近真实环境。
  • 前端项目:Vitest + React Testing Library,配合 MSW 拦截 API 请求。前端 SDD 的规格文档通常是组件交互描述,我会让 AI 先根据规格生成组件骨架,再补测试。
  • Java 项目:JUnit 5 + Testcontainers,数据库依赖比较多的场景用 Testcontainers 起真实容器,虽然慢一点但可信度高。

6. 落地 SDD 时的常见问题与避坑指南

市面上讲方法论的文章很多,但真正落地时你才会碰到各种糟心问题。这里我把自己踩过的坑整理成 Q&A 形式,希望对你有帮助。

6.1 文档写到什么程度算“够”?

这是新手最容易纠结的问题。我的经验是:写到“如果明天你休假,另一个人只靠文档就能把这个功能做出来”的程度就够了。不需要面面俱到,但关键分支、异常场景、约束条件必须写清。

如果你是独立开发者,没有“另一个人”,那就假设“一个月后的你”是另一个人。因为一个月后你的记忆早模糊了,文档就是你的外置大脑。

6.2 文档和代码不一致怎么办?

这是 SDD 最大的敌人。代码改了三版,文档还在最初的状态,那我前面强调的“唯一事实来源”就崩了。

我目前的解法有两个:

  • 文档尽量和代码放在同一个仓库,修改代码时必须一起修改文档,把这个要求写进 PR 模板或者 CI 检查里。
  • 把验收标准揉进测试代码里,文档描述与测试挂勾。测试是活的,文档描述如果和测试矛盾,以测试为准,同时回头修正文档。

6.3 AI 生成代码后擅自“加戏”怎么办?

AI 经常会自作主张加一些你没有要求的逻辑,比如默认值、自动重试、花哨的日志格式。遇到这种情况,不要直接骂 AI 笨,这是你技术规格写得不够严。你需要在规格文档里明确“什么可以做”和“什么不能做”,比如:

禁止事项: - 不要在 service 层直接使用 session,统一从 repository 层访问 - 不要引入规格文档之外的第三方依赖 - 不要自动创建或修改数据库表结构

6.4 AI 就是理解不了某个业务规则,怎么办?

我曾经遇到过一个复杂的状态流转规则,AI 反复生成都不对。后来我发现问题不是 AI 笨,而是我把规则写得太抽象了。我改成用状态表 + 示例流程来描述:

订单状态:pending → paid → shipped → completed - pending 状态下允许取消,跳转 cancelled - paid 状态下允许退款,跳转 refunded - shipped/completed 状态下不允许取消和退款 示例: 1. 用户下单 → pending 2. 用户支付 → paid 3. 管理员发货 → shipped 4. 用户确认收货 → completed

AI 一下就明白了,代码一次通过。这说明写文档时,示例比抽象描述有效 10 倍。

6.5 测试跑不过,但功能看起来是对的,怎么办?

千万不要手工把测试改成通过。先把测试代码和实现逻辑逐行对照,确认是测试写错了还是实现写错了。如果是实现错,把错误信息 + 期望结果 + 实际结果贴回 AI 让它修;如果是测试错,那就大大方方改测试——毕竟测试也是文档的一部分,文档错了当然要改。

7. 再说几句大实话

从我自己的实践来看,SDD 最大的价值不是约束 AI,而是约束我自己。我写文档的过程,就是逼自己想清楚需求边界和业务逻辑的过程。很多时候在写文档阶段我就发现问题了,根本不用等代码跑起来再返工。这一点放在 AI 时代尤其重要,因为 AI 把写代码的成本和义务压到了极低,如果自己也不动脑子,那代码质量就会迅速塌方。

如果你刚接触 SDD,我建议你先别搞太大,从一个小模块开始练手:写一份 1 页纸的需求说明、几行关键接口定义、三个测试用例,然后让 AI 实现,看看整个过程顺不顺畅。跑通一次之后,你就知道这套方法论好在哪了。我现在几乎所有的 AI 辅助开发任务都会强制执行 SDD 流程,效率反而比之前“想到哪写到哪”高得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:46:41

用语义检索和向量数据库实现浏览器历史自然语言搜索

你有没有过这种经历:想找回一个上周看过的网页,只记得内容大意是“讲解如何用Docker部署Nginx反向代理”,但完全不记得网址、标题、甚至大概的访问时间。传统浏览器历史记录只能按域名和标题做关键词匹配,你去翻历史记录&#xff…

作者头像 李华
网站建设 2026/9/8 19:45:29

OpenMontage HeyGen Video Agent 提示词生产示例与可复用模板解析

OpenMontage HeyGen Video Agent 提示词生产示例与可复用模板解析 【免费下载链接】OpenMontage Worlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding ass…

作者头像 李华
网站建设 2026/9/8 19:45:03

dsh第三方插件加载实战:从安装到排错完全指南

1. 从“装不上”到“玩明白”:dsh第三方插件到底该怎么加载 如果你搜到这篇文章,多半跟我前几天一样:打开dsh的配置目录,想给这个终端工具装上几个第三方插件,结果不是报 plugin tree failed to load ,就…

作者头像 李华
网站建设 2026/9/8 19:42:55

手搓UDS Bootloader|全网独家复现0x31编程校验与0x11复位服务、解析联动时序与复位分类、助力ECU固件校验重启、OTA刷写、整车诊断稳定落地

目录 一、前言 二、核心服务原理与量产定位 2.1 0x31编程校验例程核心能力 2.2 0x11 ECU复位服务核心机制 2.3 双服务量产联动逻辑(核心重点) 2.4 复位类型细分与场景适配 三、标准报文格式与NRC错误码全解析 3.1 0x31编程校验例程完整报文 3.1.1 请求报文(上位机→…

作者头像 李华