Vibe-Coding时代-AI写代码越快为什么项目越容易腐化
Vibe Coding 时代,代码写得越来越快,为什么项目反而更容易烂掉?
这段时间我一直在用 AI 写项目,也一直在看别人怎么用 Claude Code、Codex、omp这类 Coding Agent。用得越多,一个感觉越明显,现在写出能跑的代码已经没那么难了,难的是保证这些代码以后还能继续改。
以前开发一个功能,一天可能只推进几百行代码。你自己写、自己调、自己看,代码增长的速度和你理解它的速度大致还能对得上。
现在完全不是这回事了,一个 Agent 跑几十分钟,可以改几十个文件,顺手补类型、加状态、抽公共层、写测试,再把 UI 也一起改了。表面上效率高得离谱,但代码生成速度已经开始超过人的 review 速度了。你每天打开 PR 一看,改动量已经远远超出你能逐行读完的范围,而这些改动里到底埋了什么,只有下一次出 bug 时你才知道。
这也是我最近看到几条讨论后感触特别深的地方。其中一条讨论把 AI 写代码大概分成两种场景。side project、个人玩具,很多时候确实可以 Vibe Coding,需求大概说清楚,让 AI 先写,跑起来之后不断反馈修改,最后结果对了就行。但生产级项目完全是另一回事。一个功能"现在能跑",只能说明 happy path 没炸。真实系统里还有一大堆东西藏在后面。
- 用户连续点击两次会怎样?
- 请求成功了,但本地状态更新失败怎么办?
- cancel 之后,已经排队的任务还会不会继续执行?
- 程序重启以后,旧状态能不能正确恢复?
- 两个请求同时修改同一份数据会发生什么?
- 外部 API 超时、重试、重复回调时会不会产生重复副作用?
- 一次看似合理的公共抽象,会不会把两个未来变化方向完全不同的业务绑死?
- 这段代码半年以后另外一个人还能不能看懂、敢不敢改?
金融、支付、权限、数据迁移、长任务、agent runtime 这类系统尤其明显。最终结果正确,远远不够。
AI 时代最危险的代码,很多时候看起来还挺漂亮
以前我会比较担心 AI 写出明显很烂的代码,比如一个几百行的大函数、变量乱命名、到处复制粘贴。现在我反而觉得,这类问题还算好发现,至少一眼能看出来不对。有一种代码更让人头疼,局部看非常合理,甚至整洁得让人舒服,但整体方向错了。
举个很常见的情况。AI 看到两个模块里有几段相似逻辑。
A:创建任务 → 调度 → 更新状态
B:创建任务 → 调度 → 更新状态
它很容易得出一个结论,这里存在重复逻辑,应该抽象一个统一调度器。于是 UniversalTaskManager 出来了。刚写完的时候非常整洁,Code Review 也容易觉得“这抽象不错”。
问题在半年以后才慢慢显现。A 开始需要暂停、恢复、优先级,B 开始需要幂等、批处理、失败补偿。两个业务只是今天长得像,它们未来变化的原因完全不同。原本两段十几行的重复代码,现在变成了一个谁都不敢动的共享核心,每次改 A 的逻辑都怕把 B 炸掉,每次改 B 都要先去确认 A 的流程还能不能走通。
AI 特别容易做这种"看起来高级"的错误抽象。它很擅长识别表面模式,你给它两段结构相似的代码,它几乎一定会尝试合并。但它不天然知道你这个业务未来会沿什么方向变化,哪些模块会分道扬镳,哪些耦合迟早要拆。这也是 Vibe Coding 项目慢慢腐化的原因之一,代码写得丑还在其次,要命的是结构绑得太早。
另一个很容易漏掉的东西,操作顺序
还有一个评论让我印象很深,它讲的是时序问题。普通验收清单很容易检查。
订阅成功 ✅
取消成功 ✅
重新订阅成功 ✅
三个功能单独看全部正常。但 Bug 可能藏在这个顺序里。
订阅
↓
取消
↓
旧订阅的异步清理还没有结束
↓
立刻重新订阅
↓
旧清理任务把新的订阅一起删掉
单个功能都没错,组合起来炸了。这种问题在生产系统里非常常见,因为真实 Bug 大量出现在状态迁移之间,而 Coding Agent 最擅长检查的恰恰是“某个函数有没有问题”和“最终状态是不是正确”这两件事。
再看一种场景。一个任务走过这样的状态。
Running
→ Cancelling
→ Cancelled
这里应该追问的是,Cancelled 之后,还有没有任何路径能重新产生副作用。
再看一种更复杂的情况。
Running
→ App Crash
→ Restart
→ Recovering
→ Running
这时候除了确认“恢复功能能不能跑”,还要看同一个任务会不会被执行两次、旧进程残留的 tool call 会不会继续回来写状态、checkpoint 和真实副作用之间会不会出现不一致、用户点击 Cancel 和程序恢复同时发生时谁赢。
这些问题每一个都可以单独造成线上事故,但你靠一句“请充分考虑 edge cases”很难让 AI 稳定地把它们全想到。
我后来开始觉得,AI 写代码前应该多一个阶段
以前很常见的工作流是这样的。
需求
↓
让 Agent 写代码
↓
测试
↓
Code Review
↓
继续改
这套流程的麻烦在于,很多重要的决定发生得太晚了。代码都已经生成几千行以后,你再发现状态所有权不对、模块边界不对、数据模型不支持恢复、Cancel 的定义从来没有说清楚、验收只覆盖了最终结果没有覆盖执行过程。
到这个时候再让 AI 重构,通常只会越改越乱,因为它对着已有代码做局部修复,但底层结构的问题还在。
我现在更认可这样的顺序。
需求
↓
读当前仓库
↓
研究类似成熟项目踩过的坑
↓
确定最小可信架构
↓
状态 / 不变量
↓
Edge Cases / 时序
↓
验收契约
↓
再开始实现
这里面有两个概念我个人觉得极其重要!
第一个是最小可信架构。这个词是我们做这套流程时自己定义的,软件工程里还没有这种正式标准。意思很简单,只引入当前需求确实需要的结构,但这些结构必须足够支撑状态管理、失败恢复、依赖边界、维护和验证。它不会因为"生产级"三个字就默认给你安排 EventBus、Repository、Factory、Manager、Queue、Worker、Domain Layer 全家桶,也不会因为"保持简单"就把所有逻辑堆进一个文件。
第二个是最小可信改动,主要针对已有项目。如果现有架构虽然没那么漂亮,但已经稳定跑了很久,新功能完全可以沿着现有边界正确实现,那就先别重构。AI 很喜欢顺手"优化架构",但我见过不少生产事故就是从这种没有必要的大改开始的。架构没出问题时不要去动它,等到它真的挡住了新功能再说。
深度参考 GitHub,比让模型凭空想 Edge Case 有用得多
还有一条做法我现在非常认同。做复杂功能时,让 AI 深度参考 GitHub 上类似的成熟项目。但这里的「参考」不能止于读 README 然后抄架构。README 只能告诉你这个项目现在长什么样,对理解工程决策帮助有限。
在另一些地方能找到项目以前到底在哪里栽过跟头。
Tests
Issues
PR
Release Notes
历史 Regression
源码能告诉 AI「他们怎么实现」,Issue 和 Regression Test 会告诉 AI 为什么最后不得不这样实现。一个 Commit Message 里写着「fix: prevent stale callback from overwriting new state」,背后可能是一次线上事故。这种信息比 AI 自己凭空想出来的 Edge Case 有用得多,因为它代表的是一个团队已经付过代价的教训。
比如一个成熟项目可能专门补过这些问题。重启恢复时重复执行、stale state 覆盖新状态、Cancel 后还有异步任务继续写入、UI 在大量数据下滚动后关键按钮消失、cwd 改变以后配置仍然沿用旧目录、retry 导致重复 Side Effect。每一条背后都对应着一次真实的故障,而你自己的项目如果做的是类似的事情,大概率也会踩同样的坑。
与其让模型坐在那里凭空列 30 个 Edge Case,我更愿意让它先去看看别人到底踩过什么。
当然,参考不能变成复制。一个成熟项目用了 Redis、EventBus、Worker,并不代表你的项目也需要。每个借鉴都应该多问一句,它在那里解决的具体问题,我们现在是不是真的存在。如果答案不够明确,就不要引入。
验收清单也得换一种写法
很多 AI 生成的验收清单其实没什么用,类似“测试正常流程”“测试异常处理”“验证状态正确”“检查用户体验”这种。看起来什么都说了,但你拿着它去执行的时候会发现,根本不知道该从哪一步开始,也不知道做到什么程度算通过。
我更喜欢一种"人体工学"的验收方式。这个说法来自前面那串讨论,我的理解是,验收清单应该真的适合人或者 Agent 一条条执行。 每一条都要有明确的前置条件、操作步骤、预期结果和失败信号。
比如任务取消这个场景。
AC-007:取消后禁止继续产生副作用
前置条件
- 当前任务处于 Running
- 已完成一个 Tool Call
- 下一个 Tool Call 已进入等待队列
操作
1. 点击 Cancel
2. 等待 UI 显示 Cancelled
3. 等待一段时间
4. 重启应用
5. 重新打开该任务
预期
- Cancelled 后不再出现新的 tool-start
- 已完成的历史记录仍然存在
- 重启后任务仍是 Cancelled
- 旧执行不会重新恢复
证据
- UI 状态
- Event Log
- 持久化状态
失败信号
- Cancelled 后仍出现新的 Tool Call
- 重启后重新变成 Running
这种验收清单有一个很大的好处,实现 Agent 不需要重新猜“完成到底是什么意思”,做 Review 的人也不用从几千行代码里反推设计意图。“这个验收条目过了没有”是一个可以直接回答的问题。
所以我最后做了一个 Skill,vibe-preflight
前面这些问题想多了以后,我和 ChatGPT 一起把它整理成了一个 Agent Skill。
vibe-preflight
GitHub 地址在这里。
https://github.com/iZiTTMarvin/vibe-preflight
它的定位很明确,在 Claude Code、Codex 这类 Coding Agent 开始写代码之前,先做一次工程预检。它不负责实现任何功能,只负责把“什么叫做正确地完成这件事”说清楚。主要流程大概是这样的。
判断这次值不值得 Preflight
↓
读取现有仓库
↓
按风险决定是否做 GitHub 考古
↓
最小可信架构 / 最小可信改动
↓
状态模型 + 不变量
↓
对抗式找 Edge Cases
↓
人体工学验收清单
↓
REQ / INV / RISK / AC 可追踪
↓
READY FOR IMPLEMENTATION
↓
停止
做这个 Skill 的时候我特别克制了一件事,没有继续把它膨胀成“万能开发 Skill”。流程太重本身也会把项目搞坏,改一个 CSS、修一个非常明确的小 Bug,根本没必要做完整架构设计。所以现在它自己会先判断任务复杂度,简单的直接输出 PREFLIGHT NOT NEEDED,复杂项目才逐渐增加深度。
还有一个我很在意的规则,Preflight 阶段绝对不能偷偷开始实现。它最后只能停在 READY FOR IMPLEMENTATION 或者 BLOCKED。如果是 READY,我更建议直接开一个新的 Claude Code / Codex 会话,把生成的 Blueprint 交给实现 Agent。实现阶段只做实现,预检阶段只负责画边界。两个阶段的上下文分开,互不干扰,也更干净。
它和 grill-with-docs 有什么区别
grill-with-docs 给了我很多启发。它很擅长持续追问,把模糊的产品语言、术语和关键决策问清楚,帮你在动手之前先把"到底要做什么"理顺。
但我自己想解决的问题不太一样,我更关心的是工程层面的风险。AI 开始写生产级代码以前,到底应该把哪些坑先排掉。所以 vibe-preflight 更关注当前仓库的真实结构、GitHub 上类似项目踩过的坑、最小可信架构、状态所有权、不变量、操作顺序和时序空窗、错误抽象、可执行验收、从需求到验收的可追踪关系,以及实现阶段什么时候必须停下来重新做 Preflight。
我不敢说用了它就不会产生屎山。代码质量最终还是取决于人、模型、项目本身和后续 Code Review 的质量。但我越来越确定一件事,想让项目长期可维护,光研究"怎么让 Agent 写得更快"是不够的,还得在它开始写之前,把正确的边界画出来。
Vibe Coding 已经把“写代码”这一步压缩得非常便宜了。接下来拉开项目质量差距的,大概是架构判断、边界意识和验证能力,以及开发者能不能控制住 AI 那种"看到机会就继续写"的冲动。
这也是我做 vibe-preflight 最主要的原因。
更多推荐



所有评论(0)