大型语言模型本质上是一台概率猜测机,而这些猜测机器正被用于重写生产代码。这被称为 AI 代码重构。我想我们必须问的问题是:让这些猜测机器在企业级代码中自由发挥真的安全吗?
传统的简单代码重构意味着改变程序。它涉及调整程序的内部结构,比如让它看起来像这样并进行修改,但我们并未改变其外部行为。对外部世界而言,这个程序的表现依然如故。如果外部视图没有改变,那么为什么要进行代码重构呢?
让我举一个例子说明为何需要这样做。假设代码中有一个名为 temp2 的变量,这几乎无法向任何阅读该代码的人传达有效信息。将其重命名为人类可理解的名字,例如“customer state”,这就是重构,具体来说是可读性重构。
或者,我们有一段逻辑代码,随着时间推移被多次复制粘贴,结果发现这段代码存在一个错误。现在需要在多处修复这个 bug,因此将所有相关代码整合到一个地方以一次性解决该问题,这是消除重复性的例子。
再比如我们有一个非常长的函数,它似乎永无止境,并在其中执行了半打不同的任务。如果我们将这些拆分成不同的小块,那就是为了降低复杂度的重构。这些都是代码重构的好处:内部结构变得更好了,但外部视图保持不变。
如果长时间跳过这些清理工作,就会累积成所谓的“技术债务”。这意味着代码变更所需的时间会比预期更长,甚至简单的修复也可能变得不再简单。因此,代码重构有助于减少技术债务。
事实证明,许多代码重构工作归结为模式识别——在代码库中反复出现的老问题。而谁最擅长模式识别呢?人工智能,也就是 AI 模型。感谢大家的收看。
稍后我们将回到关于部署这些 AI 模型(即概率猜测机)以更改生产代码的担忧,但首先让我们更好地定义 AI 代码重构是如何工作的。我认为可以将其分为两类:内联重构和代理模式重构。
第一类是内联重构,这通常发生在 IDE 或代码编辑器内部。在这里,它作为开发者的助手在输入代码时协同工作。例如,它可能发现像 temp2 这样笨拙的变量名并建议更好的名称;或者开发者在 IDE 中高亮一段代码,工具便会提议将其提取为一个独立的函数。
内联重构用于小型局部修复,直接发生在代码编辑器中,因此称为“内联”。而另一种类型是较新的代理模式重构。什么使重构具有代理性质呢?与一次建议一个编辑不同,代理工具接收的是一个目标。
这个目标可能是“将此库升级到最新版本”,这是一个很好的目标;但也可能更模糊,比如“清理整个模块”。设定好目标后,代理重构会在整个代码库中自主工作。
与内联不同之处在于这是自治的:它独立运行,利用代理的模式匹配能力并在进行更改时保持对大型代码上下文的关注。但这也将我们带回到那些担忧上:猜测机器在 largely 无人监督的情况下自主地扫过数百个文件。
它预测看起来合理的代码,因此可能会整理掉看似未使用但实际上因某种原因仍存在于代码库中的内容。例如,也许是一个处理闰年的晦涩函数?只有当日历翻到 2 月 29 日时你才会发现实际上需要它,但那时为时已晚。
因此,这需要一些严格的安全护栏。让我们谈谈这些安全护栏:代理重构通过循环工作来管理风险。我将把它画成一个圆圈,代理按顺序一遍又一遍地执行相同的步骤直到工作完成,尽管不同的代理工作方式可能略有不同。
正如我提到的中心起点是什么?我们从一个由用户提供目标开始。人类开发者交给代理某种要实现的目标,可能是升级库或清理模块等。现在过程从制定计划开始:代理计算出需要更改的内容及其顺序的有序步骤列表,并会不断更新该计划。
为了帮助制定计划,它必须执行读取操作,即打开涉及的文件并映射其中的内容,如文件中的函数、类以及它们如何组合在一起。然后进入搜索阶段:代理在项目的其余部分及依赖库中搜索相关代码。
如果有一个函数从许多其他地方被调用,那么就是搜索阶段会找到所有这些调用点。此时上下文已基本确立,代理进入下一步:报告。
它会返回按优先级从高到低排列的发现结果,每个发现都有各自的推理依据。例如:“嘿,这个函数忽略了异常,这将在生产中隐藏故障,这是建议的修复方案。”这将是一份报告中非常有用的条目。
到目前为止,人类开发者除了设定目标外做得并不多,因为这是自主的。但正是在这里,人类重新进入循环:基本上是从报告中挑选代理应该修复的发现结果。通常在此阶段会有某种程度的人类审批。
一旦我们批准了要处理的内容,代理很可能会创建当前代码库状态的某种快照作为起始状态,以便在需要时可以撤销更改。
如果必要的话,它会介入并执行其操作。它实际上会开始修改代码,并对需要修补的特定行应用补丁。在此过程中,它会携带循环早期积累的所有上下文信息。一旦完成修补步骤,我们就进入验证阶段,此时代理会编写并运行测试,同时重建项目。
由于某些内容实际上已经发生变化,它可以显示与初始快照的差异对比,以及我们逐行修补的具体内容。如果测试失败且需要修复某些问题,它可以回滚更改,并再次进入循环反复执行。这个循环正是回答视频开头提出的问题的关键:即 AI 代码重构是否安全?
正如你在该循环中所见,代理式的概率性猜测并不会直接进入代码库。还记得那个晦涩的闰年函数吗?在这个循环中,它可能实际上不会被移除,因为要么人类开发者不会在收到的报告中批准该操作,要么验证阶段的失败测试用例很可能会捕捉到它。
这实际上只是运行测试和构建的过程。这意味着整个循环可以嵌入 CI/CD 管道中,与日常开发并行运行。代理式 AI 就像另一位开发者一样。在离开循环之前,还有几件事我想指出。首先是这里的修补步骤可以完全跳过概率性猜测,转而使用确定性方法。
例如,某些工具会将代码解析为结构化树,比如抽象语法树或更丰富的无损语义树(后者还跟踪类型和格式),然后作为对该树的操作进行修改。以重命名为例,它会在树中找到符号,然后更新所有对它的引用。
这类操作是确定性的而非概率性的。此外还有强化学习。在验证步骤中,每一个被接受或拒绝的修复都是一个训练信号;每一个通过或未通过的测试用例也是一个训练信号。因此,模型会被训练以倾向于那些被接受且能通过的更改。
这意味着 AI 代码重构建议会随着时间推移而不断改进。那么,这把我们带向了哪里呢?AI 代码重构可以通过在大型代码库中进行内部更改来帮助偿还技术债务,但它必须以验证输出的方式进行。
至少在今天,它仍然需要让人类保持在循环中参与决策。