mcpField
D
dry-refactoring

dry-refactoring

github.com/kucherenko开发6.5k3.6k 次安装今天更新
代码仓库

关于 dry-refactoring

dry-refactoring 是一套针对 jscpd 检出的源代码重复内容的引导式重构流程。它帮助开发者分类重复片段、评估重构价值、选择抽取策略,并通过再次扫描验证修改。

主要功能

  1. 01运行 jscpd 并解读重复报告
  2. 02优先处理精确及近似重复
  3. 03重构前筛选候选项
  4. 04选择抽取与参数化策略
  5. 05重新扫描验证修改

适用场景

  1. 01重构 jscpd 发现的复制粘贴代码
  2. 02审查改名或近似重复候选项
  3. 03检查修改后的重复度变化

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kucherenko/jscpd --skill dry-refactoring

安装后在 Claude Code 中输入/dry-refactoring确认已加载。

详细介绍

dry-refactoring

用于消除源代码中复制粘贴重复内容的引导式工作流。检测克隆后,请运行 jscpd,再使用本工作流。

先决条件

首先,运行 jscpd 来识别重复内容:

npx jscpd --reporters ai <path>

在混合使用相关格式(例如 JavaScript 和 TypeScript)的代码库中,添加 --cross-formats,以便检测跨两种格式的克隆:

npx jscpd --reporters ai --cross-formats "js-ts" <path>

对于较大的代码库,添加 --summary,在克隆列表旁查看重构热点概览——其中会列出文件和文件夹,并通过 dup% 列显示各文件中的重复内容占比:

npx jscpd --reporters ai --summary <path>

默认扫描只报告完全相同的副本,应优先重构这类内容:完全相同的克隆几乎总是实际的复制粘贴内容。再进行两轮扫描,可以找到粘贴后经过编辑的副本。这两轮扫描的噪声更多:它们会有意忽略名称、值或少量语句,因此也会找出只是看起来相似的代码块(模型和 DTO、配置表、测试设置、生成的代码、共享惯用写法)。只有在处理完完全相同的克隆后,才逐类使用严格设置运行这些扫描,并将报告的结果视为需要查看的线索,而非必须修复的问题:

# Type-2: renamed copies (other variable names, other constants), reported as "(renamed)".
# Raise --min-tokens: with identifiers ignored, a short block is mostly placeholders.
npx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>

# Type-3: near-miss copies (one or two edited lines, or JS/TS/Python functions with the same structure),
# reported as "[~0.91 gap]" and "[~0.85 ast]". Widen only if the tight run finds nothing.
npx jscpd --reporters ai --max-gap-lines 1 --similarity <path>

有关完整的选项参考,包括跨格式分组语法、克隆类型后缀以及如何阅读摘要,请参阅 jscpd 技能。

工作流

  1. 在目标路径上使用 --reporters ai 运行 jscpd(对于较大的代码库,添加 --summary 以确定起点:dup% 高且令牌数量大的文件最值得优先处理)
  2. 解析每条克隆记录,识别两个重复位置(文件 + 行范围)及其类型:没有后缀表示完全相同的副本,(renamed) 表示仅名称或值不同,[~N gap] 表示中间有少量经过编辑的行,[~N ast] 表示结构相同的一对函数
  3. 从源文件中读取两个代码片段
  4. 理解重复代码的作用;对于重命名克隆和相似克隆,准确列出两边的差异
  5. 处理前先筛选重命名克隆和相似克隆。 如果符合以下任一情况,请跳过这对代码并说明原因:两边虽然形状相同,但执行的操作不同(例如针对不同枚举的 switch,或语义无关的两个 reducer);相似之处是有意保留的样板代码(模型、DTO、配置、路由表、测试固件);代码由工具生成;共享抽象需要使用类似 processData 这样含义模糊的名称;或代码对少于约 10 行。只有能够删除代码并为提取出的内容起一个准确名称的代码对,才进入下一步
  6. 设计重构方案:提取共享函数、类、模块或常量;具体策略取决于类型(见下文)
  7. 应用重构——更新两个位置以及所有其他用法
  8. 使用相同的标志重新运行 jscpd,确认克隆已消除,且修改过的文件的 dup% 降低;(renamed) 克隆不会出现在默认运行结果中,因此要再次使用 --ignore-identifiers 检查
  9. 按影响从大到小处理剩余克隆:先处理完全相同的克隆,再处理重命名克隆,最后处理相似克隆。单独报告跳过的候选项及其原因,避免有人误将归一化运行的计数当成实际重复内容

