半古法编程 vs Vibe coding:可持续开发的理性选择
一场猛烈的Vibe Coding之后……
三万行核心代码,加上提示词和各种配置文件,总计二十六万行。一个基于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%
乍看之下,这些数据似乎不算致命。
但实际情况是,这个项目目前已暂时停摆。原因很简单——改不动了。牵一发而动全身,而“全身”散落在各个文件,彼此纠缠不清。
到了这个阶段,本该进行精细的延迟优化和LLM回答稳定性提升,尤其是prompt微调。但AI编码到了这一步明显力不从心。若亲自下场改动,理解成本极高……说实话,项目已处于失控状态。
事情是如何一步步走向失控的
故事的开始总是光鲜。第一周,开发速度飞快,一切看似顺利。把一个简单的MVP丢给AI,十分钟内它就帮忙拆分好文件结构,引入了LangGraph和状态持久化。
接下来搭FastAPI脚手架、加前端、完善LangGraph图。
那会儿AI生成的代码还能被Review一下。反正等AI完成指令也需要时间,闲着也是闲着。框架清晰,文件不大,每个文件的职责一目了然。这给了人虚假的信心,让人开始相信: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。无论长短文,无论中英文。
再贴一下此时的项目复杂度,很少在百分制上获得这么高的分数(笑):
- 圈复杂度Top(Radon)
96分:langgraph_app/graph/coach_unified_nodes.py中的unified_coach_turn_node75分:langgraph_app/graph/coach_nodes.py中的diagnose_answer_node73分:api/routers/interview_kb.py中的interview_kb_train_answer73分:langgraph_app/graph/coach_nodes.py中的apply_tactic_node73分:langgraph_app/graph/coach_nodes.py中的generate_question_node70分:langgraph_app/kb/profile_build.py中的run_merged_profile
一般情况下,合格范围是11–20。
- 认知复杂度Top(Cognitive-complexity)
94分:langgraph_app/kb/interview_kb/migration.py中的migrate_progress_and_sessions92分:langgraph_app/graph/coach_unified_nodes.py中的unified_coach_turn_node89分:langgraph_app/kb/profile_build.py中的run_merged_profile84分:langgraph_app/graph/coach_nodes.py中的diagnose_answer_node68分:api/routers/interview_kb.py中的interview_kb_train_answer68分:langgraph_app/graph/coach_nodes.py中的generate_question_node
一般情况下,合格范围是16–25。
- 耦合度(import可视化,Pydeps)
langgraph_app文件import耦合图
api文件import耦合图
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成绩作为先验证据、自己实验中的模型准确率作为内部判定依据,再加一层参数规模作为辅助论据。听起来很有道理。
当把这三层证据链加到实验计划里,拿去给ClaudeCode(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校验单行长度,搭配文件行数限制防超标。
在可持续的开发中,“慢就是快”确实是有一定道理的。