mcpField
R
refactoring-optimization

refactoring-optimization

github.com/jablonkai开发440 次安装今天更新
代码仓库

关于 refactoring-optimization

refactoring-optimization 是一项在保持行为不变的前提下重整代码的技能。它为开发者提供以验证为先的流程,从建立安全网、逐步修改到检查测试、差异和 API。

主要功能

  1. 01先确认测试通过并建立回滚点
  2. 02借助对应技术栈的 Linter 识别异味
  3. 03按局部到跨模块的顺序安排修改
  4. 04每次只做一项重构并立即验证
  5. 05性能优化前先测量

适用场景

  1. 01拆分过长函数且保持行为不变
  2. 02安全合并重复逻辑
  3. 03检查引用后移除无用代码
  4. 04为新功能理顺代码结构

安装

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

$npx skills add https://github.com/jablonkai/skills --skill refactoring-optimization

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
flutter-dart.md
kotlin-kmp.md
other-languages.md
refactoring-catalog.md
rust-cpp.md
swift-apple.md

详细介绍

重构优化

目的

在不改变代码行为的前提下,调整可运行代码的结构。输入是难以阅读、难以修改,或在四处重复的代码;输出则是在保持行为不变的同时,结构更佳的代码,每一步之后测试仍然通过,而且生成的差异便于审阅。

纪律才是重点。任何人都能重写一个函数,让它读起来更清晰;难点在于证明它的行为没有改变。因此,这里的顺序始终不变:建立安全网,做一个小而可逆的改动,验证,然后重复。掌握这项技能的价值在于坚持不跳过这些步骤,而不在于知道多少巧妙的重构技巧。

这是配套技能中的 执行 部分。code-analyzer 会检查项目并报告问题;这项技能则针对其中一项发现实际修改代码。

适用场景

  • 函数变得过于庞大,任何人都难以记住其全貌,需要拆分
  • 相同逻辑存在于三四处,而且各处已经开始出现差异
  • 代码无法到达或没有被引用,但没人敢删除
  • 名称、魔法数字或布尔标志参数让调用点难以阅读
  • 代码早于某项语言特性,而该特性能更直接地表达代码意图
  • 重构是实现某项功能的前提——先理清结构,再添加功能

不适用的场景:

  • 查明整个项目有哪些问题 → 使用 code-analyzer,然后再回到这里
  • 修复错误、崩溃或失败的测试 → 使用 error-debugging(这是有意改变行为)
  • 编写这次重构所需的测试作为安全网 → 先使用 test-generation
  • 整理本次会话中刚写的代码 → 内置的 simplify 技能更轻量
  • 提升代码速度 → 先测量;参见下文的性能

优先转到更具体的技能

有些技术栈针对所需的特定结构调整提供了专用技能。遇到适用的专用技能时,应调用它,而不是重新编写其中的指导;使用本技能来遵循外围流程——安全网、步骤顺序、验证和回滚。

场景调用的技能
Compose 状态放置位置不当,或有状态的屏幕与 UI 混杂compose-state-hoisting、compose-state-holder-ui-split
Compose 组件使用布尔标志而非内容插槽compose-slot-api-pattern
Compose 修饰符链、布局包装器、硬编码根节点compose-modifier-and-layout-style
Compose 频繁重组compose-recomposition-performance、compose-stability-diagnostics
Kotlin 协程作用域与取消结构kotlin-coroutines-structured-concurrency
Kotlin 领域类型中的基本类型偏执kotlin-types-value-class
Dart 机械式 lint 修复(dart fix --apply)dart-run-static-analysis
可改用模式匹配的 Dart if/else 链dart-use-pattern-matching
Flutter Widget 树结构调整、溢出、嵌套flutter-fix-layout-issues
Flutter 分层与状态管理清理flutter-apply-architecture-best-practices

Swift、Rust、C++ 和纯 Kotlin/JVM 没有专用技能——本技能可直接用于这些语言。

前置条件

1. 有安全保障吗? 没有安全保障就重构,无异于重写代码并寄希望于一切顺利。开始第一次编辑前,必须满足以下三点:

  • 测试现在是通过的。 绿色基线能让“测试变红了”具有意义。如果测试本来就是红的,请先停下来修复问题或将其隔离,否则测试信号毫无价值。
  • 工作树干净,且更改已提交。 只有存在可回退的内容,回滚才只是执行 git checkout。在未提交的工作基础上重构会毁掉这些更改。
  • 待更改的代码有测试覆盖。 不要求覆盖整个项目,只需覆盖要重构的具体行为。如果没有覆盖,请明确说明,并提出先编写特征测试(test-generation),而不是盲目继续。

如果没有测试,而用户仍想继续,那由用户决定——但要明确说明风险,将步骤拆得比平时更小,并更加依赖编译器和代码检查工具。

2. 使用的是什么技术栈? 根据构建文件判断,而不是根据文件扩展名——代码检查工具、惯用写法和引用检查工具都在构建文件中声明。

