两棵重复的 Go 语法树通过实时分析中枢汇聚为一个可复用的实现。
博客 chevron_right 查找 Go 中的重复代码:Deslop 新增 Go 支持

查找 Go 中的重复代码:Deslop 新增 Go 支持

Go 支持采用与 Deslop 其他语言相同的结构化克隆检测流水线。这意味着用 tree-sitter 解析而非正则表达式、归一化标识符和字面量而非匹配原始代码行、将最严重的重复排在最前面而非抛出一堆无序警告,并在仓库变化时通过 LSP + MCP 提供实时反馈。

真正有用的问题,不再是两个 Go 文件是否包含相同的文本,而是它们是否用不同的外衣包裹着同一个想法。

Go 支持来自社区贡献

Go 支持由 Lance Haig 在 PR #310 中贡献。他的工作加入了解析器和最初的端到端语言接线,覆盖 .go 文件发现、归一化、报告、编辑器激活、测试夹具和回归测试。

Go 支持究竟添加了什么

Deslop 现在会发现 .go 文件,并用 tree-sitter-go 解析它们。解析器保留程序结构,同时移除那些不应让克隆藏起来的拼写差异:

  • 函数、字段、类型、包、空白标识符和标签等名称都会坍缩为统一的结构标记;
  • 整数、浮点数、rune、字符串、布尔值、nil 和 iota 都会坍缩为统一的字面量标记;
  • 注释会从比较中消失;
  • 复合字面量与函数字面量会保留其形状,因为这种形状是有价值的证据;
  • package 子句和 import 声明会被视为前置部分,因此重复的 import 不会伪装成重复的业务逻辑。

归一化后的语法树会进入工作原理中记录的同一套指纹、同级节点窗口、词元、信号融合、聚簇和排名阶段。Go 不是外挂在系统上的文本扫描器,而是共享引擎正式支持的解析器。

Go 也已经接入面向人类的界面。报告和筛选器中会将该语言显示为 Go,.go 文件会唤醒编辑器扩展,而实时闭环则会在 Go 缓冲区发生变化时刷新发现结果。

为什么重复的 Go 代码可能难以察觉

Go 的简洁是一项优势,但一致的代码形状也很容易被复现。人类会复制它们,编码智能体的复现速度更快。

常见的藏身之处都很熟悉:

  • 绑定输入、进行校验、调用服务、映射错误并写入响应的 HTTP 处理程序;
  • 只有实体名称或查询不同的仓库方法;
  • 使用相同守卫逻辑和上下文传递的中间件;
  • 散落在多个包中、重复出现的 DTO 到领域模型映射器;
  • 只有字面量略有变化的重试和错误包装路径;
  • 初始化代码逐渐膨胀成多个近乎相同测试夹具的表驱动测试。

gofmt 会让这些副本保持漂亮的一致性。另一个包中经过重命名的处理程序,在审查时仍然可能显得合理,因为代码整洁、可以编译,也符合本地风格。维护成本会在后来才显现出来:一份副本得到了安全修复,而另一份没有。

Go linter 不一定是克隆检测器

围绕这个问题的搜索通常会包含 “Go linter”,但 lint 涵盖了几种不同的工作。go vet、Staticcheck 以及 golangci-lint 收集的各项检查,非常擅长发现可疑结构、正确性问题、可简化之处、风格问题和未使用代码。它们处理的问题,与仓库范围内的结构性重复并不相同。

Go 已经有一个专门的克隆检测器:dupl,也可以通过 golangci-lint 使用。它会序列化 Go AST,用后缀树进行搜索,并刻意忽略节点值,让经过重命名的代码片段仍能匹配。它自己的文档也提醒,这种方法可能产生误报,并使用最小词元序列来控制噪声。

Deslop 位于工作流中的另一个位置。它将归一化后的结构指纹与近似匹配信号结合起来,按影响对发现结果排序,并向编辑器和编码智能体同时暴露实时结果。dupl 是一项实用的批量克隆检查;Deslop 则是智能体内循环中的仓库记忆。

检测器会寻找什么

一次有用的 Go 重复代码检查,需要覆盖的不只是精确复制粘贴:

  1. Identical code(Type I) —— 除允许的空白差异外,Go 源文本相同。
  2. Nearly identical code(Type II / 接近原文的 Type III) —— 重命名或略有编辑的副本,例如 CustomerHandler 变成 AccountHandler。
  3. Similar code(Type III) —— 仍有大量复制内容,但语句或控制流改动较大。
  4. 行为相同、代码不同(Type-4) —— 两个实现以明显不同的语法解决同一个问题;这个可选层使用嵌入(embedding)。

Go 的新解析器在前两类中作用最大。一旦名称、常量和注释不再主导比较结果,复制来的函数就不能仅靠让智能体重命名每个局部变量而消失不见。

预防胜过清理冲刺

在合并之后发现重复的 Go 代码很有用。在重复落地之前阻止它,则更好。

Deslop 的 MCP 服务器为编码智能体提供由实时工作区支撑的 find-similar 工具。在智能体编写另一个处理程序、映射器、仓库方法或测试辅助函数之前,它可以提交拟议的代码形状,并询问仓库中是否已经存在。强匹配会让任务从“编写新的实现”变成“复用或扩展规范实现”。

实际的闭环如下:

  1. 描述或起草你将要添加的 Go 代码单元;
  2. 在动笔实现之前调用 find-similar;
  3. 检查相似度最高的代码位置;
  4. 选择复用、抽取,或有意识地接受这处重复;
  5. 在文件发生变化时,让实时 LSP 刷新报告。

MCP 配置请参阅 AI 集成,完整的预防模型则见面向编码智能体的 MCP。

发现重复的 Go 代码之后该怎么办

不要自动重构。克隆是证据,不是裁决。

  • 抽取 —— 当两个处理程序、映射器或校验路径属于一个稳定的抽象,并且将一起变化时。
  • 复用 —— 当其中一个实现已经过测试且应作为权威实现,而另一个应当调用它时。
  • 接受 —— 当重复比抽象引入的耦合更便宜,或生成代码与测试夹具本就有意保持独立时。

真正危险的是意外的重复:两份本应一起变化的副本,却由不知道另一份存在的人分别维护。

在 Go 仓库上试试

按照入门指南安装 Deslop,打开一个 Go 工作区,然后运行:

deslop .

CLI 会输出 JSON、文本和 HTML 报告。VS Code 扩展会添加实时发现结果,以及按最严重者优先排列的簇视图。连接 MCP 服务器后,你的编码智能体就能在动笔之前查询同一份实时分析。

从第一个簇开始。它是实测影响最大的重复,而不仅仅是扫描器碰巧先遇到的那个。