同时开发几个网站或者几个软件的时候,会发生一件很有意思的事情
2026-09-10 17:12:19来源:科技行者
你有没有想过一个问题:如果一家公司同时要做十个不同的网站项目,最后会变成什么样子?
大概率是这样的:每个网站都有自己的联系表单,每个表单都要检查邮箱格式对不对,每个团队都各自写了一遍这个检查逻辑。十个项目,十份几乎一样又不完全一样的代码。哪天老板说邮箱验证规则要改,十个项目就要改十遍,改漏一个都算你没做完。
这不是段子,这是软件行业几十年来一直存在的真实痛点。现在这个痛点被放大了,因为写代码的不再只是人类程序员,还有大量的AI编程智能体在批量生产代码。KAIST的研究团队盯上了这个问题,写了一篇论文,专门讨论当AI要连续生成一堆相关软件项目时,应该怎么避免代码越写越乱。
(资料图片)
**AI写代码写多了,也会"发福"**
先说一个背景知识。
现在的AI编程智能体已经能独立生成不算简单的完整应用了,从头到尾写出一个能跑起来的网站或工具,这在几年前是不可想象的。但真实的软件开发场景里,几乎没有人只做一个孤立的项目。企业通常维护的是一整套相关应用的组合,它们共享业务逻辑、界面风格、操作习惯,只是各自独立部署。
问题就出在这里。如果让AI一个一个项目地做,每个项目都是从零开始理解需求、从零开始写代码,那么共享的那部分逻辑就会被重复实现无数次。更麻烦的是,已经有研究(SlopCodeBench)发现,AI智能体在长时间维护代码的过程中会产生"slop",也就是代码依然能跑,功能依然正确,但会变得越来越啰嗦、越来越绕、结构越来越乱。这个现象有点像人变胖,功能没坏,但整体状态在变差。
如果是N个AI智能体分别独立维护N个项目,这个"发福"过程会被放大N倍,因为每个智能体都在各自的轨道上重新发明轮子,各自积累自己的冗余代码,彼此之间的差异也会越来越大。
研究团队把这个场景定义成一个新问题,叫做**Super Library Agent问题**,简称SLA。
超级库智能体问题:当一个AI智能体需要按顺序生成N个相关的应用程序时,它不仅要完成每个应用本身,还要同时维护一个跨应用共享的"超级库",把可复用的代码组件放进去,供后续应用调用。
这个设定听起来简单,实现起来却处处是坑。
**朴素方案为什么会失败**
研究团队先搭了一个最朴素的方案,作为对照组。这个方案的逻辑很直白:一个编码智能体先生成新应用的代码,然后另一个库维护智能体扫描已有的所有代码,把重复出现的部分提取到共享库里,同时把老代码改成调用新库的形式。
听起来很合理对不对?但实际跑起来,这套朴素方案暴露出两个致命问题。
第一个问题是提取召回率低。
功能上完全等价的两段代码,写法可能天差地别。一个开发者可能用try-catch包一层再读写localStorage,另一个开发者可能封装成一个独立函数再调用,表面上看完全不像同一个东西。如果库维护智能体只靠表面相似度或者代码文本匹配去找可复用的部分,很多真正该合并的代码就会被漏掉。
这就好比你让一个新来的仓库管理员去清点货架,只凭包装盒长得像不像来判断是不是同一种商品。结果同一款零食,因为换了一批新包装,就被当成了两种完全不同的商品分别上架。仓库越来越大,重复的货架也越堆越多,管理员却浑然不觉,因为他根本没有意识到自己漏掉了什么。如果不换个判断标准,货架的冗余只会越滚越大。
第二个问题是迁移的正确性没法保证。
这个问题更隐蔽。把一段本地代码提取到共享库里,绝不只是把代码剪切粘贴那么简单。原来调用这段代码的地方需要改成导入库的写法,依赖这段代码的其他函数也要跟着调整,有时候连周边的接口设计都要联动修改。一个不够细致的迁移智能体,很可能只把明面上能看到的那部分代码换掉了,却漏掉了背后一连串的连锁调用关系,结果留下一堆调用不到的死代码,或者干脆引用报错。
**候选引导式提取:先猜再验证**
针对第一个问题,研究团队设计了一套叫做**索引式候选提取**的机制。
核心思路是:不要直接比较代码文本长得像不像,而是先让AI把每一段代码翻译成一句自然语言摘要,再拿这些摘要去做匹配。
代码分块*:研究团队用一种叫做AST(抽象语法树)的技术,把每个代码文件切分成函数、类、模块这样有语义边界的小块。切好之后,用一个轻量级的AI模型给每一小块写一句不超过160个字符的英文摘要,说明这段代码是干什么的。
这个转换的意义在于,两段写法完全不同但功能一样的代码,翻译成自然语言之后,摘要往往会长得很像。比如一个"用useState加try-catch读写localStorage"的写法,和另一个"直接调用getItem和setItem"的写法,翻译成人话都是"从本地存储读取并持久化数据"。这时候一个专门的候选选择智能体,就可以拿着这些摘要去跨应用比对,找出哪些模式在两个以上的应用里反复出现。
这就好比一个精通十几种方言的翻译,不管你说的是四川话还是广东话,只要意思是"我想喝一杯热水",他都能听出来这是同一个需求。而如果换成一个只会按发音死记硬背的助理,四川话的"要杯热水"和广东话的"要杯热水"发音完全不同,他可能压根反应不过来这是同一件事,然后就把这两个需求当成两回事分别处理,重复劳动就这么产生了。
论文里还提到一个细节,叫做**预提取代码库整合**。
在真正跨应用做提取之前,先对每个新生成的应用内部做一次"自我整理",把同一个应用里重复或者高度相似的本地实现先合并成一个模块。这一步的意义在于,如果不先做这个内部整理,直接跨应用去找共享模式,那个负责跨应用提取的智能体会因为看不到应用内部已经有的重复,而漏掉本该被识别出来的抽象机会。实验数据也验证了这一点:去掉这一步之后,冗余度(Verbosity)从0.0994涨到0.1264,结构侵蚀度(Erosion)从0.0987涨到0.1263,代码行数也明显变多。
**上下文感知迁移:不止替换,还要顺藤摸瓜**
针对第二个问题,也就是迁移容易出错的问题,研究团队提出了**上下文感知依赖迁移**这套机制,里面有两个关键设计。
第一个是**提取轨迹**。
每次库维护智能体新增或更新一个共享库里的组件,都会同时生成一份结构化的记录文档,写清楚这个组件是从哪些应用的哪些代码块提取出来的、为什么这个模式可以被泛化、以及原来的代码应该怎么替换成新的库调用方式。这份记录会交给负责迁移的智能体,让它不用重新猜测两者的对应关系,直接照着说明书操作。
第二个是**调用图条件约束**。
对于每一个待迁移的候选项,系统会自动分析代码的调用关系,把这段代码的调用者和被调用者列出来,一起打包提供给迁移智能体,提示它需要联动修改的范围有哪些。
这两个设计合起来,效果类似于装修房子时,工头不光告诉工人"把这堵墙拆了",还附上一张标注了水管走向和电线位置的图纸。如果没有这张图纸,工人可能真的只是把墙拆了,但墙里埋的水管和电线该怎么接、接到哪里,全靠工人自己现场猜。猜对了没事,猜错了轻则漏水,重则短路。论文的消融实验显示,去掉调用图条件约束之后,准确率从77.21%掉到74.48%,冗余度和结构侵蚀度双双上升,说明去掉这个约束之后,迁移智能体确实会留下更多没清理干净的死代码。
**实验结果:数字说话**
研究团队在两个基准测试上验证了这套完整方案,一个叫WebGen-Bench,专门测试网站生成能力,另一个叫PaperBench,专门测试能否复现AI科研论文里的实验代码。
在WebGen-Bench上,完整方案(论文里称为SLA-FULL)相比零样本基线(每个项目独立生成,互不复用)取得了这样的结果:
代码总行数下降9.0%,token总量下降6.7%,冗余度下降38.0%,同时功能准确率还提升了1.5%,视觉外观评分基本持平。
在PaperBench上,代码行数下降5.0%,token量下降7.4%,结构侵蚀度下降10.4%,功能得分反而提升了2.6%。
这里有一个很值得说的对比。有一个后处理式的对照方法叫做Librarian,它的做法是等所有项目都写完了之后,再统一做一次库重构。它确实在MDL(一种衡量代码信息熵的指标,数值越低说明代码越规整)这个指标上做到了最优,但它的结构侵蚀度反而比零样本基线还要高。这说明单纯为了压缩代码体积而做的后处理重构,可能会把复杂度都堆到几个共享组件里,代码总量看着小了,但局部反而更臃肿难懂了。这也侧面证明了论文强调的观点:库的构建应该是在线的、跟着开发进度同步进行的过程,而不是完事之后再补救。
**维护阶段的真正考验**
光看初次构建的效果还不够,真正的考验在后续维护。
研究团队设计了一个后续实验:先让各个方法把八个项目按顺序都跑完,然后统一下发一个跨应用的政策更新,比如"所有联系表单必须校验邮箱格式包含@和句号"这样的通用规则,看看不同方法要改多少代码才能满足这个新要求。
结果非常直观。零样本方案因为每个项目完全独立,需要在每一个用到邮箱校验的项目里各自加一遍校验逻辑,总共改动了936行代码。而完整方案因为已经把邮箱校验的逻辑沉淀进了共享库里,只需要在库里改一次,绝大多数应用甚至一行代码都不用动,总改动量只有256行,减少了超过70%。
这个对比像极了你家小区突然通知所有单元都要统一更换门禁卡系统。如果每栋楼的物业各自为政,那就得挨个楼栋分别采购、分别施工、分别培训住户,十栋楼干十遍。但如果小区本来就有一套统一的门禁管理平台,物业只需要在后台改一次配置,所有楼栋自动同步生效。这里的差别不是谁更聪明,而是有没有提前把"共同的东西"抽出来放在一个大家都认的地方。
除了改动量变小,研究团队也检验了改动之后原来的功能有没有被破坏,结果显示完整方案在保持原有功能、满足新需求、维持视觉质量这几项上,表现都跟其他方法处在同一水平线,没有出现"为了省事而牺牲质量"的情况。
**库里到底装了什么样的东西**
论文还做了一个挺细致的分析,看共享库里沉淀下来的组件到底是些什么类型。
研究团队把高复用组件(被八个应用里至少四个引用的组件)分成三类:基础界面元件(比如按钮、页脚这类通用控件)、行为型钩子工具(比如处理本地存储、搜索过滤这类逻辑封装)、以及页面级或业务级模式(比如联系表单、卡片网格这类组合型组件)。
结果显示,除了那个后处理式的Librarian方法之外,其他方法提取出来的基础界面元件数量差不多,但完整方案在行为型工具和页面级模式这两类上明显更丰富。举个例子,朴素方案提取出来的往往是Header、Footer这种最基础的组件,而完整方案能进一步提取出useFilteredList(过滤列表逻辑)、FeatureCardGrid(功能卡片网格)这类更高阶的抽象。
这说明基于自然语言摘要的提取方式,确实拓宽了库的语义覆盖范围,而不只是把浅层的、显而易见的重复找出来。
论文还统计了库组件的复用广度分布。完整方案平均能沉淀出13.3个导出组件,其中被6到8个应用共同引用的高频组件平均有4.8个,相比之下朴素方案只有2.0到2.7个。这意味着完整方案不是简单地把复用密度堆高在少数几个组件上,而是真的构建出了一个规模更大、被更多应用真实用到的共享库。
**一个额外的发现:成熟的库能让新项目写得更好**
论文最后还做了一个挺有意思的补充实验,检验一个已经积累了一定规模的共享库,能不能反过来帮助生成全新的应用。
研究团队让一个普通的编码智能体去生成八个内容展示类应用,一组不给它任何共享库,另一组给它之前实验里已经沉淀好的完整版共享库,其他条件全部一致。
结果显示,有库可用的那一组,界面测试准确率从80.95%提升到84.35%,外观评分也略有提升,同时应用本地的代码量明显减少,即使把库本身的代码也算进去,总代码行数依然下降了约11%。
这个现象其实挺符合直觉:一个新手厨师如果厨房里已经备好了高汤、酱料这些半成品,做菜的时候就能省去很多重复劳动,把精力放在真正体现这道菜特色的部分上,做出来的菜反而可能更精细。反过来,如果什么都要从头熬制,时间和精力都被基础工序占掉了,成品质量未必能保证。
**论文也坦诚的局限**
研究团队在论文里很坦率地承认了两个局限。
第一个是目前还没有专门为这类问题设计的原生基准测试。现有的WebGen-Bench和PaperBench都不是为长周期库演化设计的,研究团队是把单任务基准改造成了顺序任务序列来凑合用的。这意味着被维护的应用本身就是基准测试产物,没有真实用户、没有提交历史,补丁量的减少能不能在真实项目里复现,其实还没有被验证过。
第二个局限是维护过程的度量方式。目前的可维护性评估主要靠代码行数、重复度、冗余度这些代理指标,这些指标能反映一部分信号,但没法完全衡量一个代码库是不是真的更容易被人理解、修改、扩展。真正的可维护性需要通过反复的实际变更才能被检验,而论文目前只测了一轮维护。
Q&A
Q1:Super Library Agent问题解决的核心痛点是什么?
A:当AI智能体需要连续生成多个相关的应用程序时,如果各自独立开发,会导致相同的业务逻辑在每个项目里重复实现,而且长时间维护还会让代码变得越来越冗余混乱。Super Library Agent的思路是让智能体在生成新应用的同时维护一个共享库,把跨应用复用的组件沉淀进去,从而减少重复、提高整体可维护性。
Q2:索引式候选提取和普通的代码相似度匹配有什么区别?
A:普通匹配靠比较代码文本或结构相似度,容易漏掉写法不同但功能相同的代码。索引式候选提取先把每段代码翻译成自然语言摘要,再基于摘要做跨应用匹配,这样即使两段代码写法完全不同,只要功能描述相似就能被识别为可复用候选,大幅提高了提取召回率。
Q3:这套方法在实际测试中效果如何?
A:在WebGen-Bench测试中,完整方案相比独立生成的基线,代码行数减少9%,token量减少6.7%,冗余度减少38%,同时功能准确率还提升了1.5%。在后续的维护测试中,需要改动的代码量比独立生成方式减少超过70%。






















