AI 写代码为什么总「跑偏」?从 Spec Kit 到 CLAUDE.md 的规范驱动开发实践
前言
在实际使用 Claude Code 等 AI 编程工具的过程中,开发者普遍遇到一个痛点:描述了功能需求,代码能跑,但完全不符合项目的实际约束。本文从问题根源出发,结合 Spec Kit 方法论和 Claude Code 原生的 CLAUDE.md 机制,系统介绍规范驱动开发的思路与落地方式。
一、AI 编程的「需求漂移」从何而来
先看几个典型场景:
- 你说「做一个用户登录功能」,它给你写了 JWT + bcrypt 的完整方案,但项目用的是 session-based auth。
- 你说「优化一下这个查询」,它直接重构了整个数据层。
- 你说「加个分页」,它顺手把 REST API 改成了 GraphQL。
这不是 Claude Code 的 bug,而是大模型的结构性工作方式决定的。
1.1 根本原因:语义缺口的默认填充
大模型生成代码时,依赖的是训练数据里的「概率最优解」。prompt 越模糊,模型越倾向于输出统计意义上最常见的实现方式。
链路如下:
模糊的 prompt
→ 模型填补语义缺口
→ 用通用最佳实践替代未说清的约束
→ 输出结果偏离实际项目约束
具体来看这个例子:
❌ 模糊 prompt:
"帮我写一个文件上传功能"
✅ AI 的「合理」推断:
- 用 multer 处理 multipart/form-data
- 文件存到 /uploads 目录
- 返回文件 URL
- 加上文件大小限制
❌ 但项目实际约束:
- 已有 AWS S3 封装好的 upload util
- 文件路径有特定格式规则
- 需要与现有 audit log 系统集成
- 有自己的 error handling 约定
四个约束,一个没出现在 prompt 里,AI 全部用默认方案替代。代码能跑,但接入系统需要大量改造。
1.2 为什么多轮纠正不解决问题
很多开发者的第一反应是「多轮对话逐步纠正」。这条路走得通,但有两个隐藏陷阱:
- 上下文消耗:每次纠正都在消耗上下文窗口,后续任务不保证记住之前确立的约束。
- 会话隔离:新开一个对话,之前所有纠正全部清零,需要重新建立 context。
在 vibe coding 场景下问题更突出:跟着 AI 节奏走,一边生成一边微调,等回过头来,代码架构已经和最初设想相差甚远。
二、Spec Kit:把约束写成文档
GitHub Spec Kit (11 万+ Stars)解决的正是这个问题。核心思路很直接:与其每次重新解释约束,不如把约束写成文档,让 AI 每次工作前先读它。
Spec Kit 的核心是一个 spec.md 文件,示例结构如下:
# 项目规范
## 技术栈约束
- Runtime: Node.js 18, TypeScript 5.x
- 数据库: PostgreSQL via Prisma ORM(禁止直接写 SQL)
- 认证: 使用 /lib/auth 的 session 封装,不引入新的 JWT 库
- 文件存储: 统一使用 /lib/storage 的 S3 封装
## 代码风格
- 错误处理: 统一 throw AppError 类,不使用 console.error
- API 响应格式: { data, error, meta } 结构
- 文件命名: kebab-case,组件用 PascalCase
## 禁止事项
- 不修改 /lib 目录下的基础设施代码
- 不引入 spec 里未列出的新依赖
- 不修改数据库 schema,只读操作
## 当前任务
[在这里描述具体任务]
AI 读完这份规范再开始写代码,语义缺口被规范填充,漂移空间大幅压缩。
三、CLAUDE.md:Claude Code 的原生规范方案
如果使用的是 Claude Code,有一个更原生的方式:CLAUDE.md 配置文件。这是 Claude Code 使用技巧里最容易被忽视的功能之一。
3.1 工作机制
在项目根目录创建 CLAUDE.md,Claude Code 会自动读取它作为每次会话的系统上下文,无需在每次对话里手动粘贴约束说明。
3.2 CLAUDE.md 模板
# CLAUDE.md
## 项目背景
这是一个 SaaS 产品的后端服务,使用 Express + TypeScript + PostgreSQL。
## 核心约束
1. 所有数据库操作通过 `src/db/` 里的 repository 层,不直接调用 Prisma
2. 认证逻辑在 `src/middleware/auth.ts`,不要重复实现
3. 日志统一用 `src/lib/logger`,基于 pino
## 代码生成规则
- 新增 API endpoint 必须在 `src/routes/` 对应文件里注册
- 数据验证用 zod,schema 放在 `src/schemas/`
- 错误处理继承 `AppError`,定义在 `src/errors/`
## 测试约定
- 单元测试用 vitest
- 测试文件和源文件同目录,命名 `*.test.ts`
## 当前上下文
Sprint 目标:完成用户权限模块,本周不做性能优化
3.3 团队协作价值
将 CLAUDE.md 提交到 Git 仓库,团队里所有人使用 Claude Code 时都会自动加载这份上下文。这相当于把 AI 的 onboarding 文档版本化管理,新成员和 AI 都能从同一份规范出发。
四、两种方案的核心逻辑
Spec Kit 和 CLAUDE.md 解决的是同一个问题:让 AI 在你的约束空间里工作,而不是在统计意义上的最优解空间里工作。
| 维度 | 传统 prompt 工程 | 规范驱动开发 |
|---|---|---|
| 约束表达 | 散落在每次 prompt 里 | 集中在规范文档里 |
| 会话持久性 | 新对话重置 | 通过文件持久化 |
| 团队一致性 | 依赖个人习惯 | 版本化、可共享 |
| 维护成本 | 每次重复描述 | 一次写入,持续生效 |
| 漂移风险 | 高 | 低 |
用一个类比来理解:你不会跟新入职的工程师说「写个登录功能」然后等交付。你会先给他项目文档、代码规范、架构说明,再分配具体任务。AI 需要同样的 onboarding,只是它每次「入职」都要重新来一遍——除非你把这个文档固化下来。
五、实操:5 分钟搭起规范体系
Step 1:列出「AI 最容易搞错的地方」
通常包括以下几类:
- 自定义工具库和封装(已有的 util、helper)
- 特殊的目录结构约定
- 团队代码风格要求(命名、注释、错误处理)
- 不能动的遗留代码区域
- 禁止引入的依赖或技术栈
Step 2:创建 CLAUDE.md
touch CLAUDE.md
按模板填充,重点放在两类内容:
- 禁止做什么:明确列出不允许的操作,比「请遵循最佳实践」更有效。
- 必须用哪些现有封装:列出路径,避免 AI 重新实现已有功能。
Step 3:迭代完善
每次 Claude Code 跑偏,问自己:这条约束,CLAUDE.md 里有没有写? 没有就补进去。一两个 sprint 之后,你会有一份相当完善的规范文件。
进阶用法:任务级 spec 文件
对于复杂功能模块,可以在 CLAUDE.md 基础上再加一个任务级的 spec 文件:
# 目录结构建议
.claude/
specs/
payment-module.md
user-permissions.md
notification-system.md
在 Claude Code 对话里显式引用:
"请先读 .claude/specs/payment-module.md,然后实现支付回调处理"
这种方式特别适合需要多次对话才能完成的大型功能模块,每次对话都从同一份 spec 出发,保证实现方向一致。
六、常见问题
Q:CLAUDE.md 写多长合适?
没有固定标准,但有个原则:只写「AI 容易用错误方式实现的约束」,不要把所有项目文档都塞进去。过长的 CLAUDE.md 会稀释关键约束的权重。建议控制在 100 行以内,核心约束用列表格式清晰表达。
Q:CLAUDE.md 和 prompt 里的约束冲突怎么办?
CLAUDE.md 作为系统上下文加载,优先级高于单次 prompt。如果某个任务需要临时突破某条约束,在 prompt 里明确说明「本次任务例外」即可。
Q:非 Claude Code 工具也能用这套方法吗?
可以。Spec Kit 的 spec.md 方案与工具无关,手动在对话开始时粘贴规范内容同样有效。CLAUDE.md 是 Claude Code 的原生机制,其他工具需要查看各自的系统 prompt 或上下文配置方式。
总结
AI 编程的需求漂移本质上是信息缺口问题,不是 AI 不够聪明。把隐式约束显式化,让 AI 在有边界的空间里工作,漂移问题基本可以得到解决。
核心建议:
- 把 CLAUDE.md 列为每个新项目 checklist 的第一项
- 重点写「禁止事项」和「必须使用的现有封装」
- 每次跑偏后反手补充到规范文档里,形成迭代闭环
- 复杂模块使用任务级 spec 文件进一步约束
前期 20 分钟建立规范体系,节省的是后期几十次纠错往返的时间成本。
参考资源
- GitHub Spec Kit
- Claude Code 官方文档
- Claude Code CLAUDE.md 配置说明
如果需要使用 Claude Code,可以私我。也欢迎有需求的企业进行深度合作。
更多推荐
所有评论(0)