codebase-refactoring
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesecodebase-refactoring
代码库重构
A guided pass over a codebase's three biggest maintenance costs — duplicated
code, dead code, and complexity — using jscpd to find each and a strategy to
fix it. Use this skill when asked to "clean up", "improve", "refactor" or
"pay down tech debt in" a codebase, rather than to fix one specific bug.
针对代码库三大维护成本——重复代码、死代码和复杂度——进行一次有指导的梳理,使用 jscpd 逐一发现并制定修复策略。当被要求"清理"、"改进"、"重构"或"偿还技术债"代码库时使用本 skill,而不是修复某个特定 bug。
Start here: measure, then prioritize
从这里开始:测量,然后确定优先级
bash
npx jscpd --health --reporters ai <path>health 74 B (duplication 75, dead-code 72, complexity 76; 93 code lines)The health score is a weighted mix of three sub-scores, each a share of the
code lines — high is good, 100 is clean. The lowest of the three names where
the codebase actually hurts most; if it isn't the order below, do that
dimension first instead. on a dimension (e.g. ) means
jscpd could not measure it — dead-code detection needs JavaScript,
TypeScript or Python to be a real share of the code — skip that step rather
than treating it as clean.
n/adead code n/aFor the full picture behind that score — largest duplicated formats, most
complex files, largest dead-code findings, all in one screen — run instead. Its output (the default) is the only
reporter with that per-file detail; on prints
the same one-line summary shown above, not the breakdown, so don't reach for
it expecting more than the score. Use on if
you need the detail in a parseable form.
npx jscpd --dashboardconsole--reporters ai--dashboard--reporters json--dashboardThe three passes below are otherwise independent and safe to run in any
order; duplication → dead code → complexity is the default because
fixing duplication first means step 2 isn't wasting effort tracing call
sites that a later extract-function pass will move anyway, and clearing
dead code before step 3 means the files ranked for simplification are ones
actually worth simplifying, not ones about to be deleted.
bash
npx jscpd --health --reporters ai <path>health 74 B (duplication 75, dead-code 72, complexity 76; 93 code lines)健康评分是三个子评分的加权混合,每个子评分占代码行数的一定比例——分数越高越好,100 表示完全干净。三个子评分中最低的那个指出代码库真正最痛的地方;如果不是下面的顺序,则应优先处理该维度。某维度显示 (例如 )表示 jscpd 无法测量——死代码检测需要 JavaScript、TypeScript 或 Python 在代码中占据实际比例——跳过该步骤,而不要将其视为干净。
n/adead code n/a要查看该评分背后的完整信息——最大的重复格式、最复杂的文件、最大的死代码发现,全部集中在一个屏幕上——请改用 。其 输出(默认)是唯一包含每个文件详细信息的 reporter; 搭配 打印的是上面所示的一行摘要,而非详细分解,因此不要期望获得比评分更多的信息。如果在 上需要可解析格式的详细信息,请使用 。
npx jscpd --dashboardconsole--dashboard--reporters ai--dashboard--reporters json下面三个步骤彼此独立,可按任意顺序安全执行;重复 → 死代码 → 复杂度 是默认顺序,因为先修复重复意味着第 2 步不会浪费精力追踪调用点(后续提取函数的操作可能会移动这些调用点),而在第 3 步之前清除死代码意味着要进行简化排序的文件是真正值得简化的文件,而不是即将被删除的文件。
Step 1 — Duplicated code
第 1 步 — 重复代码
bash
npx jscpd --reporters ai --summary <path>Find and read each clone, decide whether it is a real copy worth merging,
and apply the matching refactoring strategy (extract function, parameterize,
extract a shared module or constant, or a base class/template for
structural duplication). Full guided workflow, triage rules for
renamed/near-miss clones, and worked examples per clone kind:
→ dry-refactoring — run this now, then
come back here for steps 2 and 3. Its own hotspot ranking is the
same one the health score's duplication dimension reads from, so a file it
flags as a duplication hotspot is the same file that dragged the score down.
--summarybash
npx jscpd --reporters ai --summary <path>查找并阅读每个克隆,判断它是否为值得合并的真正副本,然后应用相应的重构策略(提取函数、参数化、提取共享模块或常量,或针对结构性重复使用基类/模板)。完整的指导工作流、针对重命名/近似克隆的分类规则,以及每种克隆类型的实例:
→ dry-refactoring — 立即运行此步骤,然后回到此处进行步骤 2 和 3。其自身的 热点排名与健康评分中重复维度读取的排名相同,因此被标记为重复热点的文件正是拉低评分的文件。
--summaryStep 2 — Dead code
第 2 步 — 死代码
bash
npx jscpd --dead-code --reporters ai <path>basta dead code report — 18 findings across 30 files (22.1% of 272 lines)
Confidence is 0-100; anything below 90 has a listed reason it may be wrong. Verify a finding before deleting the code.
unused-file typescript/src/legacy-export.ts:1:1 confidence=95 typescript/src/legacy-export.ts is never imported and is not an entry point
unused-export typescript/src/invoice.ts:21:17 confidence=85 exported function `renderReceipt` is never imported
unused-symbol typescript/src/invoice.ts:29:10 confidence=90 function `describeTotal` is never used in typescript/src/invoice.ts
unused-import typescript/src/invoice.ts:2:10 confidence=100 `roundToCents` is imported but never usedThis is a graph traversal from the project's entry points (
///, scripts, framework
conventions, or ), not a reference count — a helper whose
only caller is itself dead is reported too, so deleting one finding can make
another true that wasn't reported yet. Covers JavaScript, TypeScript, JSX,
TSX, Vue, Svelte, Astro and Python.
package.jsonmainbinexportsscriptspyproject.toml--entry <glob>bash
npx jscpd --dead-code --reporters ai <path>basta dead code report — 18 findings across 30 files (22.1% of 272 lines)
Confidence is 0-100; anything below 90 has a listed reason it may be wrong. Verify a finding before deleting the code.
unused-file typescript/src/legacy-export.ts:1:1 confidence=95 typescript/src/legacy-export.ts is never imported and is not an entry point
unused-export typescript/src/invoice.ts:21:17 confidence=85 exported function `renderReceipt` is never imported
unused-symbol typescript/src/invoice.ts:29:10 confidence=90 function `describeTotal` is never used in typescript/src/invoice.ts
unused-import typescript/src/invoice.ts:2:10 confidence=100 `roundToCents` is imported but never used这是从项目入口点( 的 ///、 的 scripts、框架约定或 )出发的图遍历,而非引用计数——唯一调用者是自身且自身已死的辅助函数也会被报告,因此删除一个发现可能使另一个尚未被报告的发现变为真实。支持 JavaScript、TypeScript、JSX、TSX、Vue、Svelte、Astro 和 Python。
package.jsonmainbinexportsscriptspyproject.toml--entry <glob>The four categories, and what to do with each
四个类别及其处理方式
| Category | Meaning | Usual fix |
|---|---|---|
| Never imported anywhere, not an entry point | Delete the file |
| Exported, but no other file imports it | Delete it, or drop the |
| A module-private declaration nothing in its own file references | Delete it |
| Imported but never referenced in the file | Delete the import line |
| 类别 | 含义 | 通常修复 |
|---|---|---|
| 从未在任何地方被导入,也不是入口点 | 删除该文件 |
| 已导出,但没有其他文件导入它 | 删除它,或如果同一文件中的某些内容仍在使用它,则去掉 |
| 模块私有的声明,其自身文件未引用 | 删除它 |
| 已导入但未在文件中被引用 | 删除导入行 |
Workflow
工作流
- Run with (add
--reporters aito focus on one category,--dead-code-categoriesif test-only usage shouldn't count as "used",--include-teststo also flag an entry file's own unused exports, normally excluded since its whole surface is the public API)--include-entry-exports - Sort by confidence, high to low. Everything is 0-100; below 90 the finding
carries a stated reason it might be wrong — /
evalcalls, decorators the analyzer doesn't recognize, a wildcard re-export, the name showing up only inside a string literal, or a file elsewhere that failed to parse (seegetattrin the JSON report). Read the reason before deleting;statistics.unparsedFiles(default 60) only sets the floor for what's reported, it doesn't make a low-confidence finding safe--min-confidence - For each finding above your comfort threshold, confirm it by hand — grep the symbol name across the repo (dynamic access, a string-based router, a template file jscpd doesn't parse can all hide a real use) — then apply the fix from the table above
- Run the project's own test suite and build after each batch of deletions, not only jscpd's re-run: dead-code analysis proves nothing reaches the code from an entry point, not that removing it can't break a build step
- Re-run after a batch: a file whose only import was the one you just deleted may now be dead itself — that cascade is expected, keep going until a clean pass, not just once
--dead-code - Report what you skipped and why (low confidence, a reason you couldn't rule out, or intentionally-kept dead code such as a public library export) separately from what you actually removed
- 使用 运行(添加
--reporters ai以聚焦某一类别,--dead-code-categories如果仅测试使用不应算作"已使用",--include-tests以同时标记入口文件自身未使用的导出,通常排除这些导出因其整个表面就是公共 API)--include-entry-exports - 按置信度从高到低排序。所有值均为 0-100;低于 90 的发现附带了可能出错的原因——/
eval调用、分析器无法识别的装饰器、通配符再导出、名称仅出现在字符串字面量中,或其他地方的文件解析失败(参见 JSON 报告中的getattr)。删除前先阅读原因;statistics.unparsedFiles(默认 60)仅设置报告的下限,并不会使低置信度的发现变得安全--min-confidence - 对于每个高于您舒适阈值的发现,手动确认——在仓库中 grep 符号名称(动态访问、基于字符串的路由器、jscpd 无法解析的模板文件都可能隐藏真实用途)——然后应用上表中的修复
- 在每次批量删除后运行项目自身的测试套件和构建,而不仅仅是重新运行 jscpd:死代码分析只证明没有从入口点到达该代码,并不证明删除它不会破坏构建步骤
- 在批量处理后重新运行 :一个其唯一导入正是您刚删除的文件的文件现在可能自身也成了死代码——这种级联是预期的,继续直到完全通过,而不是只运行一次
--dead-code - 将您跳过的内容及其原因(低置信度、无法排除的原因或有意保留的死代码(例如公共库导出))与实际删除的内容分开报告
Step 3 — Big and complex files
第 3 步 — 庞大且复杂的文件
bash
npx jscpd --complexity --reporters ai --summary-top 10 <path>bash
npx jscpd --complexity --reporters ai --summary-top 10 <path>or, ranked alongside duplication and size:
或与重复和大小一起排名:
npx jscpd --reporters ai --summary --summary-by complexity <path>
undefinedComplexity by complexity (6 files, 6 folders):
files (tokens/lines/size/cx):
c/checkout.c 176/32/710/13
rust/status.rs 112/24/481/9
swift/Profile.swift 88/21/446/8
`cx` is a language-aware cyclomatic-complexity estimate — roughly one path
per function plus one per branch (`if`, loops, `&&`/`||`, `match`/`switch`
arms, the ternary and Elvis operators, each counted the way the language
actually spells it) — read as a ranking signal, not an exact metric. Prose
and data files (Markdown, JSON, YAML, lock files, …) always score `0`; a
large README is not a refactoring target. jscpd's own health score treats a
file with complexity **50 or higher** as "complex" (the threshold its
`dead code`-style half-life curve is calibrated against) — a reasonable
default cutoff for "worth reading" if the summary doesn't make an obvious
one clear.npx jscpd --reporters ai --summary --summary-by complexity <path>
undefinedComplexity by complexity (6 files, 6 folders):
files (tokens/lines/size/cx):
c/checkout.c 176/32/710/13
rust/status.rs 112/24/481/9
swift/Profile.swift 88/21/446/8
`cx` 是语言感知的圈复杂度估计——粗略地讲,每个函数一条路径加上每个分支一条路径(`if`、循环、`&&`/`||`、`match`/`switch` 分支、三元和 Elvis 运算符,每种都按语言实际拼写方式计数)——应作为排名信号而非精确指标。散文和数据文件(Markdown、JSON、YAML、锁文件等)始终得 `0` 分;大型 README 不是重构目标。jscpd 自身的健康评分将复杂度 **50 或更高** 的文件视为"复杂"(其 `dead code` 风格的半衰期曲线以此阈值校准)——如果摘要未明确揭示明显目标,这是一个合理的"值得阅读"默认截止点。Workflow
工作流
- Take the top N files by complexity (and, from the form, by size — a huge low-complexity file, e.g. a big data table, is a different problem than a small high-complexity one)
--summary - Read each file and name the actual driver: deep nesting, a long function
doing several unrelated things, a large /
switch-chain over a type or state, or a file that is really several modules glued togetherif - Pick the matching strategy:
- Deep nesting → guard clauses / early return, flatten the happy path
- Long function, several responsibilities → extract function per responsibility, named for what it does
- Branching over a fixed set of cases (type, enum, status) → replace with a lookup table/map, or polymorphism if each case already has its own type
- File doing too much → split along its actual seams (one export group per file), not an arbitrary line-count cut
- Refactor one file at a time; keep behavior identical — this is a complexity pass, not a rewrite. If a duplication clone (step 1) or a dead branch (step 2) turns up while reading, still handle it in its own step so the reason for each change stays traceable
- Re-run on the touched files to confirm
--complexityactually dropped — a split that only moves code around without reducing branching per function didn't fix anything, it just renamed the problemcx
- 取复杂度最高的前 N 个文件(并从 形式中按大小排序——一个巨大但低复杂度的文件(例如大型数据表)与一个小而高复杂度的文件是不同的问题)
--summary - 阅读每个文件并指出真正的驱动因素:深层嵌套、一个执行多项无关任务的长函数、针对类型或状态的大型 /
switch链,或一个实际上由多个模块拼合而成的文件if - 选择匹配的策略:
- 深层嵌套 → 使用卫语句 / 提前返回,让主路径更扁平
- 长函数,多个职责 → 按职责提取函数,以所做的事情命名
- 针对固定集合的分支(类型、枚举、状态)→ 替换为查找表/映射,或如果每个案例已有自己的类型则使用多态
- 文件职责过多 → 按其实际接缝拆分(每个文件一个导出组),而非按任意行数切割
- 一次重构一个文件;保持行为一致——这是复杂度梳理,不是重写。如果在阅读时发现重复克隆(步骤 1)或死分支(步骤 2),仍在其相应步骤中处理,以便每次更改的原因保持可追踪
- 对修改过的文件重新运行 ,确认
--complexity确实下降了——仅仅移动代码而不减少每个函数的分支数量并未修复任何问题,只是重命名了问题cx
Step 4 — Confirm the improvement
第 4 步 — 确认改进
bash
npx jscpd --health --reporters ai <path>Compare the score and grade to the Step 0 measurement. A dimension that got
worse (duplication crept back in while extracting a shared complexity fix,
say) is a signal to revisit that step, not just note it — re-run the
relevant pass (, , ) rather than only
trusting the one aggregate number.
--summary--dead-code--complexitybash
npx jscpd --health --reporters ai <path>将评分和等级与第 0 步的测量值进行比较。某个维度变差(例如在提取共享复杂度修复时重复代码悄悄回潮)是重新审视该步骤的信号,而不只是记下——重新运行相关步骤(、、),而不是只信任这一个聚合数字。
--summary--dead-code--complexityTips
提示
- Don't run all three passes on a codebase you've never seen before without
reading first — on a project where only one dimension is actually bad, spending equal effort on all three wastes time on the two that are already fine
--dashboard - A file (coverage, security scan results) can be layered onto the same score if the project already tracks those; it doesn't change anything about this workflow, it just adds more dimensions to Step 0
--health-input - Health scores from two runs are only comparable when built from the same
dimensions — if a dimension went from to measured (or the reverse) because the file mix changed, say so rather than reading the raw score delta as improvement or regression
n/a - Commit each step separately (duplication fixes, dead-code removal, complexity simplification) — a reviewer verifying "did this actually reduce X" wants a diff scoped to X
- 不要在不先阅读 的情况下对从未见过的代码库运行所有三个步骤——在只有一个维度真正很差的项目上,对三个步骤花费相同精力会在两个已经很好的维度上浪费时间
--dashboard - 文件(覆盖率、安全扫描结果)可以叠加到同一评分上(如果项目已跟踪这些);这不会改变此工作流的任何内容,只是为第 0 步增加更多维度
--health-input - 两次运行的健康评分仅在基于相同维度构建时才可比较——如果某个维度因文件组合变化而从未被测量()变为已测量(或相反),请说明,而不要将原始评分增量解读为改进或退步
n/a - 每次提交分开(重复修复、死代码移除、复杂度简化)——审查者验证"这真的减少了 X"时需要的是限定于 X 的 diff