一、一顿 Vibe Coding 猛如虎之后……
前段时间,我花17天时间,用 Agent 模式完成一个 LangGraph 双图 + 语音交互 + 多轮对话 + 上线部署 + 线上控的全栈应用,三万行核心代码、加提示词和各类文档共二十六万行内容,日均产出两千行核心代码,外加求职和面试。这很疯狂,全过程可以说几乎没有古法编程、完全 Vibe Coding。然而,现在回顾之后,我的感受却是:这是在“温水煮青蛙”。等反应过来,已经来不及回头啦哈哈……
并非危言耸听,来看看我的项目真实数据吧:
- 开发周期:3 月 28 日 - 4 月 14 日
- 总提交数:63 次
- Python 代码:131 文件,24,652 行
- TypeScript/TSX 前端代码:40 文件,7,780 行
- 纯代码合计:~32,000 行
- 含 prompt 模板、配置、文档:~262,000 行
工作量是有的,那么问题是什么呢?
- 超过 500 行的 Python 文件:8 个(最大 2,018 行)
- 超过 500 行的前端文件:4 个(最大 1,043 行)
- 节点函数文件
coach_nodes.py函数数:38 个,包含了一整个图的节点 - 服务函数文件
coach_service.py函数数:46 个,所有 prompt 构建 + 辅助函数 + LLM 调用函数混在一起 - 测试代码:~900 行(不足源码的 3%)
再贴几个复杂度数据:
-
圈复杂度 Top (Radon)
96langgraph_app\graph\coach_unified_nodes.pyunified_coach_turn_node75langgraph_app\graph\coach_nodes.pydiagnose_answer_node73api\routers\interview_kb.pyinterview_kb_train_answer73langgraph_app\graph\coach_nodes.pyapply_tactic_node73langgraph_app\graph\coach_nodes.pygenerate_question_node70langgraph_app\kb\profile_build.pyrun_merged_profile
-
认知复杂度 Top (Cognitive-complexity)
94langgraph_app\kb\interview_kb\migration.pymigrate_progress_and_sessions92langgraph_app\graph\coach_unified_nodes.pyunified_coach_turn_node89langgraph_app\kb\profile_build.pyrun_merged_profile84langgraph_app\graph\coach_nodes.pydiagnose_answer_node68api\routers\interview_kb.pyinterview_kb_train_answer68langgraph_app\graph\coach_nodes.pygenerate_question_node
-
耦合度(import 可视化)(Pydeps)
- \langgraph_app 文件 import 耦合图:
- \api 文件 import 耦合图:
这个项目目前已经暂时停摆了,原因很简单——改不动……牵一发而动全身。而“全身”又散落在各个文件里。到这个阶段本该进行精细化的延迟优化和 LLM 回答稳定性加强,尤其是针对 Prompt 的微调,但 AI coding 到这阶段明显力不从心了;而我此时想去自己改动的话,需要的理解成本是巨大的……好吧,说实话,这个项目已经是失控状态了……
二、事情是怎么逐步失控的
故事的开始总是很美好,前一周,速度很快,一切看起来都很顺利。我把一个简单的 MVP 丢给 AI,十分钟就帮我拆分好文件结构、引入了 Langgraph 和状态持久化。接下来就是搭 FastAPI 脚手架、加前端、完善 LangGraph 图。这个阶段 AI 生成的代码我还会 Review 一下,反正也要等它完成我的指令,闲着也是闲着。框架是清晰的,文件也不大,我对每个文件的职责是清楚的,这些都给我建立了虚假的信心,我真的开始相信,AI 写的代码质量不错,我只要描述需求就行了。
中间的一周,开始密集地添加功能和准备部署,知识库、积分系统、账号系统、监控系统,每天都在加新模块。但这个阶段,我逐渐越来越记不清每个文件是干什么的了;增加得太快了,我连文件结构分几层、几块都开始模糊了。我尝试 Review,却越来越跟不上 AI 的思路:为什么微调一个接口的功能、它改了五六个文件呢?打开那些被修改的文件,我能看懂函数,但连不起来,要反应好久才能搞懂为什么要改这儿。逐渐地,我开始放弃读代码了,每次提需求或者报错,就等 AI 改完,能跑就算完成了。
真正让我心态有点崩的一步,是部署。到这阶段我已经完全依靠 AI 改代码,部署又非常容易出错;然而等镜像构建和传到服务器的时间又不可预测,有的时候要一个半小时,有的时候两分钟就部署完了。我就等啊等,经常等一个小时,部署失败,把报错复制给 AI,让 AI 改,改完继续部署,继续失败,继续 AI 改。由于我不看代码,又很相信 AI,我就等啊等,连续好几天卡在这上面。每天晚上决定部署完就睡觉,然后反复出错、等、AI 改、再部署、再等……很快嗷,天就亮了……天亮了还没部署成功的时候真的很绝望……
什么时候 AI 才亲手打碎我对它的信任的呢?有一天晚上,我在疲惫地等部署,依然是反复失败,但我看到怎么一直是同一个错误呢?改了好几次还是不成功,我有点懊恼。AI 说,我给你个最稳的办法,你去云服务器改一下某某文件的某某参数,这次肯定能成功。我真去改了。改前我还问了一下,我说改这个文件会不会让云服务器和 GitHub 的代码版本产生差异、导致镜像构建完、自动部署那一步失败啊?AI 笃定地说,不会。我问是因为同样问题之前出现过。等我改完,这次部署又是格外的漫长……但果不其然,一个多小时之后还是失败了。原因正是因为 Git 版本没对齐……嗯,量变产生质变,到了这里,我终于肯相信,AI 写代码没那么靠谱了……
最后一小周全在修 Bug,移动端语音问题、回声问题、历史会话列表恢复,还有仍然如旧的部署失败。当然还是只能用 AI,这时候的代码已经很复杂了,我即使知道 AI 不靠谱,也回不了头了……我试了试去理解代码,让 AI 一点一点给我讲,但真的是积重难返啊;就只是个去重逻辑,我都要一层一层架构往下深入,从整体到局部慢慢理解,极其费时,因为太陌生又太复杂了。终于看到具体代码这一步的时候,真无语啊,AI 用英文常用的 SequenceMatcher 来给我的短中文去重……SequenceMatcher 主要基于最长公共子序列切分,按单个字或字母匹配。英文单词间有空格做天然分隔,按单字匹配效果尚可;但中文没有空格分隔,这种匹配法会把"你好吗"和"你好啊"判为高度相似(因为前两个字一模一样),而"方案可行"和"可行方案"反而相似度不高(因为字的顺序不一样)。一般做法应该是先分词(jieba 等),再在词语级别做相似度比较,或者干脆用 Embedding 做语义去重。嫌这些太重的话自己写个函数,把中文转成拼音、用SequenceMatcher 比较拼音字符串也有一定效果。我说当时怎么我这个应用用起来像“鬼打墙”一样、我说过的话应用里的 AI 还会重复,不管提示词怎么强调都没效果……等我再一扒,好家伙~ 所有涉及去重的环节,AI 全给我用的同一个 SequenceMatcher ……无论长短文,无论中英文……
三、Agent Coding 到底是工程未来,还是 Demo 幻觉?
我逐渐意识到一个事实,被吹得神乎其神的 Agent Coding、Vibe Coding,它适合的问题空间,可能比宣传中窄得多。它不是写代码最适合的终极形态,而更像是适合作为:
- 原型工具:极快地创建一个 MVP 进行验证;
- 整理工具:重构已有的代码;
- 分析工具:分析数据趋势和错误根因;
- 半自动写代码工具:在严格的规模和架构约束下的代码生成。
而不是无监督地替代程序员。
我经历的,恰恰是过度放权后的一个经典模式:初期代码少,Agent 改又快又可 Review;接下来改动越来越跨文件、跨层级;再后来持续补丁式修复,哪有 Bug 就补 Fallback、补 if/else 分支,冗余代码飞速膨胀。如果要给这个阶段定个分界线的话,我觉得是在理解成本增长速度超过代码增长速度之后,事情便开始一发不可收拾了,我开始失去全局掌控,项目开始被 AI 反向支配。
最可怕的是,这个过程是在静默中发生的,什么时候陷进去的,很难意识到。
但这里的问题是,为什么 Agent 模式容易造成代码膨胀呢?我总结了以下几个方面原因。
这个 AI 倾向于“加”而不是“改”
我感觉这个 AI 天然偏向“打补丁”而不是去找全局最优雅的解。这样既安全,又省 Token。它遇到需要改功能的情况,不是删旧功能、重整架构、压缩复杂度,而是加封装、加兼容、加工具、加兜底。“既可以、又可以”是它的信条。于是复杂度逐渐爆炸。它非常擅长保守路径,它明显是觉得:“多做点总没错的”。究其机制,我想这也许跟 LLM 的训练方式有关——LLM 在逐 Token 预测的时候接触的代码修改样本,大多以新增代码为主(GitHub 上的 diff 日志里新增远多于删减);另外大多数 LLM 受限于上下文窗口长度,没法理解跨度较大的代码,导致它倾向于在局部打补丁而不是全局重构。
本人没有做好架构层面的约束
我没提前做文件结构约束、文件大小限制、模块定义等等。AI 没约束,“自由发挥”也很正常。
Review 没跟上产出
这个的确就是慢慢沉沦的过程了。正如前文提到的“温水煮青蛙”,刚开始挺舒服,后果却不太美妙。正常古法编程流程应该是写几十上百行就测一下,我是 AI 写五百行只要几分钟,我要 Review 500 行就久多了,心生倦怠,慢慢就变成“跑通就行”了,渐渐就失去掌控了。
四、是在唱衰 Vibe Coding 吗?非也,非也
既然让 AI Coding 去写代码而不加节制的话,项目极容易失控。那 Vibe Coding 就没有前景了吗?
我觉得不是,因为已经有模型在这方面做得不错了,可以借以下例子说明我的感受;但强调下,这些内容并非严格对比,只是主观使用感受:
当我在选择自己的论文实验中需要用到的对比模型时,Cursor(馅是 Codex 5.3的)说,你得有足够证据证明你的强模型比弱模型强,我建议构建三层证据链,公开 Benchmark 成绩作为先验证据、自己的实验中模型准确率作为内部判定依据,再加一层参数规模作为辅助论据。我觉得很有道理。当我把这三层证据链加到实验计划里、拿去给 Claude Code(Opus 4.6)审一审时,它说,你这是过度设计,用自己的实验准确率这一项就够用了。对啊!只要实验梯度成立,另外两层证据链不改变结论。我有点吃惊,因为它在主动做减法。
另外一个例子是,当我的 Pilot 实验中、返回结果绝大部分为空时,我发现是因为实验模型的深度思考开关是默认打开的,我设置的 2048 Max_tokens 限制、模型在思考过程就用完了,导致“content”来不及产出就被截断了。针对这个问题,Codex 5.3:“针对空内容做一次短答重试”,经典保守思路;那 Opus 4.6 咋说的呢:“这不只是一个参数问题,而是一个实验设计决策。推理模型 vs 非推理模型:你必须做一个选择”。局部 vs 全局一目了然了。
当然我这种对比并不公平,我只是为了展示使用过程中长期感知到的现象,没有严格控制变量。
我又开始思考,Claude 为什么能反其道而行之,既擅长做减法、又具有全局思维呢?由于 A 社没有公开过 Claude 的相关模型训练细节,我猜测有以下几个方面。
猜测一:Claude 被训练得更像架构工程师,且有很强的反过度设计偏好
普通模型往往更关注怎么完成这个局部任务,Claude 却常常从整体视角去看问题。与其说它是个系统构建者,不如说它更像是个系统维护者。它内部就像有一个老到的资深工程师在审视:“这东西能删掉吗?”它可能训练出了避免没必要的复杂设计、避免做维护成本过高的设计、偏向简洁风格的倾向。很多其他模型奖励的是“回答完整、覆盖全面、别漏”。后者明显是加法。这是偏好,不只是智力。
猜测二:Claude 似乎眼光更长远,不仅关注任务完成,同时也擅长优化长期成本
就算我没说,但我感觉 Claude 会默认在完成给定任务的同时,尽量降低后续维护成本和项目复杂度。它在“方案能不能成立”的基础上默认增加了二阶考虑。这点在日常使用中很明显。我的猜测是,Claude 的模型和 Agent 的目标,都接近“在任务成功的前提下尽量付出最小成本”,尤其是如果它的目标函数中隐含了复杂度惩罚项,它做出减法的可能性就更大了……纯猜测,我没有任何内部消息。
(比较明牌的)三:Claude 是在整个项目上下文里思考,而不是单轮答题
尽管 Cursor 看起来也是基于项目回答,还有模有样的按项目区分对话,但使用中能明显发现,它的回答只是基于这轮对话的内容,只有在需要的时候才会调用工具看代码。它不会在最开始的几轮对话中尝试尽量对项目建立整体理解。而正是因为 Claude 的长 Context,它能存下更多更丰富的项目相关信息,这给它提供了从项目整体去看问题的素材。这点从文件结构就可以验证,在触发 Claude 的记忆功能之后,就会出现 “~/.claude/projects/<项目文件夹>/memory/”,记忆有触发条件、对话后 Claude 会在后台默默整理一下,还挺萌的。
这些既有模型本身的因素,也有产品层的功劳。可能来自模型本身的训练偏好,也可能来自 System Prompt 中的指令引导,或者两者兼有。
这些特点让我看到了 AI Coding 从“劳动者”向“治理者”转变的苗头。AI 开始会做“熵减”。我认为,这种维护型智能、治理型智能的方向似乎比一味追求速度更有前景,比展示模型反复左右脑互博的形式主义更是强到姥姥家去了。
而再回到代码膨胀和失控问题上的话,我可能要说,失控也许是因为大多数 Coding Agent 的优化目标只奖励功能增长,不奖励复杂度下降,或者至少是在后者付出的成本不太够。
五、半古法编程策略
现在我在实践新的 AI Coding 方法。既然昂贵的 Claude 我用不起,那就从策略层做点改变吧。
先手工做约束
项目开始前我先写了个万字 MD 文档,里面写了项目背景、功能、目标、模块分层、评估方案、分析计划、文件结构等等内容,最重要的是增加了硬性代码预算,即,单文件不超 300 行,单函数不超 50 行。我还手动建好了文件结构。
严格限制任务粒度
最好一次只生成 1 个函数,最多一次生成一个一两百行的脚本,绝不能直接让它一口气完成一整个 Pipeline。
建立 AI 禁令
禁止 AI 不经过我允许修改文件结构,禁止 AI 自行创建新文件,禁止 AI 在文件间联动修改,禁止 AI 未经我允许引入我不了解的库,禁止文件间双向 Import。
保留适度摩擦
目前的项目中我已经很久没用 Agent 模式了,一直在用 Ask 模式。我宁可自己手动复制粘贴一遍 AI 给出的内容,也不想再让它在我的项目里进行文件的联动修改了。追求速度的模式可能会让人觉得摩擦越低越好,但工程里适度摩擦更安全。我的最简方式就是把“复制粘贴一遍”当成摩擦。
六、当代码膨胀速度超出人类 Review 速度时……
显然我这个失控应用不是用 Claude 做的,原因无他,Claude 太贵了……Token 哗哗地往外流啊……所以我暂时还没法尝试如果用 Claude 做的话、这个应用还会不会像这样失控。我的主观感觉是应该会好得多得多。
总结来看,或许当下 AI Coding 的核心问题不再只是生成速度,而是代码控制权设计。AI Coding 的瓶颈不再是 AI 写的代码够不够快、能不能解决问题,而是怎么能让人类始终走在代码前面。就像在我这个项目中,AI 让生成代码的成本趋近于零,但代码的维护成本没变啊(甚至更高了因为我对它的理解程度更低),最昂贵的环节——理解、调试、重构——全都得靠人工。当代码产出速度远超理解速度时,其实我是在给未来的自己挖坑、制造 Review 债务。另外这个项目的周期太紧张了,时间约束也是代码膨胀问题的根因之一。
没办法,这个项目我只能单独再拿出两周来重构,目前的计划大概是:先用 Vulture 检测死代码,那些被 AI 和稀泥增加的同一功能的不同函数需要找出来、进行合并精简;再按单一职责拆超大文件,目标是每个文件不超 300 行;然后用 Pydeps 梳理反向依赖、循环依赖,保证所有 Import 都是单向的;接着深入核心代码,把主要功能的函数逻辑 Review 一遍,捋清所有分支判断的依据;最后再运行 pytest --cov --cov-fail-under=60 检查测试覆盖率;最后的最后补一个 CI 门禁防止膨胀,Flake8 校验单行长度,搭配文件行数限制防超标。
在可持续的开发中,“慢就是快”确实有一定道理呢。