AI生成的代码难以维护分支
AI能够低成本地产生大量代码变更,导致上游项目频繁重构,使得下游分支维护变得几乎不可能。与传统人工重构不同,AI变更不关心下游影响,造成分支质量下降和合并冲突。
如果你曾经维护过一个项目的分支,或者提交过一个长期未能合并的拉取请求,你很可能对这种情况感同身受:上游代码不断更新,当你执行 git rebase 或 git merge 时,原本独立且整洁的变更突然被合并冲突覆盖。有时这还可以应付——邻近的函数改变、文件移动、类型重命名,花几分钟修复并重新运行测试即可。但有时情况远没有那么简单。维护者可能对整个项目进行了格式化,或者重新组织了所有模块,甚至重写了你的变更所依赖的内部结构。这时你的分支不再是一个分支,而变成了一个考古项目。
这种痛苦由来已久。它不仅涉及枯燥的机械性工作,还伴随着风险。你最初编写的版本是基于特定的上游状态进行测试的,你对周围的代码了如指掌,每一行都有合理的原因。但经过三次痛苦的变基后,目标悄然改变——你不再试图保留原始变更的质量,而只是想让代码重新编译通过,让测试通过,在解决相同概念冲突于五个略有不同的文件时保持理智。于是,原本不存在的错误和安全漏洞被引入,分支的质量在每一次变基后逐渐下降。
令人烦恼的是,过去这种情况相对罕见。大规模、影响广泛的上游变更确实存在,但它们需要付出真正的人力成本。维护者必须判断一次大规模重构是否值得付出痛苦,并且必须亲自动手完成。因此,大多数项目存在某种自然的阻力。然而,人工智能消除了大量这种阻力。虽然廉价实验很有用,但它也改变了提交的形态。在“氛围编码”的项目中,提交往往变得非常庞大,以至于成为一种模式。一个提示词产生了一个影响广泛的差异(diff),维护者查看结果、运行测试、认可方向,然后提交。一次提交,大量更改。生成那个差异的成本微乎其微,但其他人整合它的成本却很高。
一次大型的AI生成重构对于按下回车键的人来说可能很便宜,但对于任何维护本地更改、下游补丁集或长期运行的拉取请求的人来说,代价可能极其高昂。项目不仅改变了行为,还改变了形态。而分支依赖于形态。分支不仅仅是代码的副本,它是一系列关于代码位置、函数划分、哪些内部边界足够稳定可以构建其上、哪些文件可以在不影响其他部分的前提下更改的假设。当每一次上游提交都重新洗牌这种形态时,分支就失去了锚点。
对于那些重要但无法立即合并的改动来说,这尤其糟糕。维护者可能同意想法但希望采用不同的API;改动可能需要更多测试;可能对某个部署有用但对上游来说太特殊;或者项目审查进度缓慢因为大家都很忙。如果上游项目不断被提示词重写,那么理智合并的窗口就会大大缩小。要么你的工作迅速合并,要么立刻开始腐化——不是因为逻辑错误,而是因为周围的代码被搅成了另一种形态。一段时间后,维护分支变得几乎不可能。你可以停止从上游更新,这意味着分支慢慢变成独立项目;你可以继续变基,这意味着花费越来越多的时间修复无关全局编辑造成的损害;或者你可以放弃,让AI从零生成你自己的竞争版本。
最后一个选项很糟糕,但恰恰是激励导向所在。如果上游将代码视为可随意根据心情全局重写的可丢弃文本,那么下游用户最终也会以同样的方式对待上游。为什么还要费心维护一个拒绝保持稳定形态的项目的分支呢?
故意的大规模变更和随意的变动之间存在区别。人类的重构通常会留下一些“伤疤”。你可以看到维护者试图最小化损害:差异有边界,提交消息解释原因,出现兼容层,旧路径存活一段时间,审阅者询问这是否会伤害下游用户。而AI生成的代码自然不会关心这些。它优化的是满足当前工作区中的提示词,不知道某人的分支中存在哪些补丁系列,不关心一个小函数重命名会在十个未合并的拉取请求中造成冲突,也感受不到让其他人重做工作的社会成本。可分支性曾经是项目的一个品质,而AI使得摧毁它比以往任何时候都更容易。