mcpField
R
rust-refactor

rust-refactor

github.com/pproenca开发218317 次安装11 周前更新
代码仓库

关于 rust-refactor

面向 Rust 开发者的重构技能,通过诊断问题、转换步骤和自查清单,帮助识别并简化代码复杂度。

主要功能

  1. 01防御式与进取式重构模式
  2. 02四类重构诊断问题
  3. 03六种具体转换方法
  4. 04按领域拆分模块
  5. 05分阶段迁移类型
  6. 06重构后自查清单

适用场景

  1. 01移除仅被转发的参数
  2. 02拆分承担多个职责的文件
  3. 03逐步迁移旧类型与消费者
  4. 04将互斥的可选字段改为枚举

安装

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

$npx skills add https://github.com/pproenca/dot-skills --skill rust-refactor

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

附带文件AGENTS.mdmetadata.json

详细介绍

Rust 重构框架

本技能将教你审视可运行的 Rust 代码,发现其中不必要的复杂性。每次重构都从一个诊断问题开始,遵循一种转换模式,并以自检清单结束。


两种重构理念

动手修改代码前,先判断自己处于哪种模式。

防御模式: 为了安全而增加抽象。拆分类型。创建 From 桥接。审查每个使用方。适用于:安全关键代码、类型演进、多 crate 依赖。

进取模式: 删除间接层。移除转发参数。合并层级。当抽象增加了复杂性却没有提升安全性、协调层只负责转发,或参数始终为 None 时使用。

明确自己处于哪种模式。该删除时不要增加抽象;该增加时也不要删除安全层。


4 个诊断问题

修改任何代码之前,先检查这些问题。它们决定要重构什么。

1. “这个参数是不是只被转发了?”

信号: 函数体只将参数传递给另一个函数。本地逻辑不依赖该参数。 操作: 移除它。在实际使用的位置改为访问环境或全局状态。先从最底层移除,再逐层修复编译错误。

2. “这个功能标志还需要吗?”

信号: 阶段为 Stable 或等效状态,默认启用,且没有回滚计划。 操作: 移除标志。删除条件判断。重命名受标志控制的函数(init_if_enabled -> init)。移除监控脚手架。

3. “这个文件是不是做了太多事情?”

信号: 文件超过约 500 行,且其中的 impl 块处理不同领域(例如线程、日志和任务)。 操作: 按领域(即它处理的内容)拆分,而不是按层拆分。参见下文的模块拆分指南。

4. “这个结构体是否包含互斥的可选字段?”

信号: 存在 2 个或更多 Option<T> 字段,但同一时间只能设置其中一个。代码中有类似 if a.is_some() { assert!(b.is_none()) } 的逻辑,或有仅适用于某种模式的 x_config 字段。 操作: 将其转换为枚举,每个变体只携带相关数据。

// BEFORE: invalid states representable
struct Auth { api_key: Option<String>, oauth_token: Option<String>, storage: Option<TokenStore> }
// AFTER: each variant carries only what it needs
enum Auth { ApiKey(String), OAuth { token: String, storage: TokenStore } }

6 种重构转换

转换 1:转发参数 -> 环境访问
// BEFORE: metrics threaded through every signature, never used locally
pub async fn process_batch(db: &Database, config: &Config,
    metrics: Option<&MetricsClient>) {           // forwarded
    for item in db.pending_items(config).await? {
        process_item(db, item, metrics).await?;  // forwarded again
    }
}
// AFTER: ambient access at the single point of use
pub async fn process_batch(db: &Database, config: &Config) {
    for item in db.pending_items(config).await? {
        process_item(db, item).await?;
    }
}
pub async fn process_item(db: &Database, item: Item) {
    let result = transform(item)?;
    db.save(result).await?;
    if let Some(m) = metrics::global().as_ref() { m.counter("items_processed", 1); }
}

协调方式: 先从最底层移除。每个中间提交都必须能够编译。

转换 2:单体文件 -> 按领域拆分模块
// BEFORE: src/runtime.rs (950 lines, four concerns)
// AFTER:
src/runtime/mod.rs       (~30 lines: struct def, init, re-exports)
src/runtime/threads.rs   (~200 lines)
src/runtime/logs.rs      (~250 lines)
src/runtime/jobs.rs      (~300 lines)
src/runtime/cache.rs     (~120 lines)

每个文件都是同一结构体的 impl 块扩展。测试随对应代码一起移动。

转换 3:一次性大规模类型变更 -> 增量式 From 桥接
// STEP 1: New types alongside old, with From bridges
impl From<&LegacyPolicy> for NetworkPolicy {
    fn from(value: &LegacyPolicy) -> Self {
        match value {
            LegacyPolicy::FullAccess => NetworkPolicy::Enabled,
            _ => NetworkPolicy::Restricted,
        }
    }
}
// STEP 2: Runtime carries both representations simultaneously
pub struct Permissions {
    pub legacy: LegacyPolicy,         // existing consumers keep working
    pub network_policy: NetworkPolicy, // new consumers use richer types
}