重构策略

提取函数——重复的是一段逻辑时:

// Before: same block in two places
// After: shared function called from both places

提取模块/工具——重复内容跨越不同领域的多个文件时:

// Move shared logic to a shared utility file and import it

提取常量或配置——重复的是数据或配置时。

模板/基类——重复的是结构时(例如,重复的类结构)。

参数化——适用于 (renamed) 克隆。这两段代码是在不同名称或值上运行的同一算法,因此应将差异部分变为参数:

// Before: computeCartTotal(items) and computeBasketTotal(entries), same body, other names;
//         limits-dev.js and limits-prod.js, same shape, other numbers
// After: one function whose parameters are the identifiers that differed,
//        or one function reading the values that differed from a config object

如果重命名克隆唯一的差异是字面量,那么缺少的是常量或配置项,而不是函数。

统一近似副本——适用于 [~N gap] 克隆。阅读未匹配的行:差异通常只在一处,例如一个守卫条件、一条日志调用或一个额外字段。提取共有部分,并将差异部分作为参数传入:

// Before: saveUser and saveAccount, identical except one inserted validation line
// After: one saveRecord(record, { validate }) with the inserted line behind the option,
//        or the inserted line moved to the caller before the shared call

如果差异改变了含义,而非只是增加一个步骤,则保留两个函数,但提取它们共有的部分。

合并相似函数——适用于 [~N ast] 克隆。结构相同,但名称、字面量和部分语句不同。先比较这两个函数;ast 分数表示它们有多少内容相同(0.9 表示只有一处改动的副本,0.75 表示有几处新增语句并修改了名称的副本)。提取共有框架,并通过参数、策略对象或回调注入差异部分:

// Before: buildInvoice(order, customer, taxRate) and buildCreditNote(refund, account, vatRate):
//         same loop, same rounding, one extra guard and one extra log call in the second
// After: buildDocument(source, party, rate, { filter, onBuilt }) used by both

低于约 0.8 时,这些代码通常只是采用了相同惯用写法,而非共享实现;除非摘要显示这些文件本身也是热点,否则不要处理。

务必确保:

  • 更新所有调用点,而不只是 jscpd 报告的两个调用点
  • 重构后测试仍然通过
  • 提取出的抽象具有清晰、明确的名称
  • 重新运行时使用与发现克隆的那次运行相同的检测标志

提示

  • 优先处理行数最多的克隆——它们影响最大
  • 使用摘要中的 dup% 列安排处理顺序:重复行占比高的大文件能带来最大的收益
  • 测试文件之间的克隆可能表明缺少测试辅助工具
  • 不相关模块之间的克隆可能意味着缺少共享工具
  • 跨格式克隆(同一逻辑分别出现在 .js 和 .ts 文件中,并通过 --cross-formats 找到)通常意味着代码移植后未删除原始实现——应整合为一个实现(通常保留 TypeScript 版本)并更新导入,而不是再提取第三份共享副本
  • 同一文件中有很多 (renamed) 克隆通常意味着缺少一个抽象,而非缺少多个抽象:提取前先寻找共有结构。多个 测试 文件中有很多 (renamed) 克隆通常无需处理:测试用例本来就应当相似
  • 在团队审查这些检测结果是否适用于当前代码库之前,不要使用 Type-2/Type-3 检测设置 CI 门禁(--threshold、--fail-on-new-clones);应对完全相同的检测运行设置门禁
  • --similarity 支持 JavaScript、TypeScript、Python、Java、Kotlin、Scala、C#、Go、Rust、C、C++、PHP、Ruby、Swift 和 Clojure,也支持 Markdown 代码块以及 Vue、Svelte 和 Astro 脚本中的代码;对于其他语言,应依赖完全匹配和 --max-gap-lines 检测
  • 使用 --min-lines 10 过滤噪声,聚焦有意义的重复代码
  • 设置 CI 门禁时,应为每组检测标志分别使用一个 --baseline:重命名和相似度检测与完全匹配检测采用不同的克隆指纹

评论

支持 Markdown