
简介Simian是一款广受欢迎的代码重复检测工具这份资源包基于其2.3.35版本完整提供了跨平台运行所需的核心组件与配套资料适合Java、C#、C/C、JavaScript等语言的开发团队使用。通过检测源代码中的相似语句结构开发者可以有效发现复制粘贴编程、冗余逻辑等问题并将Simian集成到持续集成流水线中防止新增重复代码。包内共59个文件既有jar/exe主程序与DLL运行库也有HTML官方文档、PDF许可证、DTD/XSL配置模板、GIF/PNG界面图标等整体压缩包仅3.43MB轻量且便于分发。目前已有1416人学习下载。借助包内的javadoc文档、安装指南、功能列表和变更说明使用者不仅能快速完成Maven或Gradle环境集成并设定检测阈值还能深入理解报告格式与规则参数同时附带的JDK日志和许可证文件有助于排查环境问题、规范商业使用。在日常开发中持续运用Simian可有效优化代码结构、降低维护成本提升项目长期可维护性。这一工具包非常适合正在搭建代码质量检查体系的技术团队以及希望快速上手重复代码检测的个人开发者。1. 只按文件路径和行号说话simian是什么、能接住哪些场景接手一个没人愿意碰的遗留模块时最怕的不是代码风格乱而是同一段逻辑被复制到了三个地方你只改了其中一个入口线上另一个入口还在走旧规则。干这种活的同事多半会给你推荐一个工具名simian。它就是一个Java编写的命令行代码重复检测工具扫一遍指定目录把所有文本层面重复出现的代码区段标出来精确到文件路径、起始行号和重复长度。它解决的是一类很具体的问题在动手改任何一处代码之前先知道哪些地方还藏着同一个逻辑的副本。适合在重构前画风险地图、在CI入口做硬拦截、在代码评审阶段定一条可量化的重复率红线。2. 先立住判断simian的检测原理、同类工具选型与最小跑通2.1 重复检测靠的是token不是语义原理与它的能力边界simian扫描源文件时会先把注释、空白和空行全部丢掉再把代码切成一串词法单元也就是token。它不看你的代码结构树不做数据流分析只在一个滑动窗口里找完全相同的token序列。只要两段代码的token序列一致就判为重复。这个设计带来两个直接后果。第一个是它对手工复制粘贴的“包装”非常敏感把变量名改了、换行缩进打乱了、中间塞了几个空行只要骨架课体一致照样命中。第二个是它对“语义相同但写法不同”完全无能为力——同一个逻辑用for循环写一遍、用Stream流写一遍token序列完全不同它不会认为这两段相关。所以它擅长的是“机械副本检测器”而不是“设计坏味道评审员”。你用它来找的是“谁和谁长得一样”而不是“谁应该长得一样”。能力边界还体现在输出粒度上它报告的最小单位是“重复块”不是“重复的方法名”或“重复的类”。单文件内部重复它能查跨文件、跨目录重复也能查。对大工程来说正因为不读AST扫描速度才快得能塞进流水线。一个几千文件的模块在线下环境里用单JAR跑通常也就是一到两分钟出报告。这个速度决定了它适合当门禁而不是只在重构月被拿出来用一次。2.2 和CPD、SonarQube、jscpd怎么选一张对比表说清差异做重复检测的可选工具不少团队里最容易争的是“用自带的还是单独接一个”。我接触过的项目里最常见的四个候选是simian、PMD自带的CPD、SonarQube平台里自带的重复度量以及前端圈子里常用的jscpd。它们各有各的适用场景不是简单的谁替代谁。候选工具运行形态语言覆盖与CI集成最合适干什么simian单JAR命令行多语言退出码可直接当门禁仓库级重复红线、重构前扫雷CPDPMD自带命令行/构建插件多语言需解析报告再判断和PMD规则检查做成一套SonarQube重复度量服务端平台多语言在平台体系内团队持续看板、历史趋势jscpdNode命令行前后端脚本为主CLI可直接调用前端仓库快速自查选型逻辑其实很简单看你们想要的是“一个平台”还是一个“硬门禁”。SonarQube是好东西但它重要部署服务端、要维护规则集、要和现有账号体系打通而且它给的重复率是汇总指标定位到具体哪个文件哪一行重复时体验远不如专用工具细致。CPD能做类似的事但它的输出格式和阈值控制没有simian直接。所以常见做法是想在仓库里加一条“重复超过阈值就构建失败”的硬规则又不想为此引入一套服务端基础设施simian是最轻的选择。它不跟漏洞检查集成不值得为它建平台它只负责一个死磕的指标那就是重复。2.3 本地最小跑通解压、写样例、跑出第一份报告先把工具跑起来再说别的。simian的发布形态是zip包解压后得到一个带版本号的JAR目标机器只要装了Java运行环境就能用。下载来源通常是项目主页的release产物我用的时候习惯把zip固定放到工具目录而不是直接扔在/tmp因为后面CI里要复用它版本得锁住。# 解压工具包路径按你本地实际位置调整 mkdir -p ~/tools/simian unzip simian-*.zip -d ~/tools/simian # 确认Java环境可用 java -version # 准备一个带重复代码的示例工程 mkdir -p /tmp/simian-demo/src cp /tmp/simian-demo/src/OrderChecker.java /tmp/simian-demo/src/PayChecker.java # 两个文件内容故意保持一样只改类名和几个变量名这段准备步骤里最后一条cp不是生产技巧是验证技巧把同一个文件复制一份改几个标识符再让simian扫。它能命中说明工具按token判重的机制是生效的它扫不出来多半是关键参数被调坏了。# 最小扫描命令指定阈值指定扫描范围 java -jar ~/tools/simian/simian-*.jar \ -threshold6 \ $(pwd)/src/**/*.java这里的-threshold6表示“重复块超过6行才报告”单位是代码行规模不是字符数。阈值越小报告越碎越大越容易漏。$(pwd)/src/**/*.java用的是simian自己的通配符语法不是shell的glob必须用双引号包起来否则bash会先把它展开simian就收不到真正的匹配规则。第一次跑通看到输出里出现类似“Found duplicated ... blocks”的文字说明环境没问题后面所有调参都在这条命令的形态上做文章。3. 从“能跑”到“能信”核心参数与典型命令3.1 threshold决定准心不同阈值的误报与漏报规律把simian跑通不难难的是让报告“可信”。最影响可信度的就是-threshold。它的默认值是6意思是重复内容达到或超过6行才被报告。这个数字不是玄学它其实是扫描的焦距焦距短看到的东西多、噪声也多焦距长看到的都是大块重复但小段漏网全放过去了。阈值取值报告特点推荐场景4短块大量涌入误报明显上线前的专项排查6默认敏感和噪声比较平衡日常告警810只报大块误报少CI流水线硬门禁我一般建议第一次扫描用6先把全貌拿回来看报告里重复块的size分布。如果6行到10行的重复块占绝大多数说明仓库里的重复以小段为主这时候CI阈值定在8或10是合理的不会天天误报如果大量重复块在20行以上说明存在“整个方法复制”级别的风险CI阈值可以直接定到10以上集中盯大块。反过来如果扫完一个文件都没报但肉眼明显看到两个方法就差一个参数名那大概率是阈值定高了把threshold降到4再看一眼。这个步骤是整套调参里最关键的动作先看size分布再定门禁线而不是拍脑袋选一个数字。3.2 language、include/exclude多语言工程的文件范围控制真实工程往往不止一种语言simian的-language参数支持一次传多个语言用花括号和逗号把它们包起来。常见写法和文件范围排除放在一起java -jar ~/tools/simian/simian-*.jar \ -threshold8 \ -language{java,kt} \ -exclude**/build/** \ -exclude**/generated/** \ -exclude**/test/** \ $(pwd)/src/**/*.java $(pwd)/src/**/*.kt-language{java,kt}的花括号是simian自己的语法不是shell通配符必须在命令行里用双引号包住否则bash会把{java,kt}拆成两个参数传进去扫描范围就被改写了。-exclude用的也是simian内部的通配符规则**可以跨目录匹配。这里建议至少排除三类目录构建输出目录、生成的模型类目录、测试目录。测试代码里的重复有其特殊性需要单独评估不应该和业务代码混在同一个门禁里。文件范围用通配符而不写死路径是为了让命令在本地和CI里用同一份。常见做法是把这条命令写进脚本相对路径基于仓库根目录展开。还有一种做法是按文件后缀重复写多个glob比如src/**/*.java和src/**/*.kt各写一份这比只靠-language猜文件要稳定因为language控制的是“用什么规则解析”glob控制的是“哪些文件参与扫描”两者各管一摊。3.3 让输出更适合CI的命令行开关ignoreLiterals、failOnDuplication等除了threshold和范围还有几个开关值得在进CI之前先搞清楚。它们不是锦上添花是直接决定门禁会不会天天误报。-ignoreLiterals忽略字符串和数字的差异。两个结构一样的日志方法只有消息文案不同默认会被判为重复打开这个开关后这类“模板重写”就不再打扰你。注意同一段SQL和取参逻辑因为字面量一字不差即使打开了也还是会被报告。-ignoreIdentifierCase和-ignoreCharacterCase分别忽略标识符大小写和字符大小写。适合代码库里有历史命名风格差异的场景。-ignoreModifiers忽略public/private这类访问修饰符的差异。DTO的getter/setter经常只有访问级别不同打开这个开关能让真正的结构重复暴露出来。-multiset把零散、互相靠近的短重复片段聚合成一个大集合。想知道“这一整片到底重复了多少”而不是看单个小块时用这个。-failOnDuplication扫描结束只要存在报告之外的重复块就返回非0退出码这是CI门禁最直接的用法。-balanceParentheses括号不平衡时代码解析容易出错打开它可以让扫描器更宽容地处理括号层级变化。组合起来我通常会这样配一个适合周期性门禁的命令java -jar ~/tools/simian/simian-*.jar \ -threshold8 \ -language{java,kt} \ -ignoreLiterals \ -ignoreModifiers \ -exclude**/build/** \ -exclude**/generated/** \ -reportstext,html \ -failOnDuplication \ $(pwd)/src/**/*.java $(pwd)/src/**/*.kt \ simian-report.txt这里的顺序会影响排查体验阈值放最前面语言和排除紧跟其后ignore开关调整判重规则-reports同时输出文本和HTML最后-failOnDuplication让脚本能拿到退出码。 simian-report.txt把终端输出落盘既方便排查又给后续解析留下原始素材。现在这组开关组合起来报告里的重复块数量会明显下降剩下来的基本都是“真重复”。值得说明的是-ignoreModifiers和-multiset这类开关在大目录上会明显增加扫描耗时不要在超大工程里一股脑全开先跑一次不带multiset的版本再看要不要叠加。注意-language{java,kt}和-exclude里的通配符都是simian自己解析的不是bash的。任何情况下都要用双引号包住否则shell会先展开花括号和星号命令行为和你预期完全不一样而且不会报错只会“少扫”或“多扫”。4. 把报告读成重构清单输出格式与代码定位4.1 解读text报告的重复块行号、长度和文件分布怎么对应跑完扫描后text报告是最直接的信息源。它是逐条列出重复块的格式类似下面这样的结构Found duplicated 1 blocks of size 8 starting at line 12 of file src/main/java/com/order/OrderChecker.java and starting at line 31 of file src/main/java/com/pay/PayChecker.java第一行的“size 8”代表这个重复块的规模是8行代码后面跟的是两个文件各自的起始行号。不同版本的simian输出措辞可能略有差异但核心信息永远是三个东西重复块多大、在哪个文件哪一行开始、和哪个文件哪一行重复。拿到这条信息后的动作不是马上改代码而是先在编辑器里打开这两个文件对照着看。对照时有一个细节容易翻车simian报告的起始行是token断点所在行不一定是你肉眼看到的“代码块第一行”因为注释和空行已经被忽略不计了。所以打开文件后往上看几行往往才能看到完整的重复片段。确认“这是同一段逻辑”之后再看重复块内部的差异点比如参数名不同、日志文案不同、边界判断不同。这些差异决定了后续抽取方法时要不要带参数。4.2 HTML报告适合做的事把重复块变成评审议题命令行跑的时候加上-reportshtmlsimian会额外生成一份可交互的HTML报告。它把每个重复块的两个入口做成交叉链接点击后能在两份源码之间快速跳转。这东西在代码评审时特别好用把生成目录配好、范围控制好之后直接把HTML文件丢到评审群里让两边模块的负责人坐在一起按重复块的编号逐个过。我遇到的实际情况是评审时会问三个问题这段代码两边谁会改得更频繁如果只改一处另一处会不会崩能不能抽到一个公共方法里且不改变行为这三个问题如果都能答上来这个重复块才值得进重构清单。HTML报告的定位不是给机器看的是给两个活人看的。它让“重复”从一个抽象指标变成一个可以逐条打勾的具体议题。评审完的结论需要有人登记。常见做法是把遗漏的“故意重复”块记录在排除清单里下一次扫描用exclude跳过把“需要抽公共方法”的块登记为重构任务直接关联到迭代排期。这个过程走完报告才真正变成了可执行的重构清单而不是跑完就压箱底的黑匣子。4.3 扫描完不急着改分类降噪、区别对待故意重复扫出来的重复块不应该被一视同仁。我把它们分成三类处理。第一类是值得抽的两边逻辑完全一样只是落的包不同。这类抽取后能直接减少后续多处长改的维护成本是simian最有价值的部分。第二类是故意重复的比如协议里的状态枚举、各端必须保持一致的配置常量。这类不是不处理而是在代码里写清楚“此列表需与某某端同步”并且将来同样结构再次出现时不再报。第三类是风格相似的重复相同的try-catch骨架但业务内容完全不同这类开-ignoreLiterals或其他参数降噪即可盲抽成通用方法反而会让调用关系变复杂。分类做完后再看要不要动代码。重复检测的目的不是把重复率压到零那是表演真正的目的是把“什么地方在重复”这件事变成一张可以排序的清单。按重复块大小排改动成本低的先做按改动风险排两边都被频繁改动的优先处理。这张清单就是接下来一到两个迭代的重构排期来源。5. simian使用中的常见问题与排查误报、漏报与性能5.1 误报集中爆发先查阈值和ignore开关而不是改代码现象一次扫描出来几百个重复块几乎每个Service文件都中标代码评审根本没法看。 原因最常见的是三个因素叠加——threshold设得太低**/generated/**没有排除以及没有打开-ignoreLiterals。有些工程的代码生成器输出目录比src/main还深不排除的话生成代码会被当成“人工重复”扫出来。 解决先把生成目录排除再单独打开-ignoreLiterals最后再把阈值从5调到6或8。记住一个原则每次只动一个开关扫一次对比一次结果不要三个参数一起改否则根本不知道是哪个参数起作用。误报并不可怕可怕的是因为误报就直接关掉整个重复检测。5.2 大段重复扫不出来三个依次排查的方向现象肉眼明显看到两个Service方法几乎一样simian却一条报告都不出。 原因可能出在三个地方。第一个是threshold设得比实际重复块还大重复块本身只有7行阈值却在8自然石沉大海。第二个是代码中间被插入了不重复的语句比如一边多了一行日志把token序列切成了前后两段每段都低于阈值。第三个是文件编码不统一文件里有乱码token解析到乱码处直接被中断。 解决按顺序排查。先把threshold降到4跑一遍看有没有更小的命中再看两个方法中间是否夹着差异语句最后用file -bi检查两个文件的编码是否一致统一成UTF-8并配置git自动转换换行符后再跑。这一步做完仍没有结果那就是两段代码只在语义上相同、token层面完全不同靠simian解决不了得靠Code Review或人工快速比对。5.3 工程大扫描慢文件排除与分块扫描的取舍现象全仓库一次扫描耗时十几分钟每次进流水线都卡在simian这一步构建超时。 原因扫描范围过大最常见的是把依赖目录、构建输出目录、前端缓存目录都扫了一遍。这些目录里大量代码是生成的或复制进来的扫它们不仅慢结果还没有参考价值。 解决优先排除三类目录构建输出目录如target、build、dist、VCS目录、外部依赖目录。排完还慢就按模块拆开扫每个模块独立跑、独立定阈值让常规重复在模块内就能被发现跨模块的大块重复单独加一个汇总任务。把一个大扫描拆成流水线里多个并行小任务比优化单次扫描参数更立竿见影。5.4 行号对不上或报告打不开编码、换行和生成文件的影响现象报告里说重复块起始于第40行打开文件发现第40行是空行实际代码在第42行导致评审时反复定位错位。 原因simian的行号是基于token解析的不是基于编辑器显示的行号文件里如果带BOM或者混用Windows与Unix换行符行号会额外偏移。HTML报告打不开多半是项目路径里有中文名或空格浏览器对本地文件的路径转义处理不一致。 解决统一仓库文件为UTF-8无BOM并在git配置里开启换行符自动转换。生成的HTML报告路径不要放在带中文和空格的目录下如果项目根目录本身带空格把报告输出到一个临时目录再打开比如/tmp/simian-report.html。这两个动作解决掉报告的行号和浏览器展示基本就不再出幺蛾子。6. 让它值回票价把simian接进流水线并盯住趋势6.1 版本和阈值锁进配置把simian接进CI的第一步不是写流水线脚本而是把工具版本、阈值、语言和排除规则全部锁在一个配置里。常见做法是准备一个项目级的simian.properties或环境变量模板流水线只读取它。工具版本锁住是趋势可比的前提如果每次升级后判重口径变了历史数据就不能再和本期对比。阈值也建议从配置读取而不是散落在多个脚本里。6.2 把重复块数变成趋势指标流水线里跑simian同时保留“失败”和“数量”两个信息我一般用一个脚本完成#!/usr/bin/env bash set -euo pipefail SIMIAN_JAR$1 THRESHOLD${SIMIAN_THRESHOLD:-8} java -jar $SIMIAN_JAR \ -threshold$THRESHOLD \ -language{java,kt} \ -exclude**/build/** \ -exclude**/generated/** \ -failOnDuplication \ $(pwd)/src/**/*.java $(pwd)/src/**/*.kt \ report.txt 21 || true COUNT$(grep -o Found duplicated [0-9]* report.txt | awk {s $3} END {print s0}) echo dup_blocks$((${COUNT:-0})) trend.csv这段脚本里|| true不是掩盖失败脚本要先拿到报告落盘后续再让专门的gate任务根据退出码决定是否阻塞合入两者分开处理一个负责记录趋势一个负责门禁拦截。grep匹配的是simian报告里的固定措辞正式接入前先在本仓库跑一次确认这个模式的匹配结果正确。趋势文件每天追加一行重复块总数几个迭代后就能看出重复是在下降还是又回潮了。我现在接手一个新工程第一件事不是部署什么平台而是把simian跑一遍。不是想刷一个低重复率的数字是想在相同代码块之间看出这个工程的历史包袱。之后改方法、抽公共逻辑、改签名都会先翻出这份报告看一眼确认没有另一半在等着改。这个习惯值回票价希望帮到你。本文还有配套的精品资源点击获取