refactoring-patterns について
refactoring-patterns は、観察可能な動作を変えずに既存コードの構造を改善する開発スキルです。コードの問題点と名前付きの変換パターンを整理し、テストで確認する小さな手順で安全なリファクタリングを導きます。
主な機能
- 01コードの問題点と対処法の一覧
- 02メソッド構成のパターン
- 03オブジェクト間での責務移動
- 04データと条件分岐の変換
- 05テストで検証する手順
活用シーン
- 01長いメソッドの分割
- 02データを持つクラスへの処理の移動
- 03ネストした条件分岐の簡素化
- 04機能追加前のコード構造の整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wondelai/ skills --skill refactoring-patternsインストール後、Claude Code で/refactoring-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/composing-methods.mdmoving-features.mdorganizing-data.mdrefactoring-workflow.mdsimplifying-conditionals.mdsmell-catalog.md詳細
リファクタリングパターンフレームワーク
既存コードの観察可能な動作を変えずに、内部構造を改善するための規律あるアプローチです。すべてのリファクタリングは、同じサイクルに従います。テストが通ることを確認し、小さな構造変更を一つ適用し、テストが引き続き通ることを確認します。
基本原則
リファクタリングは書き直しではありません。テストに裏付けられた、動作を維持する小さな変換を積み重ねることです。 コードの動作は変えず、構成だけを変えます。大規模な一括書き換えが失敗するのは、構造の変更と動作の変更を組み合わせるため、何が問題を引き起こしたのか特定できなくなるからです。
基盤となる考え方: 質の低いコードは、時間的な制約のもとで成果を届けた結果として自然に生じるものであり、性格上の欠点ではありません。コードの臭いは、構造が劣化していることを示す客観的なシグナルです。臭いのカタログは「どこを見るべきか」を示し、リファクタリングのカタログは「何をすべきか」を示します。
スコアリング
目標:10/10。 クイック診断 の8項目のうち、合格した項目数で構造の品質を評価します。score = round(passed / 8 × 10) とし、単一の臭いが深刻な場合はスコアを下げます。スコアの目安:
- 9-10:明らかな臭いが残っておらず、各関数が一つのことだけを行い、名前から意図が分かり、重複が解消され、適切な箇所で条件分岐にポリモーフィズムを用い、リファクタリングした経路をテストでカバーしています。
- 5-6:多少の臭い(長いメソッドや重複など)は残っていますが、構造はおおむね健全です。
- ≤3:絡み合った条件分岐、神クラス、至るところにある重複など、臭いが広範に存在する状態、または安全にリファクタリングするためのテストがない状態です。
現在のスコアを必ず示し、スコアを下げている臭いを挙げ、10/10に到達するために必要な具体的なリファクタリングを列挙してください。
リファクタリングパターンフレームワーク
コード構造を体系的に改善するための6つの重点領域:
1. きっかけとしてのコードの臭い
基本概念: コードの臭いは、より深い構造上の問題を示す表面的な兆候です。バグではなく、設計によってコードの理解、拡張、保守が難しくなっていることを知らせるサインです。それぞれの臭いには、問題を解決する名前付きのリファクタリングが対応しています。
効果がある理由: 名前の付いた臭いがあれば、「気に入らない」といった主観ではなく、客観的な基準をチームで共有できます。「これはフィーチャー・エンヴィーです」と指摘すれば、修正方法が明確になります。
主なポイント:
- 臭いは5つのファミリーに分類されます。肥大化、オブジェクト指向の濫用、変更の妨げ、不要なもの、結合です
- 最もよく見られる臭いは長いメソッドで、最もコストがかかるのはコードの重複です
- 何をするのか説明するコメントが必要なメソッドは臭いです。代わりに、そのブロックを抽出して名前を付けてください
- ショットガン手術(1つの変更が多くのクラスに及ぶ)と発散的変更(1つのクラスに多くの変更理由がある)は、責務の配置が誤っていることを示す対照的なサインです
- プリミティブ執着(小さなドメインオブジェクトの代わりに生の文字列や整数を使うこと)は、エラーと重複を広げます
コードへの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| 10行を超えるメソッド | メソッドの抽出 | ループ本体を calculateLineTotal() に移す |
| 1つの変更が多くのクラスに及ぶ(ショットガン手術) | メソッド/フィールドの移動 | 分散した振る舞いを1つのクラスに集約する |
| 多くのメソッドで同じパラメーターを使う | パラメーターオブジェクトの導入 | startDate, endDate → DateRange |
| コピー&ペーストされたロジック | メソッドの抽出+メソッドの引き上げ | 共通メソッドまたは基底クラスで共有する |
臭いの名前と修正方法を特定する必要がある場合は、references/smell-catalog.md を参照してください。5つのファミリー(肥大化、OOの濫用、変更の妨げ、不要なもの、結合)のすべてについて、検出のヒューリスティクスと、それぞれに対応するリファクタリングを記載しています。
2. メソッドの構成
基本概念: リファクタリングの多くはここから始まります。長いメソッドを、文章のように読める、名前の明確な小さな処理に分割します。高レベルの手順から、役割がはっきりしたヘルパーに処理を委譲します。
効果がある理由: 意図が伝わる名前を付けた短いメソッドを使うと、コメントが不要になり、バグがひと目でわかり、再利用も可能になります。名前ですべてがわかるなら、メソッド呼び出しを読む負担はありません。
主なポイント:
- Extract Method は最も重要なリファクタリングです。まずこれを習得してください
- コメントを書きたくなったら、そのブロックを抽出し、コメントをメソッド名に使います
- 本体が名前と同じくらい明確なら、Inline Method を使います。価値のない間接化はノイズになります
- 複数の場所で使う計算値には Replace Temp with Query を使います。一時変数が 2 つの役割を担っている場合は、Split Temporary Variable を使います
- ローカル変数が複雑に絡み合っていて抽出できない場合は、Replace Method with Method Object を使います。ローカル変数はフィールドになります
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| コメント付きのブロック | Extract Method | // check eligibility → isEligible() |
| 1 回だけ使う一時変数 | Inline Variable | const price = order.getPrice() を削除 |
| 単純に処理を委譲するメソッド | Inline Method | 1 回しか使わない場合、return deliveries > 5 をインライン化 |
| 複雑に絡み合ったローカル変数が多いメソッド | Replace Method with Method Object | 新しいクラスのフィールドにローカル変数を移す |
メソッド単位の変換を行う際は、references/composing-methods.md を参照してください。Extract/Inline Method、Extract/Inline Variable、Replace Temp with Query、Split Temporary Variable、Replace Method with Method Object の手順と、変換前後のコードを確認できます。
3. オブジェクト間での機能の移動
基本概念: オブジェクト指向設計で重要なのは、責任をどこに持たせるかという判断です。Feature Envy、過剰な結合、クラスサイズの偏りから、メソッドやフィールドが誤ったクラスにあるとわかったら、適切な場所に移動します。
効果がある理由: 使用するデータから離れた場所にメソッドがあると、クラス間に見えない依存関係が生じます。そのため、1 つの論理的な変更が複数のファイルに波及し、Shotgun Surgery につながります。メソッドとデータを同じ場所に置けば、変更を 1 つのクラスに限定できます。
主なポイント:
- メソッドが自クラスよりも別のクラスの機能を多く使う場合は Move Method を使います。Move Field も同様です
- 1 つのクラスが 2 つの役割を担っている場合は Extract Class を使い、変更の軸に沿って分割します。役割が少なすぎる場合は Inline Class を使います
- Hide Delegate はデメテルの法則を徹底します。呼び出しの転送がクラスの主な役割になったら、Remove Middle Man で元に戻します
- この緊張関係はケースごとに解決します。呼び出しの連鎖が不安定ならデリゲートを隠し、単なる転送なら中間者を取り除きます
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| メソッドが別のクラスに依存している | Move Method | Order の calculateShipping() を ShippingPolicy に移す |
| 500 行以上の神クラス | Extract Class | Address のフィールドとメソッドを独立したクラスに移す |
クライアントが a.getB().getC() を呼び出す | Hide Delegate | a.getCThroughB() を追加する |
| クラスが呼び出しを転送するだけ | Remove Middle Man | クライアントがデリゲートを直接呼び出せるようにする |
責任をどこに持たせるか判断する際は、references/moving-features.md を参照してください。Move Method/Field、Extract/Inline Class、Hide Delegate、Remove Middle Man の手順を確認できます。
4. データの整理
中心的な考え方: 生データ、つまりマジックナンバー、公開されたフィールド、整数の型コードは、見つけにくいバグを生み、ドメイン知識を分散させます。振る舞いをカプセル化し、不変条件を保証するオブジェクトに置き換えます。
効果がある理由: int 型の金額には丸め規則も通貨コードもありません。Money オブジェクトはそのすべてをカプセル化するため、ビジネスルールを一か所にまとめられ、型システムがコンパイル時にエラーを検出します。
主なポイント:
- マジックナンバーをシンボリック定数に置き換える — 最も簡単なデータのリファクタリングで、意図を明確にします
- データ値をオブジェクトに置き換えると、プリミティブ型への執着(
EmailAddress、Money、Temperature)を解消できます - フィールドをカプセル化し、コレクションをカプセル化する — 生のフィールドや変更可能な内部リストを公開しないでください
- 型コードが振る舞いに影響する場合はサブクラスに置き換え、サブクラス化が現実的でない場合はストラテジーに置き換えます
- 同一性のセマンティクスが必要な場合は値を参照に変更します(コピーではなく、共有する一つの
Customer)
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
if (status == 2) | マジックナンバーを置き換える | if (status == ORDER_SHIPPED) |
あらゆる場所で渡される String email | データ値をオブジェクトに置き換える | 検証機能を備えた EmailAddress クラス |
| ゲッターが変更可能なリストを返す | コレクションをカプセル化する | Collections.unmodifiableList(items) を返す |
switch 文を伴う int typeCode | 型コードをサブクラスに置き換える | Employee → Engineer、Manager |
プリミティブ型をオブジェクトに置き換える場合は、references/organizing-data.md を参照してください。データ値をオブジェクトに置き換える、値を参照に変更する、マジックナンバーを置き換える、フィールド/コレクションをカプセル化する、型コードを置き換える各バリエーションの手順を説明しています。
5. 条件ロジックの簡素化
中心的な考え方: 深くネストした if/else ツリー、長い switch 文、散在する null チェックは、最も読みづらく、バグが発生しやすいコードです。名前の付いたリファクタリングを使って条件を分解、統合し、より明確な構造に置き換えます。
効果がある理由: 6 分岐の条件式では、読者はすべての経路を頭の中でたどらなければなりません。名前を付けて抽出した分岐は、それ自体が説明となり、ポリモーフィズムによって「このケースを忘れた」という種類のバグをまとめて排除できます。
主なポイント:
- 条件を分解する:条件、then 分岐、else 分岐を名前付きメソッドに抽出します
- 条件式を統合する:同じ結果になる条件を、名前付きのチェックにまとめます
- ネストした条件式をガード節に置き換える:エッジケースを早い段階で処理して戻り、主要な処理経路のインデントを浅く保ちます
- 条件式をポリモーフィズムに置き換える方法は、型に基づく条件式の最善策です
- 特殊ケース(Null Object)を導入すると、散在する
if (x == null)チェックをなくせます。アサーションを導入すると、前提条件に違反した場合に即座に失敗させられます
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
複雑な条件を伴う長い if 文 | 条件を分解する | isSummer(date) と summerCharge() を抽出する |
深くネストした if/else | ガード節に置き換える | エッジケースを先に処理して早期に戻り、主要な処理経路を平坦にする |
| オブジェクトの型に基づく switch 文 | 条件式をポリモーフィズムに置き換える | 各型が独自の calculatePay() を実装する |
あらゆる場所にある if (customer == null) | 特殊ケースを導入する | 安全なデフォルト動作を備えた NullCustomer |
分岐を整理する場合は、references/simplifying-conditionals.md を参照してください。条件の分解/統合、ガード節、条件式のポリモーフィズムへの置き換え、特殊ケース、アサーションについて、変更前と変更後の例を掲載しています。
6. 安全なリファクタリングのワークフロー
基本概念: リファクタリングが安全なのは、テストで保護されている場合に限ります。ワークフローは機械的です。テストを実行する(成功)、小さな変更を1つ適用する、テストを実行する(成功)、コミットする。テストが失敗したら、リバートします。壊れたリファクタリングのデバッグはしないでください。
効果がある理由: 小さなステップなら、失敗の原因が明らかになります(最後に行った変更が原因です)。リバートには数秒しかかかりません。一方、大規模な一括書き換えの失敗をデバッグすると、数日かかることがあります。
主なポイント:
- 3回ルール:重複は1回目なら許容し、2回目で記録し、3回目でリファクタリングします
- 準備的リファクタリング:機能を追加する前に、実装しやすくなるよう構造を変更します。理解を深めるためのリファクタリングや、コードに目を通して手を加える際のリファクタリングによって、コードを継続的に改善できます
- リファクタリングをしない場合:書き換えのほうが簡単な場合、テストがなく、追加も現実的でない場合、またはコードがまもなく削除される場合
- まず明瞭さを高め、その後プロファイリングで測定したボトルネックを最適化します。明瞭なコードは調整しやすいためです
- 抽象化による分岐と並行変更を活用すると、長期間存続するブランチを作らずに、本番環境で大規模なリファクタリングを実施できます
コードでの適用例:
| 状況 | パターン | 例 |
|---|---|---|
| 機能を追加しようとしている | 準備的リファクタリング | まず挿入箇所を整理する |
| 同じロジックの3つ目のコピー | 3回ルール | 今すぐ共通ロジックを抽出する |
| 本番環境での大規模な API 変更 | 抽象化による分岐 | 抽象化レイヤーを追加し、呼び出し元を移行して、古い経路を削除する |
| 広く使われているメソッドの名前変更 | 並行変更 | 新しい名前を追加し、古い名前を非推奨にして、移行後に削除する |
大規模またはリスクの高いリファクタリングの前に、references/refactoring-workflow.md を参照してください。テストが成功した状態を保つ一連のサイクル、リファクタリングをする場合としない場合、パフォーマンス、抽象化による分岐、並行変更について詳しく説明しています。
よくある間違い
| 間違い | 失敗する理由 | 対処法 |
|---|---|---|
| テストなしでリファクタリングする | 動作の変化を検出する安全網がない | まず特性テストを書く |
| 大規模な一括書き換え | 構造の変更と動作の変更が混在し、デバッグできなくなる | 可能な限り小さなステップに分け、それぞれの後にテストする |
| 機能追加と同時にリファクタリングする | 2つの作業を同時に行うと、どちらの変更も検証できない | 先にリファクタリングしてコミットし、その後に機能を追加してコミットする |
| 呼び出し元を更新せずに名前を変更する | ビルドが失敗する、またはデッドコードが残る | IDE の名前変更機能を使い、すべての参照を検索する |
| 小さなメソッドを抽出しすぎる | 名前が不適切だと、明瞭さを損なうだけの間接性が生じる | 各メソッドの名前によって、本体を読む必要がなくなるようにする |
| コードの臭いのカタログを無視する | 実証済みの手法を使わず、修正方法を一から考え直すことになる | 名前の付いたコードの臭いを学ぶ。それぞれに対応するリファクタリングがあります |
| いずれ廃棄されるコードをリファクタリングする | 破棄予定のコードを磨いても無駄になる | コードの寿命を確認し、投資に見合うか判断する |
| リファクタリング中に最適化する | 明瞭さとパフォーマンスを混同する | まず明瞭さを高め、次にプロファイリングを行い、その後にホットパスを最適化する |
簡易診断
| 質問 | いいえの場合 | 対応 |
|---|---|---|
| 開始前にテストは成功していますか? | 安全網がありません | まずテストを書き直すか修正します。失敗している状態でリファクタリングしてはいけません |
| 修正しようとしているコードの臭いを特定できますか? | カタログに基づかず、直感でリファクタリングしています | コードの臭いを特定し、対応するリファクタリングを適用します |
| 各メソッドは約10行以下ですか? | 長いメソッドの可能性があります | メソッド抽出で名前付きのステップに分割します |
| 各クラスに変更理由は1つだけですか? | 変更の発散または大きすぎるクラスの可能性があります | クラス抽出で責務を分離します |
| 重複したコードブロックはありますか? | 最もコストの高いコードの臭いです | 共通メソッドまたは基底クラスに共通ロジックを抽出します |
| 適切な箇所で条件分岐の代わりにポリモーフィズムを使っていますか? | switch 文が残っています | 条件分岐をポリモーフィズムに置き換えます |
| 各ステップの後にコミットしていますか? | 作業を失ったり、変更が混在したりするリスクがあります | テスト成功から次のテスト成功までの各変更後にコミットします |
| 変更後、コードは読みやすくなっていますか? | リファクタリングで複雑さが増しています | リバートし、別の方法を試します |
さらに読む
既存のコードを改善するための決定版ガイドです。
- "Refactoring: Improving the Design of Existing Code (2nd Edition)" Martin Fowler 著
- "Working Effectively with Legacy Code" Michael Feathers 著(テストのないコードに対応する補完書)
- "Clean Code: A Handbook of Agile Software Craftsmanship" Robert C. Martin 著(命名とスタイルの原則を補完)
著者について
Martin Fowler は Thoughtworks のチーフサイエンティストであり、アジャイルマニフェストの署名者です。また、カタログ形式で名前を付けたリファクタリングを一般的な開発に広めた『Refactoring: Improving the Design of Existing Code』(1999年、第2版は2018年)の著者でもあります。彼のカタログは、主要なすべての IDE に搭載されている自動リファクタリングツールの基盤となっています。