rust-refactor について
Rust 開発者向けのリファクタリングスキルです。診断質問、変換パターン、レビュー用チェックリストを使い、不要な複雑さを見つけて減らします。
主な機能
- 01防御的・積極的なモード
- 024つの診断質問
- 036種類のリファクタリング変換
- 04ドメイン別のモジュール分割
- 05段階的な型移行
- 06作業後のレビュー用チェックリスト
活用シーン
- 01転送するだけのパラメーターの削除
- 02複数の責務を持つファイルの分割
- 03レガシー型の段階的な移行
- 04排他的なオプションの enum 化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pproenca/ dot-skills --skill rust-refactorインストール後、Claude Code で/rust-refactorを入力して読み込まれたか確認します。
同梱ファイルAGENTS.mdmetadata.json
詳細
Rust リファクタリングフレームワーク
このスキルでは、動作する Rust コードを見て、不要な複雑さを見つける方法を学びます。すべてのリファクタリングは診断のための問いから始まり、変換パターンに従って進み、最後にセルフレビューチェックリストで締めくくります。
2 つのリファクタリング方針
コードに手を加える前に、どちらのモードで作業するか決めます。
防御モード: 安全性のために抽象化を追加します。型を分割します。From による橋渡しを作成します。すべての利用箇所を監査します。セキュリティが重要なコード、型の進化、複数クレートにまたがる依存関係で使用します。
攻めのモード: 間接参照を削除します。転送されるだけのパラメーターを削除します。レイヤーを統合します。抽象化が安全性を高めず複雑さだけを増している場合、仲介役が単に転送しているだけの場合、パラメーターが常に None の場合に使用します。
どちらのモードで作業しているかを把握してください。削除すべきときに抽象化を追加せず、追加すべきときに安全性を高めるレイヤーを削除しないでください。
4 つの診断の問い
コードに手を加える前に、これらの問いを確認してください。何をリファクタリングするかを判断できます。
1. 「このパラメーターは単に転送されているだけか?」
兆候: 関数本体では、そのパラメーターを別の関数に渡しているだけです。ローカルロジックはそれに依存していません。 対応: パラメーターを削除します。実際に使用する箇所で、環境変数やグローバルアクセスに置き換えます。最下層から削除し、コンパイルエラーを上位の層へ順に修正します。
2. 「この機能フラグはまだ必要か?」
兆候: ステージが Stable または同等の状態で、デフォルトで有効になっており、ロールバックの予定もありません。
対応: フラグを削除します。条件分岐を削除します。フラグで制御されていた関数名を変更します(init_if_enabled -> init)。監視用の足場も削除します。
3. 「このファイルは多くのことをしすぎていないか?」
兆候: ファイルが約 500 行を超えており、異なるドメイン(スレッド AND ログ AND ジョブなど)を扱う impl ブロックが含まれています。
対応: レイヤーではなく、扱うドメインごとに分割します。以下のモジュール分割ガイドを参照してください。
4. 「この構造体には、同時に存在しないはずのオプションフィールドがあるか?」
兆候: 一度に設定されるのは 1 つだけであるべき Option<T> フィールドが 2 つ以上あります。if a.is_some() { assert!(b.is_none()) } のようなコードや、特定のモードでのみ適用される x_config という名前のフィールドがあります。
対応: 各バリアントに関連するデータだけを持たせた enum に変換します。
// 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
}
積み重ねた変更 -- 各ステップを、個別にコンパイル可能なコミットとして実施します。
- 新しい型と
Fromによる橋渡しを追加する - 古い型と並行して、新しい型をランタイム全体に渡す
- 利用箇所を 1 つずつ移行する(サブシステムごとに 1 コミット)
- すべての利用箇所の移行が完了してから、レガシー型を削除する
変換 4:不要になった機能フラグ -> すっきりと削除
// BEFORE: init_if_enabled(config) -> Option<Handle>
// AFTER: init(config) -> Handle (unconditional, renamed)
完全なクリーンアップ手順:
- すべての呼び出し箇所からフラグのチェックを削除する
- フラグで制御されていた関数名を変更する:
init_if_enabled->init - 戻り値の型を簡素化する:
Option<Handle>->Handle - 機能レジストリでフラグを
Stage::Removedに移動する - 条件分岐を完全に削除する(デフォルトを変更するだけにしない)
- Cargo.toml の
#[cfg(feature = "...")]を確認する -- これらの依存関係を常時有効にする - 旧経路と新経路を追跡していた比較・差異メトリクスを削除する
- 機能フラグの定義を削除する
順序:リリース -> 安定化 -> 構造を整理 -> 最適化 -> フラグを削除 -> 足場を削除。
変換 5:計測を伴うクレートの抽出
コードの整理だけではなく、ビルドのパフォーマンスを改善します。
# 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)
抽出の手順:
- 依存関係が自己完結している、ドメインとして一貫性のある境界を特定します
- 最小限の Cargo.toml を含む新しいクレートを作成します
- コードを移動し、後方互換性を保つために元のクレートで再エクスポートします
- ワークスペースのマニフェストとロックファイルを更新します
- 次のコマンドで検証します:
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 に置き換える → 防御的なアサーションを削除する。
「配線し直すのではなく、書き直す」原則
バグが関数の配線ではなくロジックにある場合は、明示的な順序付きチェックを使って本体を書き直します。たまたま正しい結果を返す既存の 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
1つでも不合格なら、まだ完了していません。リファクタリングの完了を宣言する前に修正してください。