构建文件 / 特征技术栈参考文档
build.gradle.kts 中包含 kotlin("multiplatform")、commonMainKotlin Multiplatform / CMPkotlin-kmp.md
build.gradle(.kts) 中包含 com.android.applicationKotlin/JVM、Androidkotlin-kmp.md
pubspec.yamlFlutter / Dartflutter-dart.md
Package.swift、*.xcodeproj、*.xcworkspaceSwift(iOS/macOS)swift-apple.md
Cargo.tomlRustrust-cpp.md
CMakeLists.txt、conanfile.txt、*.cppC / C++rust-cpp.md
package.json、pyproject.toml、go.mod、pom.xmlJS/TS、Python、Go、Javaother-languages.md

只加载一个技术栈参考文档。混用当前项目未涉及的语言惯用写法,会让所有参与代码库开发的人都觉得建议不对劲。

3. 还有谁在修改这个文件? 如果文件有尚未合并的拉取请求,大规模重构可能导致合并冲突,造成的成本超过清理代码带来的收益。开始前请先检查:

git log --since='2 weeks ago' --oneline -- <path>   # recent churn
git branch -a --contains HEAD >/dev/null 2>&1        # and any open branches touching it

如果该文件改动频繁,优先采用多个已合并的小型重构,而不是一次性进行大型重构。

工作流

步骤 1:用工具查找坏味道,而不是靠阅读

每种技术栈都自带了解自身代码坏味道的 Linter;与手动阅读相比,它的检查更全面,也更少带有主观倾向。先运行 Linter,让它确定检查重点——技术栈参考文档中列出了准确的命令和值得启用的规则。

然后,再检查 Linter 不擅长发现的问题,这主要是存在细微差异的重复代码,以及结构有问题而非仅仅不美观的代码:

坏味道检查内容常用重构方式
重复代码相同的 5 行以上代码出现在 3 处以上,或逐渐产生差异的近似副本提取函数/类,或将差异参数化
函数过长有多个修改原因;需要注释才能读懂提取出命名清晰的步骤
嵌套过深if/for 达到 3 层以上;正常流程位于最内层使用卫语句、提前返回、反转条件
参数列表过长参数达到 5 个以上,或连续出现多个布尔值引入参数对象;按标志拆分函数
魔法值含义不明的字面量、重复的字符串键命名常量、枚举或密封类型
名称误导名称未能准确体现代码的行为重命名——使用可追踪引用的方式,切勿直接查找并替换
死代码未被引用的符号、不可达分支、过时的标志确认引用确实不存在后再删除
基本类型偏执到处都用 String 表示 ID、Int 表示金额、Long 表示时间戳使用值类型/包装类型
过度关注他人数据某个函数主要操作另一类型的数据将函数移到该类型中

refactoring-catalog.md 中详细介绍了坏味道与重构方式的对应关系,以及各种重构的具体操作;如果无法根据坏味道判断合适的做法,请查阅该文档。

步骤 2:按从安全到复杂的顺序安排工作

不同重构的风险差异很大,而工作的顺序会影响有多少改动能经受住评审。先做机械式重构:它们可以单独验证,能缩减代码,也常常能让结构性问题显现出来,从而使较难的决策变得明了。

  1. 自动化且可撤销——格式化工具、dart fix --apply、cargo clippy --fix、IDE 级别的重命名。由机器检查;值得单独提交,以免淹没真正重要的差异。
  2. 局部且范围有限——提取函数、引入常量、添加卫语句。只涉及一个文件,不影响调用方。
  3. 模块内的结构性改动——提取类、移动方法、引入参数对象。调用方需要修改,但都在仓库中,编译器也能找到它们。
  4. 跨模块改动——修改公共 API、将平台代码移到 commonMain、替换抽象。首次编辑前需要盘点所有调用方,通常值得拆分为多次改动。

在进行超出第 1 级的任何编辑前,先给出此计划。如果用户事后否决重构计划,之前的工作就白费了;如果用户能及早调整计划,代价则很小。

步骤 3:每次只进行一项重构

工作单元是一项重构,而不是一个文件,也不是一个会话。每项重构之间,代码都能编译,测试也都能通过——正是这个不变量,让犯错的代价从一天缩短到几分钟。

  • 严格保持行为不变,包括那些没人预期的行为:错误消息、顺序、空值/空值处理、溢出,以及其他程序会解析的日志。如果你认为某种行为是 bug,那就作为单独的变更处理——报告它,不要借重构之名悄悄修正。
  • 每一步只改变结构或行为,不能两者兼改。 混合差异无法审查;出了问题时,也无从判断是哪一部分导致的。
  • 重命名和移动时使用能感知引用关系的工具(Serena 的 rename_symbol、safe_delete_symbol、find_referencing_symbols;IDE 的重构功能)。文本搜索会漏掉动态引用,也会匹配到注释和字符串中的无关内容。
  • 不要重新格式化未修改的内容。 对 400 行无关内容只做空白字符修改,会掩盖真正重要的那 12 行。
  • 让代码比你发现它时更加一致。 只用一次的“更好”模式,若与周围 20 个调用点不同,只会让代码库更难读,而不是更容易读。
步骤 4:每一步之后都进行验证

每次重构后都运行测试,不要等到最后再运行。命令见技术栈参考;筛选输出,只保留失败信息。

