
一、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}/status -
status 字段从 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 发给 AI:
@spec.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 Coding | Spec 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 不了解你的业务上下文,它会用"常见业务逻辑"来填充空白。规则没写进 spec,AI 就自己猜,猜错了你还要回来改。
验收标准(GIVEN-WHEN-THEN)
这是整份 spec 最值钱的部分。写完之后可以直接让 AI 生成测试用例,也可以用来验证生成的代码是否正确。
2.2好 Spec vs 烂 Spec
2.2.1烂 Spec(典型的 AI 会乱搞)
## 用户注册功能 Spec
实现用户注册,包括:
- 收集用户信息
- 验证信息
- 保存到数据库
- 发送欢迎邮件
这份 spec 的问题:
-
"收集用户信息"------哪些字段?必填吗?
-
"验证信息"------什么验证规则?手机号格式?密码强度?
-
"保存到数据库"------哪张表?有没有唯一约束?
-
"发送欢迎邮件"------同步还是异步?失败了怎么办?
AI 面对这份 spec 只能自己猜。猜出来的结果和你预期的可能差 50%。
2.2.2好 Spec(AI 能精确执行)
## 用户注册功能 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 格式非法:400,message: "手机号格式不正确"
- password 不符合强度要求:400,message: "密码需包含大小写字母和数字,8-20位"
- phone 已注册:409,message: "该手机号已注册"
- nickname 超过20字符:400,message: "昵称不能超过20个字符"
## 验收标准
### 场景一:正常注册
GIVEN 手机号 13800138000 未注册
WHEN POST /api/auth/register { phone, password, nickname }
THEN 返回 201,userId 不为空,phone 脱敏,users 表新增一条记录
### 场景二:手机号重复
GIVEN 手机号 13800138000 已注册
WHEN POST /api/auth/register { 同一手机号 }
THEN 返回 409,不创建新记录
### 场景三:密码不符合要求
GIVEN 任意手机号
WHEN POST /api/auth/register { password: "123456" }
THEN 返回 400,message 说明密码强度要求
## 技术约束
- 不引入新的依赖
- 密码强度校验用正则,不用第三方库
- 不发送短信(暂不需要验证码)
两份 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)
↓
Spec
3.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 文档。
要求:
提取所有接口(方法+路径+参数+响应格式+错误码)
提取业务规则,每条一句话
提取边界条件和异常处理
写出 GIVEN-WHEN-THEN 验收场景(至少覆盖正常流程和主要异常)
标注 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:商品不存在
- 409:ONLINE 状态的商品不允许直接 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 'ONLINE'(ONLINE/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 的商品,除非 status=DELETED 明确传入
5. 库存可以为 0(允许缺货状态)
---
## 边界条件
- price = 0:400,price 必须大于 0
- stock < 0:400,stock 不能为负数
- name 超过 50 字:400
- imageUrl 非 URL 格式:400
- ONLINE 商品尝试删除:409,"请先下架商品再删除"
---
## 验收标准
### 场景一:正常新增并上架
GIVEN 数据库中不存在同名同分类商品
WHEN POST /api/admin/products { name, category, price, stock }
THEN 返回 201,商品 status=ONLINE,数据库新增记录
### 场景二:重复商品
GIVEN 已存在 name="iPhone 16", category="ELECTRONICS" 的商品
WHEN POST /api/admin/products { 相同 name 和 category }
THEN 返回 409
### 场景三:ONLINE 商品直接删除
GIVEN 商品 id=1,status=ONLINE
WHEN PUT /api/admin/products/1/status { "status": "DELETED" }
THEN 返回 409,数据库状态不变
### 场景四:下架后删除(正常流程)
GIVEN 商品 id=1,status=ONLINE
WHEN PUT .../status { "status": "OFFLINE" },再 PUT .../status { "status": "DELETED" }
THEN 两次均返回 200,最终状态为 DELETED
### 场景五:列表不返回已删除
GIVEN 存在 status=DELETED 的商品
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: false
OpenAPI 的优势:
-
有配套的 Swagger UI,可以直接在浏览器里查看和测试接口
-
有代码生成工具(OpenAPI Generator),可以自动生成 Controller 骨架、客户端 SDK
-
前后端可以先对齐接口 spec,再同步开发(前端用 mock server,后端实现真实逻辑)
局限:
-
格式严格,写起来比 Markdown 啰嗦得多
-
只能描述接口,描述不了业务规则、状态机、数据模型
-
对 AI 生成代码的指导效果和 Markdown Spec 差不多,但写作成本更高
我的建议:
-
团队项目、需要前后端对齐接口的场景:用 OpenAPI
-
个人项目或快速迭代:用 Markdown Spec,更快
5.2 Database Schema 工具
数据模型的标准化工具主要有:
DBML(Database Markup Language)
dbml.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"的工具。典型用法:
-
写好数据模型 Spec(Markdown 或 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 Workspace
GitHub 推出的 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 和团队。