news 2026/9/8 8:04:45

用Simian精准定位重复代码,在CI中守住代码质量门禁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Simian精准定位重复代码,在CI中守住代码质量门禁

简介:代码重复检测工具 Simian(Similarity Analyser)的完整发行包,支持 Java、C#、C、C++、JavaScript 等多种语言,面向开发与测试人员,用于在持续集成与代码审查中快速定位重复代码、降低维护成本。压缩包共 59 个文件、约 3.43MB,核心内容包含 jar/exe 可执行程序、38 个 HTML 帮助文档、DTD/XSL 报表样式定义、运行所需的 DLL 与图片资源,以及许可证与说明文本;其中帮助文档覆盖功能介绍、安装指南、客户案例等,目录结构清晰,便于本地部署查阅。目前已有 1410 人浏览/学习。通过本包可获得开箱即用的 Simian 工具及完整文档,既可直接在命令行或构建脚本中使用,也能借助 Javadoc 了解 API 与检测规则;实际检测时可根据项目情况配置敏感度阈值,并输出重复代码报告供团队整改。持续使用可有效减少冗余代码,提升代码整洁度与软件稳定性。 维护过老项目的朋友,大概率都经历过这样的事:一个线上 bug 明明已经修好,隔几天又冒出来,排查到最后发现,同一个逻辑在另一个文件里还有一份几乎一样的拷贝,刚才的修复压根没覆盖到。这种问题的根子就出在重复代码上,而我今天要聊的 simian,就是专门用来定位这类问题的代码重复检测工具。

simian 全名 Similarity Analyser,是一个命令行工具,目标很单纯:扫一遍代码库,把重复的代码块全部揪出来。不管是 Java、C#、JavaScript 还是 Python,只要把源码喂给它,它就会基于词法分析找出结构相似的片段,输出一份可读的报告。它特别适合几类人:天天跟遗留系统打交道的开发、准备做大规模重构的团队、想在 CI 里加一道自动质量门禁的运维。接下来我从原理、参数、CI 集成到落地经验,完整走一遍。

1. 为什么先要跟重复代码较劲

1.1 重复代码的三个代价

很多人对重复代码的第一反应是“不好看”,但不好看只是最轻的代价。真正的代价在维护阶段才会爆发出来。第一个代价是修复漏洞要修多份,线上环境里同一个逻辑出现两次,就意味着你每次排查都要把所有副本找齐,漏掉一个就等于没修。第二个代价是需求变更要在每个副本上重复实现,加一个字段、改一个状态判断,原来改一处的地方现在要改五处,改完还得祈祷副本之间没有细微差异。第三个代价是认知负担,新接手项目的人看到五六份“几乎一样但又不完全一样”的代码,根本分不清哪一份才是可靠版本,连改 bug 都不知道以哪份为准。

这三个代价叠加在一起,就形成了典型的“代码腐化”正循环:重复越多,越不敢改;越不敢改,重复越多。等到团队决定重构的时候,才发现连重构的边界都很难划清楚,因为同样的逻辑散落在不同的包和模块里。所以工具检测重复代码,本质上是在给团队的“技术债”做体检,把最值得还的那部分债务先找出来。

1.2 simian 的检测原理:比你想的更聪明

simian 不是拿 diff 去逐行比较文本,而是先把源代码切分成 token 流。token 就是代码里的最小语法单元,比如关键字、变量名、数字、字符串、运算符,每个都对应一个类型。切完之后,simian 会在这些 token 序列里寻找重复出现的子序列,只要模式足够相似,就会被识别为重复块。

这意味着什么?意味着它抓的是“结构”而不是“字面”。举个例子,下面两段代码变量名完全不同:

int a = 10; a = a + 1; System.out.println(a);
int b = 20; b = b + 1; System.out.println(b);

普通文本 diff 会认为这是一大段差异,但 simian 在归一化变量名之后,会认定这两段是重复代码。这正是我们真正需要关心的重复:逻辑结构相同、只是名字不同,日后改逻辑的时候还是得改多份。用通俗的话说,它干的事情像用指纹比对去识别同一个人,而不是去比对照片的清晰度。

另外,simian 是直接扫描源码的,不需要先编译,也不需要项目能跑起来。这对大型遗留系统特别友好,很多老项目连依赖都拉不下来、根本编译不过,但只要你手里的源码是完整的,simian 就能扫。

1.3 同类工具怎么选

市面上能检测重复代码的工具不少,我用得比较多的主要是这三个。

对比项simianPMD CPDJSCPD
支持语言40+ 种,覆盖主流与冷门语言约 20+ 种,偏后端生态主要针对 JavaScript / TypeScript
分析方式token 级词法分析token 级词法分析AST / token 混合
输出格式text / XML / JSON / HTML 等text / XML / CSV 等JSON / HTML 等
CI 集成命令行 + 退出码,容易接入命令行 + Maven/Gradle 插件npm 脚本
授权商业授权,也有免费社区版开源免费(Apache 2.0)MIT 开源

简单来说,追求开源、团队预算敏感,PMD CPD 是非常好的备选;纯前端项目用 JSCPD 最省事;但如果你的项目是个多语言仓库,或者你希望一个 jar 包就能搞定所有语言、配置项又足够细,simian 更省心。我下面的例子都以 simian 为准,因为它在 CI 里的“一行命令 + 退出码”方案实在太方便了。

2. 5分钟跑通第一份重复报告

2.1 下载与准备

simian 的官方发布形式是一个独立 jar 包,不需要安装客户端。你只需要在机器上装好 Java 8 以上的运行环境,然后去官网把 jar 包下载下来,放到一个固定目录。我自己习惯放在~/tools/simian.jar,这样所有项目都能共用。

记得确认一下 Java 环境正常:

java -version

能正常打印版本号就行。接着下载 simian 的 zip 包,解压后找到里面的 jar 文件。我用的版本是 2.5.x,命令行参数在这几个版本里变化不大,你可以放心照着下面的命令操作。

2.2 第一条扫描命令与文本报告

进入你的项目根目录,执行:

cd /path/to/your-project java -jar ~/tools/simian.jar -includes="**/*.java" -threshold=6 -reportFormatter=text

这里有几个参数要解释一下。-includes指定要扫描的文件模式,支持通配符;**/*.java表示递归匹配所有子目录下的 Java 文件。-threshold=6表示重复块小于 6 行的直接忽略,这个默认值也是 6,一般不建议一开始调太低,否则报告会非常长。-reportFormatter=text指定输出纯文本报告。

在 macOS 或 Linux 的 shell 里,引号是必须的,不然 shell 会把**展开成具体文件列表,导致参数走样;Windows PowerShell 下同样建议保留引号。

跑完之后,文本输出大致是这个样子:

Simian 2.5.10 - Similarity Analyser Checking 1,245 files Found 24 duplicate blocks of repeated code across 38 blocks in the set * Duplicate 1 lines: 12 in file: src/main/java/com/example/OrderService.java starting at line 45 in file: src/main/java/com/example/PaymentService.java starting at line 130

最后一行很关键:它明确告诉你在哪个文件、哪一行到哪一行重复了。第一次跑完看到几十个 duplicate 很正常,千万别慌,这正好说明检测工具在发挥作用。

2.3 XML 报告也能导出

文本报告适合人看,但如果你想把扫描结果归档、生成自定义图表,或者交给其他工具消费,建议导出 XML 格式。命令也很简单:

java -jar ~/tools/simian.jar -includes="**/*.java" -threshold=6 -reportFormatter=xml -outputFile=simian-result.xml

-outputFile把报告写进文件,不会刷屏。XML 里会包含每个重复块的文件路径、起始行、行数等结构化信息,方便脚本解析。如果你喜欢更直观的网页展示,也可以试试-reportFormatter=html,生成一份带样式 HTML,直接扔给团队成员看比发一段命令行输出要友好得多。

3. 参数调优:让报告真正可执行

3.1 threshold 阈值怎么定

-threshold是我用下来影响最大的参数。设得越低,报告越长,误报也越多;设得越高,报告越短,但那些“只有 10 行重复但影响很大”的问题可能会漏掉。

我的经验是分项目类型来定。老项目、历史包袱重的,先跑-threshold=20或者-threshold=15,目的是把“巨型重复块”先暴露出来,这类重复通常最值得重构。新项目、代码质量要求高的,可以直接上-threshold=6,甚至压到-threshold=4,重点盯紧新增代码有没有走捷径复制粘贴。

我更推荐的做法是“探测法”:先跑一个更宽松的阈值,比如 20,看报告总量;如果结果很少或者没有,再把阈值降到 15、10、6,直到报告数量达到一个“能看完但不会太少”的平衡点。对我而言,这个平衡点通常是在 50 到 100 个重复块之间,再多了团队看了就麻木,无法形成行动力。

3.2 忽略规则不是越多越好

simian 提供了一批-ignore开头的参数,用来忽略一些细节差异,比如字符串、数字、变量名、花括号位置、标识符大小写等。我常用的组合是:

java -jar ~/tools/simian.jar -includes="**/*.java" -threshold=8 -ignoreStrings=true -ignoreVariableNames=true -ignoreCurlyBraces=true

-ignoreStrings=true会忽略字符串字面量本身的差异,比如一个项目里两段代码结构一样,只是提示文案不同,打开这个开关之后它们会被视为重复。-ignoreVariableNames=true会忽略变量名的差异,前面举的int aint b的例子,就是靠这个参数识别为重复的。-ignoreCurlyBraces=true用于忽略花括号换行位置的不同,在 C 系语言里经常能减少一批不痛不痒的报告。

但有一点要泼冷水:忽略规则不是开得越多越好。比如你把-ignoreStrings=true开了,那么“两段逻辑完全相同、只有硬编码文案不同”的重复就不会被报告出来,而这恰恰是修改时最容易被漏掉的那部分。我通常的做法是先不开任何忽略规则跑一遍,看误报的真实占比,再针对性打开某几个。千万不要为了“让报告变绿”而把所有忽略开关都打开,那等于白跑。

3.3 针对不同语言的配置细节

simian 对大部分语言是自动识别扩展名的,但有些场景需要手动指定。比如 C++ 的头文件和源文件混在一起、或者自定义了文件后缀,可以用-language=cpp强制指定,避免识别失败或误判。支持的语言里我常用到的有javacsharpcpppythonjavascript等。

Python 项目要特别注意缩进敏感的问题。Python 代码块本来就靠缩进区分,重复检测的“行数”和实际逻辑块在视觉上会有偏差,经验是先不要把 threshold 压得太低,8 行起步,跑完再人工翻一下报告。C/C++ 项目里,头文件里大量宏声明、函数声明,很容易因为结构相似被误报,这时候用-excludes=**/*.h或者把外部依赖目录排除掉,报告会干净很多。

另外还有一个-ignoreBlocks参数,可以跳过你指定的代码块类型。比如项目里有一段自动生成代码或序列化定义,不想被纳入重复检测,可以配置对应的块模式,让 simian 在扫描时直接跳过。

4. 把 simian 变成团队的质量门禁

4.1 脚本与 Maven 里的落地方式

命令行工具要变成团队都能用、能接进流程的东西,第一步是把它封装成脚本。我通常会在项目根目录放一个simian-check.sh

#!/usr/bin/env bash set -euo pipefail SIMIAN_JAR="${SIMIAN_JAR:-$HOME/tools/simian.jar}" CHECK_DIR="${1:-src/main/java}" THRESHOLD="${THRESHOLD:-8}" java -jar "$SIMIAN_JAR" \ -includes="$CHECK_DIR/**/*.java" \ -threshold="$THRESHOLD" \ -failOnDuplication=true \ -reportFormatter=text

这里的核心是-failOnDuplication=true。它会让 simian 在发现重复代码时返回非 0 的退出码,这样set -e的脚本就会直接失败,整个流程被卡住。这个开关是把它变成质量门禁的关键。

Maven 项目的话,可以用 exec-maven-plugin 在 verify 阶段调用。大致配置如下:

<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <phase>verify</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>java</executable> <arguments> <argument>-jar</argument> <argument>${project.basedir}/tools/simian.jar</argument> <argument>-includes=${project.basedir}/src/main/java/**/*.java</argument> <argument>-threshold=8</argument> <argument>-failOnDuplication=true</argument> </arguments> </configuration> </execution> </executions> </plugin>

这样跑mvn verify的时候,如果有重复代码,构建就会红掉。不过 exec 插件的参数引号在不同环境下坑比较多,我更推荐直接用脚本,CI 里调脚本最省事。

4.2 GitLab CI 里加一道闸

接入 GitLab CI 时,我的.gitlab-ci.yml里一般会多一个 job:

simian-check: stage: test script: - java -jar tools/simian.jar -includes="src/main/java/**/*.java" -threshold=8 -failOnDuplication=true -reportFormatter=xml -outputFile=simian-report.xml artifacts: paths: - simian-report.xml when: on_failure

跑失败时,XML 报告会自动作为构建产物保存下来,开发直接下载报告就能看到具体是哪几个文件、哪几行重复,不用去翻 CI 日志。如果是 Jenkins,思路完全一样,执行一段 shell 命令,再把输出文件归档就行。

这里有一个实践上的建议:如果你第一次在存量项目上开这道闸,千万不要直接用最严格阈值并立刻 fail。先跑几天“仅扫描、不拦人”的巡检模式,收集基线数据,再根据基线逐步收紧。不然团队第一天的 CI 就是红的,接下来就是无尽的豁免申请和抱怨。

4.3 阶梯式目标,别一刀切

把重复检测作为长期门禁,我建议分三个阶段走,每个阶段对应一个明确的阈值和目标。

阶段阈值目标门禁状态
第一阶段30清理超大块重复,解决最危险逻辑拷贝仅记录,不强制失败
第二阶段15消灭跨模块、跨服务重复,推进核心抽象扫描 + 警告,鼓励整改
第三阶段6-8常规代码重复长期可控CI 强制失败

第一阶段和第二阶段可以交替进行,比如先把 30 行以上的重复清完,再把阈值降到 15。这个过程其实是在用工具引导团队“由大到小”地还债,比“一刀切必须低于某个指标”更现实,也不会让团队觉得你在拿工具卡人。

5. 常见问题与团队落地经验

5.1 误报太多,先别急着关参数

使用 simian 最常见的抱怨就是误报。比如大量的 DTO、getter/setter、测试脚手架被判定为重复。我处理这类问题的顺序是:先看报告分布,再决定怎么处理。

现象常见原因处理方式
DTO / getter/setter 大量重复样板代码结构天然一致-excludes排除相关目录,或-ignoreBlocks跳过样板块
测试数据、字符串常量重复测试里同一组数据出现多次提取公共测试常量,或排除测试资源目录
自动生成代码被报告生成的代码存在拷贝排除generated-sourcestarget等目录
注释内容带来干扰相同注释被多处复制关注代码逻辑,肉眼过滤;或配置忽略注释块

另外我强烈建议把第一次扫描的结果保存下来作为基线,后续每优化一轮就重新扫一次,对比重复块数量是涨是跌。只看“最近有没有新增重复”比看“总量还有多少”更有指导意义。

5.2 从报告到重构的推进顺序

拿到报告之后,不要试图一次性把所有重复都清掉。我比较推荐的顺序是:先处理同一文件内的重复,再处理跨文件重复,最后处理跨模块重复。同一文件内的重复风险最低,抽个方法或者提取局部工具函数就能解决,适合给团队建立信心。跨文件重复通常意味着需要抽取公共类或工具类,风险中等。跨模块重复是优先级最高的,因为它往往暴露了模块边界划分不合理,这种也不建议在普通迭代里顺手改,最好单独排重构任务。

每次改完,重新跑一遍 simian,确认相关重复块从报告里消失,并且重复数没有因为“复制出一个新变体”而增加。这个“改前跑一遍、改后再跑一遍”的习惯,价值非常大。

5.3 团队推行重复检测的三条建议

第一条,先出基线报告,让大家对现状有共识。很多团队对“代码重复严重”没有量化概念,你把第一份扫描结果贴到项目群里,比嘴上反复强调管用得多。

第二条,门禁放在评审之前。在 CI 里把-failOnDuplication=true打开,合并请求不通过,重复代码就不会流到主干。注意要配合阈值阶段调整,避免短时间内把存量项目全部卡死。

第三条,允许阶段性豁免,但要有截止时间。对历史模块可以先放进白名单,只要求新代码和被改动到的代码通过检测,同时约定一个清理老债务的排期。这样团队不会因为“存量太重”产生对抗情绪,新代码的质量也能持续守住。

我个人在实际操作中的体会是:simian 不会替你把重复代码改好,但它能把藏了多年的卫生死角一次性拍在你面前。我第一次扫一个模块时,发现一段五十行的事务处理逻辑被复制到了五个地方,当时第一反应不是生气,而是庆幸现在才知道。从那以后,我每次大重构之前都会先跑一遍 simian,把重复清单当作战地图用。建议你也找个不忙的下午,把项目悄悄扫一遍,然后再决定要不要让团队把这道门禁开起来。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 8:02:55

本科毕业论文AI工具实测:从文献阅读到降重,十款神器组合方案

先交代一下背景&#xff1a;我自己写本科毕业论文那会儿&#xff0c;还没有现在这么多AI工具&#xff0c;靠的是 CtrlC/V 和泡图书馆&#xff0c;最后被导师在开题报告上批了“文献综述像说明书”。毕业之后做了几年技术内容相关的工作&#xff0c;也一直在帮学弟学妹看论文、改…

作者头像 李华
网站建设 2026/9/8 8:02:47

AI技能(Skills)设计与实践:从零构建可复用的智能体能力

1. 从“skills”聊起&#xff1a;为什么这个词突然成了技术圈的热词如果你最近泡在开发者社区或者关注AI应用落地&#xff0c;会发现“skills”这个词出现的频率越来越高。它不是一个新概念&#xff0c;但在大模型应用爆发之后&#xff0c;被重新赋予了非常具体的含义——指的是…

作者头像 李华
网站建设 2026/9/8 8:02:16

非结构化文档解析、向量索引与Embedding:RAG三件套部署实战

做Agent应用最烦的不是写那个调LLM的循环&#xff0c;而是“数据进得来、知识找得着”。做过RAG的兄弟应该都有同感&#xff1a;文档切得稀碎、向量化维度对不上、检索结果一团糟——这些锅最后全得背在Agent身上。前阵子给一套Agent框架搭基础检索设施&#xff0c;正好完整走了…

作者头像 李华
网站建设 2026/9/8 8:00:13

Cursor限制国内访问?Opus 4.6不可用?自建网关+API替换方案实操

1. 这波封禁风波&#xff0c;先别急着“换船”最近圈里讨论最凶的话题&#xff0c;就是 Cursor 对国内网络环境的限制&#xff0c;以及连带传出的 Opus 4.6 等模型无法正常调用的问题。很多朋友在群里说“一觉醒来&#xff0c;模型列表里多了个感叹号”“回复到一半直接报错”&…

作者头像 李华
网站建设 2026/9/8 7:58:58

Win10下Visual C++ 6.0 SP6绿色版部署与老项目编译实战指南

简介&#xff1a;面向需要在Windows 10上继续使用经典VC6的开发者&#xff0c;这份英文绿色版基于官方SP6二次绿化&#xff0c;专门解决旧编译器在新系统下的兼容痛点。其已集成调试崩溃补丁与Win10运行补丁&#xff0c;能保证日常编辑、编译和调试基本稳定&#xff1b;残留的“…

作者头像 李华
网站建设 2026/9/8 7:58:42

用Flask和SQLite开发轻量公文签收系统,告别纸质台账

简介&#xff1a;一套基于经典ASP技术的简单公文签收系统&#xff0c;面向ASP初学者及需要快速搭建轻量公文流转应用的小型组织。系统覆盖文件上传、流程定义、自动提醒、签收审批、状态追踪、版本控制与权限管理等核心环节&#xff0c;结构简单却功能完整&#xff0c;适合作为…

作者头像 李华