mcpField
R
refactor-clean

refactor-clean

github.com/dzhng開発1.4k337 回インストール今日更新
リポジトリ

refactor-clean について

コードの責務を明確にし、構造を簡潔にするリファクタリングをコーディングエージェントに促すスキルです。冗長な仕組みを削除し、具体的な要件がある互換性だけを残して、利用側で動作を検証します。

主な機能

  1. 01概念ごとの責務を明確化
  2. 02冗長な入力とアダプターの削除
  3. 03責務に沿った肥大モジュールの分割
  4. 04利用側での動作検証
  5. 05動作に対する変更規模の確認

活用シーン

  1. 01重複実装の統合
  2. 02責務が混在するモジュールの分割
  3. 03不要になった互換経路の削除

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dzhng/skills --skill refactor-clean

インストール後、Claude Code で/refactor-cleanを入力して読み込まれたか確認します。

詳細

クリーンなリファクタリング

古い形を、今日設計するならコードベースが望む、よりシンプルな形に置き換えます。リファクタリングとは、問題の横に互換性レイヤーを追加することではありません。あらゆる概念に明確な置き場所が一つだけある状態になるまで、所有権を移すことです。これは両方向に当てはまります。重複した N 個の所有者を一つに統合する場合もあれば、過負荷の一つのモジュールを分割し、その中に隠れていた複数の所有者を切り分ける場合もあります。

ワークフロー

  1. 構造的なリファクタリングが不要な場合も含め、変更するすべてのパスで名前を見直します。それぞれの名前が、その概念の現在のスコープを表しているか確認してください。誤解を招くファイル、シンボル、インポートされたバインディング、共有エクスポートはすぐに名前を変更し、同じ作業で、すべての利用箇所、テスト、ドキュメント、エージェントへの指示を更新します。シナリオ固有のコードに限ってシナリオ名を残し、その後、古い参照が残っていないか再検索します。
  2. 明確な所有者が一つもない概念を特定します。複数の所有者に重複している概念か、複数の概念が一つの過負荷のモジュールに融合している概念です。それぞれに一つの所有者を持たせるべき対象を特定します。環境、価格ルール、ジオメトリのソース、ステートマシン、データ契約、レンダラーのフェーズ、API の形状、UI の状態、テストの判定基準などです。
  3. 現在の所有者と利用者をすべて見つけます。ラッパー、エイリアス、処理箇所ごとの定数、コピーされた構造体、移行に関するコメント、「一時的な」分岐は、確証が得られるまで堆積物として扱います。
  4. チェックを緩めたり、入力を任意にしたりする前に、削除の作業を行います。影響を受けるパスにある入力、フラグ、ガード、フォールバック、アダプターごとに、それがなくなった場合にどの必須動作が壊れるかを確認します。書き込み元、転送経路、読み取り側、バリデーター、利用者をたどり、正式な所有者と照らし合わせます。冗長な入力を任意にするだけでは完了ではありません。削除しても必須動作が保たれるなら、その仕組みと、呼び出し箇所、テスト、ドキュメントに残る不要物を削除します。
  5. 残った概念はすべて、本来あるべき場所に移します。ゼロから設計するならどのモジュールがその概念を所有するかを選び、古い呼び出し箇所がその所有者を直接利用するようにします。古い経路は同じ作業で削除するか、統合します。
  6. 互換性を維持するのは、特定された利用者と、削除すると壊れる具体的な動作がある場合に限ります。安全に無視できる古い入力は、今後届く可能性があるというだけでパーサー、バリデーター、アダプターを必要としません。ブリッジが必要な場合は最小限に保ち、削除条件を定めます。仮定上の呼び出し元は根拠になりません。
  7. 影響を受ける失敗時や再試行の経路も含め、利用者を通して動作を検証します。コード、テスト、ドキュメント、エージェントへの指示を対象に、削除した仕組みを再検索します。影響を受けるパスに残した各仕組みについて、名前の付いた利用者と、その仕組みが保つ動作が明確になった時点でのみ完了とします。残したブリッジと、その削除条件を報告します。