堆叠式变更——每一步都是单独且可独立编译的提交:

  1. 添加新类型和 From 桥接
  2. 在运行时中与旧类型并行传递新类型
  3. 逐一迁移使用方(每个子系统各提交一次)
  4. 只有在所有使用方都迁移后,才移除旧类型
转换 4:失效功能标志 -> 干净移除
// BEFORE: init_if_enabled(config) -> Option<Handle>
// AFTER:  init(config) -> Handle  (unconditional, renamed)

完整清理顺序:

  1. 从所有调用点移除标志检查
  2. 重命名受标志控制的函数:init_if_enabled -> init
  3. 简化返回类型:Option<Handle> -> Handle
  4. 在功能注册表中将该标志移至 Stage::Removed
  5. 完全移除条件分支(不要只是更改默认值)
  6. 检查 Cargo.toml 中的 #[cfg(feature = "...")],将这些依赖改为无条件依赖
  7. 移除用于跟踪新旧路径差异的比较指标
  8. 删除功能标志定义

顺序:发布 -> 稳定 -> 清理结构 -> 优化 -> 移除标志 -> 移除脚手架。

转换 5:提取 crate 并进行测量

这不仅是代码组织问题,也关乎构建性能。

# 1. Measure baseline
cargo build -p parent-crate --timings   # record check + test compile time
# 2. Extract: create new crate, move code, add re-exports for backward compat
# 3. Measure after
cargo build -p parent-crate --timings   # compare
# 4. Report in commit message:
#    cargo check: 57.08s -> 53.54s (~6.2% faster)
#    cargo test --no-run: 2m39.9s -> 2m20s (~12.4% faster)

提取步骤:

  1. 找出边界清晰、领域一致且依赖自包含的部分
  2. 创建新的 crate,并使用最精简的 Cargo.toml
  3. 移动代码,在原 crate 中保留重新导出,以保持向后兼容
  4. 更新工作区清单和锁文件
  5. 验证:cargo test -p parent-crate && cargo test -p new-crate
转换 6:将互斥选项转换为枚举
// BEFORE: three options, only one valid at a time
struct ShellMode {
    direct_cmd: Option<String>,
    zsh_fork_config: Option<ZshForkConfig>,
    pty_handle: Option<PtyHandle>,
}
// AFTER: disjoint union, invalid states unrepresentable
enum ShellMode { Direct(String), ZshFork(ZshForkConfig), Pty(PtyHandle) }

步骤: 检查构造位置,确认有效组合 -> 定义枚举变体 -> 替换结构体构造 -> 将 if x.is_some() 替换为 match -> 移除防御性断言。


“重写,而非重新接线”原则

如果 bug 出在函数的逻辑中(而非其接线方式),请用明确、有序的检查重写函数体。不要委托给一个碰巧能产生正确结果的现有 API。

// WRONG: rewire through existing API ("happens to work")
fn resolve_policy(input: &Input) -> Policy {
    default_policy(input).override_with(input.overrides())
}
// RIGHT: explicit ordered checks
fn resolve_policy(input: &Input) -> Policy {
    if input.is_admin() { return Policy::FullAccess; }
    if input.has_restriction("network") { return Policy::NetworkDenied; }
    if input.is_sandbox_mode() { return Policy::ReadOnly; }
    Policy::Standard
}

显式实现更易审查、更易测试,也更能抵御委托实现未来的变化。


模块拆分指南

按代码操作的对象划分,而不是按架构层划分。每个领域文件都包含共享结构体上该领域方法的 impl 块。

错误:models.rs / services.rs / controllers.rs(按层拆分)。 正确:runtime/threads.rs / runtime/logs.rs / runtime/jobs.rs(按领域拆分)。

规则:

  • 测试要随代码一起移动。绝不要将测试留在原文件中。
  • mod.rs 只包含重新导出。目标长度约为 30 行。
  • 每个文件少于 500 行。超出时,说明其中包含子领域。
  • 优先使用命名类型,而不是零散参数。2 个或更多相关值始终一起传递时 -> 使用结构体。
  • 提取提交中不得更改逻辑。将代码作为一个整体移动。使用 cargo test 验证。

自我审查清单

每次重构后都要执行此检查。每一项都必须通过。

After refactoring, verify:
[ ] Every intermediate state compiles (no big-bang rewrites)
[ ] From bridges exist for any split types
[ ] Tests moved with their code (not left behind)
[ ] No new single-use helper functions introduced
[ ] Removed more code than you added (or justified why not)
[ ] No forwarded parameters remain (each param is used locally)
[ ] Module re-exports are clean (public API in mod.rs)
[ ] Feature flags for stable features removed
[ ] Structs with mutually exclusive options converted to enums
[ ] Build performance measured before/after crate extraction
[ ] Feature flag removal includes function renames and branch deletion
[ ] Chose the right refactoring mode (defensive vs offensive)
[ ] Function logic rewritten, not just rewired through delegation

如果有任何一项未通过,就说明尚未完成。请先修复,再宣布重构完成。

评论

支持 Markdown