最近在看“从 Vibe Coding 到 Spec Coding”这组资料时,我最有共鸣的一句话是:
Code is a lossy projection of intent.代码是意图的有损投影。
这句话的价值在于,它把很多团队正在经历的问题说透了:我们不是不会“写代码”,而是经常没有把“为什么这样写”变成可验证、可协作、可演进的规范。
本文默认你使用 Node.js 20 + TypeScript + Vitest + Claude Code,目标不是做一个 Demo,而是把 AI 辅助开发拉到生产级。
问题分析:Vibe Coding 为什么总在后期失速
Vibe Coding 的优势是快,尤其适合探索期。但它在生产阶段常见 4 个问题:
-
- 意图丢失:Prompt 只保留“怎么做”,很难沉淀“为什么这样做”。
-
- 结果漂移:多人协作时,每个人的 Prompt 风格不同,输出标准不一致。
-
- 验证滞后:先写代码再补测试,返工成本高。
-
- 经验不可复用:一次性对话很难迁移到下一个项目。
所以关键不是否定 Vibe,而是给它一个更稳定的“骨架”——Spec Coding。
解决方案:Spec Coding 的三层规范模型
把规范当成 source of truth,代码当成实现产物。推荐用三层结构:
-
- 功能规范(What) 定义用户故事、验收标准、边界条件。
-
- 架构规范(How - 语言无关) 定义数据模型、API 契约、安全约束、性能目标。
-
- 实现规范(How - 语言相关) 定义技术栈版本、测试框架、编码约束、发布要求。
在 Claude Code 中,对应关系通常是:
- •
CLAUDE.md:项目级总规范 - •
.claude/rules/:分层规则 - •
specs/*.md:功能级规范 - •
/plan:把规范拆成可执行任务
实战示例:用规范驱动通知系统(可运行)
下面给一套最小可运行样例,体现“先规范,后实现,再验证”。
步骤 1:先写 specs/notification.md
# 用户通知系统规范## 功能需求1. 支持站内通知、邮件通知、推送通知2. 通知类型:系统公告、订单状态、促销活动、安全提醒3. 支持已读/未读、批量已读4. 用户可按渠道与类型配置偏好## 数据模型- notifications: id, userId, type, channel, title, content, isRead, createdAt- preferences: userId, type, channel, enabled## 验收标准- 未读数在标记已读后实时正确- 批量已读后用户未读数为 0- 偏好变更后立即生效## 非功能性要求- 所有输入参数必须校验- 错误要有明确错误码- 关键行为可测试
步骤 2:实现 src/notification-service.ts
export type NotificationType = 'system' | 'order' | 'promo' | 'security'export type Channel = 'in_app' | 'email' | 'push'export interface Notification { id: string userId: string type: NotificationType channel: Channel title: string content: string isRead: boolean createdAt: Date}interface PreferenceKey { userId: string type: NotificationType channel: Channel}export class NotificationService { private notifications: Notification[] = [] private preferences = new Map() constructor(seed: Notification[] = []) { this.notifications = [...seed] } // 统一生成偏好键,避免拼接逻辑分散 private prefKey(key: PreferenceKey): string { return `${key.userId}:${key.type}:${key.channel}` } // 更新通知偏好,默认 true 表示开启 updatePreference(input: PreferenceKey, enabled: boolean): void { this.preferences.set(this.prefKey(input), enabled) } // 判断某类通知渠道是否开启 isEnabled(input: PreferenceKey): boolean { const value = this.preferences.get(this.prefKey(input)) return value ?? true } // 获取用户通知,支持按已读状态过滤 list(userId: string, isRead?: boolean): Notification[] { return this.notifications.filter((n) => { if (n.userId !== userId) return false if (typeof isRead === 'boolean' && n.isRead !== isRead) return false return true }) } // 标记单条已读,不存在时抛错,方便上层返回 404 markAsRead(userId: string, notificationId: string): void { const target = this.notifications.find( (n) => n.userId === userId && n.id === notificationId ) if (!target) throw new Error('NOTIFICATION_NOT_FOUND') target.isRead = true } // 批量标记已读 markAllAsRead(userId: string): number { let affected = 0 for (const n of this.notifications) { if (n.userId === userId && !n.isRead) { n.isRead = true affected += 1 } } return affected } // 未读数用于前端角标展示 unreadCount(userId: string): number { return this.notifications.filter((n) => n.userId === userId && !n.isRead).length }}
步骤 3:验证 tests/notification-service.test.ts
import { describe, expect, it } from 'vitest'import { NotificationService } from '../src/notification-service'const seed = [ { id: 'n1', userId: 'u1', type: 'system', channel: 'in_app', title: '系统维护', content: '今晚 22:00 维护', isRead: false, createdAt: new Date('2026-04-09T10:00:00Z') }, { id: 'n2', userId: 'u1', type: 'promo', channel: 'email', title: '限时活动', content: '全场 8 折', isRead: false, createdAt: new Date('2026-04-09T11:00:00Z') }] as constdescribe('NotificationService', () => { it('markAsRead 后未读数应减少', () => { const service = new NotificationService([...seed]) expect(service.unreadCount('u1')).toBe(2) service.markAsRead('u1', 'n1') expect(service.unreadCount('u1')).toBe(1) }) it('markAllAsRead 后未读应为 0', () => { const service = new NotificationService([...seed]) const affected = service.markAllAsRead('u1') expect(affected).toBe(2) expect(service.unreadCount('u1')).toBe(0) }) it('偏好关闭后 isEnabled 应返回 false', () => { const service = new NotificationService([...seed]) service.updatePreference( { userId: 'u1', type: 'promo', channel: 'email' }, false ) expect( service.isEnabled({ userId: 'u1', type: 'promo', channel: 'email' }) ).toBe(false) })})
运行方式:
npm i -D typescript vitest tsxnpx vitest run
在 Claude Code 中的推荐指令
@specs/notification.md请按规范实现通知系统,严格分 4 步执行:1) 先输出 plan;2) 按任务逐步实现;3) 每步都运行测试;4) 完成后做一次 code review 并列出风险项。
这条指令的重点是:把“生成代码”变成“执行规范 + 验证规范”。
最佳实践与常见陷阱
最佳实践:
-
- 规范与代码同仓库同版本管理,PR 同时审查。
-
- 每个规范都带验收标准和非功能性要求。
-
- 规范变更必须触发自动化测试,防止实现漂移。
常见陷阱:
-
- 规范只写 happy path,漏掉错误处理与边界条件。
-
- 把“实现细节”误写成“需求事实”,导致规范过早僵化。
-
- 只让 AI 生成,不做验证,把草稿当成成品。
渐进迁移:从 Vibe 到 Spec 的可执行路径
-
- 第 1 阶段(探索)
用
Vibe Coding在 30 分钟内验证想法是否成立。
- 第 1 阶段(探索)
用
-
- 第 2 阶段(沉淀)
把有效结论抽成
specs/*.md,补齐验收与约束。
- 第 2 阶段(沉淀)
把有效结论抽成
-
- 第 3 阶段(重建) 基于规范重做生产实现,并强制测试与审查。
这个路径的核心是:速度来自 Vibe,质量来自 Spec。
总结
Spec Coding 不是替代 Vibe Coding,而是把 AI 编程从“会写”推进到“可交付”。
当你开始认真维护 CLAUDE.md、rules、specs,你已经在做工程化的 AI 开发。
一句话收尾: 规范不是文档附属品,规范本身就是代码。
如果你也在把 AI 从“会写”推进到“可交付”,欢迎留言说说你团队现在最卡的是规范、测试,还是协作流程。
参考资料
- • 从 Vibe Coding 到 Spec Coding:AI 编程的进化之路
2026.04.09 17:26 沪 · 赵巷KFC
📌 声明:本文由 AI 辅助完成