除了测试通过之外,以下三项检查还能发现测试遗漏的问题:

  • 编译器/代码检查工具没有警告——重构后出现新警告,通常意味着有内容只移动了一半。
  • 差异仅包含你计划修改的内容。 继续之前先阅读 git diff。意外修改很常见,在这里发现代价低,在代码审查时发现代价高。
  • 公共 API 保持不变,或者变更是有意为之,并且所有调用方都已更新。对于已发布的库,仓库外的调用方无法通过编译器找到——请查阅技术栈参考中的二进制兼容性工具。

如果测试失败,就撤销这一步,不要继续调试。按设计,这一步的改动很小;重新正确地做一次,比修补它更省事——而需要调试的重构就不算重构。

步骤 5:说明改了什么,以及保留了什么
## Refactorings applied
<one line per refactoring: what moved/merged/was deleted, and why it is better>

## Verification
<command run, and the real result — after which step>

## Behavior preserved
<how you know: tests, compiler, caller inventory>

## Left alone
<smells found but not addressed — as `file:line` — with the reason>

## Found while refactoring
<bugs or surprises discovered, reported not fixed>

最后两个部分才是价值累积之处。重构时顺手悄悄修复 bug,会造成无人能审查的变更;而你有意保留的代码异味,则是下一位处理者需要的信息。

关于优化的说明

“优化”有两种不同含义,需要采用不同的规范。结构清理——上述所有内容——安全且可逆。性能优化则不然:它以牺牲可读性来换取速度,而且常常误判耗时所在。

因此,如果请求涉及速度,先测量。进行性能分析,找到实际的热点路径,只修改那一处,然后再次测量。没有优化前后的数据,优化就只是一次没有经过验证、也没有明确收益的可读性退步——技术栈参考正是因此列出了各语言的性能分析工具。热点循环中的算法复杂度问题值得立即修复;其他问题都等有了数据再处理。

操作

操作用户意图输出
refactor(默认)“整理一下这段代码”“重构这个函数”保持行为不变的修改,并逐步验证
plan“我们应该先重构什么”按风险排序的分步计划;不修改代码
duplicates“这里有很多复制粘贴的代码”找出重复代码,并在适合的地方提取共享抽象
simplify“这个函数太复杂了”拆分为命名步骤,并展平嵌套结构
deadcode“删除未使用的代码”经过引用验证的删除,并说明动态引用方面的注意事项
rename“这些名称容易让人困惑”根据引用情况,在声明和调用处进行重命名
modernize“改成符合惯用写法的形式”根据代码检查工具,为当前语言版本整理惯用写法

关键约束

  • 重构期间绝不改变行为。 这是整个实践赖以成立的唯一规则: 如果行为可能改变,这就是一次改写,需要作为改写进行审查和测试。
  • 基线状态未通过或未知时,绝不重构。 没有通过测试的起始状态,就无法归因故障,而重构也会因揭示了原有 bug 而受到指责。
  • 绝不删除无法证明没有引用的代码。 反射、依赖注入、序列化、平台入口点以及特定构建变体的代码无法通过文本搜索发现——技术栈参考文档会列出这些情况各自隐藏的问题。
  • 不要在同一项修改中既重构又添加功能。 审查者无法将两者区分开来,git bisect 也无法做到。
  • 不要修改生成的代码。 应修复生成器或模板;重新生成的输出会覆盖修改,而且差异会误导所有人。
  • 不要只根据两处重复代码提取抽象。 重复代码比错误的抽象成本更低——第三处重复才会揭示哪些部分确实存在差异。
  • 没有测量结果就不要优化。 见上文;未经测量的性能优化通常会牺牲清晰度,却毫无收益。
  • 不要追逐指标。 圈复杂度和代码行数能指出值得查看的代码,但它们不是目标;把代码拆成八个函数,整体上也可能更糟。
  • 没有涵盖仓库外使用者的调用方清单,就不要重构公共 API。 对于仓库内的使用者,编译器就是清单;对于仓库外的使用者,则什么都没有。
  • 如果“正确”的结构取决于主观判断,就停下来询问——分层、模块边界和抽象选择都承载着意图,仅凭代码本身无法还原这些意图。

参考资料

  • refactoring-catalog.md — 从代码异味到重构的对应关系、各项重构的具体操作、特征测试和回滚
  • kotlin-kmp.md — Kotlin 和 Android 惯用写法、detekt/ktlint、KMP commonMain 上移和 expect/actual 精简、Compose 路由、二进制兼容性
  • flutter-dart.md — dart analyze/dart fix、提取组件与辅助方法、const 构造函数、资源释放、嵌套结构
  • swift-apple.md — SwiftLint、提取协议、guard 提前返回、拆分 SwiftUI 视图、@MainActor 和并发规范
  • rust-cpp.md — cargo clippy 和 --fix、迭代器链、用 ? 替代嵌套匹配、精简 clone()/unwrap();RAII、智能指针、const 正确性、clang-tidy
  • other-languages.md — JS/TS、Python、Go 和 Java 的工具与惯用写法,适用于需要这些语言的项目

评论

支持 Markdown