3行でいうと
- 外から見た振る舞いを変えず、内部構造を理解しやすく整える。
- 自動テストを前提に、小さな変更を一つずつ積み重ねる。
- 直すべきコードの兆候(コードの臭い)を24種に整理する。
向いている人
コードの読みやすさや保守性を、日々の開発で高めたいプログラマー。
読む意味
レビューで指摘するときの共通語(コードの臭い、各技法の名前)を持てる。変更を安全に進めるための考え方と手順を学べる。
読むときの注意
第2版のコード例はJavaScriptで、初版のJavaとは異なる。リファクタリングは自動テストが前提で、テストがない領域ではまずテストの整備を優先し、締切直前や使い捨てのコードでは控えるべきだとされる。
どんな本か
マーチン・ファウラーが、ケント・ベックの協力を得て2018年に出した第2版です(初版は1999年)。リファクタリングとは、コードの外から見た振る舞いを変えずに、内部の構造を整理して理解や修正をしやすくすることです。この言葉を業界の共通語として定着させた本で、実務者が日常で使える手順をカタログにまとめた点に価値があります。第2版ではコード例がJavaからJavaScriptに書き改められました。
全12章です。第1章は演劇会社が顧客向けの請求明細書を計算するコードを題材に、改善の流れを実演します。第2章で原則、第3章で改善が必要なコードの兆候「コードの臭い」(24種)、第4章でテストの作り方を扱い、第5章でカタログの読み方を説明したあと、第6章から第12章が、関数の抽出、カプセル化、機能の移動、データの再編成、条件記述の単純化、APIの変更、継承の扱いといった個々の技法のカタログです。各技法は名前、動機、手順、例の形で示されます。
方法論の核は、自動テストを前提に、一度に一つの小さな変更をしてはテストで振る舞いが変わっていないことを確かめる「小さなステップ」です。機能追加とリファクタリングを同時にせず、帽子を換えるように切り分ける「二つの帽子」の考え方も示されます。逆に、書き直した方が速いほど壊れたコード、リリース直前、使い捨てのコード、テストがない領域では、リファクタリングを控えるべきだとも論じます。
中身の柱
- 第1章: 請求明細書のコードを題材に、改善の流れを実演する。
- 第2章: リファクタリングの定義、行う理由、行わない場面、性能との関係。
- 第3章: 改善が必要なコードの兆候「コードの臭い」を24種挙げる。
- 第4章: テストの重要性と、自己テストコードの作り方。
- 第6〜12章: 抽出、カプセル化、移動、条件の単純化、継承などの技法カタログ。
あわせて読む本
- Clean Code同じ設計分野で、読みやすいコードの書き方を扱う
書評データベースの全文を読む
概要
【訂正】旧版の記述では原著出版年が1999年とされていたが、1999年はマーチン・ファウラーが刊行した初版(Java言語による第1版)の出版年である。本ノートが扱う「第2版」は2018年にケント・ベックの協力を得て刊行されたものであり、コード例がJavaからJavaScriptへ全面的に書き改められている。年号をこの2018年に訂正した。
『リファクタリング』は、コードの外部的な振る舞い(実行結果)を一切変えずに、内部の構造だけを整理・改善していく体系的な技法群を確立した、ソフトウェア工学における記念碑的著作である。「リファクタリング(refactoring)」という言葉自体を業界標準の語彙として定着させた本であり、ウィリアム・オプダイクの博士論文(1992年)における先駆的な理論研究を土台にしつつ、実務者が日常的に使える具体的な手順(レシピ)としてカタログ化した点に最大の功績がある。ファウラーは、優れたコードとは「意図が明確に読み手に伝わるコード」であると定義し、リファクタリングとはその可読性・保守性を、機能を壊すことなく段階的に高めていく規律あるプロセスだと位置づける。第2版では、静的型付け言語であるJavaに代わり、動的型付けかつマルチパラダイムなJavaScriptがコード例に採用され、より現代的な開発言語の読者にも実践しやすい内容に刷新されるとともに、63種のリファクタリングのうち17種が新たに追加された(1st版から残ったのは53種、削除されたのは10種)。
本の構成
本書は全12章から成る。第1章「リファクタリング:はじめの一例」では、演劇会社の顧客向け請求明細書を計算する具体的なコードを題材に、リファクタリングの一連の流れを実演し、読者が全体像をつかめるようにしている。第2章「リファクタリングの原則」ではリファクタリングの定義、実施すべき理由、行うべきでない場面、リファクタリングとパフォーマンスの関係などを論じる。第3章「コードの臭い」では改善すべきコードの兆候を24種にわたってカタログ化する。第4章「テストの構築」ではテストの重要性と自己テストコードの作り方を扱う。第5章「カタログの紹介」は、以降の各リファクタリング項目がどのような形式(名前・スケッチ・動機・手順・例)で記述されるかを説明する導入章である。そして本書の大半を占める第6章から第12章にかけて、「第一の技法グループ」「カプセル化」「特性の移動」「データの再編成」「条件記述の単純化」「APIのリファクタリング」「継承の取り扱い」という7つの分類のもとに、個々のリファクタリング手法が名前・動機・手順・具体例とともにカタログ形式で列挙される。
主要な内容
基礎概念:リファクタリングとは何か
ファウラーはリファクタリングを「ソフトウェアの外部から見た振る舞いを変えずに、理解や修正が容易になるように、その内部構造を変化させること」と定義する。関連してよく引用される指針が「二つの帽子」の比喩である。開発者は「機能追加の帽子」をかぶって新しい振る舞いを付け加えるか、「リファクタリングの帽子」をかぶって構造を整理するかのどちらかに集中すべきであり、両方を同時に行おうとするとミスが増えると説く。リファクタリングを行うべき典型的な契機として、①同じような変更を3回目に行うとき(三度目の法則)、②新機能を追加する前にコードを理解しやすくしておくとき、③バグを修正する際にその原因を見つけやすくするとき、④コードレビューの場、が挙げられる。
逆に、リファクタリングを行うべきでない、あるいは控えるべき場面についても本書は明確に論じている。第一に、対象のコードがあまりにも壊れていて、部分的な改善よりも書き直した方が明らかに速いと判断できる場合には、リファクタリングではなく再設計を選ぶべきだとする。第二に、リリース直前の締め切りが迫っている状況では、リファクタリングが持つ「一時的にコードを不安定にするリスク」を取るべきではなく、機能を安全に届けることを優先すべきだとされる。第三に、今後触る予定のない使い捨てのコード(プロトタイプや使い切りのスクリプトなど)には、将来の保守性を高める投資であるリファクタリングの価値がそもそも乏しい。第四に、自己テストコードが整っていない領域にいきなり大規模なリファクタリングを仕掛けることも避けるべきで、その場合はまずテストを整備すること自体を優先課題とすべきだとされる。またリファクタリングとパフォーマンスの関係については、リファクタリングの多くは一時的に処理速度を犠牲にする可能性があるが、構造が明快になった後の方が最終的なパフォーマンスチューニングの効果測定・改善箇所の特定が容易になるため、「まず正しく動作し理解しやすい形に整理し、それから計測に基づいて最適化する」という順序を推奨している。
コードの臭い(Code Smells) ― 第3章、24種
「コードの臭い」とは、それ自体はバグではないが、将来的な保守性の低下を予告する構造的な兆候を指す概念で、本書はこれを24種類に整理している。
- 謎めいた名前(Mysterious Name) ― 関数・変数・クラスの名前がその役割を的確に表しておらず、命名だけではコードの意図が伝わらない状態。最も基本的な臭いとされ、Rename系のリファクタリングでまず解消すべき対象となる。
- 重複したコード(Duplicated Code) ― 同一または酷似した処理が複数箇所に存在する状態。修正のたびに全箇所を直す必要が生じるため、Extract Function等で一箇所に統合する。
- 長い関数(Long Function) ― 一つの関数が多くの処理を詰め込みすぎて長大化している状態。「関数は短ければ短いほど良い」という方針のもと、Extract Functionによる分割が推奨される。
- 長いパラメータリスト(Long Parameter List) ― 関数の引数が多すぎて呼び出しの理解と保守を困難にしている状態。Introduce Parameter ObjectやPreserve Whole Objectでまとめる。
- グローバルなデータ(Global Data) ― どこからでも書き換え可能な共有状態。値がいつ変わったか追跡しづらく、Encapsulate Variableでアクセスを制御下に置く。
- 可変データ(Mutable Data) ― 同じデータ構造が複数箇所で書き換えられ不具合の温床となる状態。Separate Query from Modifierなどで更新箇所を限定する。
- 発散的変更(Divergent Change) ― 一つのモジュールが、性質の異なる複数の理由でそれぞれ別々に変更される状態。単一責任の原則違反であり、Split PhaseやExtract Classで責務ごとに分割する。
- 散弾銃手術(Shotgun Surgery) ― 一つの変更要求のために多数のクラスへ少しずつ修正が必要になる状態。関連ロジックをMove Function等で一箇所に集約して緩和する。
- 特性の横恋慕(Feature Envy) ― ある関数が自分の所属クラスよりも他クラスのデータに強い関心を示す状態。Move Functionで本来居るべき場所へ移す。
- データの群れ(Data Clumps) ― 複数のデータ項目が常にセットで登場する状態。Extract Classでひとまとめのオブジェクトに昇格させる。
- 基本型への執着(Primitive Obsession) ― 電話番号や金額のような概念を単純な文字列や数値のまま扱い続ける習慣。Replace Primitive with Objectで専用の値オブジェクトに置き換える。
- 重複したスイッチ文(Repeated Switches) ― 同じ条件分岐がコードの複数箇所に繰り返し現れる状態。Replace Conditional with Polymorphismで一元化する。
- ループ(Loops) ― 従来のfor/whileループ。map・filter・reduce等のパイプライン処理(Replace Loop with Pipeline)の方が処理の意図を読み取りやすいとされる。
- 怠け者要素(Lazy Element) ― リファクタリングの結果、存在意義がほとんどなくなった関数やクラス。Inline FunctionやInline Classで取り除く。
- 疑わしき一般化(Speculative Generality) ― 「将来必要になるかもしれない」という予測だけで作られた、現在使われていない過剰な抽象化。YAGNI原則に反するとされ、Collapse HierarchyやInline Functionで単純化する。
- 一時的属性(Temporary Field) ― クラスのフィールドが特定の状況でしか値を持たない状態。Extract Classでその変数群を専用クラスへ切り出す。
- メッセージの連鎖(Message Chains) ―
a.getB().getC().getD()のようにオブジェクトを次々にたどる連鎖的な呼び出し。Hide Delegateで委譲を隠蔽する。 - 仲介人(Middle Man) ― クラスの多くのメソッドが単に別オブジェクトへ委譲しているだけの状態。Remove Middle Manで呼び出し元に直接委譲先を呼ばせる。
- 内部者取引(Insider Trading) ― 別々であるべきモジュール同士が互いの内部データに過度に依存し密結合になっている状態。Move FunctionやMove Fieldで境界を引き直す。
- 巨大なクラス(Large Class) ― 単一クラスが多くのフィールド・メソッドを抱え込み責務が肥大化した状態。Extract Classで新しいクラスへ切り出す。
- 異なるインターフェースの代替クラス群(Alternative Classes with Different Interfaces) ― 同じ役割の複数クラスが異なるメソッド名・シグネチャを持ち置き換えて使えない状態。Change Function Declarationでインターフェースを揃える。
- データクラス(Data Class) ― getter/setterだけで意味のある振る舞いを一切持たないクラス。Move FunctionでロジックをそのDataClass自身に移す。
- 拒否された遺贈(Refused Bequest) ― サブクラスが親クラスから継承した機能の一部しか使わず残りを無効化している状態。Push Down MethodやReplace Subclass with Delegateで見直す。
- コメント(Comments) ― コードの意図説明を大量のコメントに頼っている状態。著者は「コメントを書きたくなったら、まずコードそのものを自己説明的にリファクタリングできないか考えよ」と説く。
第6章 第一の技法グループ(11種)
もっとも基本的で登場頻度の高い11種類のリファクタリング。以下、それぞれについて変換の前後の姿を簡潔に示す。
- 関数の抽出(Extract Function) ― ひとまとまりの処理を、内容を的確に表す名前を持つ独立した関数として切り出す、最も基本的かつ高頻度の技法。【変換例】before: 長い関数の中に、コメントで説明されるような一塊の処理(例えば注文の詳細を出力する数行)が埋め込まれている。after: その一塊が
printOwing()のような意図の伝わる名前を持つ独立した関数として切り出され、元の関数にはその関数を呼び出す一行だけが残る。 - 関数のインライン化(Inline Function) ― 関数の中身が名前以上に単純明快で間接参照が理解の妨げになる場合、呼び出しを本体コードで置き換える逆操作。【変換例】before:
moreThanFiveLateDeliveries(order)という関数が、実質的にorder.deliveryDate - order.placedOn > 5という一行を返すだけなのに、呼び出し側がわざわざ間接的にこの関数を経由している。after: 関数呼び出しをやめ、呼び出し箇所に直接その式を書き込む。 - 変数の抽出(Extract Variable) ― 複雑で読み取りにくい式の一部に、意味を表す名前の変数を導入し可読性を高める。【変換例】before: 基本価格・数量割引・送料をまとめて計算する一行の複雑な式が、意味の区切りなく書かれている。after:
basePrice、quantityDiscount、shippingといった名前の変数にそれぞれの部分式の結果を代入してから、それらを合計して返す。 - 変数のインライン化(Inline Variable) ― 変数名が元の式以上の説明力を持たない場合に、参照を元の式で置き換える逆操作。【変換例】before:
let basePrice = order.basePrice; return (basePrice > 1000);のように、変数が元の式をそのまま複製しているだけで説明力を加えていない。after:return order.basePrice > 1000;のように変数参照を元の式に戻し、不要な間接参照を取り除く。 - 関数宣言の変更(Change Function Declaration) ― 関数名や引数の並び・数を、役割をより正確に表すものに変更する(旧版のRename Method・Add Parameter等を統合)。【変換例】before:
circum(radius)という関数名が、実際には円周ではなく別の値を返しているなど、名前や引数が実態と食い違っている。after: 関数名をcircumferenceに変更し、引数名も役割が伝わる形に付け替える。 - 変数のカプセル化(Encapsulate Variable) ― 広い範囲から直接参照・変更されるデータへのアクセスをgetter/setter経由に限定し、将来の変更を容易にする。【変換例】before:
defaultOwnerというグローバル変数が、コードのあちこちから直接読み書きされている。after:getDefaultOwner()/setDefaultOwner()という関数を用意し、全ての参照をこの関数経由に置き換えることで、変更を一箇所に限定する。 - 変数名の変更(Rename Variable) ― 変数の役割が名前から読み取れない場合に、意図を的確に表す名前へ付け替える。【変換例】before: 変数名が
aやdataのように汎用的すぎて、保持している値の意味が読み取れない。after:outstandingBalanceのように、値の意味を的確に表す名前に置き換える。 - パラメータオブジェクトの導入(Introduce Parameter Object) ― 常にセットで渡される複数のデータ項目を一つのオブジェクトにまとめ、関数シグネチャを単純化する。【変換例】before:
amountInvoiced(startDate, endDate)、amountReceived(startDate, endDate)のように、同じ(startDate, endDate)という組が複数の関数シグネチャに繰り返し登場する。after:DateRangeオブジェクトを導入し、amountInvoiced(aDateRange)のように1つの引数にまとめる。 - 関数群のクラスへの集約(Combine Functions into Class) ― 同じデータのまとまりを共有して動作する複数の関数を一つのクラスにまとめる。【変換例】before:
base(reading)、taxableCharge(reading)のように、同じreadingデータを引数として繰り返し受け取る関数がモジュールレベルに散らばっている。after:readingをコンストラクタで受け取るReadingクラスを定義し、各関数をそのメソッドとしてまとめてreadingの受け渡しを省略できるようにする。 - 関数群の変換への集約(Combine Functions into Transform) ― 元データから複数の派生値を計算する関数群を、派生データを含む新しいレコードを返す一つの「変換」関数に集約する。【変換例】before: 同じ生データから
baseChargeやtaxableChargeをその都度別々の関数で計算しており、呼び出し側もそれぞれを個別に呼び出している。after:enrichReading(reading)という変換関数が、元のデータに派生フィールドを追加した新しいレコードを一括で返し、呼び出し側はその結果を使い回す。 - フェーズの分離(Split Phase) ― 一つの処理に混在する複数の関心事(例:入力解析と計算)を、明確に分かれた逐次的な段階に分割する。【変換例】before: 商品コードと数量の文字列を解析する処理と、価格を計算する処理が一つの関数の中で入り交じっている。after: 入力を中間データ構造に変換する「解析フェーズ」の関数と、その中間データを受け取って計算する「計算フェーズ」の関数の2段階に分割する。
第7章 カプセル化(9種)
- レコードのカプセル化(Encapsulate Record) ― 構造体的な生のレコードを、フィールドへのアクセスを制御できるクラスに置き換える。【変換例】before:
organization = {name: "Acme Gooseberries", country: "GB"}という生のオブジェクトリテラルのフィールドに、コード各所からorganization.nameのように直接アクセスしている。after:Organizationクラスを作りgetName()のようなアクセサ経由に統一し、内部表現を後から自由に変更できるようにする。 - コレクションのカプセル化(Encapsulate Collection) ― クラスが保持するリスト等を外部に直接公開せず、専用メソッド経由でのみ変更できるようにする。【変換例】before: クラスが
getCourses()でコース配列そのものを返しており、呼び出し側が直接.push()して配列を書き換えられてしまう。after: 配列のコピーを返す、あるいはaddCourse()・removeCourse()といった追加・削除専用のメソッドだけを公開し、外部からの直接変更を防ぐ。 - 基本データ型のオブジェクトへの置き換え(Replace Primitive with Object) ― 単純な数値や文字列に、専用の振る舞いを持たせられる独自クラスを導入する。【変換例】before: 電話番号を単なる文字列として保持し、市外局番の切り出しなどのロジックがあちこちに重複している。after:
TelephoneNumberという専用クラスを導入し、市外局番の取り出しや書式化をそのクラスのメソッドに集約する。 - 問い合わせによる一時変数の置き換え(Replace Temp with Query) ― 一度だけ計算され変数に格納される値を、必要な時に呼び出せる問い合わせメソッドに置き換える。【変換例】before:
let basePrice = quantity * itemPrice;のように一度計算した値を変数に格納し、以降その変数を参照している。after:basePrice()という問い合わせメソッドに置き換え、値が必要になるたびに計算し直す形にすることで、後続のExtract Functionを行いやすくする。 - クラスの抽出(Extract Class) ― 一つのクラスが複数の役割を担ってしまっている場合に、関連するフィールドとメソッドの一部を新しいクラスへ分離する。【変換例】before:
Personクラスが、名前の管理だけでなく電話番号関連のロジック(市外局番の解析など)まで抱え込んでいる。after: 電話番号に関するデータとロジックをTelephoneNumberという新しいクラスへ切り出し、Personはそのインスタンスを保持するだけにする。 - クラスのインライン化(Inline Class) ― 存在意義が薄くなったクラスを別のクラスに統合して消し去る逆操作。【変換例】before:
TelephoneNumberクラスが単一のフィールドしか持たず、ほとんど何もしていない。after: そのクラスのフィールドとメソッドを利用側のPersonクラスに統合し、TelephoneNumber自体を削除する。 - 委譲の隠蔽(Hide Delegate) ― クライアントが委譲先のメソッドを直接呼び出している場合に、委譲元に仲介メソッドを設けて内部構造への依存を隠す。【変換例】before: クライアントコードが
aPerson.department.managerのように、Personが持つdepartmentオブジェクトを経由してさらにmanagerを取得している。after:Personにmanager()という委譲メソッドを追加し、クライアントはaPerson.manager()だけを呼べばよくなり、departmentの内部構造への依存がなくなる。 - 仲介人の除去(Remove Middle Man) ― 委譲になりすぎたクラスに対し、クライアントに委譲先を直接呼び出させる逆操作。【変換例】before:
Personクラスの多くのメソッドが単にdepartmentオブジェクトへ処理を委譲しているだけで、実質的な付加価値がない。after: クライアントにaPerson.department.managerのように委譲先オブジェクトを直接取得させ、Person側の仲介メソッド群を削除する。 - アルゴリズムの置き換え(Substitute Algorithm) ― ある処理を実現するアルゴリズムを、より明快・簡潔・高性能な別のアルゴリズムにまるごと置き換える。【変換例】before: 複数の条件分岐とループを使って国名リストの中から特定の候補を探す複雑な自作ロジックが書かれている。after: 同じ結果を返す標準ライブラリの
findメソッドなど、より簡潔で理解しやすいアルゴリズムに丸ごと置き換える。
第8章 特性の移動(9種)
- 関数の移動(Move Function) ― ある関数が自分のモジュールより他モジュールの要素と強く関連する場合、より適切な場所へ移す。【変換例】before:
trackSummary()という関数が、自分の所属モジュールよりもtrackモジュールのデータを頻繁に参照している。after: その関数をtrackモジュール側へ移し、呼び出し元は移動後の場所を参照するように更新する。 - フィールドの移動(Move Field) ― データ項目が現在のクラスより別クラスでより頻繁に使われる場合、本来あるべきクラスへ移動する。【変換例】before:
interestRateというフィールドがAccountTypeクラスに置かれているが、実際には個々のAccountごとに参照・計算されることの方が多い。after: フィールドをAccountクラスへ移動し、AccountType側からの参照はAccount経由に置き換える。 - 文の関数への移動(Move Statements into Function) ― 関数の呼び出し箇所すべてに繰り返し書かれる処理を、関数自体の内部に取り込む。【変換例】before:
photoDiv(p)を呼び出す全ての箇所で、直後に同じtitle(p)の呼び出しを重複して書いている。after:title(p)の呼び出しをphotoDiv(p)関数の内部に取り込み、呼び出し箇所の重複コードをなくす。 - 文の呼び出し元への移動(Move Statements to Callers) ― 関数内の、呼び出し元によって必要・不要が分かれる処理を関数の外へ押し出す逆操作。【変換例】before:
renderPerson()関数の中に、特定の1つの呼び出し元でしか必要とされない写真描画処理が埋め込まれている。after: その処理を関数の外に出し、必要とする呼び出し元だけがそれを個別に呼ぶようにする。 - インラインコードの関数呼び出しへの置き換え(Replace Inline Code with Function Call) ― 既存関数と同じ処理がベタ書きされている箇所を、その関数の呼び出しに置き換える。【変換例】before: 既に
appliesToMass(country, resultingCode)という関数が存在するにもかかわらず、その関数と同じ判定ロジックが呼び出し箇所に重複してベタ書きされている。after: そのベタ書き部分を関数呼び出し1行に置き換え、重複を解消する。 - 文のスライド(Slide Statements) ― 関連するコード行が離れた場所に散らばっている場合、隣接させて後続のリファクタリングの下準備をする。【変換例】before: 商品追加の処理と、その合計金額を計算する処理の宣言が離れた場所に散らばっており、後続のExtract Functionがしづらい。after: 関連する変数宣言や処理を隣接する位置へ移動し、意味のまとまりを可視化してから抽出しやすくする。
- ループの分離(Split Loop) ― 一つのループ内で複数の異なる集計・処理を同時に行う場合、関心事ごとに別々のループへ分割する。【変換例】before: 1つのループの中で、合計給与の集計と最高給与の記録という2つの異なる処理を同時に行っている。after: それぞれの集計を担当する2つの別々のループに分割し、後にどちらか一方をExtract Functionしやすくする。
- ループのパイプラインへの置き換え(Replace Loop with Pipeline) ― 明示的なループ文を、map・filter・reduce等のコレクション操作の連鎖に置き換える。【変換例】before: for文で配列を1件ずつ走査しながら、フィルタリングと変換処理を手続き的に行っている。after:
filter()とmap()をチェーンさせたコレクションパイプラインに置き換え、処理の意図を宣言的に表現する。 - 死んだコードの削除(Remove Dead Code) ― もはやどこからも呼び出されない不要なコードを削除する。バージョン管理があれば復元できるという安心感が前提となる。【変換例】before: 過去の仕様変更で使われなくなった関数やフラグ分岐が、呼び出されないままコードベースに残り続けている。after: バージョン管理システムに履歴が残っていることを拠り所に、その死んだコードを削除する。
第9章 データの再編成(5種)
- 変数の分割(Split Variable) ― 一つの変数が処理途中で異なる役割のために何度も再代入される場合、役割ごとに別の変数へ分割する。【変換例】before:
tempという1つの変数が、処理の前半では「周囲長」、後半では「面積」という異なる役割のために使い回されている。after:perimeterとareaという役割ごとに独立した変数へ分割する。 - フィールド名の変更(Rename Field) ― レコードやクラスのフィールド名が意味を的確に表していない場合、より説明的な名前に変更する。【変換例】before:
organization.nameというフィールド名が汎用的すぎて、実際には会社名を指すことが名前から読み取れない。after:organization.titleのように、フィールドの実際の意味に即した名前に変更する。 - 導出変数の問い合わせへの置き換え(Replace Derived Variable with Query) ― 他のデータから計算できる値をわざわざ変数として保持・更新し続けるのをやめ、問い合わせ関数に置き換える。【変換例】before:
discountedTotalという変数を保持しつつ、discountが変わるたびに手動で再計算・再代入している。after:get discountedTotal()という問い合わせメソッドに置き換え、必要な時に都度計算することで、更新漏れによる不整合を防ぐ。 - 参照から値への変更(Change Reference to Value) ― データ構造を、共有・書き換え可能な「参照」ではなく、内容が同じなら同一とみなせる「値」として扱う。【変換例】before: 2つの
TelephoneNumberオブジェクトが同じ値を持っていても参照(オブジェクトID)が違うために等価と判定されず、共有された参照を書き換えると意図せず他の場所にも影響が及ぶ。after: 値が同じであれば同一とみなせる不変な値オブジェクトとして扱い、更新の際は新しいオブジェクトに置き換える。 - 値から参照への変更(Change Value to Reference) ― 同じ実体を表す複数のコピーを、一つの共有される参照にまとめ更新の一貫性を保証する逆操作。【変換例】before: 同じ顧客を表す
Customerオブジェクトが注文ごとに個別にコピー生成され、一方を更新しても他方に反映されない。after: 顧客ごとに1つだけ共有される参照(リポジトリで管理される単一のインスタンス)にまとめ、更新が全ての参照箇所に反映されるようにする。
第10章 条件記述の単純化(6種)
- 条件記述の分解(Decompose Conditional) ― 複雑なif-then-else構造の条件式や各分岐の処理を、意味の伝わる名前を持つ関数に分解する。【変換例】before: 季節や日付に応じて料金を計算する、条件式も各分岐の処理も長く複雑なif-then-else文が書かれている。after: 条件部分を
summer()、各分岐の処理をsummerCharge()のような意味の伝わる関数に分解する。 - 条件記述の統合(Consolidate Conditional Expression) ― 判定結果は異なるが実行される処理が同じ複数の条件チェックを一つにまとめる。【変換例】before:
if (seniority < 2) return 0;、if (monthsDisabled > 12) return 0;、if (isPartTime) return 0;のように、判定条件は異なるが結果が同じ複数のif文が連続している。after: これらを論理和でまとめた1つの条件式(isNotEligibleForDisability())に統合する。 - 入れ子になった条件記述をガード節に置き換える(Replace Nested Conditional with Guard Clauses) ― 深くネストしたif文を、例外的なケースを早期リターンで処理する「ガード節」の形に書き換える。【変換例】before: 正常系の処理が、何段にもネストしたif-elseの奥深くに埋もれている。after: 例外的な条件を先頭で早期リターンするガード節として並べ、正常系の処理をネストの外に出して直線的に読めるようにする。
- 条件記述をポリモーフィズムに置き換える(Replace Conditional with Polymorphism) ― 種類によって処理を切り替える条件分岐を、サブクラスごとのメソッドオーバーライドに置き換える。【変換例】before: 鳥の種類によって鳴き声や羽ばたき方を切り替える
switch(type)文が複数のメソッドに重複している。after: 種類ごとのサブクラス(EuropeanSwallowなど)を用意し、それぞれがオーバーライドしたメソッドで固有の振る舞いを実装することで、switch文自体を排除する。 - 特別ケースの導入(Introduce Special Case) ― nullチェック等の重複した例外処理を、特殊な状況を表す専用オブジェクト(Null Objectパターン)の導入で一元化する。【変換例】before:
customerが存在しない場合のcustomer === nullチェックとデフォルト値の設定が、コードの至るところに重複している。after:UnknownCustomerのような特別なケース専用オブジェクトを導入し、通常のオブジェクトと同じインターフェースでデフォルトの振る舞いを返させることで、nullチェックの重複を排除する。 - アサーションの導入(Introduce Assertion) ― コードが暗黙のうちに前提とする条件をassert文として明示し、ドキュメントとデバッグ支援を兼ねさせる。【変換例】before:
discountRateが0以上であることを前提に計算しているが、そのことがコード上どこにも明示されていない。after:assert(discountRate >= 0)という表明をコードに追加し、前提条件を文書化しつつ違反時に早期に検知できるようにする。
第11章 APIのリファクタリング(10種)
- 問い合わせと更新の分離(Separate Query from Modifier) ― 値を返すと同時に副作用も引き起こす関数を、問い合わせ関数とコマンド関数に分離する。【変換例】before:
getTotalOutstandingAndSendBill()のように、金額を返す処理と請求書送信という副作用が1つの関数に同居している。after: 値を返すだけの問い合わせ関数getTotalOutstanding()と、副作用を起こすコマンド関数sendBill()に分離する。 - 関数のパラメータ化(Parameterize Function) ― わずかに異なるリテラル値だけが違うほぼ同じ関数群を、値を引数として受け取る一つの関数に統合する。【変換例】before:
tenPercentRaise(person)とfivePercentRaise(person)のように、率だけが異なるほぼ同じ内容の関数が複数定義されている。after:raise(person, percentage)のように率を引数として受け取る1つの関数に統合する。 - フラグ引数の削除(Remove Flag Argument) ― 呼び出し元がフラグを渡して分岐を制御している関数を、分岐ごとの明示的な複数の関数に分割する。【変換例】before:
setDimension(name, value)のような関数が、内部でnameの値によって全く異なる処理に分岐する隠れたフラグとして使われている。after:setHeight(value)、setWidth(value)のように、分岐ごとに独立した明示的な関数に分割する。 - オブジェクト全体の受け渡し(Preserve Whole Object) ― オブジェクトから個別の値を取り出して渡すのではなく、そのオブジェクト自体をまるごと渡す。【変換例】before: 呼び出し元が
withinRange(aRoom.daysTempRange.low, aRoom.daysTempRange.high)のように、オブジェクトから個別の値を取り出して渡している。after:withinRange(aRoom.daysTempRange)のようにオブジェクト自体をまるごと渡し、関数内部で必要な値を取り出す形にする。 - パラメータを問い合わせに置き換える(Replace Parameter with Query) ― 呼び出し元が計算して渡す引数を、関数内部で導出する問い合わせに置き換え引数リストを短くする。【変換例】before: 呼び出し元が
finalPrice = basePrice * discountLevelのように自分で計算した値を毎回引数として渡している。after: 関数内部でdiscountLevelを導出できるようにし、その引数自体を関数シグネチャから取り除く。 - 問い合わせをパラメータに置き換える(Replace Query with Parameter) ― 関数が外部の可変な状態に依存する場合、その依存をなくし呼び出し元から明示的に値を渡す逆操作。【変換例】before: 関数内部で外部の可変なグローバル状態(例:
thermostat.currentTemperature)を直接参照しており、テストや再利用がしづらい。after: その値を引数として明示的に受け取るように変更し、関数を外部状態から独立させる。 - 設定メソッドの削除(Remove Setting Method) ― 生成後に変更されるべきでないフィールドに対し、不用意に再設定できてしまうsetterを削除する。【変換例】before:
Personクラスのidフィールドが生成時にしか設定されないはずなのに、setId()というsetterがいつでも呼び出せる状態で公開されている。after: そのsetterを削除し、idをコンストラクタでのみ設定される不変な値として扱う。 - コンストラクタをファクトリ関数に置き換える(Replace Constructor with Factory Function) ― コンストラクタの制約を避けるため、生成の窓口を通常の関数に置き換える。【変換例】before:
new Employee(name, typeCode)のように、呼び出し元がコンストラクタと具象クラスの型コードを直接知っている必要がある。after:createEmployee(name, typeCode)というファクトリ関数を用意し、内部でどのサブクラスをインスタンス化するかを隠蔽する。 - 関数をコマンドに置き換える(Replace Function with Command) ― 複雑な処理を行う関数を専用のコマンドオブジェクトにカプセル化し、実行前準備や取り消しを持たせやすくする。【変換例】before: 引数の多い複雑な計算処理が1つの大きな関数として実装されており、途中経過の保持や部分的な差し替えがしづらい。after: その処理を
Commandオブジェクトとしてクラス化し、実行前の準備をコンストラクタで、実行本体をexecute()メソッドで担わせることで、状態の保持や取り消し処理を追加しやすくする。 - コマンドを関数に置き換える(Replace Command with Function) ― コマンドオブジェクトの複雑さが不要になった場合、単純な関数呼び出しに戻す逆操作。【変換例】before: 単純な計算のためだけに、実行前の準備や後片付けを行わないコマンドオブジェクトが用意されている。after: そのコマンドクラスの複雑さが不要になったため、単純な関数呼び出しに戻す。
第12章 継承の取り扱い(11種)
- メソッドの引き上げ(Pull Up Method) ― 複数のサブクラスに重複するメソッドを共通のスーパークラスへ移す。【変換例】before:
SalesmanとEngineerという2つのサブクラスが、ほぼ同じ内容のget annualCost()メソッドをそれぞれ個別に実装している。after: そのメソッドを共通のスーパークラスEmployeeへ引き上げ、サブクラス側の重複した実装を削除する。 - フィールドの引き上げ(Pull Up Field) ― 複数のサブクラスが同じ目的で個別に持つフィールドをスーパークラスへ移す。【変換例】before: 複数のサブクラスがそれぞれ同じ目的で
nameという同名のフィールドを個別に宣言している。after: そのフィールドをスーパークラスへ引き上げ、サブクラス側の重複した宣言を削除する。 - コンストラクタ本体の引き上げ(Pull Up Constructor Body) ― サブクラスのコンストラクタに共通する初期化処理をスーパークラスのコンストラクタへ移す。【変換例】before:
ManagerとEngineerのコンストラクタが、それぞれthis.name = name; this.id = id;という同じ初期化処理を個別に持っている。after: 共通部分をスーパークラスのコンストラクタへ移し、サブクラスのコンストラクタからsuper(name, id)を呼び出す形にする。 - メソッドの押し下げ(Push Down Method) ― スーパークラスのメソッドが一部のサブクラスでしか使われない場合、該当サブクラスへ移す逆操作。【変換例】before: スーパークラス
Employeeに定義されたget quota()メソッドが、実際にはサブクラスのSalesmanでしか使われていない。after: そのメソッドをSalesmanクラスへ押し下げ、他のサブクラスからは見えないようにする。 - フィールドの押し下げ(Push Down Field) ― スーパークラスのフィールドが一部のサブクラスでしか使われない場合、該当サブクラスへ移す。【変換例】before: スーパークラスのフィールドが特定の一部のサブクラスでしか参照されていない。after: そのフィールドを該当のサブクラスへ移し、無関係なサブクラスの責務を減らす。
- タイプコードのサブクラスへの置き換え(Replace Type Code with Subclasses) ― 種類を表すタイプコードフィールドを、ポリモーフィズムが使える継承構造に置き換える。【変換例】before:
Employeeクラスがtypeという文字列フィールド("engineer"、"manager"など)を持ち、switch(type)で振る舞いを切り替えている。after:typeごとのEngineer、Managerサブクラスを作り、ポリモーフィズムでオーバーライドされたメソッドに振る舞いを持たせる。 - サブクラスの削除(Remove Subclass) ― スーパークラスとほぼ変わらない、または使われなくなったサブクラスを削除し単純なフィールドに置き換える。【変換例】before:
MaleEmployeeというサブクラスがスーパークラスとほぼ同じ振る舞いしか持たず、存在意義が薄れている。after: そのサブクラスを削除し、代わりにスーパークラス上の単純なgenderフィールドで区別する。 - スーパークラスの抽出(Extract Superclass) ― 似た振る舞いを持つ複数クラスの共通部分を、新設する共通のスーパークラスへ引き上げる。【変換例】before:
EmployeeとDepartmentという別々のクラスが、nameやmonthlyCost()のような似た振る舞いをそれぞれ個別に実装している。after: 共通部分を新設するPartyのようなスーパークラスへ引き上げ、両クラスがそれを継承する形にする。 - 階層の統合(Collapse Hierarchy) ― スーパークラスとサブクラスの違いがほとんどなくなった継承階層を一つのクラスに統合する。【変換例】before:
EmployeeとそのサブクラスSalariedEmployeeの間にほとんど違いがなくなっている。after: 両者を1つのクラスへ統合し、不要になった継承階層を解消する。 - サブクラスの委譲への置き換え(Replace Subclass with Delegate) ― 継承で表現していた差分を、別オブジェクトへの委譲(コンポジション)で表現し直し、単一継承の制約を回避する。【変換例】before:
Bookingの特殊なケースを表すPremiumBookingサブクラスがあるが、後から別の切り口(会場の特別ルール)でも派生させたくなり、単一継承では表現しきれない。after:PremiumBookingの差分ロジックをPremiumBookingDelegateという別オブジェクトへの委譲として実装し直し、複数の観点からの拡張を組み合わせられるようにする。 - スーパークラスの委譲への置き換え(Replace Superclass with Delegate) ― 「is-a」の関係が実は成立していない継承関係を、委譲関係に置き換えて解消する。【変換例】before:
StackクラスがVectorを継承しているが、StackはVectorの全ての操作を意味的に許容すべきではなく、is-a関係が破綻している(リスコフの置換原則違反)。after: 継承をやめてVectorのインスタンスをフィールドとして保持する委譲関係に置き換え、Stackが本当に必要なメソッドだけを公開する。
テストと小さなステップの規律、そしてリファクタリングを行わない判断
本書は「テスト」をリファクタリングの前提条件として重視し、堅牢な自動テストスイート(セルフテストコード)があって初めて安全にリファクタリングできると強調する。マイケル・フェザーズの「テストがないコードはレガシーコードである」という主張とも共鳴するこの考え方は、テスト駆動開発(TDD)とリファクタリングを不可分のプラクティスとして結びつけた。テストが存在することで、リファクタリングの各ステップの直後にテストを実行し、外部から見た振る舞いが本当に変わっていないことを機械的に確認できるようになる。逆に言えば、テストによる安全網がない状態でのリファクタリングは、構造改善のつもりが振る舞いの変化(バグの混入)を見逃す危険と常に隣り合わせであり、本書はそうした状況ではまずテストを書くこと自体を最優先の作業とすべきだと説く。
また「ボーイスカウトルール(来た時よりも美しく)」という指針のもと、コードに触れるたびに少しずつ改善を積み重ねる漸進的な習慣が推奨され、リファクタリングは必ず一度に一つの小さな変更を行い、その都度テストを実行して振る舞いの不変性を確認しながら進めるべきだと説かれる。この「小さなステップの原則」は本書全体を貫く方法論的な核であり、一歩の変更を極小化することで、万が一テストが落ちた際にも原因となった変更箇所を即座に特定でき、変更を取り消すコストも小さく抑えられるという実務上の利点をもたらす。ファウラーはこれを「小さなステップを踏めば踏むほど、安心して速く進める」というパラドックスとして説明し、一見遠回りに見える細分化されたステップの積み重ねこそが、結果的に最も速く安全な変更経路になると論じる。実務上の具体的な運用としては、1つのリファクタリングが完了しテストが通るたびにバージョン管理システムへコミットする(あるいはコミット前段階の作業単位として区切る)習慣が推奨されており、これによって問題が生じた際に直前の安定した状態へ即座に戻れるようになる。
リファクタリングを行うべきでない場面についての判断も、本書の重要な一部を成す。前述の通り、修正よりも書き直しが速いほど劣化したコード、リリース直前で新たなリスクを取れない状況、二度と触られない使い捨てのコード、テストという安全網が整っていない領域、といった状況では、リファクタリングよりも他の対応(書き直し、機能追加の凍結、テスト整備の優先)を選ぶべきだとされる。この「いつ行うか」と「いつ行わないか」の両方の判断基準を併せ持つことこそが、リファクタリングを単なる「コードをきれいにする作業」ではなく、費用対効果を意識した工学的な意思決定として位置づける本書の特徴である。
具体例
【訂正】旧版の記述では「第1章の映画レンタル料金計算の例」としていたが、これは1999年刊行の第1版(Java版)の例であり、2018年の第2版では例が刷新されている。第2版第1章の題材は、悲劇(tragedy)・喜劇(comedy)といった演目の種類と観客数に応じて、演劇会社が顧客(複数の劇団)向けの請求明細書(statement)の金額と鑑賞ポイントを計算するプログラムである。当初のコードは、演目の種類ごとに異なる計算式が一つの長い関数に詰め込まれ、プレーンテキストの明細書を出力する形で書かれている。本書はこのコードに対し、Extract Function・Split Phase・Replace Conditional with Polymorphismなどを段階的に適用し、まず計算ロジックを演目種類ごとのポリモーフィックなクラス構造に整理した上で、同じ計算結果を再利用してHTML形式の明細書も出力できるように拡張する過程を、1行ずつのコード変更とテスト実行を伴いながら詳細に実演する。この改修により、新しい演目の種類(例えば叙情詩:pastoral)を追加する際にも、既存のコードにほとんど手を加えずに拡張できる構造が実現される。
この第1章の実演は、本書全体の方法論を凝縮した縮図になっている。最初にコードの現状を理解するためのテストを整備し、続いて長い計算関数からExtract Functionで小さな関数を切り出し、切り出した関数群からSplit Phaseで「計算」と「フォーマット」の関心事を分離し、最後に演目の種類ごとの条件分岐をReplace Conditional with Polymorphismでクラス階層に置き換える、という一連の流れが、1つ変更するたびにテストを実行するという小さなステップの原則に忠実に沿って進められる。この過程で、当初は数十行あった1つの巨大な関数が、最終的には演目ごとのポリモーフィックなクラスとその薄いラッパー関数群へと姿を変え、新しい演目タイプの追加が既存コードの修正なしで可能になる「開放閉鎖原則」に適った構造へ至る様子が具体的に示される。またケント・ベックが考案した「テスト駆動開発」の実践や、ウォード・カニンガムが提唱した「技術的負債」のメタファー(コードの保守コストを財務的負債になぞらえる比喩)が、本書内で言及される。
実践への応用
本書の手法は日々のコーディング作業に直接組み込むことができる。開発者は新機能を追加する前にまず対象コードを理解しやすい形にリファクタリングし(「まずリファクタリング、それから機能追加」という順序)、機能追加自体は別のコミットとして分離することでレビュアーが変更内容を追いやすくなる。バグ修正の際には、まず該当箇所の可読性を高めるリファクタリングを行うことでバグの原因を発見しやすくなるという効果も期待できる。日常的な実践としては、変数名・関数名を推敲するRename系のリファクタリング、長い関数を意味のある単位に分割するExtract Functionを頻繁に行う習慣、そしてリファクタリングは必ず自動テストで振る舞いの不変性を確認しながら小さなステップで進めるという規律が推奨される。
チーム開発においては、コードレビューの基準として「コードの臭い」のカタログを共有言語として使うことで、レビューの指摘が具体的かつ建設的になる効果もある。プルリクエストの中で「機能追加」のコミットと「リファクタリング」のコミットを意識的に分離し、レビュアーがそれぞれを別の観点で検証できるようにすることも、本書の「二つの帽子」の考え方をチーム開発に応用した実践例といえる。統合開発環境(IDE)の「自動リファクタリング機能」(Rename・Extract Methodなどをツールが自動実行する機能)を積極的に活用することも、本書の技法を安全かつ効率的に実践する上で推奨される。さらに、リファクタリングに着手する前には必ず対象コードの自己テストの有無とカバレッジを確認し、不足していればテストの追加を最初のタスクとして扱うこと、そして1つのリファクタリングを終えるごとにテストを実行しコミットするという小さなサイクルを徹底することが、日々の実務でこの方法論を機能させる鍵となる。逆に、リリース直前や使い捨てコードなど、本書が挙げる「リファクタリングを行うべきでない」条件に該当する場面では、無理に構造改善に着手せず他の優先事項に集中するという判断も、実践上重要な応用の一つである。
位置づけ・評価
本書は『達人プログラマー』『Clean Code』と並んでソフトウェア工学における実践知の古典の一つに数えられ、「リファクタリング」という語を業界の共通語彙として定着させた歴史的意義を持つ。アジャイル開発・エクストリームプログラミング(XP)・テスト駆動開発といった2000年代以降のソフトウェア開発方法論の思想的基盤を成す著作であり、統合開発環境における自動リファクタリング機能の実装にも直接的な影響を与えた。第2版でJavaScriptに刷新されたことで、静的型付け言語だけでなく動的型付け言語の開発者にも実践しやすい内容となり、刊行から四半世紀以上を経てなお、ソフトウェア保守性を論じる際に必ず参照される基礎文献としての地位を保っている。