ルール

  • すべての行は負債です。最良の仕組みとは、書かずに済む仕組みです。 ガード、バリデータ、収束プロトコル、クリーンアップ処理のいずれを構築する前でも、次の 2 つのチェックがそれを止めます。

    1. プラットフォームがすでにこれをやっていませんか? 回避しようとしているコンポーネントやフレームワークを、コードで回避する前に読みましょう。その cron、リトライ、デフォルト、ライフサイクルを確認します。すでに自身を掃除しているコンポーネントのそばに手作りの掃除役を置くのは純粋な負債であり、それを不要にしたコンポーネントを知らない誰かがデバッグすることになります。
    2. それが防ぐ失敗は、実際の結果を何か変えますか? ガード対象の条件を、その利用者までたどりましょう。利用者が無関心なら — 順序を気にしない利用者に順序チェックを渡す、自分たちのコードしか生成しないデータに対するバリデーションなど — そのガードは何も検証しておらず、書いてはいけません。よくある形として、単一の書き込み経路がすでに確立している不変条件を読み取り時に再チェックする場合があり、その分岐は手で壊された行でしか発火しません。どの呼び出し元がその不正な状態を生み得るか自問しましょう。「誰かがデータベースを編集しない限り、いない」が正直な答えなら、その分岐を削除します。「不整合になるかもしれない」は理由になりません。「そうなれば利用者が誤った動作をする」なら理由になります。 基準は「これは正しいか?」ではありません。防御的なコードはたいてい正しいものです。 基準は「この行が存在しなければ、何が、誰にとって壊れるか?」です。具体的な答えがなければ、その行は書きません。
  • 変更を測定し、大きさと振る舞いの比が悪い場合は形状の欠陥として扱いましょう。 変更を受け入れる前に、それが実際に何を要したかを数えます。テストとドキュメントを除いたプロダクションコード、そしてロジックを除いたコメントです。説明が大半を占める差分は、機構が大半を占める差分とは同じではないからです。

    git diff --numstat <base> -- ':!specs' ':!*.test.*' ':!**/test-harness'
    

    次に、合計だけでなく追加された行を読みます。大きな行数を要する小さな振る舞いの変更は危険信号です。 これは、修正が既存の形状に適合するのではなく、それと戦っていることを示す最も信頼できる兆候です。一般的なケースが属する場所に特殊ケースを重ねている、本来の所有者のそばに 2 人目の所有者を導入している、マージすべきだった 2 つを橋渡しするラッパーを挟んでいる、といった形です。正しい対応は、コードが望むであろう形状から作り直すことであり、なぜ大きくならざるを得なかったかを説明する段落を添えてコミットすることではありません。その説明こそが臭いであり、緩和策ではありません。 正直さに関する 2 つのルール、これがなければ数字は何の意味も持ちません。変更がそれ以外では触れていないファイルでのフォーマッタの揺れは、その変更の一部ではなく、コミットに含めてはなりません。そして、正味のカウントがほぼゼロでも、実際の重みを隠していることがあります。新しい cron、テーブルのカラム、インデックス、エンドポイント、依存関係、あるいは設定フラグは、誰かが今や所有し保守する対象となるサーフェスです。そのため、それらはカウントとは別に明示してください。

  • 既存アーキテクチャのサンクコストを過大評価しないでください。「すでに存在し、動いている」ことは、ある形を維持する理由にはなりません。コーディングエージェントは大規模なアーキテクチャの切り替えを安価にするため、リファクタリングの大きさは、それが置き換えるコードの量ではなく、最終状態の品質で測ってください。振る舞いが維持されなければならない場合は、消費者側のサーフェスでテストによって固定し、その下のアーキテクチャを入れ替えてください。ただし、ほとんどの場合、古い継ぎ目さえそのまま生き残るべきではありません。大規模なリファクタリングは、古いインターフェースを新しい基盤の上に忠実に再構築する機会ではなく、コードベースが今日望むであろう形にそれらを描き直す機会です。

  • N 個のアダプターよりも 1 つの共有プリミティブを優先してください。アダプターが許容されるのは、外部境界において、または短命な移行の継ぎ目としてのみです。

  • **あまりに多くの概念を抱えるモジュールは分割してください。分解もまたリファクタリングです。**重複のマージの双対です。1 つのファイルや関数が無関係な責務を蓄積してしまったときは、所有権の継ぎ目に沿って分割し、各部分が読者が名前を付けられる首尾一貫したスライスを所有し、元のものはコンポジションへと薄くなるようにしてください。**約 1000 行を超えると、ファイルはスメルであり、判決ではありません。**モジュールによってはその大きさに値します(凝集したステートマシン、生成されたテーブル、内部結合が密な 1 つのアルゴリズム)。しかし、数十のルートを登録したり、多くのドメインをインラインで処理したりする god-file は、凝集ではなく蓄積された責務です。分割が正しいのは、それぞれの新しいモジュールが名前を付けられる責務を所有し、依存関係がそれとともに移動することでインポートの圧力が下がるときだけです。誤っているのは、1 つの概念を読者が再構成しなければならない複数のファイルに散らばらせる、行数削減のための分割です。

  • **何か新しいものを構築する前に、すでに何が存在するかを知ってください。**新しい仕組み(形、計算、アセット、ステートマシン、データコントラクト)を書く前に、これを行うもの、または共有できるほど十分に近いものをコードベースで検索してください。何がそこにあるかを知って初めて、本当の判断ができます。それを再利用する、ほぼ重複する 2 つを統合する、または共有コアを両者が呼び出す独立したモジュールに抽出する、という判断です。そしてその判断は、始める前に属するのであって、後から付け足すものではありません。再利用が自動的に答えになるわけではありません。既存のものが間違っているか、本当に異なる場合もあり、そのときは意図的に新しく構築します。これが防ぐ失敗は、すでにそこにあるものを知らないまま構築することです。一致しなければならない 2 つの実装が、その後静かに乖離し、それぞれがもう一方のすでに確定した詳細(向き、単位、エッジケース、順序)を再導出してしまいます。

  • デフォルトで開発専用の互換性を保持しないでください。未出荷の足場は、直ちにクリーンなコントラクトへ移行させるべきです。

  • **来歴のシグナリングはゼロに。ものはそれが何であるかで名付け、どこから来たかで名付けてはなりません。**来歴をエンコードした名前、すなわち分割元のメガファイルを接頭辞にしたスライスファイル、foo-v2/foo-new/foo-legacy、それを生み出した実験にちなんで名付けられたモジュール、置き換えた API にちなんで名付けられたラッパーは、そこに居合わせなかった読者には何の情報ももたらさず、かつて古いものであった者を積極的に誤らせます。古いものがなくなればなおさらです。テストはこうです。今日入ってきた、歴史を何も知らない人が、この名前を選ぶでしょうか?その名前が背景説明なしにしか意味をなさないなら、名前を変えてください。歴史は git に住み、識別子には住みません。同じルールが来歴のコメント(「以前はこれは…」「X から移動」)も打ち消します。それらはコードではなく差分を記述しており、参照対象が消えた瞬間に腐ります。 名前変更はルーチンのメンテナンスであり、別のプロジェクトでも、許可を求める理由でもありません。消費者が必要とする規模で、積極的に行ってください。大規模な機械的差分は、誤解を招く名前を保持する理由にはなりません。内部識別子と、永続化されるキーおよび外部コントラクトを区別してください。後者は、上記の互換性ルールの下で、識別された消費者に対してのみ保持します。古い語彙を保持する内部エイリアスを残さないでください。

  • **拒否よりも冪等なコントラクトを優先してください。**ある操作が 2 回要求されうるとき — 応答が失われた後のリトライ、ユーザーが同じボタンをもう一度クリックすること、リプレイされた webhook — 要求された最終状態に到達することは、エラーではなく成功すべきです。「X をインストール」で、X が属する場所にすでにインストールされている場合は成功です。そのものを返してください。拒否が正しいのは、2 回目の要求が 1 回目とは本当に異なる意味を持つときだけです。すなわち、別の何かがすでにその名前を占めており、要求を履行するとそれを破壊したり隠したりしてしまう場合です。逆にしてしまっている兆候は、呼び出し側が通常の動作を取り戻すためにあなたのエラーコードを特別扱いしなければならない、またはリトライ経路に競合する「すでに存在するか?」という事前読み取りが必要になることです。これは書き込みを超えて当てはまります。存在しないものの削除、購読解除、「完了としてマーク」の切り替えはすべて本質的に冪等であり、それらを 404 や 409 にすることは、あらゆる呼び出し側に記帳処理を押し付けます。

  • **モデルを、本番が実際に書き込むものに制約してください。**どの書き手も生成しないものまで許容するスキーマ — すべてのフローが 1 つの要素しか保存しないリスト、何も到達しない状態 — は、あらゆる消費者に一般ケースの負担を課します。兆候はこうです。データが 1 つしか含みえないのに、消費者が「でもどれ?」と尋ねることです。制約は書き込み経路で強制してください。実際の用途が現れたときに広げてください。

  • 乖離がバグの類型であった場合は、統計、テスト、またはデバッグ出力で所有権を見えるようにしてください。

  • **コードがすでに計算している値を再導出するチェックは、それから乖離します。**ジオメトリ、状態、または導出量を独立に再計算するテストや第 2 の消費者は、紙の上では見えない差異 — 演算順序や丸めの微妙さ — をめぐってコードと食い違い、誤った判定を発することがあります。所有者が計算した値をエクスポートし、そのチェックにそれを消費させてください。そうすれば、コードが生成したものを正確に強制できます。すなわち、計算の所有者は 1 つであり、たまたまほとんど一致する 2 つではありません。

  • **対称または特徴のないプレースホルダーは、それが代理するものの向きや座標のバグを隠すことがあります。**対称的な代役は、座標系が反転していてもいなくても同じようにレンダリングされるため、本物の非対称なアセットがそれを露呈するまで欠陥は見えないままです。プレースホルダーを本物のアセットに入れ替えるときは、それがレンダリングされることだけでなく、向きとフレーミングを再検証してください。

  • 仕様またはハンドオフは、機械的なファイル一覧ではなく、新しい不変条件で更新してください。

  • リファクタリングが無関係な振る舞いへと広がり始めたら、それをスライスしてください。まず共有コントラクトを着地させ、その後、レビュー可能なパスで消費者を移植してください。

コメント