
一、Spec Coding和Vibe Coding的区别对比维度Spec Coding (规范驱动编程)Vibe Coding (直觉驱动编程)核心思想先写规范再写代码。用结构化的“蓝图”约束AI的生成过程。用自然语言描述意图让AI直接生成代码侧重快速实现想法。工作流程线性流程需求 → 设计 → 任务拆解 → 代码实现。螺旋式迭代原型生成 → 快速测试 → 优化迭代。核心优势质量与可控性高。代码准确、可维护性强一次通过率可比Vibe Coding高30%-50%。开发速度快。能快速响应需求变更在敏捷开发中可将响应速度提升60%。主要劣势前期投入大。需要投入时间编写和维护规范文档。可靠性与维护性差。容易产生逻辑漏洞项目风险可能增加25%且代码难以长期维护。适用场景企业级复杂项目如金融、医疗等对准确性、合规性要求高的系统。快速原型验证、个人项目或需求频繁变动的探索性任务。技术依赖需要Spec文件和验证器等多重组件。主要依赖大语言模型LLM本身。提出者/来源由AWS在其AI IDE产品Kiro中正式提出。由前OpenAI联合创始人Andrej Karpathy于2025年提出。1.1 Vibe Coding 方式打开 Cursor开始对话帮我写一个商品下架接口AI: 好的我来写一个 PUT /products/{id}/offline 接口...写了一半发现要处理库存下架的时候库存清零吗AI: 根据一般业务逻辑下架时库存应该保留但标记为不可售...于是 AI 加了 available false 字段又发现历史订单要展示商品名称历史订单里商品名称要怎么处理AI: 建议在订单表里冗余存储商品名称...于是 AI 又改了订单表结构三小时后功能跑通了。但你回头一看接口路径是/products/{id}/offline不是最开始说的/products/{id}/statusstatus 字段从 enum 变成了 boolean第 8 次对话时 AI 悄悄改的订单表多了 3 个冗余字段但有 2 个实际没用上available 和 status 两个字段功能重叠产生了逻辑矛盾你需要花额外的时间去理解、修复这些“意外产出”。1.2 Spec Coding 方式动手之前先写一份 spec## 商品下架功能 Spec ### 目标 商品下架后对用户端不可见但历史数据保持完整。 ### 接口 PUT /api/admin/products/{id}/status Body: { status: OFFLINE } Response: 204 No Content ### 状态机 ONLINE → OFFLINE下架用户端隐藏 OFFLINE → ONLINE重新上架 DELETED软删除不可恢复历史数据保留 ### 数据变更 - products.status 字段更新 - 不清零库存库存独立管理 - 不修改订单历史 ### 边界条件 - 有未完成订单时不允许下架返回 409 - 已是 OFFLINE 状态再次下架返回 200幂等把这份 spec 发给 AIspec.md 按照这份规格实现接口包括 Service、Controller、异常处理结果一次生成代码和规格完全一致边界条件都处理了。1.3 Intent Drift 的三种模式上面的案例里Vibe Coding 出了问题本质上都是Intent Drift意图漂移。常见的三种模式模式一渐进妥协你说“用枚举表示状态”AI 第 12 次对话时说“用 boolean 更简单”你觉得似乎有道理就同意了。10 次小妥协累积下来最终方案已经面目全非。模式二上下文遗忘AI 在对话第 3 条说“不冗余存储商品名称”在第 28 条完全忘了又建议你在订单表加冗余字段——两次建议互相矛盾AI 自己不知道。模式三隐式假设你说“历史订单里还能看到商品信息”AI 理解成“需要在订单表冗余存商品名”其实你的意思是“通过 JOIN 查产品表就够了”。这个假设从来没有被明确说出来最终实现方向完全跑偏。1.4对比Spec Coding 的出发点是AI 是执行者规格是契约。Vibe CodingSpec Coding开始方式直接描述需求开始写先写 spec 再让 AI 实现AI 的角色共同设计者执行者决策记录散落在对话历史里集中在 spec 文件里意图偏移每轮对话都可能漂移spec 锁定意图AI 只能在范围内执行可复现性同一个需求再跑一次结果可能不同spec 不变结果稳定适合场景探索、原型、不确定方向时明确需求、正式功能、需要可维护的代码不是说 Vibe Coding 不好——探索新功能、验证思路时还是直接聊。Spec Coding 是在需求明确之后进入“正式实现”阶段时的工作方式。二、spec.md 结构设计——AI 看得懂的需求文档Spec Coding 的核心是一份写给 AI 看的规格文档。这节直接给大家一个标准模板讲清楚每个部分的作用然后用好 Spec 和烂 Spec 的对比让你看清楚差距在哪。2.1标准 spec.md 模板# [功能名称] Spec ## 背景与目标 !-- 一两句话说清楚为什么要做这个解决什么问题 -- ## 范围 ### In Scope做什么 - ... ### Out of Scope不做什么 - ... ## 接口定义 ### [接口名称] - 方法GET / POST / PUT / DELETE - 路径/api/xxx - 请求参数... - 响应格式... - 错误码... ## 数据模型变更 ### 新增表 / 修改表 - 表名... - 字段说明... ## 业务规则 - 规则一... - 规则二... ## 边界条件与异常处理 - 场景一xxx 时返回 xxx - 场景二xxx 时抛出 xxx 异常 ## 验收标准GIVEN-WHEN-THEN ### 场景一正常流程 - GIVEN: 前置条件 - WHEN: 触发动作 - THEN: 期望结果 ### 场景二异常流程 - GIVEN: ... - WHEN: ... - THEN: ... ## 技术约束 - 不使用 xxx如不引入新的外部依赖 - 性能要求xxx - 安全要求xxx ## 不在本 Spec 范围内的设计决策 !-- 明确说明哪些细节由 AI 自行决定避免 AI 在这些地方纠结 --每个部分的作用背景与目标告诉 AI 为什么做不是让它理解业务是让它知道优先级。如果有冲突偏向哪个方向。范围Out of Scope 最重要我的经验Out of Scope 比 In Scope 更关键。AI 有填充倾向——它会主动帮你把“看起来相关的”东西一起实现了。明确说不做什么能拦住 80% 的意外改动。接口定义越精确越好。路径、方法、参数名、响应格式写到字段级别。模糊的接口定义是意图漂移的主要入口。业务规则AI 不了解你的业务上下文它会用“常见业务逻辑”来填充空白。规则没写进 specAI 就自己猜猜错了你还要回来改。验收标准GIVEN-WHEN-THEN这是整份 spec 最值钱的部分。写完之后可以直接让 AI 生成测试用例也可以用来验证生成的代码是否正确。2.2好 Spec vs 烂 Spec2.2.1烂 Spec典型的 AI 会乱搞## 用户注册功能 Spec 实现用户注册包括 - 收集用户信息 - 验证信息 - 保存到数据库 - 发送欢迎邮件这份 spec 的问题“收集用户信息”——哪些字段必填吗“验证信息”——什么验证规则手机号格式密码强度“保存到数据库”——哪张表有没有唯一约束“发送欢迎邮件”——同步还是异步失败了怎么办AI 面对这份 spec 只能自己猜。猜出来的结果和你预期的可能差 50%。2.2.2好 SpecAI 能精确执行## 用户注册功能 Spec ## 背景与目标 新用户通过手机号注册账号注册成功后可以登录系统。 ## Out of Scope - 第三方登录微信、支付宝不在本次范围 - 邀请码注册机制不在本次范围 - 短信验证码暂不接入后续迭代 ## 接口定义 ### POST /api/auth/register 请求体 { phone: 13800138000, // 必填11位手机号 password: Abc12345!, // 必填8-20位含大小写字母和数字 nickname: 张三 // 选填1-20个字符默认为手机号后4位 } 响应201 Created { userId: uuid, phone: 138****8000, // 脱敏显示 nickname: 张三, createdAt: 2025-01-15T10:30:00Z } 错误码 - 400参数校验失败附带具体字段错误信息 - 409手机号已注册 ## 数据模型 users 表新增字段无使用现有表结构 现有 users 表id(uuid), phone(unique), password_hash, nickname, created_at, status ## 业务规则 1. 密码存储bcrypt 加密不存明文 2. 手机号唯一重复注册返回 409不透露已注册信息 3. nickname 为空时自动设置为手机号后4位格式 用户xxxx ## 边界条件 - phone 格式非法400message: 手机号格式不正确 - password 不符合强度要求400message: 密码需包含大小写字母和数字8-20位 - phone 已注册409message: 该手机号已注册 - nickname 超过20字符400message: 昵称不能超过20个字符 ## 验收标准 ### 场景一正常注册 GIVEN 手机号 13800138000 未注册 WHEN POST /api/auth/register { phone, password, nickname } THEN 返回 201userId 不为空phone 脱敏users 表新增一条记录 ### 场景二手机号重复 GIVEN 手机号 13800138000 已注册 WHEN POST /api/auth/register { 同一手机号 } THEN 返回 409不创建新记录 ### 场景三密码不符合要求 GIVEN 任意手机号 WHEN POST /api/auth/register { password: 123456 } THEN 返回 400message 说明密码强度要求 ## 技术约束 - 不引入新的依赖 - 密码强度校验用正则不用第三方库 - 不发送短信暂不需要验证码两份 spec 的差距不只是“详细程度”而是AI 的自由发挥空间第一份给了 AI 几十个决策点第二份只剩实现细节。三、从 PRD 到 Spec系统转化流程与实战案例手头有 PRD产品需求文档怎么把它变成上一节说的那种 spec这节给出一套系统的转化流程带一个完整的电商商品管理功能作为实战案例。3.1 PRD 里哪些信息有用PRD 是写给人看的spec 是写给 AI 看的。同样是需求文档关注点完全不同PRD 的内容对 AI 的价值处理方式业务背景、用户故事低——AI 不需要理解业务上下文压缩成一两句目标说明功能列表中——确认范围直接转成 In Scope / Out of Scope交互原型图中——提取字段和流程翻译成接口参数和业务规则异常流程高——直接转成边界条件逐条保留验收标准高——直接转成 GIVEN-WHEN-THEN直接保留并格式化上线时间、责任人无用丢掉UI 设计稿细节无用后端 spec丢掉核心转化原则把“产品想要什么”转成“AI 需要实现什么”。3.2转化流程PRD ↓ ① 提取功能范围In/Out Scope ↓ ② 提取接口列表路径、方法、参数 ↓ ③ 提取业务规则每条规则一句话 ↓ ④ 提取边界条件异常流程逐条列出 ↓ ⑤ 补充技术约束PRD 里没有但开发知道的 ↓ ⑥ 写验收标准GIVEN-WHEN-THEN ↓ Spec3.3实战商品管理功能3.3.1原始 PRD节选商品管理功能背景运营人员需要在后台管理平台上管理商品信息。功能需求商品列表支持按名称、分类、状态筛选分页展示新增商品填写商品基本信息后上架编辑商品修改已有商品信息商品状态管理上架、下架、删除字段要求商品名称必填最长50字分类必填从预设分类选择价格必填大于0库存必填整数描述选填主图选填URL格式业务规则上架商品不能直接删除需先下架下架商品可以重新上架删除后不可恢复软删除不物理删除验收标准运营可以在5秒内搜索到指定商品价格修改后用户端立即生效3.3.2转化过程步骤一确认范围PRD 说“商品管理”但没说商品图片上传怎么处理是传 URL 还是上传文件→ 先问产品这里假设传 URL分类是固定的还是动态的→ 先问产品这里假设是预设分类枚举有没有批量操作→ PRD 没提Out of Scope步骤二用 AI 辅助补全 spec把 PRD 原文发给 AI用以下 Prompt你是一个有经验的 Java 后端工程师。以下是产品需求文档请帮我转化为后端 API 的 spec 文档。要求1. 提取所有接口方法路径参数响应格式错误码2. 提取业务规则每条一句话3. 提取边界条件和异常处理4. 写出 GIVEN-WHEN-THEN 验收场景至少覆盖正常流程和主要异常5. 标注 Out of Scope没有明确说明的功能技术栈Spring Boot 3.x JPA MySQL现有代码结构标准 Controller-Service-Repository 三层PRD 内容如下[粘贴 PRD]3.3.3转化后的 Spec# 商品管理后台接口 Spec ## 背景与目标 提供商品的增删改查接口供运营后台使用。 ## In Scope - 商品 CRUD - 商品状态管理上架/下架/软删除 - 商品列表查询分页筛选 ## Out of Scope - 商品图片上传本次只支持传图片 URL不做文件上传 - 商品分类管理分类枚举在代码里写死不做 CRUD - 批量操作 - 商品导入/导出 --- ## 接口定义 ### 1. 商品列表 GET /api/admin/products Query 参数 - name: String选填模糊匹配商品名称 - category: String选填精确匹配分类枚举 - status: String选填ONLINE/OFFLINE/DELETED默认不含 DELETED - page: int默认 0 - size: int默认 20最大 100 响应 200 { content: [ { id: 1, name: iPhone 16, category: ELECTRONICS, price: 5999.00, stock: 100, status: ONLINE, createdAt: 2025-01-15T10:00:00Z } ], totalElements: 150, totalPages: 8, page: 0, size: 20 } ### 2. 新增商品 POST /api/admin/products 请求体 { name: iPhone 16, // 必填1-50字符 category: ELECTRONICS, // 必填枚举ELECTRONICS/CLOTHING/FOOD/OTHER price: 5999.00, // 必填 0最多2位小数 stock: 100, // 必填 0 的整数 description: ..., // 选填最长500字符 imageUrl: https://... // 选填URL格式 } 响应 201返回创建的商品完整信息含 id、createdAt 错误 - 400参数校验失败 - 409同名同分类商品已存在 ### 3. 修改商品 PUT /api/admin/products/{id} 请求体同新增所有字段选填只更新传入的字段 响应 200返回更新后的完整商品信息 错误 - 400参数校验失败 - 404商品不存在 - 409修改名称后与现有商品重复 ### 4. 修改商品状态 PUT /api/admin/products/{id}/status 请求体{ status: ONLINE / OFFLINE / DELETED } 响应 200{ id: 1, status: OFFLINE } 错误 - 404商品不存在 - 409ONLINE 状态的商品不允许直接 DELETED - 400状态值非法 --- ## 数据模型 products 表 - id: BIGINT AUTO_INCREMENT PK - name: VARCHAR(50) NOT NULL - category: VARCHAR(20) NOT NULL枚举字符串 - price: DECIMAL(10,2) NOT NULL - stock: INT NOT NULL DEFAULT 0 - description: VARCHAR(500) - image_url: VARCHAR(500) - status: VARCHAR(10) NOT NULL DEFAULT ONLINEONLINE/OFFLINE/DELETED - created_at: DATETIME NOT NULL - updated_at: DATETIME NOT NULL - UNIQUE KEY uk_name_category(name, category) --- ## 业务规则 1. ONLINE 状态商品不能直接设为 DELETED必须先 OFFLINE 2. OFFLINE 状态可以改为 ONLINE 或 DELETED 3. DELETED 状态不可变更终态 4. 列表查询默认不返回 DELETED 的商品除非 statusDELETED 明确传入 5. 库存可以为 0允许缺货状态 --- ## 边界条件 - price 0400price 必须大于 0 - stock 0400stock 不能为负数 - name 超过 50 字400 - imageUrl 非 URL 格式400 - ONLINE 商品尝试删除409请先下架商品再删除 --- ## 验收标准 ### 场景一正常新增并上架 GIVEN 数据库中不存在同名同分类商品 WHEN POST /api/admin/products { name, category, price, stock } THEN 返回 201商品 statusONLINE数据库新增记录 ### 场景二重复商品 GIVEN 已存在 nameiPhone 16, categoryELECTRONICS 的商品 WHEN POST /api/admin/products { 相同 name 和 category } THEN 返回 409 ### 场景三ONLINE 商品直接删除 GIVEN 商品 id1statusONLINE WHEN PUT /api/admin/products/1/status { status: DELETED } THEN 返回 409数据库状态不变 ### 场景四下架后删除正常流程 GIVEN 商品 id1statusONLINE WHEN PUT .../status { status: OFFLINE }再 PUT .../status { status: DELETED } THEN 两次均返回 200最终状态为 DELETED ### 场景五列表不返回已删除 GIVEN 存在 statusDELETED 的商品 WHEN GET /api/admin/products不传 status 参数 THEN 返回结果中不包含 DELETED 商品 --- ## 技术约束 - 不引入新的依赖 - 分页用 Spring Data JPA Pageable不手写分页 SQL - 模糊查询用 JPA Specification不用 native query四、让 AI 做 Spec Review写完 spec 之后在动手实现之前先让 AI 做一轮检查请 review 以下 spec指出 1. 遗漏的边界条件 2. 业务规则是否有冲突 3. 接口设计是否有明显问题 4. 有没有隐含的假设没有明确写出来 [粘贴 spec 内容]我的经验AI 的 review 通常能发现 2-5 个你没想到的边界情况值得跑一遍。五、OpenSpec 工具链——Spec Coding 的标准化框架5.1 OpenAPI——接口 Spec 的事实标准OpenAPI原 Swagger是目前最成熟的接口规格标准用 YAML 或 JSON 描述 REST API。一份 OpenAPI spec 长这样openapi: 3.0.3 info: title: 收货地址 API version: 1.0.0 paths: /api/user/addresses: post: summary: 新增地址 requestBody: required: true content: application/json: schema: $ref: #/components/schemas/CreateAddressRequest responses: 201: description: 创建成功 content: application/json: schema: $ref: #/components/schemas/Address 400: description: 参数校验失败 422: description: 地址数量已达上限 components: schemas: CreateAddressRequest: type: object required: [name, phone, province, city, district, detail] properties: name: type: string minLength: 1 maxLength: 20 description: 收件人姓名 phone: type: string pattern: ^\d{11}$ description: 手机号 province: type: string maxLength: 20 city: type: string maxLength: 20 district: type: string maxLength: 20 detail: type: string maxLength: 100 isDefault: type: boolean default: falseOpenAPI 的优势有配套的 Swagger UI可以直接在浏览器里查看和测试接口有代码生成工具OpenAPI Generator可以自动生成 Controller 骨架、客户端 SDK前后端可以先对齐接口 spec再同步开发前端用 mock server后端实现真实逻辑局限格式严格写起来比 Markdown 啰嗦得多只能描述接口描述不了业务规则、状态机、数据模型对 AI 生成代码的指导效果和 Markdown Spec 差不多但写作成本更高我的建议团队项目、需要前后端对齐接口的场景用 OpenAPI个人项目或快速迭代用 Markdown Spec更快5.2 Database Schema 工具数据模型的标准化工具主要有DBMLDatabase Markup Languagedbml.dbdiagram.io 开发的简洁数据库描述语言Table users { id bigint [pk, increment] phone varchar(11) [not null, unique] nickname varchar(20) [not null] created_at datetime [not null] } Table user_addresses { id bigint [pk, increment] user_id bigint [not null, ref: users.id] name varchar(20) [not null] phone varchar(11) [not null] province varchar(20) [not null] city varchar(20) [not null] district varchar(20) [not null] detail varchar(100) [not null] is_default tinyint(1) [not null, default: 0] created_at datetime [not null] updated_at datetime [not null] indexes { user_id (user_id, is_default) } }DBML 可以在 dbdiagram.io 里在线渲染成 ER 图也可以导出建表 SQL。Liquibase / Flyway用于管理数据库版本迁移不是“写 Spec”的工具而是“执行 Spec”的工具。典型用法写好数据模型 SpecMarkdown 或 DBML让 AI 生成 Flyway 迁移脚本把迁移脚本提交到仓库Flyway 自动执行5.3把工具链串起来我目前用的工作流需求/PRD ↓ ① Markdown spec.md手写AI 辅助 ↓ ② AI Review spec检查漏洞 ↓ ③ 分层生成代码Entity → Service → Controller ↓ ④ Flyway 迁移脚本AI 从数据模型 spec 生成 ↓ ⑤ 提交代码 spec.md 一起进仓库大型团队的工作流可选升级版需求/PRD ↓ ① Markdown spec.md手写AI 辅助 ↓ ② 前后端对齐接口导出为 OpenAPI YAML ↓ ③ 前端用 OpenAPI Mock Server 独立开发 后端用 Markdown Spec 驱动 AI 生成代码 ↓ ④ DBML → ER 图给产品/架构 review Flyway 脚本给 DBA review ↓ ⑤ 联调 验收5.4 AI 辅助工具链除了 Spec 格式标准还有一些 AI 原生工具专门为 Spec Coding 场景设计Cursor 的 引用在 Cursor 里直接spec.md引用规格文件AI 会把文件内容注入上下文。这是目前最简单高效的“把 Spec 喂给 AI”的方式。spec/user-address.md 按照 spec 实现 Service 层 参考 src/service/UserService.java 的代码风格Claude Projects可以把 spec 文件上传到 Claude Project作为持久上下文。适合长时间在同一个项目上工作的场景——不用每次都粘贴 spec。GitHub Copilot WorkspaceGitHub 推出的 AI 工作流工具支持从 issue 描述到代码的一键生成。目前还在 beta 阶段。5.5现阶段的实用建议我不建议一上来就追求工具链的“完整性”。工具是为效率服务的过度配置反而降低效率。从这里开始就够了项目根目录建specs/文件夹每个功能一个specs/功能名.md用 Cursor 的引用 spec 文件Spec 和代码一起提交 git等项目规模变大了再考虑引入 OpenAPI、DBML 这类工具。my-project/ ├── specs/ │ ├── user-address.md │ ├── order.md │ └── payment.md ├── src/ │ └── ... └── db/ └── migrations/ ├── V1__create_users.sql └── V2__create_orders.sql从手写 Markdown Spec 起步到按需引入 OpenAPI、DBML、Flyway再到利用 AI 工具高效协作——Spec Coding 的工具链始终围绕一个核心用最合适的成本把需求精准地翻译给 AI 和团队。