尧图精选

AI编程重蹈软件工程覆辙:从面条代码到工程化约束的必经之路

🕒 发布时间:2026/9/13 10:33:55 📁 来源:尧图网络
AI 编程正在重蹈人类的覆辙最近大半年我几乎每天都要花四五个小时跟各种AI编程工具打交道——从补全类插件到能独立跑完整任务的Agent都有涉猎。起初确实很兴奋感觉像是多了个不知疲倦的结对程序员。但用得越深一个场景就越来越清晰我在代码评审里看到的那些AI生成的杰作总让我想起十年前刚入行时自己写出的那些自以为很酷、实际上让同事骂街的代码。这不是巧合这是一条正在重演的老路。人类编程花了几十年才学会的那些教训——写过面条代码才知道要结构化搞出软件危机才知道要工程化经历过依赖地狱才知道要管理依赖——AI编程正在以另一种形式、更快的速度把这些坑全部重新踩一遍。这篇文章我想聊聊我观察到的具体现象、背后的原因以及作为一线开发者我们还能怎么补救。1. 人类当年是怎么一步步学乖的——先看清我们踩过的坑在讨论AI编程之前有必要先把人类自己那段黑历史摆出来。因为你会发现AI现在的行为模式跟早期人类的编程方式惊人地相似。早期程序员几乎没有任何规范和约束。GOTO语句满天飞代码执行到哪跳到哪整个程序像一团打了结的毛线球。后来业界发现这种写法根本没法维护才逐步形成了结构化编程的概念——顺序、分支、循环三大结构搞定一切逻辑。再到后来面向对象、函数式、设计模式、分层架构每一次进步几乎都是因为前面那种写法带来了惨痛的教训我们才被迫往前走了一步。上世纪六十年代末有一个很著名的软件危机核心症状就是软件规模一大开发周期就彻底失控预算超支、Bug多到修不完、项目直接烂尾。这个问题不是一两个团队遇到的而是整个行业普遍性的危机。正因为痛得够狠才催生了后来的一整套软件工程方法论——需求分析、系统设计、模块化开发、测试体系、代码评审全部都是被现实毒打过之后沉淀下来的东西。再说一个程序员都有切身体会的坑过早优化。我刚工作那会儿特别迷恋各种奇技淫巧写个遍历非要搞个位运算写个数据组装非要上一个复杂的设计模式。当时觉得自己可厉害了但过两个星期自己回来看这段代码都得琢磨半天更别说别人。后来才明白一个朴素的道理代码首先是写给人看的其次才是给机器执行的。可读性、可维护性、结构清晰这些不是软性指标而是真正的生产力。还有一个更深刻的教训是全局一致性。人类程序员在大项目里特别容易顾此失彼——改了一个模块的接口忘了同步其他模块新增了一个配置项忘了更新全部调用方。这个问题本质上是因为单个程序员的一次性工作记忆是有限的而大型系统的信息量远超个体所能承载的上限。为了对抗这个问题才发展出了接口契约、自动化测试、CI/CD这些工程手段用流程和工具去兜住个体的认知瓶颈。我之所以要把这些旧账翻出来是因为等一下你会发现AI编程当前面临的绝大多数问题几乎都能在人类编程历史上找到原型。它正在用一种新的皮囊装着旧时代的灵魂。2. 现场观察AI写出的代码正在复刻当年的面条代码先说我自己的一个典型经历。上个月接了个需求要把一个内部数据同步模块重构一下支持多租户的数据隔离。我用了个AI编程助手给它描述了需求它很快生成了整段同步逻辑。第一眼看过去挺像那么回事——函数命名清晰、有类型标注、还有注释。但等我把代码完整读进去越看越不对劲。整个模块的主控流程里正常的业务分支之间塞了三个异常处理分支和两个重试逻辑它们之间还有互相跳转。某个工具函数的返回值在成功时是对象失败时返回null但调用方一个地方用了null检查另一个地方却直接去访问属性靠的是外层try-catch兜底。最关键的是AI自己生成的一个缓存策略方法在三天后的另一个需求里被我自己手动调用过一次结果它内部竟然还在递归调用同一个方法——等于平白多了一层无意义的堆栈。这种代码如果让新手写出来评审时会被打回去重写。但问题是它是AI生成的而且生成得异常流畅、异常自信。我后来在几个技术社区聊了聊发现整个现象非常有普遍性AI擅长生成局部合理、整体失控的代码。单看任何一个函数都有模有样但拼在一起模块之间缺乏统一的设计约束像是不同的人在不同时间各写各的。AI极度偏爱防御性过度。它会在每个可能为null的地方做判断在每个循环里都套一个状态检查最终代码行数膨胀到人类无法轻易审阅的规模。AI对上下文窗口之外的内容完全无感。它只认识它看到的那段代码和对话如果这个模块有历史包袱、有既定约定而你没有在一开始就告诉它它就会凭自己的经验自由发挥大概率跟整个系统的风格是脱节的。这像什么就像所有人都不写注释、不用版本管理、想在哪跳转就在哪跳转的远古时代。AI编程确实规避了人类在打字速度、命名疲劳上的限制但它同时也以极高的效率生产出了类似形态复杂、结构脆弱的面条代码。以前一个程序员写面条代码祸害的是一个模块现在AI以每小时几千行的效率生成面条代码祸害的是整个代码库。更讽刺的是当我去问AI这段代码会不会过度设计了它会非常礼貌地承认你说得对这里逻辑确实有点复杂可以简化。它知道什么是好的代码但它默认不会主动给你好代码只给你最像好代码的东西。这个区别非常关键。3. 你以为它在思考其实它在模仿——AI重蹈覆辙的根因剖析人类程序员写出烂代码通常是因为经验不足、沟通不到位、或者时间压力太大。AI写出类似的烂代码根因跟人完全不同但表现出来的症状几乎一模一样。要理解这件事得先拆一下AI编程工具的工作范式。当前的AI编程模型本质上是下一个Token预测。它并没有一个真正的需求模型也没有一个对整个项目和系统架构的全局认知。它的工作方式是根据你当前的代码上下文、你之前说的话、以及它从海量代码库里学到的统计规律生成一个在概率上最接近人类程序员会写出来的代码的序列。问题就出在这个最接近人类程序员上。它从训练数据里学到的人类的平均水平不是人类工程实践最佳水平。海量开源代码里固然有很多优秀范例但也有大量业余项目、教学Demo、论坛贴子里的碎片代码还有各家公司风格迥异的内部实践。AI做的是把这些全部平均化得出一个看起来最普遍的写法。这就解释了为什么AI经常会给出过度工程化的方案。因为在它见过的庞大语料里面对用户登录这个场景有人用简单函数实现有人引入完整的权限框架有人套三层架构加DTO转换还有人顺手把观察者模式塞进去。统计上出现在各种博客和技术文章里的复杂方案占据了更高的权重因为这些内容更常被写成教程并被收录进训练集。于是AI在平均之后更倾向于输出一个看起来架构完整、涉及N个类和接口的方案而不是一个最短的、能跑的、容易维护的写法。人类失去全局视野是因为大脑工作记忆有限AI没有全局视野是因为它的上下文窗口有上限且它的训练目标根本不包含维护一个长期稳定的架构演进这类目标函数。你给它的上下文里只放了一个文件的代码它就只能基于这一个文件做决策你给它放了整个项目它能着眼的时间范围也就是这个项目当前快照的短时范围。它不会像资深工程师那样在动手前先在脑子里过一遍这个模块未来半年可能要扩展三个方向所以现在接口要收敛一点。它看到的是当前这个任务不是这个系统的未来。这背后还有一个更让人忧虑的事实AI训练的目标函数是生成的代码通过测试的概率或者是与人类偏好对齐的程度但它从来就不是代码在两年内可维护的总成本最低。换句话说它在做一个非常短视的优化——让当前的对话、当前的任务得到最顺利的完成。至于这个完成动作是否会破坏系统的整体一致性是否引入了隐性的架构债是否让后来维护的同事想骂人这些因素在当前的训练体系里几乎无法被有效建模。所以AI已经踩上的坑不是偶然而是源于范式本身。它没有一个全局的、持续演进的软件系统的内部模型。在这个意义上它甚至比当年的人类新手还要脆弱——因为它会非常流利地把错误包装得像是正确。4. 最该警惕的不是它写错而是它太流畅——当能力幻觉撞上工程现实很多开发者对AI编程最大的误解是觉得它写得快了所以我也快了。在简单任务上这是成立的。一旦任务复杂度上来快反而会变成最大的风险来源。我做一个内部的接口联调时遇到过一个很诡异的案例。AI生成了一个新的支付回调处理函数逻辑看起来完整字段校验、签名验证、幂等判断一应俱全。我当时赶进度大概扫了一遍就合进去了。上线当天晚上凌晨两点告警响起来——某个渠道的回调被重复处理了三次产生了三笔重复入账。我登录上去排查整整花了三个小时才定位到原因AI生成的幂等判断用了一个刚刚好不在它自己生成的代码里定义的Redis Key前缀而那个前缀是我在另一个被AI当作参考却理解错了语义的旧模块里使用的。两边用的字符串拼接方式微妙地不同导致幂等键永远不命中。这个错误的可怕之处不在于有Bug——人写的代码也有Bug。而在于它误导性的流畅感签名验证完就给人一种剩下的一定没问题的错觉。它没有给出任何这里的幂等设计我认为需要再确认一下的信号而是以一种极其自信的方式完成了整段逻辑。这种流畅的自信是AI输出风格中最有迷惑性的部分。在工程实践里我们通常会通过编码规范和评审制度来防范人为失误。可AI生成的代码在格式上太规整了命名规范、换行漂亮、注释齐全它天然会降低评审者的警惕性。你看到一份格式完美的PR第一反应是这代码作者很清楚自己在做什么但实际可能完全不是这么回事。更要命的是AI还会回避承认不确定性。如果你在对话里问它这段代码有什么风险吗它往往会列几个风险点。但如果你不主动问它绝对不会主动把我这里其实拿不准挂在嘴边。人类程序员在不确定的时候会说这块我不太确定你再看看但AI的奖励机制决定了它会被训练成倾向于给出一个确定的答案而不是一个诚实的答案。这就构成了一种危险的循环代码越流畅人工评审越松懈评审越松懈缺陷越晚被发现缺陷越晚被发现修复成本就越高。而成本最高的那些缺陷往往不是写错的逻辑而是那些看起来完全正确、但放在整个系统里就是不对的语义错位。这恰恰就是当年软件危机的另一个翻版——不是没人写代码而是没人能hold住所有代码。5. 不让AI重蹈覆辙的三个关键动作——从工程管理视角给AI套上缰绳说了一堆问题总得给点出路。我自己实践下来AI编程是可以安全使用的但它不能替代工程管理它必须被纳入工程管理体系里。有三件事是最关键的。先给系统定宪法。很多AI编程工具都支持自定义指令或项目级上下文文件类似AGENTS.md、CLAUDE.md这类项目说明书。这是当前约束AI最有效的手段。我在每个项目里都会维护一份详细的项目约定文档里面写清楚这个项目的架构分层是怎么样的、哪些目录放什么职责、接口命名的历史约定、不允许引入哪些重依赖、数据访问统一走哪一层、日志规范是什么。每次让AI干活之前我都会在上下文里带上这份约定。实测下来这么做之后AI代码风格偏离项目的概率大幅下降。这跟团队里给新人发一份开发规范文档是同一个逻辑——你不能指望新人天生就知道这些约定。把代码评审的强度提到最高。AI生成代码的正确评审状态不是扫一眼而是逐行读。我给自己定了一个规则AI生成的代码我必须能向自己解释清楚每一个分支存在的理由解释不清楚的地方一律打回去让AI重写或者自己手动改。尤其是异常处理、缓存、并发和异步逻辑这几个领域几乎就是AI幻觉的重灾区。如果你在一片AI生成的代码里看到一个防御性的判断先问一句这个判断真的有必要吗它是为了防什么如果AI答不上来这段代码大概率是它在模仿防御而不是真正防御。把任务切到AI能处理的最小粒度。现在的AI编程工具上下文窗口再大也扛不住把整个大型系统塞进去让它做设计决策。我现在的做法是架构设计、模块拆分、接口定义这些事永远由人来做AI只负责实现已经定义清楚的小粒度函数、组件、数据处理逻辑。每个任务的描述里我会写清楚输入输出、边界条件、性能要求、禁止事项。这本质上就是在给AI一层结构化编程的约束——就像当年人类为了对抗自身认知局限而发展出来的模块化和分工一样。你让AI负责的粒度越接近函数级它产出物的可控性就越高你让它负责系统级它大概率会交给你一坨很流畅的灾难。我后来复盘那个幂等Bug的时候最大的教训不在于AI写错了而在于我没有给它足够的上下文也没有对关键路径做足够的审查。我把一个涉及资金的关键逻辑完全是当作一个辅助编码任务交给它然后自己跳到了下一个任务上。这在流程上是不可接受的。AI可以作为强大的执行者但绝对不能成为他人工程判断的替代品。6. 把反思当作一种工程能力真正长期的解法是教会AI理解债务最后的这一点是我认为整个话题里最值得展开的。有人说AI可以无限迭代、无限修正所以它能自己摆脱这些坑。但我在实际使用中对此持审慎态度。AI在一个单次会话里确实能纠正错误。你告诉它刚才的代码过度设计了它可以立刻给你一个更简单的版本。但这种纠正是有条件的——它必须被显式触发。你指出它才改你不指出它就一路狂奔。这跟人类程序的反思能力有本质区别。资深工程师在写代码之前脑子里会自动过一遍这个方案的潜在债务和长期成本这是一种内化的、前置的反思。而AI的反思是被动的、后置的、由用户指令驱动的。换句话说AI当前没有架构审慎性的内置机制。它不是不想给你好设计而是它不知道你的项目已经积累了多少技术和架构债不知道你团队的风格偏好不知道某个看似灵活的抽象会在未来的哪个需求里卡住你。所有这些信息要么需要你手工喂给它要么需要你用严格的项目宪章去约束它。一旦这些外部机制缺失AI必然会滑向它最舒适的路径——生成一个看似合理、实则平均化的方案。所以如果把AI编程重蹈人类覆辙这个问题再往前推一步我的结论是它重蹈的不是某几条具体的错误而是没有反思机制、纯靠试错、用短期目标驱动决策这个软件工程早期阶段的内核。人类是从无数次线上事故和维护地狱里才被迫成长出工程化能力的。AI的成长则需要我们主动把工程化的约束注入它的工作流程里。这也是我个人认为现阶段使用AI编程最重要的一条心法不要把AI当成一个“不用管过程的自动完成机器”而是要当成一个“能力极强但严重缺少大局观的新人”。你要给它定义边界你要给它说明上下文你要在它交出结果后认真评审你要把团队共识写进它必须读取的文档里。这套流程和当年带新人的流程几乎一样只不过这个新人的产出速度是人类新人的几十倍所以流程的颗粒度必须更细、把关强度必须更高。AI编程确实把我们的生产力天花板抬高了一大截。但也正因为如此它把所有错误的发生速度也放大了同样的倍数。人类当年能从软件的混沌时代走到工程化时代靠的是一整套约束机制和反思习惯。现在轮到这个新兴工具了它走过的弯路和我们将要补上的约束本质上就是同一场课程的上半场和下半场。我们这些坐在评审席上的人责任就是确保下半场不会重演上半场所有的灾难。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →