『リファクタリング 第2版』要約 ― コードを「動く」から「変えられる」へ
はじめに
Martin Fowler 著『Refactoring: Improving the Design of Existing Code(第2版)』は、
コードの「動作を変えずに設計を改善する」技術を体系化した書籍です。
サンプルコードは JavaScript ですが、考え方は言語を問いません。
第1章 リファクタリング:最初の例
小さな劇団の請求書計算プログラムを題材に、リファクタリングの手順を実演しています。
出発点は1つの巨大な statement() 関数。変更要件(HTML 出力対応、新ジャンル追加)が来たとき、
コードが複雑すぎて手が出せない状態です。
改善の流れは大きく3段階です。
-
関数の分解 ― switch 文や計算ロジックを小さな関数(
amountFor、volumeCreditsFor)に抽出する。 -
フェーズの分離 ―
createStatementData()で計算データを生成し、renderPlainText()/renderHtml()で表示する構造に分ける。 -
ポリモーフィズムの導入 ― 演劇ジャンルごとのサブクラス(
TragedyCalculator、ComedyCalculator)を作り、条件分岐を排除する。
ポイント:リファクタリングは「小さなステップ × コンパイル → テスト → コミット」の繰り返しが前提です。
一度に大きく変えると、バグが混入したとき原因を特定できなくなります。
第2章 リファクタリングの原則
定義
リファクタリング(名詞): 観察可能な動作を変えずに、ソフトウェアの内部構造を変更すること。
「コードを整理する作業」全般をリファクタリングと呼ぶのは誤りで、
Fowler は「動作を保ったまま行う小さなステップの連続」と明確に定義しています。
「数日コードが壊れていた」のはリファクタリングではありません。
なぜやるのか
- 設計の劣化を防ぐ ― 手を入れるたびに設計が崩れていく。定期的なリファクタリングで健全性を保つ。
- 可読性の向上 ― 頭の中の理解をコードに移植することで、将来の自分や同僚が読めるようになる。
- バグ発見の補助 ― 構造が明確になるほど、仮定の誤りが浮かび上がる。
- 開発速度の維持(設計スタミナ仮説) ― 内部品質が高いコードは、新機能追加が速い。泥沼化したコードは逆。
いつやるか
| タイミング | 内容 |
|---|---|
| 準備的 | 機能追加の直前に、変更しやすい形に整える |
| 理解的 | コードを読みながら、理解をコードに反映させる |
| ゴミ拾い | 気づいた小さな問題をその場で直す |
| 長期的 | 数週間かけて少しずつ改善する(チーム全体で) |
基本は「機会主義的」なリファクタリングです。専用スプリントを組むのではなく、
日常的な機能追加・バグ修正の流れの中で行います。
リファクタリングしないほうがいいとき
- 触る必要のないコード(API として扱える醜いコードはそのままでもよい)
- リファクタリングより書き直しのほうが明らかに早い場合
注意点:マネージャーへの伝え方
技術的理解のないマネージャーには「リファクタリング」と言わず、
機能追加・バグ修正の一環として行うのが現実的だと Fowler は述べています(物議を醸す指摘)。
第3章 コードの悪臭
リファクタリングすべきタイミングを嗅ぎ分けるための「臭い」一覧です。
代表的なものをピックアップします。
| 臭い | 概要 |
|---|---|
| 重複したコード | 同じロジックが複数箇所にある |
| 長い関数 | 処理を読み解くのに時間がかかる |
| 長いパラメータリスト | 引数が多すぎて意図が読めない |
| グローバルデータ | どこからでも変更できる危険な状態 |
| 変更に要するショットガン手術 | 1つの変更で複数ファイルを修正する必要がある |
| データの泥団子 | いつも一緒に使うデータが別々に定義されている |
| 基本データ型への執着 | クラスで表現すべきものをプリミティブで扱っている |
| スイッチ文 | 型ごとの条件分岐が複数箇所に重複する |
| 怠慢なクラス | ほとんど役割がない |
| 推測による汎用性 | 将来使いそうだからと作った機能が実際には使われない |
第4章 テストの構築
リファクタリングの大前提は 自己テストコード の存在です。
- テストがなければリファクタリングは危険な綱渡り。
- テストは頻繁に実行できるよう高速であることが必要。
- テスト失敗時に「最後に通ったコードとの差分」が少なければ、バグの原因が一目瞭然。
xUnit 系フレームワーク(JUnit、JUnit5、テスト対象言語のテストライブラリ)の活用を推奨しています。
第5章 カタログの紹介
第6〜12章は、具体的なリファクタリング手法のカタログです。
各手法は「動機 → やり方 → 例」の形式で説明されています。
第6章 リファクタリングの最初のセット(主要手法)
| 手法名 | 概要 |
|---|---|
| 関数抽出(Extract Function) | コード片を独立した関数に切り出す |
| 関数のインライン化(Inline Function) | 呼び出しよりコードを直接書いたほうが明確なときに逆を行う |
| 変数の抽出(Extract Variable) | 複雑な式に名前を付けて変数に格納する |
| 変数のインライン化(Inline Variable) | 変数がかえって邪魔なとき、式に戻す |
| 関数宣言の変更(Change Function Declaration) | 関数名・引数名を意図を表す名前に変える |
| 変数のカプセル化(Encapsulate Variable) | 可変データへのアクセスを関数経由にする |
| 変数名の変更(Rename Variable) | コンテキストに合った名前に変える |
| パラメータオブジェクトの導入(Introduce Parameter Object) | 関連する引数をまとめてオブジェクト化する |
| 関数群のクラスへの集約(Combine Functions into Class) | 共通データを扱う関数をクラスにまとめる |
| フェーズの分離(Split Phase) | 処理を「入力の変換」と「出力の整形」に分離する |
第7章 カプセル化
設計上の重要原則として、データを直接公開しない ことが強調されています。
- レコードのカプセル化 ― 生のデータ構造をクラスに包む。
- コレクションのカプセル化 ― リストをそのまま返さず、追加・削除メソッドを通じてのみ変更させる。
- 一時変数をクエリ関数に置換(Replace Temp with Query) ― 長い関数を分解するために、まず一時変数を関数化する。
- クラスの抽出(Extract Class) ― 責務が増えたクラスを分割する。
- 委譲の隠蔽(Hide Delegate) ― 呼び出し元が内部構造に依存しないよう、メソッドで中継する。
第8章 移動機能
コードを「あるべき場所」に移す手法群です。
- 関数の移動(Move Function) ― 使う側のコンテキストに関数を移す。
- フィールドの移動(Move Field) ― データを参照される場所に近づける。
-
ループのパイプライン化(Replace Loop with Pipeline) ―
forループをfilter・map・reduceに置き換える。 - デッドコードの削除(Remove Dead Code) ― 使われていないコードは躊躇なく消す(バージョン管理があるから)。
第9章 データの整理
| 手法 | 概要 |
|---|---|
| 変数の分割(Split Variable) | 複数の役割を持つ変数を分割する |
| フィールド名の変更(Rename Field) | データ構造のフィールド名を明確にする |
| マジックナンバーの置換(Replace Magic Literal) | 数値リテラルを意味のある名前の定数に置き換える |
| 変数のカプセル化 | 再掲。グローバル変数は特に注意が必要 |
| 参照から値への変更(Change Reference to Value) | オブジェクトを値として扱い、変更不可にする |
第10章 条件付きロジックの簡素化
- 条件の分解(Decompose Conditional) ― 複雑な条件式を関数に抽出して名前を付ける。
- 条件式の統合(Consolidate Conditional Expression) ― 同じ結果の条件をまとめる。
- ガード節による入れ子条件の置換(Replace Nested Conditional with Guard Clauses) ― 早期リターンで深いネストを解消する。
-
ポリモーフィズムによる条件の置換(Replace Conditional with Polymorphism) ― 型ごとの
switchをサブクラスの override に変える。 - 特殊ケースの導入(Introduce Special Case) ― null チェックや特殊値の処理を専用クラスにまとめる(Null Object パターン)。
第11章 API のリファクタリング
- クエリと修飾子の分離(Separate Query from Modifier) ― 値を返す関数と副作用を起こす関数を分ける(コマンドクエリ分離)。
- パラメータの統合(Parameterize Function) ― 似た関数を引数で吸収する。
-
フラグパラメータの削除(Remove Flag Argument) ―
boolean引数で挙動を変えるのをやめ、関数を分ける。 - コンストラクタをファクトリ関数に置換(Replace Constructor with Factory Function) ― インスタンス生成の柔軟性を上げる。
- コマンドオブジェクトへの置換(Replace Function with Command) ― 複雑な関数をクラスに変えることで、undo やサブクラス化を可能にする。
第12章 相続の扱い
継承に関するリファクタリングです。
- メソッドのプルアップ/プッシュダウン(Pull Up / Push Down Method) ― スーパークラスとサブクラスの間でメソッドを移す。
- フィールドのプルアップ(Pull Up Field) ― 重複したフィールドを親に集約する。
- 継承をデリゲートに置き換える(Replace Subclass with Delegate) ― 継承より委譲のほうが柔軟なケースに対応する。
- スーパークラスをデリゲートに置き換える(Replace Superclass with Delegate) ― 「is-a」が不適切なとき、「has-a」に変える。
全体を通じての感想
本書のメッセージを一言でまとめると、
「リファクタリングは品質のためではなく、スピードのためにやる」
です。コードが美しいこと自体が目的ではなく、変更しやすい設計が「次の機能追加を速くする」という経済的合理性の話として一貫して語られています。
カタログ部分(第6〜12章)は辞書的な使い方が合っており、
実際に「このコード、臭うな」と感じたときに手法名を引くのが実用的です。
継続的インテグレーション(CI)・テスト駆動開発(TDD)・YAGNI(You Aren't Gonna Need It)との親和性が高く、
アジャイル開発の文脈で読むと全てが繋がります。