标题图,展示针对 AI 生成代码的四层重复代码检查清单。
博客 chevron_right AI 生成的代码与重复代码:该检查什么

AI 生成的代码与重复代码:该检查什么

AI 并不需要生成有缺陷的代码,就能让一个代码库更难维护。它只需在有人注意到之前,用两种略有差异的形态把同一个想法实现两遍即可。

这就是 AI 时代的重复代码问题。代码可能能够编译。测试可能能够通过。拉取请求看上去可能也合情合理。但此时仓库里已经有了两份实现,将来需要同样的修复。

完整的实现脉络,请参阅 Deslop 文档页面:研究背景。本文是面向那些正在权衡该先检查什么的团队的精简版本。

AI 生成的代码会制造技术债务吗?

会的。这种风险并不神秘,也并非 AI 独有。几十年来人类一直在复制代码。变化的是吞吐量。

AI 编码助手能够快速产出一个看似合理、仓库形态的答案。当提示词与此前的任务相似时,答案往往也会呈现出熟悉的形态:又一个仓储类、又一个校验函数、又一个映射器、又一个重试包装器、又一个测试夹具。这在当下很有用,在日后则代价高昂。

研究方向也在朝同一个方向发展:

这些都不意味着每一行 AI 写的代码都是糟糕的。它意味着 AI 生成的代码应当接受与人类代码相同的仓库级重复检查。

重复代码检查应当查找什么?

一项实用的 AI 时代重复代码检查不应止步于精确的行匹配。它应当查找四个层次的相似性:

  1. 精确重复代码:复制后仅改动了格式或注释的相同代码。
  2. 重命名的重复代码:结构相同、但变量名或常量不同。
  3. 近似重复代码:逻辑大体相同,但插入、删除或重排了若干语句。
  4. 行为相同、代码不同:用不同语法解决同一问题的两份实现。

经典的代码克隆检测研究将它们称为 Type-1、Type-2、Type-3 和 Type-4 克隆。Deslop 采用了这些思想,但详细的算法说明放在了研究背景中,因此本文不再重复整个文档页面的内容。

为什么行匹配还不够

基于行的重复代码工具擅长找出显而易见的复制粘贴。当 AI 改变了表层形态时,它们就力不从心了:

  • customerId 变成了 accountId
  • foreach 变成了推导式。
  • 一个辅助函数被复制后挪进了另一个类。
  • 同一条校验规则以不同的分支顺序被重写。

这正是 Deslop 从解析后的语法树而非原始文本入手的原因。它用 tree-sitter 解析每个文件,然后剥离标识符名和字面量名,使得重命名后的副本仍能匹配。它对树结构进行指纹提取,借助兄弟节点窗口和 MinHash 把网撒向近似重复,并可选择性地加入嵌入(向量嵌入)以匹配行为相同的代码。简而言之:它先比较代码结构,而不是先比较行。

完整的审计链路见工作原理研究背景

发现重复代码后该怎么办

不要把每个克隆都当成一个 bug。把它当成一个决策。

提取(Extract):当这些副本显然是同一个抽象、并且会一起变化时。

复用(Reuse):当其中一份实现应作为权威实现、其余实现应当调用它时。

接受(Accept):当重复是有意为之时——夹具、生成代码、兼容垫片,或两条现在看起来相似、但预期会分道扬镳的路径。

错误不在于接受重复,而在于因为无人度量而在不知不觉中接受了它。

为什么 Deslop 要给发现项排名

Deslop 按影响对簇进行排名,让影响更大的发现排在前面。

这对 AI 编码智能体很重要。智能体不需要一堵克隆数据的墙。它需要一个小而结构化的答案:

  • 哪个重复簇最重要,
  • 字节范围在哪里,
  • 这个簇为什么被标记,
  • 信号来自结构、词法相似度,还是嵌入。

这就是 Deslop 以 JSON 为先、并提供 LSP/MCP 路径的原因:编辑器与编码智能体都能在接近编辑动作的位置读取同一份结构化发现。