1. 先说结论:Vibe Coding 乱在哪儿,Agent 解决不了什么
过去的半年里,"Vibe Coding"从一个带有玩笑性质的词,慢慢变成了很多团队真实的工作方式。所谓 Vibe Coding,就是用自然语言描述意图,让 AI Agent 自动生成整段代码、自动改文件、自动跑逻辑,开发者只负责"给感觉"和"验收结果"。听起来很美好,但我在实际项目里连续用了三周之后,发现一个很扎心的事实:Agent 擅长让代码"从无到有",却完全不擅长让代码"从乱到有序"。
我自己经历过一个很典型的项目。某次做一个跨平台的小工具,前期需求模糊,我图快,直接开着 Agent 让它按"聊天记录"里的需求逐条实现。前三天确实快,一个能跑的原型当天就出来了。但到第二周开始加新需求时,问题集中爆发:Agent 顺手改了以前能跑的公共函数,导致其他模块崩溃;它为了修复一个报错,又引入了两个新报错;最离谱的是,它把项目里那套隐式的错误码规范完全无视,重新造了一套。那段时间我的日常变成"发现新 bug → 让 Agent 修 → 引入了另一个 bug → 继续修",项目彻底进入乱序状态。
我把这种混乱统称为"Vibe 乱序"。它的特征不是代码风格差,而是整个项目的决策链没有主人。每一段代码都是 Agent 基于局部上下文临时生成的,缺少全局设计约束,于是模块之间互相渗透、接口不断漂移、修复行为互相打架。
所以我的结论很直接:如果你指望",再强的 Agent 也只会把乱序放大。真正的有序,必须来自开发者自己搭好的工程结构、验证规则和人机分工。这篇文章就围绕这三件事展开,把我踩过的坑和最终沉淀下来的工作流完整写出来。
1.1 一个真实翻车场景:三天新需求,一周修修补补
当时项目的结构其实不大,大概二十几个文件,一个前端界面、一个后端服务、一个配置文件目录。前两周用 Agent 推进得飞快,我还挺得意。到了第三周,产品侧提了一个"看起来很简单"的新需求:给数据导出的功能加一个筛选条件。
我只用了一句话描述:"在导出模块加筛选条件,按时间范围过滤。"
这个任务如果交给人来做,正常流程是:先看导出模块现有的参数入口,再看数据源接口支持哪些字段,最后确定 UI 层怎么传值。但 Agent 的做法完全不是这样。它先找到导出函数,加了一个filterTime参数,然后发现调用方没有传这个参数,就自动改了调用方;再发现数据源层的函数签名对不上,又自动改了数据源层;最后发现配置文件的格式不匹配,又动了配置文件。
单看每一步,Agent 都是在"解决问题",但合起来看,它把一条原本清晰的调用链改得七零八落。更麻烦的是,这三次"顺手修改"都改到了别的模块正在用的公共部分。导出模块是稳了,但统计模块、日志模块全部开始报错。我花了一个下午挨个修回来,然后发现直接从周末的节奏里挤掉了两天休息时间。
这不是 Agent 笨,而是它的工作模式天然如此。每次改动都是在"当前这个报错"的牵引下进行,它看不到项目里那条"谁调用谁、谁不允许改谁"的隐式约定。你给它一个模糊目标,它就会把这个目标当成唯一优先级,其他模块的稳定性在它眼里只是背景噪音。
1.2 我对"乱序"的判断:不是代码乱,是决策链乱
很多人以为 Vibe Coding 之后项目乱了,是因为代码写得乱,比如函数太长、变量命名差、注释不够。这些确实是问题,但不是根因。
我见过一些项目,代码整洁度很好,但依然只要改一个地方,就有一堆地方跟着崩。问题的核心在于决策链乱了:谁决定模块的对外接口?谁决定错误码的规范?谁决定某个工具函数能不能改?人的团队里,这些决策有评审、有记忆、有默认惯例;换成 Agent 工作流之后,决策变成了"每一次对话里谁赢了"——哪个 Agent 最后收到指令,哪个 Agent 就有权临时改接口。
所以你会发现,项目越往后,胡乱依赖越重,改起来越心惊胆战。这不是编码能力问题,是组织方式问题。代码的有序,本质上是一种约束的产物:约束接口不变、约束依赖方向、约束数据流。Agent 本身不会给自己设这种约束,只有你——项目的主人——能把约束显式地写进结构里。
2. 为什么 Agent 写得越好,项目越容易散
先别急着怪 Agent。工具没有好坏,只有"用对场景"和"用错场景"。把 Agent 用在探索性编程、一次性脚本、Demo 原型上,它是神器;用在长期演进、多人协作、接口密集的业务系统上,它就是一个高产的破坏者。要避免破坏,得先理解它的三个先天局限。
2.1 从 token 视角生成代码的先天局限
Agent 写代码的过程,本质上是在"预测最合理的下一段字符"。它眼中从来不是一个完整项目,而是一个个 token 序列。它能处理的上下文长度有上限——即便今天的上下文窗口已经很大,能覆盖几十万 token,但代码库的规模远超这个量级。
这意味着什么?Agent 每次做决策,参考的只是它能"看得到"的那部分代码。它看不到的模块,对它来说就是不存在的。于是它极容易做出局部最优的选择:在当前文件里加一个函数最快,就绝不想到要去另一个文件里复用一个已有函数;在当前函数里加一个参数最直接,就绝不想这个参数会让其他调用方全部失配。
我做过一个实验:让 Agent 在一个明确存在公共工具函数的项目里新增一个日期格式化逻辑。提示词里甚至没提工具函数,结果它在业务层里直接复制了一个formatDate的类似实现,只是细节不同。编译能过,测试能过,但后续排查问题的时候,任何人都会在"到底哪个formatDate是真正被使用的"这个问题上浪费大量时间。这就是 token 视角的天然盲区:它不知道重复是债,只知道独立实现最省事。
2.2 上下文熔断与"持续否定修复"的死循环
比"看不见"更麻烦的,是"看过就忘"。Agent 在对话里会丢失早期上下文,尤其是当对话轮次变多、修改文件次数变多之后。我自己遇到过最典型的情况:
第一次提出需求,Agent 实现了 A 方案。运行报错,我把报错贴给它。它改成 B 方案,跑通了。然后我又提了第二个需求,它开始改代码,结果把之前 A→B 的关键改动又改回 A 方案了,原因是它已经把上下文里的 A→B 过程当成"旧信息"丢掉了。我质问它为什么回退,它的回答是"根据当前代码状态,A 方案更匹配"。它没有撒谎,它只是真的不记得自己刚才做过什么。
这就是我所说的"上下文熔断":一旦超出了它的有效记忆范围,所有早期决策对它来说都不存在了。更可怕的是,它会为了驱除一个报错,在完全不知情的情况下推翻另一个模块的安全假设。每一次"修好了这里",都可能意味着"埋了别处的雷"。
依赖 Agent 修 bug 的团队,会陷入一种很典型的死循环:新 bug → 交给 Agent → Agent 在可见上下文范围内做局部修复 → 触发另一处隐藏问题 → 继续交给 Agent → 循环。这跟新手程序员"面向报错编程"一模一样,只不过 Agent 执行这个循环的速度更快、产量更大,破坏力也更大。
2.3 隐性架构契约:Agent 看不见,却一直在破坏
任何一个活过两个迭代周期以上的项目,都会积累一堆"代码里没写出来,但所有人都默认遵守"的约定。拿我最常遇到的几类来说:
- 某些模块之间只允许通过 Service 层互调,不允许直接改对方的内部状态;
- 某个公共方法的返回格式约定是
{code, data, msg},就算某些地方只需要data,也不许绕过规范直接取; - 某些配置项的命名规则、默认值语义,是当初开会定的,不是谁拍脑袋写的。
这些约定在代码评审里靠人肉记忆维护,在文档里可能只有零散记录。Agent 呢?它读代码时完全不知道有这些约定。它只知道"这个函数返回了对象,我取 data 就能用",于是它直接response.data,不再包一层。从语法上讲对极了,从架构上讲错得离谱。
我管这些约定叫"隐性架构契约"。它们是项目有序性的真正载体。而 Agent 恰恰是这类契约的天然破坏者——它不是不守规矩,而是根本不知道规矩存在。要让 Agent 不再破坏,唯一的办法是把隐性契约变成显性约束,写进接口、写进类型、写进校验规则里。这事只有人能完成。
3. 先从工程侧收权:把代码库改造成 Agent 友好型
我们要的"有序",不是禁止 Agent 写代码,而是让它只能在"你画好的圈"里自由活动。这个圈怎么画?答案是工程结构先行。我自己的顺序是:先花时间把代码库改造到"Agent 友好"的状态,然后才允许 Agent 大规模进场。没有这个前置步骤,Agent 用起来就是在给未来埋雷。
3.1 接口即合同:先定契约,再让 Agent 填空
Agent 最喜欢的工作方式,是给它一个独立的文件、几个现成的接口签名,让它去填充实现。它最害怕的,是让它"看着这个模块的整体结构,自行理解并发挥"。所以我们要做的第一步,就是把模块之间的边界变成明确的接口签名。
我之前重构那个跨平台工具时,做的第一件事就是给每个模块定义一个"契约头文件"。比如数据访问模块,全部统一成类似的函数签名:
// 所有数据读取必须经过这个函数,任何模块不得直接查库 export async function queryRecords(params: QueryParams): Promise<QueryResult> { // 内部实现 }我把参数类型、返回类型、异常约定全部用 TypeScript 的类型系统写死。之后给 Agent 下指令时就一句话:"实现queryRecords的内部逻辑,不要改签名,不要改其他文件。"它在这个约束下再怎么发挥,破坏范围也只有这一个函数体。
这个做法的本质,是把接口从"文档建议"升级为"编译期强制"。人看文档可能偷懒不看,但 Agent 看类型签名是它最擅长的事——它至少不会改接口名,因为一改编译器就报错。类型系统是 Agent 最好用的缰绳。
3.2 合理颗粒度拆分 + TODO 化任务描述
第二个改造方向是文件粒度。Agent 处理小文件的经验远好于大文件。一个动不动一千行的"上帝模块",人看着头疼,Agent 看着更头疼。它只能基于局部信息做修改,改一处就可能把远处的状态搞坏。
所以我后来推行了一个"单文件单职责"的强制约定:一个文件不超过 200 行,一个函数不超过 40 行,一个模块只做一类事。这听起来很基础,但它在 Agent 工作流里的价值极大——因为 Agent 一次能"看全"整个文件,它做决策时会少很多盲目性。
配合小文件,还有一个非常实用的技巧:在代码里写 TODO 注释,把任务描述直接做成 TODO 化。比如我打算让 Agent 实现一个缓存模块,会先把骨架文件建好,里面写:
/** * TODO(cache): * 1. 实现 set 方法:key 为 string,value 为任意 JSON 可序列化数据 * 2. 实现 get 方法:key 不存在时返回 null * 3. 缓存过期时间统一为 5 分钟 * 4. 不要改动本文件已有的函数签名 */然后只给 Agent 一个指令:"完成 TODO 里的全部内容。"它看到目标、看到边界、看到验收条件,产出的质量会大幅提升。这个方法我屡试不爽,因为 Agent 对"TODO 里列出的明确条款"的执行力,远高于对"你看着办"的执行力。
3.3 数据流单向化:让 Agent 不越权
上面两条解决的是"单个 Agent 怎么干活"的问题,但项目乱序还有一个根源:模块之间的数据流太乱,Agent 被牵着鼻子走。
一个典型的乱序项目长这样:A 模块直接改 B 模块的内部变量,B 模块又直接读 C 模块的数据库连接,C 模块又反向依赖 A 模块的配置。这种拓扑结构下,任何一次 Agent 修改,都有可能引发连锁反应,因为它永远说不清"我改了这里,会影响到谁"。
我的做法是强制数据流单向化:页面只调服务层,服务层只调领域层,领域层只调数据访问层,任何模块禁止向上反向依赖。这不是什么新鲜理论,但它是 Agent 工作流下的生命线。因为当依赖方向明确之后,一个 Agent 修改数据访问层,最多只会影响领域层,不可能直接改坏页面。破坏范围被控制住了,你验收代码时才不会焦头烂额。
这一步没有任何取巧空间,纯靠人肉重构。我花了大概两天时间把项目的依赖理清,之后 Agent 的故障率直线下降。这笔前期投入,比后面追着 Agent 修 bug 划算太多了。
4. 提示词工程升级:从"帮我做"到"在约束下实现"
很多人在 Vibe Coding 里用的提示词是这种:"请帮我加一个导出 Excel 的功能。"这句话如果发给人类同事,对方至少还会反问:导出什么格式?哪些字段?要不要筛选?发给 Agent,它只会"贴心地"把所有可能性都实现一遍——然后大概率超过你预期地乱改了一堆文件。
我后来把提示词完全改了风格。不再说"帮我做什么",而是说"在以下约束下实现什么"。这是一个非常小的措辞转变,但它把 Agent 从一个"自由发挥的共犯"变成了"有边界的执行者"。
4.1 一个可复用的任务模板
下面是我现在给 Agent 下任务时常用的模板,直接拿去用即可:
任务目标: 在 X 模块中实现 Y 功能,达到 Z 效果。 现有约束: 1. 只允许修改文件 A、B,禁止修改文件 C、D 2. 必须复用已有函数 queryRecords,禁止新建类似函数 3. 函数签名必须保持与类型定义一致,不得改动 4. 返回格式必须符合 {code, data, msg} 规范 5. 不得引入新的第三方依赖 验收标准: 1. 单元测试通过 2. 运行 pnpm typecheck 无类型错误 3. 不改变既有测试的语义这个模板核心价值在于把"边界"写清楚了。边界越具体,Agent 的自由度越小,产出越可控。有人担心约束太多会限制 Agent 的创造力——我承认会有这个问题,但 Vibe Coding 里我们要的从来不是创造力,而是稳妥交付。创造性的部分,应该发生在你提需求、定方案的时候,而不是让 Agent 在代码里自由发挥的时候。
4.2 给 Agent 限定边界:不动哪些文件
限定"能改哪些文件",比限定"不能改哪些文件"更容易让 Agent 遵守。因为 Agent 对否定性指令的理解往往不如肯定性指令——你说"不要改 A 文件",它可能还是会因为一个报错把 A 文件改了两行;你说"只允许改 B 文件",它就会把全部精力集中在 B 里。
这里我还有一个实在的教训:永远不要把配置文件、公共类型定义文件交给 Agent 自由修改。这些文件一旦被改,影响是全项目范围的,而 Agent 根本意识不到这点。我自己经历过一次 Agent 自动往配置里加了个"看起来合理的字段",结果整个启动流程在另一台机器上全部失效,排查了半小时才发现是多出来的字段被某个序列化逻辑误读。从那以后,配置类文件我全部设置成了只读,Agent 的任何修改建议都必须通过我的批准才能落地。
4.3 让 Agent 先出方案,再写代码
给 Agent 下任务后,不要急着让它生成完整代码。哪怕任务再简单,我也习惯先在提示词里加一句:"先简要说明你的实现方案,等我确认后再写代码。"
这一步的价值在于,你可以在 Agent 动手之前就"拦住"它。很多方案思路,在"写代码"之前暴露得最清楚。有一次我让它实现一个消息推送重试机制,Agent 的方案是"直接用 setTimeout 进行重试"。这表面上能用,但完全没考虑进程重启后重试任务丢失的问题。我看了方案后直接否掉,改成"持久化重试队列"。如果我没让它先出方案,这个 bug 就会在代码生成三周后的某个深夜暴露,那滋味可不好受。
这个"先方案后代码"的流程,等于在 Agent 的工作流里加了一个人工评审点。它没办法保证 Agent 永远正确,但能保证你永远知情。
5. 验证体系:Agent 产出的代码到底能不能信
就算提示词写得再规范,Agent 依然会出错。所以 Vibe Coding 工作流里最不能省的,是一套自动化的验证体系。我把它简称为"三道门禁":类型检查、契约测试、变更审查。每一道都挡掉一类典型错误。
5.1 类型系统是第一道门禁
我现在的所有项目,只要能用 TypeScript 就绝不用纯 JavaScript;能用强类型语言就不用动态语言。原因很简单:Agent 对类型的执行力远强于对语义的执行力。你给它一个类型约束,它大概率会遵守,因为编译器会报错;你给它一个语义约定,它大概率会忽略,因为没有任何机制会阻止它。
所以我的第一道门禁是让typecheck成为所有改动的前置门槛。任何人或 Agent 的代码,如果类型检查不通过,直接禁止合并。这一条简单粗暴,但至少挡住了"改个函数名导致一百处调用断裂"这类灾难。
5.2 测试的顺序:契约测试优先于单元测试
很多开发者的习惯是先写单元测试,再写业务逻辑。但在 Agent 工作流下,我的顺序反过来,先写契约测试,再写单元测试。
所谓契约测试,就是针对那些"不能变的接口"做断言。比如,数据访问层的queryRecords必须返回QueryResult格式;某个公共函数必须保证不抛原始异常只抛包装异常;某个模块对外暴露的方法数量必须固定。这类测试不关心具体业务逻辑,只关心"接口是否稳定"。
为什么优先做这类测试?因为 Agent 破坏的往往不是业务逻辑,而是接口约定。单元测试能抓到"函数算错了",但抓不到"函数签名改了"——除非你为每个函数写一遍调用测试。契约测试刚好弥补这个盲区,它把隐性架构契约变成了机器可判定的显式检查。
我的一位朋友在某团队做过一个实验:在某个老项目里引入 Agent 工作流之前,先花一个周末把核心模块的契约测试补齐。之后一个月里,Agent 提交的代码只有三成需要返工,而没有契约测试的那段时间,这个数字是六成。
5.3 合并门禁与人工抽查的节奏
有了前两道门禁,依然不够。我之前遇到过一个最阴的 bug:Agent 写的所有测试都能过,类型检查也干净,但它把一个模块对另一个模块的"只读假设"破坏了——测试里没有覆盖别的模块读这个模块的场景,于是新代码上线后,另一个模块的边界情况直接崩了。
从那以后,我把合并门禁加上了一条规定:凡是涉及公共模块、公共函数、公共配置改动的 PR,必须有人工审查;凡是新增文件的 PR,可以走自动检查后快速合并。这等于用人工把风险集中在最容易出错的地方。
至于人工审查的节奏,我一般不逐行看 Agent 的代码,而是看 diff 里改了哪些文件和函数。只要改动面超出"任务描述里点名的文件",我就直接打回。别小看这个动作,它逼着 Agent 在后续任务里收敛自己的修改范围。验收审查的目的不是抓错,而是训练 Agent 的边界意识。
6. Agent 能力的"使用边界":哪些场景我坚决不让 Agent 碰
做了这么多工程侧改造和提示词优化之后,我心里对 Agent 的用法有了一个越来越清晰的边界。有些场景它确实能干好,有些场景请它干活就是给自己找罪受。
6.1 跨模块重构、数据迁移、公共 API 改动
先说绝对不能交给 Agent 的三类事:
- 跨模块重构:比如把某个模块从"私有状态"改成"依赖注入"。这类改动牵连所有依赖方,Agent 的上下文根本装不下全局影响面,它做一半就忘了另一半。
- 数据迁移:比如把数据库表结构改了、字段语义变化了。这种任务涉及存量数据、历史兼容、线上风险,任何一个"看起来正确"的自动修改都可能造成数据丢失。这类操作的每一步都必须人肉控制。
- 公共 API 改动:凡是那些"改了会让其他模块爆红"的接口,都必须先人肉设计新签名,再由 Agent 做批量修改——否则 Agent 会把这些公共 API 改得面目全非。
有人可能觉得不交给 Agent 太亏了。但我的实际体验是,这类任务用 Agent 省下的时间,远少于修复它造成破坏的时间。我自己统计过:一个跨模块重构如果人肉做需要 4 小时,Agent 全自动做可能只需要 1 小时生成代码,但留给我修异常的时间可能超过一天——这还不算我把所有改动逐行验证的时间。账一算就清楚了。
6.2 我保留的"人肉编码区"
现在我会有意识地在项目里设置一块"人肉编码区",专门放那些对正确性要求极高、逻辑复杂、出错代价大的代码。比如支付相关逻辑、权限判定核心、数据一致性逻辑。这些代码我全部手写,不让 Agent 碰一个字。
这不是对 Agent 的不信任,而是对风险的敬畏。Agent 可以帮你把 100 个地方做到 99 分,但只要有一个地方翻了车,翻车成本可能就超过 99 个地方省下的时间。关键路径的代码,稳定性比速度重要一万倍。
6.3 成本视角:什么时候让 Agent 干耗算力不如自己改
最后聊一个比较少人提的角度:成本。Agent 生成代码不是免费的,尤其是反复"试错式"生成——它写一个方案、你跑测试、报错、再修,一个来回消耗的 token 和时间都不少。到后期我发现,一些简单问题,比如"给某个配置项加一个默认值""调整一个函数里的过滤逻辑",我自己动手两分钟就搞定,但把任务描述清楚再交给 Agent,往返三轮可能十分钟就出去了。
我现在给自己定了一个很朴素的规则:任务复杂度低于"三分钟手写",就直接手写;任务重复度高、涉及大量样板代码,才交给 Agent。这样既不会把自己累垮,也不会让 Agent 在低价值任务上空转算力。
7. 最后说几句个人体会
经历了从"乱序"到"有序"的整个过程后,我对 Vibe Coding 的看法变了不少。它确实是这十年来让写代码效率提升最明显的一次工具革命——但前提是,你得把它放在一个足够坚固的工程框架里使用。
如果让我用一句话总结这些天的体会,那就是:Agent 是一把很快的刀,能不能切出漂亮的菜,取决于你的案板稳不稳。案板就是工程结构、类型系统、契约测试、人工审查这套东西。它们不性感,不炫酷,却是 Agent 工作流真正的底座。
最后再分享一个小技巧:如果你一开始不知道怎么改造自己的项目,就先从"下个任务只改一个文件,且用类型签名锁住接口"这种最小规则开始试。不要想着一步到位,先把最乱的一块区域划出边界,让 Agent 在边界内跑起来。等你习惯了边界感,再把其他区域逐步纳入。这个过程不一定快,但它是让 Vibe Coding 从"爽"走向"稳"唯一的路。