本文适合:已经在用 AI 辅助编程,但发现效果不稳定、经常翻车的开发者
我在上一篇文章里聊了 AI 编程的五个阶段,有读者在评论区问:
"Vibe Coding 我也试过,前几天用得挺顺的,但项目一大就开始出问题,改一个地方坏一个地方,到最后整个项目我自己都不认识了。这是为什么?"
这个问题问到点子上了。
Vibe Coding 翻车,不是因为你不会用 AI,也不是因为模型不够强——是因为有四个根本性的技术原因,大多数人不了解这些原因,所以踩了坑也不知道为什么。
这篇文章不是在否定 Vibe Coding,而是帮你建立一个更准确的心智模型,知道它在哪里会出问题,以及怎么避开这些坑。
先看现象:为什么项目越大越容易翻车
一个典型的翻车路径是这样的:
- 项目初期,用 Vibe Coding 飞快搭出原型,感觉很爽
- 项目规模扩大,开始有多个模块、多个文件、复杂的依赖关系
- 你让 AI 改一个功能,它改好了,但另一个地方悄悄坏了
- 你把报错贴给 AI,它修了这里,那里又坏了
- 循环几轮之后,整个代码库变成一坨谁也看不懂的"面条代码"
这不是偶然,这是 Vibe Coding 的系统性问题。原因有四个。
根本原因一:上下文窗口是有限的,AI 看不到整个代码库
LLM 有一个核心限制:它只能"看到"当前对话里你给它的内容。
每一次对话,AI 的视野范围就是你的输入——你粘贴的代码、你描述的需求、对话历史。这个范围有上限,叫做上下文窗口(Context Window)。
Context Engineering 全貌
▲ LLM 的上下文窗口就是它的"工作台"——所有能影响输出的信息,必须在这个有限的空间里。(图源:Weaviate)
现代模型的上下文窗口已经很大了,Claude 是 200K token,Gemini 可以到百万 token。但问题是:大多数人用 Vibe Coding 的方式,根本没有系统地把项目上下文喂给 AI。
具体会发生什么?
你的项目 AI 看到的视野
──────────────── ─────────────────────
user.service.ts ───► user.service.ts(你粘贴的)
order.service.ts (看不到)
auth.middleware.ts (看不到)
database.config.ts (看不到)
types/index.ts (看不到)
你让 AI 修改 user.service.ts,它只看到这一个文件。它不知道 order.service.ts 里有个函数依赖 user.service.ts 的某个返回值格式,也不知道 auth.middleware.ts 有个类型断言依赖用户对象的结构。
于是 AI 修改了 user.service.ts,在它的视野里是完全正确的。但它不知道自己破坏了其他地方,因为它根本看不到其他地方。
这就是为什么改一个地方会坏另一个地方——不是 AI 在乱改,是 AI 在"盲操作"。
解法
使用 CLAUDE.md(Claude Code)或 .cursorrules(Cursor)把项目上下文强制注入到每次对话中:
# 项目结构
- user.service.ts:用户 CRUD,返回 UserDTO 类型
- order.service.ts:依赖 UserDTO 的 id 和 email 字段
- auth.middleware.ts:从 JWT 解析出 userId,注入 req.user
# 修改约定
- UserDTO 的字段结构不能随意改动,下游有依赖
- 所有 Service 层方法必须返回统一的 ApiResponse<T> 格式
有了这层说明,AI 就知道边界在哪里,不会随意破坏约定。
根本原因二:LLM 不维护状态,每次对话都从零开始
这个问题比很多人意识到的更严重。
LLM 本身是无状态的。每一次新对话,它对你的项目一无所知。它不记得上次你说"这个函数要兼容旧版 API",不记得你们约定了"所有异步函数必须处理错误",不记得你三天前做的架构决策。
一个真实的翻车案例:
某团队用 Vibe Coding 开发了一个支付回调接口。当时 AI 生成了功能代码,验证了主流程,大家测试通过了就上线了。两个月后线上出现回调失败问题,排查了三天,发现接口完全没有任何错误日志、请求埋点、异常告警。
原因:AI 生成代码时,没有人告诉它"这是生产系统,必须有完整的可观测性"。AI 只知道"实现支付回调验签逻辑",它把这个任务完成得很好,但它不知道"生产系统的标准"是什么。
AI 没有错,它完成了你给它的任务。问题是你给的任务描述里没有包含"隐性的工程标准"。
解法
把项目的工程标准写成文档,每次对话时附上:
# 工程规范(必读)
- 所有外部 API 调用必须有 try-catch,失败时 logger.error 记录完整请求参数
- 涉及支付/用户数据的接口,必须有请求级别的埋点(requestId, userId, timestamp)
- 不得使用 console.log,统一用 logger 模块
- 每个 Service 方法必须有对应的 unit test
把这些规范沉淀到 CLAUDE.md,让 AI 每次都"记得"你的工程标准。
根本原因三:没有测试,AI 的幻觉无从验证
这是 Vibe Coding 最隐蔽、也最致命的问题。
AI 生成的代码,看起来很像对的代码。变量命名合理,逻辑流程清晰,注释详尽。但"看起来对"和"真的对"之间,隔着很大一条鸿沟。
来看一个真实的例子:
// AI 生成的工作日计算函数
function getWorkingDays(startDate: Date, endDate: Date): number {
let count = 0;
const current = new Date(startDate);
while (current <= endDate) {
const dayOfWeek = current.getDay();
if (dayOfWeek !== 0 && dayOfWeek !== 6) { // 排除周末
count++;
}
current.setDate(current.getDate() + 1);
}
return count;
}
这个函数看起来没问题。但如果你去测试边界情况:
// 测试:startDate 和 endDate 是同一天,且是工作日
getWorkingDays(new Date('2026-05-11'), new Date('2026-05-11'))
// 期望:1,实际:1 ✓
// 测试:startDate 比 endDate 晚(参数传反了)
getWorkingDays(new Date('2026-05-15'), new Date('2026-05-11'))
// 期望:0 或 报错,实际:0 ✓(while 条件直接不满足)
// 测试:跨年,包含法定节假日
getWorkingDays(new Date('2025-12-31'), new Date('2026-01-02'))
// 期望:1(元旦是法定假日),实际:2 ✗
// AI 没有处理法定节假日!
这个 bug 在没有测试的情况下,可能在生产环境跑了很久才被发现——或者用在了薪资计算场景,直接产生了错误数据。
Context 的常见失败模式
▲ LLM 的四类失败模式:Context Poisoning(错误信息污染上下文)、Context Distraction(被无关信息分散注意力)、Context Confusion(指令混淆)、Context Clash(信息冲突)。没有测试,这四类问题都无法被及时发现。(图源:Weaviate)
解法:TDD + AI = 最高效的组合
不是让 AI 写代码然后你来写测试,而是反过来:先写测试,让 AI 写实现。
# 你先写测试文件,明确预期行为
你:帮我实现 getWorkingDays 函数,要求通过以下测试:
- 同一天(工作日)返回 1
- 两个工作日之间返回正确天数
- 跨越周末正确排除
- 包含法定节假日时抛出提示(节假日表由 holidays 参数传入)
# AI 写实现
AI:[生成代码]
# 你跑测试
$ npm test
# 如果失败,把失败信息贴给 AI 继续修
这个流程的好处:测试是你控制的,AI 的输出必须通过你写的测试才算完成。AI 的"幻觉"无论多自洽,测试不过就是不过。
根本原因四:提示质量决定输出质量
"垃圾进,垃圾出"——这句话对 AI 同样成立,甚至更加成立。
很多人 Vibe Coding 翻车,不是因为模型差,是因为提示写得太模糊。
来看一个对比:
模糊提示:
"帮我写一个用户登录接口"
AI 生成了一个登录接口,但:
- 密码是明文存储的
- 没有限速(可以暴力破解)
- Token 有效期是 7 天(你的需求是 2 小时)
- 没有记录登录失败日志
这些都是"登录接口"应该考虑的事,但你没说,AI 就按它认为合理的方式做了。
精确提示:
"帮我写一个用户登录接口,要求:
- 密码使用 bcrypt 哈希,salt rounds = 12
- 同一 IP 登录失败 5 次后锁定 15 分钟(用 Redis 计数)
- JWT Token 有效期 2 小时,不存 Cookie,放 Authorization Header
- 登录失败时 logger.warn 记录 IP、用户名、失败原因
- 参考项目里 registerUser 函数的错误处理风格"
同样的模型,同样的 Vibe Coding,输出质量天差地别。
提示质量的三个维度
| 维度 | 差的提示 | 好的提示 |
|---|---|---|
| 约束条件 | "写一个缓存" | "用 Redis,TTL 30分钟,Key 格式 user:{id}:profile" |
| 边界情况 | "处理错误" | "网络超时重试 3 次,重试间隔指数退避,最终失败返回 503" |
| 参考上下文 | 无 | "参考项目里 OrderService 的写法风格" |
一个实用技巧:在提示里加上"参考项目里 XXX 的写法",AI 会自动对齐你的代码风格,而不是生成一段风格迥异的代码插入到你的项目里。
四个原因的关系
把四个原因放在一起看:
┌─────────────────────────────────────────────────────┐
│ Vibe Coding 翻车地图 │
│ │
│ 原因一:上下文不足 ──► AI 不知道项目全貌 │
│ ↓ ↓ │
│ 原因二:无状态 ──► AI 不记得工程约定 │
│ ↓ ↓ │
│ 原因三:缺少测试 ──► 错误无法被发现 │
│ ↓ ↓ │
│ 原因四:提示模糊 ──► AI 用"合理猜测"填补空白 │
│ │
│ 四个问题叠加 ──► 项目越大,失控越严重 │
└─────────────────────────────────────────────────────┘
这四个原因不是独立的,它们会叠加放大。上下文不足让 AI 猜测,无状态让猜测无法被纠正,缺少测试让错误积累,模糊提示让一切更不确定。
解法对照表
| 根本原因 | 核心解法 | 工具/方法 |
|---|---|---|
| 上下文窗口有限 | 系统性注入项目上下文 | CLAUDE.md / .cursorrules |
| LLM 无状态 | 把工程规范文档化 | CLAUDE.md 的规范区块 |
| 缺少测试 | TDD:先写测试,让 AI 写实现 | Jest / Vitest + AI |
| 提示模糊 | 精确描述约束、边界、参考 | 结构化 prompt 模板 |
小结
Vibe Coding 翻车,根本原因不是"AI 不够智能",而是你和 AI 之间的信息不对等:
- AI 看不到你的整个代码库
- AI 不记得你的工程约定
- AI 生成的代码没有被验证
- AI 用"合理猜测"填补了你没说清楚的部分
理解了这四个原因,Vibe Coding 就从"玄学"变成了"可控的工程实践"。
下一篇,我会聊聊 Context Engineering——如何系统性地设计"喂给 AI 的信息",让它从一次次的盲操作,变成真正了解你项目的协作者。
作者:与 AI 结对编程
关注公众号「与 AI 结对编程」,持续更新 AI 工程实践。