
简介SimianSimilarity Analyser是一款代码重复检测工具适合Java、C#、C/C、JavaScript等多语言项目开发者用于定位冗余代码与复制粘贴片段降低维护成本与缺陷风险。压缩包为工具完整发布包共59个文件、3.43MB以可执行JAR/EXE和运行依赖DLL为核心同时包含官方HTML帮助页、PDF授权说明、DTD/XSL报告格式定义、界面图标等便于快速接入Maven或Gradle构建流程并设定检测规则。已有1410人学习下载。包内附有JDK 1.5.0_13相关日志、版本更新、安装指南及客户案例页面适合在持续集成场景中实施重复代码检查的团队参考。借助Simian生成的重复代码报告开发者可以精准定位问题模块并重构持续保障代码整洁度与可维护性。 做代码评审的时候我最不想看到的不是那种逻辑绕三圈的烂代码而是一看就知道是复制粘贴过来的重复代码。逻辑复杂最多是让人头大重复代码是真的埋雷——修 A 处的时候忘掉 B 处线上问题就是这么来的。后来我开始在项目里引入simian这个轻量级的代码重复检测工具每周扫描一次把重复点直接列成清单丢给相关人员去处理。今天就把这套用法和踩过的坑整理出来给也在搞代码治理的朋友一个参考。simian 的定位很纯粹它就是一个命令行下的重复代码检测器基于 Java 运行整个工具只有一个 jar 包不需要装数据库、不需要启动服务、不需要引入一堆插件。你给它一堆源码文件它告诉你哪些地方重复了重复了多少行严重程度有多高。适合谁用技术负责人、做工程质量改进的工程师、搭 CI 流水线的同学以及所有被重复代码坑过、想给代码库做一次“体检”的人。1. 为什么我会盯上 simian 这个重复代码检测工具1.1 重复代码到底有多大杀伤力先说说我为什么对重复代码这么敏感。以前维护过一个订单模块里面有几个方法做金额格式化逻辑几乎一样只是字段名不同。有一次业务规则调整要求金额保留两位小数我在主流程方法里改了以为完事了结果测试还报错。查了半天发现同一个格式化逻辑散落在四个方法里我只改了其中一个。这种问题靠人工 review 很难根治因为人总是会漏而自动化工具不会。重复代码的代价是叠加的可读性上读代码的人要反复跳过相似片段理解成本翻倍维护上一处逻辑更新其他复制点容易成为漏网之鱼形成“改了这一处、漏了那一处”的经典事故测试上相似代码往往没有独立测试覆盖出问题要靠线上用户帮你发现。更隐性的是重复往往是设计问题的信号——该抽方法没抽、该建父类没建、该用策略模式却图省事直接复制。所以重复检测不只是“找重”更是帮团队发现代码结构上的坏味道。1.2 同类工具不少为什么先用 simian 试刀代码重复检测这个领域其实不算冷门我当时先列了几个备选PMD 自带的 CPD、SonarQube 的重复度分析、jscpd、还有 .NET 圈的 dupFinder。纠结了半天最后先用 simian 跑了半个月原因是它足够轻接入成本无限接近于零。工具运行方式跨语言接入成本适用场景simian单 jarJava 环境即可支持 C/C、Java、C#、JS 等十几种极低命令行直接跑CI 快速反馈、轻量扫描PMD CPD随 PMD 分发多语言较低但规则体系较重Java 项目已有 PMD 时SonarQube独立服务端多语言高要建服务、配库全链路质量平台jscpdNode CLI多语言中依赖 Node 生态前端项目常用dupFinderJetBrains 工具链C# 为主中.NET 项目对一个以 Java 为主、CI 已经用 Jenkins 的老项目来说simian 那种“拿 jar 就能跑”的风格太适合了不需要改动现有构建体系不用引入数据库想扫哪几个模块就扫哪几个模块。就算你项目里全是 C、Python 或者 JavaScript它也能识别。这正是我推荐先试它的核心理由用最低成本把重复代码的底裤扒出来。2. simian 的工作机制与检测逻辑2.1 基于行与 Token 的比较方式很多第一次接触 simian 的人会误以为它跟编译器看 AST抽象语法树一样能分析语义。其实不是simian 的做法要原始得多但也正因为原始它才能做到跨语言通吃。它把源文件按行拆开再把每一行解析成一串 Token标识符、关键字、操作符、字面量等过程中会抹掉空格、空行、注释这些对重复判定无意义的干扰项。然后它会用一个滑动窗口在 Token 序列上移动窗口大小由 threshold 参数控制一旦发现两段 Token 序列完全一致就把这段位置记下来作为重复候选。你可以理解成两个人各拿一沓代码逐行对但允许变量名和字符串内容不同。这种机制的好处是简单、快不依赖任何语言的语法规则坏处是它不会“理解”代码两个逻辑等价但写法形态完全不同的方法它可能判断不出来。这里有个关键点simian 检测的是“相似片段”不是“复制粘贴”的严谨证据。所以看到结果别急着当结论它只是给你指路最后判定还是得人来看。2.2 阈值设置是核心中的核心simian 最核心也最影响结果的参数是-threshold它决定“连续多少行算重复”。这个值设得太低比如 3 或者 4报告会爆炸到处都是“疑似重复”人工根本看不过来设得太高比如 20普通的方法级重复几乎全被漏掉扫描失去意义。我的经验是Java 项目一般从-threshold6开始试跑完看报告数量再调。如果报告里太多 getter/setter、DTO 字段这类结构性重复就往上调如果明显重复的代码没被抓出来就往下调。以我个人实践8 到 10 是一个比较舒服的区间既能抓住方法级重复又不会把样板代码全部翻出来。当然不同项目风格差异大最好先用一两个模块跑几轮找到适合自己的阈值再铺开到全项目。3. 环境准备与命令行实操3.1 安装与版本信息simian 是一个发布多年的老工具官网下载后拿到的就是一个 jar 文件。运行前提是机器上有 Java实测 Java 8 就能稳定跑不需要多新的版本。下载完我习惯把它放进一个统一目录比如~/tools/simian/然后加一个 shell 别名方便随手调用。alias simianjava -jar ~/tools/simian-2.5.10.jar顺手先看一眼帮助文档确认参数无误simian -help输出会列出支持的语言、报告格式和所有可选参数建议第一次用的人花五分钟读一遍比到处查资料管用。这个工具体积小、启动快扫描一个中等规模的 Java 项目大概只要几十秒CI 里直接调用完全没问题。3.2 最基本的一条命令怎么跑先拿一个小模块试水。假设项目在/data/build/service想扫src/main/java下的所有 Java 文件执行java -jar ~/tools/simian-2.5.10.jar \ -threshold8 \ /data/build/service/src/main/java/**/*.java注意路径里的**/*.java这是 simian 自己识别的递归通配符不是交给 shell 展开的所以一定要用引号包住。运行完控制台会输出类似这样的摘要Found 3 duplicate code blocks in 2 files. Total duplicate lines: 42它会把每个重复块的起始行列号、涉及文件、重复行数列出来。第一次跑的时候那种“原来这里也有一份”的感觉会特别强烈。3.3 整个项目扫描的完整示例真实项目中我不会只看控制台文本而是生成一份 HTML 报告存到指定目录方便发给团队看。下面这条命令基本是我的标准用法java -jar ~/tools/simian-2.5.10.jar \ -threshold10 \ -failOnDuplicatestrue \ -reportFormatterhtml \ -reportFiletarget/simian-report.html \ /data/build/service/src/main/**/*.java \ /data/build/service/src/test/**/*.java-failOnDuplicatestrue这行非常关键。把它加到 CI 脚本里一旦检测到重复就返回非零退出码构建就失败了等于让机器自动卡住重复代码入库。第一次接入时别急着开这个开关等重复量降到可接受范围再开否则流水线会红得没法看。报告中会按文件维度展示重复块点击就能跳到重复位置做整改会议时直接投影出来比口头说一百句都有效。4. 参数配置全解析与实用建议4.1 常用参数一览表simian 的参数不少但我实测下来真正能派上用场的就那么几个整理成表格如下方便大家直接抄作业。参数作用我的建议-threshold连续多少行判定为重复Java 项目从 8 起步-language指定扫描语言如 Java/C/C# 等多语言项目建议分别指定-failOnDuplicates检测到重复时让进程返回非零码CI 里设为 true-reportFormatter报告格式text / xml / html需要分享时用 html-reportFile报告输出路径固定到 target 目录-ignoreCurlyBraces忽略花括号差异建议 true减少误报-ignoreIdentifiers忽略标识符变量名、方法名等差异默认关开启后会更宽容-ignoreLiterals忽略数字、字符等字面量差异按需开启-ignoreStrings忽略字符串内容差异按需开启常用于日志代码多的情况-ignoreCharacters忽略指定字符如 BOM 头编码不干净时很有用这里重点提醒一下别一下子把所有ignore*参数都打开。开得越多匹配就越宽松漏报风险越大。默认配置检测出来的是“完全复制”为主开启-ignoreIdentifiers之后两个方法只是变量名不同其他一样也会被标记为重复这正是很多人想要的“逻辑重复”识别。但也有副作用比如两个方法本来只是巧合结构相似开完这个参数也会被列进去所以每次调整参数后要拿几个已知重复点做回归看结果是否符合预期。4.2 规则集配置怎么用正则控制忽略内容有人会问simian 能不能配置“忽略某些目录或文件”或者“忽略某种特定结构的重复”。它不像 SonarQube 那样有丰富的规则集但可以通过 glob 表达式和少数几个 ignore 参数实现类似效果。想排除测试代码、生成的代码就在传入路径层面控制java -jar ~/tools/simian-2.5.10.jar \ -threshold8 \ /data/build/service/src/main/java/**/*.java \ !/data/build/service/src/main/java/com/example/generated/**/*.java在路径前加!表示排除这个方式在处理 Java 项目里的 generated 目录时非常管用。如果用 Lombok 生成了一遍 getter/setter自己又手写了一遍或者用 OpenAPI 生成的模型特别多扫描结果会惨不忍睹必须先把这些噪音排除掉。4.3 与 CI 的集成方式simian 接入 CI 最直接的方式就是把上面那条命令放到流水线脚本里。我平时在 Jenkins 项目下会建一个quality.groovy里面有一个专门的 stagestage(重复代码扫描) { sh java -jar tools/simian-2.5.10.jar \ -threshold10 \ -failOnDuplicatestrue \ -reportFormatterxml \ -reportFilebuild/reports/simian.xml \ src/main/java/**/*.java }如果团队用的是 Ant 构建可以走 simian 自带的 Ant Task将 taskdef 配置好就能在构建里直接调用。Maven 项目则可以用 exec-maven-plugin 执行 jar或者找社区维护的 simian maven 插件。我不太建议为了引入 simian 专门换个构建工具命令行方式已经足够轻量再包一层反而增加维护成本。CI 集成的目标是让重复率成为门禁的一部分而不是辅助参考。刚开始可以先只生成报告、不阻断构建跑两个迭代周期让大家熟悉规则之后再打开-failOnDuplicates。5. 实际踩坑记录与排查经验5.1 误报与漏报的平衡我接入 simian 的第一周报告炸了。最典型的一类误报是大量 DTO 类之间的字段定义几乎一样比如private String userName;这种每个类都有 20 多行相似代码被标记为重复。这种不是业务意义上的坏味道就是实体结构的正常相似。解决方式很简单扫描范围排除 DTO/VO 包或者把 threshold 从 8 调到 12让这类短重复直接过滤掉。反过来漏报的问题也遇到过。有些重复发生在不同的文件里但方法签名、变量名不同完全复制行数可能只有 4 行不达阈值。这种就需要开-ignoreIdentifiers让 simian 忽略标识符差异后再判断。不过开了之后类似for (int i0; in; i)这种循环结构也会被算进去报告会再次膨胀。我的习惯是分两层跑第一次用默认配置抓“直接复制”的重复第二次开-ignoreIdentifiers抓“换个变量名的重复”两边报告对比着看兼顾漏报和误报。5.2 编码与中文注释问题simian 对文件编码比较敏感。有一次扫描一个老模块报告里把两个实际上完全一样的文件标记为“不重复”细看发现差异行全是乱码。查了一圈是文件编码不统一惹的祸——部分文件是 UTF-8部分是 GBK同一段中文注释在不同编码下解析出的字节不同被当成了内容差异。遇到这种情况我建议先统一项目源码编码或者至少在做扫描之前用-ignoreCharacters把 BOM 等特殊字符忽略掉。比如文件头携带有 BOM 时加上-ignoreCharacters\uFEFF我后来干脆把仓库里的源码全部转成 UTF-8不光是让 simian 结果更准也免得其他工具跟着踩坑。编码问题排查起来特别烦因为它不会报错只会让结果悄悄变得不可信。5.3 大仓库扫描性能优化一个几百万行的老仓库扫一次可能要两三分钟看起来还行但如果每次提交都全量扫开发同学会有意见。我试过几种优化方案第一按模块拆开扫描每个子模块单独出报告定位问题也方便第二用 glob 排除掉 generated、resource、前端构建产物这类目录扫描体量能减少一大截第三把 threshold 适度调高因为阈值越高需要匹配的行越长候选集合越小速度也会更快。还有个小技巧-verbose参数可以输出扫描进度看到底卡在哪个文件上。我遇到过一种情况某个 Java 文件外观看很普通但因为整行只有一个超长字符串simian 解析时很慢。这种文件不用特别处理但知道原因之后就不至于瞎调参数了。总体而言simian 的性能是足够日常使用的真正的瓶颈往往不是工具本身而是扫了一堆没必要扫的文件。6. 一些真实的使用体会6.1 门禁阈值设多少才合理很多团队第一次引入 simian 就问“阈值到底设成多少合适”这个问题没有标准答案但我试过几个项目后有一个大致规律Java 后端项目通常 8 到 12 比较合理前端 TS/JS 项目可以设低一点比如 6因为前端代码方法普遍短一些设太高容易漏。老项目第一次接入时建议设 15 甚至 20让报告看一眼能处理完后续每轮迭代再逐步下调给团队留出重构缓冲期。最怕的一种情况是老板拍脑袋定了个 5开发同学每天被海量误报淹没两天之后大家直接把工具卸了。门禁阈值一定要和团队当前代码质量、重构能力匹配先让机器抓住明显问题再慢慢收紧这才是可落地的节奏。6.2 从重复率报告到重构落地simian 给出报告只是第一步怎么把报告变成代码改动才是关键。我的实践是每次扫描后把重复块按模块分派给对应的开发同学要求一周内提交整改说明要么抽公共方法要么提取父类要么用策略模式至少也要在代码注释里写明“此处与某处重复改动时需同步”。这个强制动作让重复数量在三个月内降了一半以上。不过我始终觉得simian 这类工具的价值不是追求“零重复”的数字好看。有些看起来重复的结构比如两个参数类型不同的重载方法硬抽到一个函数反而牺牲可读性这种就没必要动。工具的价值是把重复暴露出来让人去判断哪些是坏味道、哪些是无害相似而不是机械地消灭所有相同代码。最后分享一个让我印象深刻的场景接入 simian 三个月后新同事入职看报告指着一个重复块问“为什么这两个方法要各写一遍”那一刻我就知道这套工具带来的不只是重复率下降更像是在团队里建立了一种“遇到重复就要顺手清理”的潜意识。如果你也被复制粘贴代码折磨过真的可以试试拿 simian 给你的代码库做一次全身检查说不定收获比预期大得多。本文还有配套的精品资源点击获取