ccusage 项目 Rust 代码相似度检测实战:用 similarity-rs 定位重复代码并安全重构
【免费下载链接】ccusagenpx ccusage项目地址: https://gitcode.com/gh_mirrors/cc/ccusage
ccusage 是一个以 Rust 为核心实现的开源 CLI 工具(npx ccusage),其生产代码分布在rust/crates与rust/adapters的多个 crate 中,包含大量 parser、loader、report 模块,天然容易积累重复的函数、impl 方法和并行结构体。本文基于仓库中reduce-similarities技能文档,讲解如何使用 Nix dev shell 内置的similarity-rs工具对.rs文件做相似度扫描、解读分数并制定重构决策。读完本文,你将掌握相似度检测的完整命令组合、每个 CLI 参数的作用边界,以及"哪些重复必须重构、哪些重复应该保留"的分诊方法。
similarity-rs 从哪来:Nix dev shell 中的similarity工具
similarity-rs并不是独立的第三方二进制,而是随 ccusage 的 Nix 开发环境一起提供。在 nix/dev-shell.nix 的buildInputs列表中可以看到similarity被显式加入(第 62 行),与ast-grep、ripgrep、fd、just等开发工具并列:
similarity ast-grep ripgrep fd因此,工具的实际命令名是similarity(skill 文档中写作similarity-rs,指代同一个 CLI)。使用方式有两种:
- 已在 Nix dev shell 内(例如通过
direnv allow初始化环境后):直接运行similarity-rs; - 在 shell 之外:需要前缀
direnv exec .进入开发环境再执行,例如direnv exec . similarity-rs .。
开发环境的初始化与依赖安装由nix/dev-shell.nix的shellHook负责:它会比较pnpm-lock.yaml与nix/tools/*/bun.lock的时间戳,在 lockfile 更新后自动执行just install,确保工具链与依赖一致(见 nix/dev-shell.nix)。
需要说明的是,这个技能只负责Rust代码的重复检测。TypeScript/JavaScript 侧的重复代码不在本工具职责范围内,应转由typescript与ast-grep技能处理(见 .agents/skills/reduce-similarities/SKILL.md 的边界说明,以及 AGENTS.md 中的技能路由表)。
基本用法:一次扫描,两个关键参数
similarity-rs直接对路径执行扫描,默认扫描当前目录.,也可以显式传入路径:
# 扫描整个仓库 similarity-rs . --threshold 0.85 --min-lines 5 # 只扫描某个 crate(例如 codex 适配器) similarity-rs rust/adapters/codex --threshold 0.85 --min-lines 5--threshold 0.85:相似度门槛
--threshold控制报告的灵敏度,0.85 表示"相似度达到 85% 及以上"的代码片段才会被报告。分数越高说明两段代码越接近:
- 100% 匹配:完全相同的代码块;
- 95%~100%:几乎一致,仅少量差异;
- 85%~95%:结构相同、细节有别。
阈值越高报告越少、越精确;调低阈值可以挖掘更多"形似而神异"的候选,但噪声也会增加。
--min-lines 5:最小片段行数
--min-lines过滤掉过短的匹配。小于该行数的代码(如一两行的赋值)不值得报告,避免把常见的短小惯用法误报为重复。
--experimental-types:补上类型定义这一路
这是本工具最容易踩的坑:函数扫描与类型定义扫描是分开的两趟 pass,因此默认运行会"静默漏掉"并行的 struct 和 enum 定义:
similarity-rs . --threshold 0.85 --experimental-types也就是说,默认跑一次只覆盖函数、方法层面的重复;如果你正在排查"几个结构体字段完全相同"这类问题,必须加上--experimental-types才会看到结果。技能文档特别提醒:默认运行会静默错过并行的 structs 和 enums,这是代码评审时最常见的漏检场景。
其他辅助参数
--print:直接打印匹配到的代码片段,便于肉眼比对,而不是只给出行号或分数;--skip-test:当形如 fixture 的测试函数淹没真实发现时,加上它跳过测试代码,让生产代码的重复浮出水面;--help:查看全部剩余参数(similarity-rs --help)。
分诊(Triage):分数只是起点,不是结论
工具给出的"相似度分数"只是线索,是否值得重构取决于代码的语义角色。技能文档给出了明确的分诊规则,按分数与形态分类:
值得重构的场景
| 检测形态 | 推荐做法 |
|---|---|
| 100% 完全匹配 | 提取共享函数或泛型(shared function / generic) |
| 95%~100% 且跨越不同类型 | 改为带 trait bound 的泛型函数 |
| 多个类型上有重复的 impl 方法 | 用带默认实现的 trait 收敛 |
| 85%~95% 的 match 分支或错误处理 | 提取共享 helper 或宏(macro) |
| 字段完全相同的并行 struct | 提取共享基类(base struct)或泛型 struct |
这五种形态覆盖了 ccusage 代码库中最常见的重复来源:多个 adapter 的 parser/report 模块之间,往往存在高度相似的 match 分支、错误处理链和字段布局,是--threshold 0.85最值得关注的对象。
应该放过的场景
以下情况通常不要动,强行重构反而降低可读性:
- 简短的
new()构造函数; - 简单的
From/Intotrait 实现; - 任何可以通过
derive宏自动生成的样板代码。
这一条与仓库的实际代码风格一致:rust/adapters下的 parser 大量使用#[derive(Debug, Default, Deserialize)]等派生宏(例如 rust/adapters/amp/src/parser.rs 中的类型定义),这类代码正是"derive 已处理、无需手写去重"的典型——它们不应该出现在重构清单里。
报告规范:输出 before/after,而不是分数列表
技能文档对输出提出了明确要求:每个存活的候选(surviving candidate)必须以具体的 before/after 形态报告,而不是罗列一堆相似度分数。原因是:
- 分数只说明"像",不说明"怎么改";
- before/after 给出可评审、可落地的重构方案,让代码评审者一眼看出改动意图。
例如,针对一个 100% 匹配的重复,报告应呈现为"重构前:两处相同函数体;重构后:提取为共享函数并两处调用",而不是"发现 3 对相似代码,分数 0.95"。
在评审流程中的定位
reduce-similarities是 ccusage 代码评审工作流中的一个环节,与 .agents/skills/rust/SKILL.md 配合使用——后者在第 33 行明确指出:"拆分大模块或排查重复时,使用reduce-similarities"。典型的触发场景包括:
- 评审
.rs文件时发现重复的函数或 impl 方法; - 发现并行定义的 struct 与 enum(需要加
--experimental-types); - 准备提取共享 helper 之前,先用工具确认重复范围。
整个技能体系通过 AGENTS.md 的路由表与 nix/agent-skills.nix 的打包机制分发:sync-agent-skills会把.agents/skills目录下的技能(含本技能)链接到.claude/skills供 Agent 在任务中按需加载。
小结
similarity-rs在 ccusage 项目中是一把"重复代码探测器":来自 Nix dev shell,一条命令即可扫描整个仓库;--threshold与--min-lines控制报告灵敏度,--experimental-types补齐类型定义扫描,--skip-test排除 fixture 噪声。拿到分数后,遵循"100% 提取共享函数、跨类型提取泛型、重复 impl 收敛为 trait、match/错误处理提取宏、并行 struct 提取基类"的分诊规则,同时放过new()、From/Into与 derive 样板——最后以 before/after 形态输出重构建议,就能把相似度扫描真正转化为可落地的代码质量改进。
【免费下载链接】ccusagenpx ccusage项目地址: https://gitcode.com/gh_mirrors/cc/ccusage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考