最終更新日:
本稿は、リファクタリングを単なるコード整理や保守作業ではなく、抽象と具体を往復する思考訓練として捉え直すことを目的とする。リファクタリングでは、重複、条件分岐、命名の揺れ、責務の集中、変更履歴、障害、要求変更といった具体的事例から、共通性、差異、変化軸、境界、不変条件を見出す抽象化が求められる。一方で、早まった抽象化は現実の差異を覆い隠し、将来の変更を困難にするため、抽象を具体的なシナリオ、反例、テスト、レビュー、図表、グラフへ戻して検証する必要がある。
本稿の中心命題は、優れたリファクタリングとは、対象をより正確に記述し、操作するための言語を構築・修正する実践である、という点にある。ここでいう言語は、自然言語やプログラミング言語に限られず、命名、型、アプリケーションプログラミングインタフェース、モジュール境界、テスト、アーキテクチャビュー、統一モデリング言語、依存グラフ、コードプロパティグラフ(Code Property Graph: CPG)などを含む広い表現体系である。Fowler と Opdyke による振る舞い保存としてのリファクタリング、Parnas の情報隠蔽、Kruchten および ISO/IEC/IEEE 42010 におけるアーキテクチャビュー、Larkin and Simon や Blackwell らによる表記法の認知的性質を接続し、リファクタリングを設計言語の更新として論じる。
最終的に本稿は、リファクタリングを通じて獲得すべき技能を、抽象化能力そのものではなく、抽象と具体を往復しながら表現を検証・修正し続ける能力として定式化する。
リファクタリングは、しばしば「コードをきれいにする」作業として理解される。しかし、その理解はリファクタリングの技術的・認知的な意義を狭く捉えている。Fowler はリファクタリングを、ソフトウェアの外部から観察可能な振る舞いを保ったまま、内部構造を改善する変更として定義している。また Opdyke は、オブジェクト指向フレームワークにおけるリファクタリングを、プログラムの意味を保存しながら構造を変換する操作として扱った。これらの定義に共通するのは、リファクタリングが単なる書式調整ではなく、既存の意味を壊さずに構造を作り替える設計行為であるという点である。
本稿は、この設計行為としてのリファクタリングを、抽象と具体を往復する思考訓練として捉え直す。ここでいう抽象化とは、具体的な差異を無視して一般化することではない。むしろ、目的に照らしてどの差異を保持し、どの共通性を取り出し、どの境界を引くべきかを判断する営みである。たとえば、重複した条件分岐、命名の揺れ、責務の集中、変更履歴、障害、要求変更は、いずれも単なる局所的な不都合ではなく、背後にある概念、変化軸、不変条件、境界の候補を示す具体的な手がかりである。リファクタリングは、これらの具体から出発し、より適切な抽象を仮説として立て、その抽象を再びコード、テスト、図表、レビュー、将来の変更シナリオへ戻して検証する過程として理解できる。
この観点から見ると、リファクタリングにおいて重要なのは、抽象化する能力だけではない。早まった抽象化は、現実に存在する差異を覆い隠し、変更時の理解を困難にし、結果として保守性を損なうことがある。したがって、リファクタリングには、抽象を作る能力と同時に、作られた抽象を疑い、具体的な反例や利用場面に照らして修正する能力が必要である。振る舞い保存はそのための最低条件であるが、振る舞いを保存していることだけでは、設計が改善されたことを意味しない。設計が改善されたといえるためには、新しい構造が対象をより正確に記述し、将来の変更をよりよく受け止め、共同作業における理解を支える必要がある。
本稿の中心命題は、優れたリファクタリングとは、対象をより正確に記述し、操作するための言語を構築し、修正する実践である、という点にある。ここでいう言語は、自然言語やプログラミング言語に限定されない。命名、型、関数、クラス、アプリケーションプログラミングインタフェース(application programming interface: API)、モジュール境界、テスト、ディレクトリ構造、図表、依存グラフ、アーキテクチャビュー、ドメインモデルはいずれも、システムを記述し、理解し、変更するための表現体系である。本稿では、この広い意味での表現体系を「設計言語」と呼ぶ。設計言語は、コードの外側にある補助的な説明ではなく、ソフトウェアが何を対象とし、何を同一視し、何を分離し、どの変化を許容するかを規定する実践的な道具である。
この議論は、ソフトウェア工学における複数の古典的論点と接続する。Parnas の情報隠蔽は、モジュール化を単なる機能分割ではなく、変化しうる設計判断を隠す構造として捉える視点を与える。Kruchten の 4+1 ビューモデルや ISO/IEC/IEEE 42010 におけるアーキテクチャ記述は、単一の図や単一の構造ではなく、関心に応じた複数の表現によってシステムを把握する必要を示している。さらに、Larkin and Simon の図的表現に関する議論や、Blackwell らの表記法の認知的次元に関する研究は、表現形式が人間の探索、推論、誤りやすさに影響することを示唆する。リファクタリングを設計言語の更新として捉えることは、これらの議論を、日々のコード変更という具体的実践の中に位置づける試みである。
本稿の構成は次の通りである。第2章では、リファクタリング、アーキテクチャ設計、モデリングの関係を整理し、リファクタリングを具体から出発する設計実践として位置づける。第3章では、抽象化の材料となる具体を、コード片だけでなく変更履歴、障害、要求、レビューコメントを含むものとして定義する。第4章では、共通性、変化軸、境界を発見する抽象化の過程を検討する。第5章では、抽象化の危険性を論じ、抽象を具体へ戻して検証する必要を示す。第6章では、命名、API、図、グラフ、ビューを含む設計言語の広がりを扱う。第7章では、リファクタリングを記述と操作の仮説を更新する認識論的実践として定式化する。第8章では、この見方をソフトウェア工学教育および実務者の継続的学習、チーム学習、コード生成支援の普及に接続する。第9章では、小さなコード変更の事例を通じて、具体から抽象が生まれ、その抽象が再び具体で検証される過程を示す。最後に第10章では、リファクタリングを専門的技能として再定義し、本稿の結論を述べる。
リファクタリング、アーキテクチャ設計、モデリングは、しばしば異なる規模の活動として区別される。リファクタリングはコードの局所的変更、アーキテクチャ設計はシステム全体の構造決定、モデリングは図や文書による表現と理解されやすい。しかし本稿の目的に照らすならば、三者は対立する活動ではなく、抽象レベルと出発点は異なるが、設計判断を表現し直す点で相似した活動として整理できる。
まず、リファクタリングとは、既存の外部的振る舞いを保ったまま、内部構造を改善する変更である(Fowler, 2018)。Opdyke(1992)は、オブジェクト指向フレームワークにおける意味保存的なプログラム変換としてリファクタリングを扱った。この定義では、対象はすでに存在するコードであり、設計上の選択は現実の実装、テスト、依存関係、運用制約によって強く制限される。したがって、リファクタリングは白紙から構造を描く活動ではなく、既存の具体物の中で、局所的かつ漸進的に設計を修正する活動である。
これに対し、アーキテクチャ設計は、複数の関心、制約、ステークホルダーを統合し、システム全体の構造とその根拠を記述する活動である。ISO/IEC/IEEE 42010:2022 は、アーキテクチャ記述を、アーキテクチャそのものではなく、それを表現する成果物として位置づけ、ビュー、ビューポイント、モデル種別などを通じて関心を分離する枠組みを与えている。Kruchten(1995)の 4+1 ビューモデルも、論理ビュー、開発ビュー、プロセスビュー、物理ビュー、シナリオという複数の観点からソフトウェアアーキテクチャを記述する必要を示した。ここで重要なのは、アーキテクチャが単一の図ではなく、複数の関心に応じた表現の集合として扱われる点である。
モデリングは、この二つを媒介する活動である。本稿でいうモデルとは、対象を完全に写し取る複製ではなく、目的に応じて重要な性質を選択した表現である。コード上の型、関数名、モジュール境界、統一モデリング言語(Unified Modeling Language: UML)の図、依存グラフ、テストケース表はいずれもモデルである。モデルは、何を同一視し、何を区別し、どの変化を重要とみなすかを示す。
このように整理すると、リファクタリングは、具体から出発する設計実践であり、特にモジュール境界、変更可能性、複数の関心の分離に関わる場合には、アーキテクチャ設計と接続する実践として位置づけられる。アーキテクチャ設計が関心を分離し、変更可能性を確保し、意味ある抽象を構築する活動であるならば、リファクタリングもまた同じ設計課題に局所的・漸進的に取り組むことがある。ただし、その出発点は要求仕様や設計文書ではなく、動作しているコード、変更履歴、バグ、レビューコメントである。したがって本稿では、アーキテクチャ設計を上位概念として参照しつつ、議論の中心をリファクタリングに置く。リファクタリングは、抽象的な設計論を、既存コードという具体的な材料の中で訓練する実践だからである。
抽象化の出発点は具体である。しかし、リファクタリングにおける具体とは、単一のコード片だけを意味しない。関数やクラスの形、重複した条件分岐、命名の揺れ、責務の集中は重要な観察対象であるが、それらは設計判断の全体を直接には示さない。リファクタリングの材料となる具体には、変更履歴、障害、要求変更、レビューコメント、運用上の制約も含まれる。
Fowler(2018)の用語を借りれば、コードスメルは、抽象化の端緒として有用である。コードスメルとは、必ずしも直ちに欠陥を意味しないが、設計上の問題を示唆する局所的な兆候である。たとえば、重複コードは共通処理の抽出を示唆するだけでなく、同じ概念が複数の場所で別々に表現されている可能性を示す。長い条件分岐は、変化する状態や方針が明示的な概念として名前を持っていない可能性を示す。命名の揺れは、チーム内で同じ対象を異なる語彙で捉えている可能性を示す。
しかし、コードスメルだけを根拠に抽象を作ると、抽象化は容易に局所最適へ傾く。Silva, Tsantalis, and Valente(2016)は、GitHub の貢献者を対象に、開発者がリファクタリングを行う理由がコードスメル除去に限定されず、理解容易性、保守性、機能追加への準備など多様であることを示した。Pantiuchina ら(2020)も、リファクタリングの動機をマイニングにより分析し、プロセス指標、製品指標、開発者の作業文脈が関与することを示している。これらの研究は、リファクタリングの判断が単なる形の悪さではなく、変更の文脈に依存することを示唆する。
変更履歴は、どの概念が一緒に変化するかを示す経験的な手がかりである。Gall, Hajek, and Jazayeri(1998)は、リリース履歴にもとづく論理結合(logical coupling)を用いて、静的な構造だけでは見えにくい結合を検出する方法を示した。Zimmermann, Weißgerber, Diehl, and Zeller(2005)も、バージョン履歴から関連変更を学習し、変更時に次に変更される可能性のある箇所を提示する方法を論じている。したがって、二つのファイルが繰り返し同じコミットで変更される場合、それは同じ設計判断に依存していることを直ちに証明するものではないが、表面的なモジュール境界を疑う観察対象にはなる。逆に、同じクラス内の二つの処理が別々の要求変更によって独立に変わるならば、そのクラスは異なる変更理由を一つの場所に閉じ込めている可能性がある。変更履歴は、抽象が現在のコードの形だけでなく、時間の中でどのように試されたかを示す。
バグやインシデントも重要である。障害は、単なる実装ミスではなく、既存の抽象が対象を十分に記述できていない箇所を明らかにすることがある。たとえば、ある状態を「有効」か「無効」かの二値で表現していたが、実際には「審査中」「一時停止」「期限切れ」が別々の意味を持っていた場合、障害は状態空間の分節の誤りを示す。Lehman(1980)がソフトウェア進化の法則で論じたように、現実世界に埋め込まれたソフトウェアは変化し続ける。したがって、具体の観察は一度きりではなく、変化の履歴を含んだ継続的な作業である。
リファクタリングの第一歩は、コードを読むことだけではない。具体的事例の集合を観察可能な形にすることである。本稿の枠組みからは、変更履歴を表にする、障害を状態遷移として描く、レビューコメントを論点ごとに分類する、要求変更をテストケースとして残す、といった課題設計が考えられる。これらは、前述の変更履歴マイニング研究から直接に教育効果が実証された手続きではなく、抽象化の根拠を明示させるための規範的提案である。その限界を踏まえても、このような作業によって、抽象化は個人の直感から、共有可能な証拠にもとづく設計判断へ移行しやすくなる。
具体を集めるだけでは設計は改善されない。リファクタリングには、具体的事例の集合から、共通性、差異、変化軸、境界、不変条件を見出す抽象化が必要である。ただし、ここでいう抽象化とは、詳細を捨てて一般化することではない。目的に対してどの差異を保持し、どの共通性を取り出し、どの境界を引くべきかを選別する営みである。
抽象化の第一の基準は、変更理由である。同じ理由で変わるものは、同じ概念または同じ設計判断に属しているかを検討する有力な手がかりになる。たとえば、税率変更のたびに同じ複数箇所が変わるならば、それらは「税計算」という抽象の下にまとめる候補になる。反対に、一つのクラスの中に、価格計算、表示文言、外部サービス連携が混在しており、それぞれが異なるタイミングで変更されるならば、それらは分離すべき関心である。ただし、同じ要求変更が複数の独立した関心を横断する場合もある。制度変更、外部サービスの仕様変更、ユーザーインターフェースの文言変更は、別個の責務を同時に変えることがある。したがって、変更履歴だけで境界を確定せず、ドメイン語彙、不変条件、反例、レビューによって検証する必要がある。
この点で、Parnas(1972)の情報隠蔽は、リファクタリングにおける抽象化の基準を与える。情報隠蔽は、単にデータや実装詳細を隠す技法ではない。より本質的には、変わりうる設計判断をモジュール内部に閉じ込め、他の部分がその判断に依存しないようにする設計原理である。したがって、よい境界は、機能の一覧からではなく、何が将来変わりうるか、どの判断を局所化すべきかという問いから引かれる。
抽象化の第二の基準は、語彙の文脈である。ドメイン駆動設計において Evans(2015)は、ユビキタス言語と境界づけられたコンテキストを重視する。ユビキタス言語とは、開発者とドメイン専門家が共有する語彙であり、境界づけられたコンテキストとは、その語彙が一貫した意味を持つ範囲である。本稿ではこれを、リファクタリングにおける命名と境界設定の原理として捉える。同じ「顧客」という語であっても、請求、配送、本人確認、サポートでは意味が異なる場合がある。その差異を無視して一つの巨大な Customer 型に統合すれば、抽象はかえって現実を不正確にする。
抽象化の第三の基準は、反復する問題状況に名前を与えることである。Gamma ら(1994)のデザインパターンは、単なる実装テンプレートではなく、繰り返し現れる設計問題と解法に名前を与える体系である。その発想は、Alexander ら(1977)のパターンランゲージに由来する。パターンは、個別の事例を超えて再利用されるが、常に文脈、相反する制約や要請(forces)、結果を伴う。ここでいう forces とは、パターンが解決しようとする状況で同時に働く制約、利害、品質要求、実装上の圧力を指す。したがって、パターン名を使うことは、抽象を導入することであると同時に、その抽象が成立する文脈を明示することでもある。
認知科学の観点からは、Gentner(1983)の構造写像理論が示唆的である。構造写像理論は、類推において重要なのは対象の表面的属性ではなく、関係構造の対応であると論じる。リファクタリングにおいても、似た名前や似たコード片をまとめるだけでは不十分である。重要なのは、それらが同じ関係構造、同じ不変条件、同じ変化軸を共有しているかである。
抽象への上昇は、具体から離脱することではない。具体を材料にしながら、どの関係を残し、どの差異を保存し、どの境界を導入するかを決めることである。よいリファクタリングは、対象を粗く単純化するのではなく、変更に耐えるために必要な区別を、コードと言語の中に刻み込む。
抽象化はリファクタリングの中心的作業であるが、抽象化そのものが常に善であるわけではない。むしろ、設計上の失敗は、抽象が不足している場合だけでなく、抽象が早すぎる場合、広すぎる場合、現実の差異を覆い隠す場合にも生じる。したがって、リファクタリングには、抽象へ上昇する能力と同じ程度に、抽象を具体へ戻して検証する能力が必要である。
Fowler(2018)と Opdyke(1992)の定義に従えば、リファクタリングの基本条件は振る舞い保存である。すなわち、外部から観察可能な振る舞い、あるいはプログラムの意味を壊さずに内部構造を変更しなければならない。振る舞い保存は、抽象化が既存の意味を破壊していないことを確認する最低条件である。テストはそのための代表的な装置であり、リファクタリング前後の振る舞いの同一性を確認する。
しかし、振る舞いを保存していることは、設計が改善されたことを意味しない。たとえば、三つの似た処理を一つの汎用関数にまとめたとしても、その三つが将来別々の理由で変化するならば、その抽象は変更を困難にする。すべての分岐を Strategy パターン、すなわちアルゴリズムや方針を交換可能な単位として表す設計パターンに置き換えたとしても、利用者がその概念を理解できず、変更のたびに複数のクラスを横断しなければならないならば、表現としては悪化している可能性がある。Kaur and Singh(2019)の系統的マッピング研究が示すように、リファクタリングが品質属性に与える影響は単純ではなく、文脈、対象、測定する品質属性によって異なる。
早まった抽象化の危険は、現実の差異を不可視化する点にある。抽象は複数の具体を一つの名前の下にまとめるが、その名前は何を同一視してよいかを暗黙に決める。もし「通知」という抽象の下に、法的通知、広告通知、障害通知をまとめたならば、それぞれの規制、優先度、再送条件、記録義務の差異が隠れる可能性がある。抽象は理解を助けるが、同時に注意を特定の方向へ誘導する。したがって、抽象の妥当性は、コードの短さや重複削減量だけで判断できない。
抽象を具体へ戻す方法はいくつかある。第一に、シナリオを用いる。将来想定される要求変更を具体的に置き、その変更がどの範囲に波及するかを確認する。第二に、反例を用いる。抽象に収まらない事例を探し、それが例外なのか、境界の引き直しを要求する証拠なのかを検討する。第三に、レビューを用いる。レビューはコードの正誤確認だけでなく、抽象名、責務、境界が共同理解に耐えるかを検証する場である。第四に、プロトタイプや図表を用いる。状態遷移図、依存グラフ、テストケース表は、抽象がどの具体を説明し、どの具体を説明し損ねるかを可視化する。
技術的負債という比喩も、この議論と関係する。Cunningham(1992)の技術的負債比喩は、初回実装として受け入れられた未成熟なコードが、理解の深化に応じて書き直されなければ、後続の開発に利子のような追加コストを生むことを示す。Allman(2012)も、Cunningham の比喩を未成熟なコードと関連づけたうえで、技術的負債は初回コードに限られず、多様な理由で生じると説明している。早まった抽象化も、当初は整った構造に見えても、対象理解や要求の変化に合わなくなった時点で返済すべき負債になりうる。現実の変化に合わない抽象は、変更のたびに回避策や例外処理を増やし、時間とともに利子を発生させる。よいリファクタリングは、抽象化することと、抽象化しないこと、あるいは抽象をいったん壊して作り直すことの往復によって成立する。
本稿がいう「言語」は、自然言語やプログラミング言語に限定されない。ソフトウェア開発においては、命名、型、アプリケーションプログラミングインタフェース(Application Programming Interface: API)、ディレクトリ構造、モジュール境界、テスト名、Architecture Decision Record(ADR)、図、依存グラフ、アーキテクチャビューが、いずれも対象を記述し、理解し、変更するための表現体系として機能する。ADR とは、アーキテクチャ上重要な判断と、その判断を必要にした文脈、採用した決定、状態、帰結を軽量に記録する文書形式である(Nygard, 2011)。リファクタリングは、この広い意味での言語を更新する活動である。
命名は最も身近な設計言語である。関数名やクラス名は、単に実装のラベルではなく、対象をどの概念として扱うかを定める。calculateFee と settleInvoice は、同じ計算を含んでいても、手数料計算と請求決済という異なる語彙体系を呼び出す。型や API も同様である。型は値の構造だけでなく、どの状態が許され、どの状態が不可能であるべきかを表す。API は、外部の利用者に対して、どの操作を安定した約束として提供し、どの判断を内部に隠すかを示す。
図やビューは、テキストとは異なる認知的性質を持つ。Larkin and Simon(1987)は、図的表現と文的表現が同じ情報を持つ場合でも、推論のしやすさが異なりうることを示した。図は、空間的配置によって関係を一箇所に集め、探索や注意配分を変える。したがって、図はコードの説明補助ではなく、設計上の推論を変える道具である。状態遷移図は状態間の遷移を前景化し、依存グラフは変更の波及を前景化し、コンテキストマップは語彙の境界を前景化する。
表記法そのものの性質も重要である。Blackwell ら(2001)は、Cognitive Dimensions of Notations という枠組みにより、表記法が利用者の活動に与える影響を、粘性、可視性、写像の近さ、暫定評価可能性などの次元で評価することを提案した。ここで Cognitive Dimensions of Notations とは、プログラミング言語、図、ユーザーインタフェースなどの表記体系が、人間の理解、変更、探索にどのような負荷や利点を与えるかを記述する語彙である。リファクタリング後のコードが「短い」だけでなく、変更しやすく、誤りにくく、途中状態を評価しやすいかを問うには、この種の語彙が必要である。
標準化されたモデリング言語も、設計言語の一部である。Object Management Group の Unified Modeling Language(UML)2.5.1 は、ソフトウェアおよびシステムの構造や振る舞いを可視化、仕様化、文書化するための標準的なモデリング言語として位置づけられている(Object Management Group, n.d.; Object Management Group, 2017)。UML は万能の表現ではないが、クラス、相互作用、状態、配置といった観点を明示的に分けることで、設計対象を複数の表現へ分解する。
さらに、グラフ表現はコードを別の言語へ翻訳する。Ferrante, Ottenstein, and Warren(1987)のプログラム依存グラフ(Program Dependence Graph: PDG)は、プログラムを制御依存とデータ依存の関係として表現した。Yamaguchi ら(2014)のコードプロパティグラフ(Code Property Graph: CPG)は、抽象構文木、制御フローグラフ、プログラム依存グラフを統合し、脆弱性発見のための問い合わせ可能な表現を構成した。これらは、コードを単なるテキスト列ではなく、関係のネットワークとして扱う。
よい表現は、対象を正確に写すだけではない。人間がどこに注意を向け、どの変更を予測し、どの誤りを避けられるかを変える。リファクタリングとは、コードの内部構造を変えるだけでなく、チームが対象を理解し、議論し、検証するための表現体系を作り替えることである。
第6章で述べた設計言語の更新は、単なる表現形式の変更ではなく、対象の区切り方と扱い方を修正する実践である。本章は、主に業務アプリケーションや情報システムにおいて、リファクタリングを、既存コードの中で対象の記述、操作、制御の仕方を作り替える認識論的実践として位置づける。ここで認識論的実践とは、知識がどのように作られ、検証され、修正されるかに関わる実践を意味する。少なくとも、ドメイン概念をコード、型、アプリケーションプログラミングインタフェース、状態遷移として表す場面では、業務上の概念、制度上の制約、組織上の責任、ユーザー行動の一部、計算機制約が、ソフトウェア上の名前、状態、不変条件、操作として表現され、実行時の振る舞いを通じて扱われる。本章では、この表現を作業の中で更新する過程を、Schön(1983)の行為の中の省察、Nersessian(2008)のモデルにもとづく推論、Gentner(1983)の構造写像理論、Dijkstra(1974)の関心の分離と接続して論じる。
本稿の観点からは、設計上の失敗の一部は、実装ミスではなく、対象の分節の誤りとして読める。ここで分節とは、対象の差異に名前を与え、どの差異を同じ概念として扱い、どの差異を別の概念として扱うかを区切ることである。ある業務概念を一つの型にまとめたが、実際には部署ごとに意味が異なる。ある状態を列挙値で表現したが、現実には遷移順序と責任主体が重要である。ある外部サービスを単なる通信先として扱ったが、実際には契約、再試行、監査、障害時の責任分界が重要である。このような場合、問題はコードが汚いことではなく、コードが対象を十分に区別できていないことにある。
リファクタリングは、この分節を修正する。命名を変えることは、対象を別の概念として捉え直すことである。関数を抽出することは、ある振る舞いに独立した意味を与えることである。クラスやモジュールを分割することは、変更理由や責任の境界を引き直すことである。型を導入することは、許される状態と許されない状態を明示することである。したがって、リファクタリングは、すでに存在する対象領域を単に整理するのではなく、その対象領域をどのように記述し、操作すべきかについての仮説を更新する。
この過程は、Schön(1983)のいう行為の中の省察と接続できる。行為の中の省察とは、専門家が作業の最中に状況からの応答を受け取り、問題の見立てを修正しながら進む実践である。リファクタリングにおいても、開発者は最初から完全な抽象を持っているわけではない。関数を抽出し、名前を与え、テストを書き、レビューを受け、次の要求変更で抽象が耐えるかを確認しながら、対象についての理解を更新する。
また、Nersessian(2008)が論じるモデルにもとづく推論は、リファクタリングの認識論を考えるうえで有用である。モデルにもとづく推論とは、対象を直接扱うのではなく、モデル、類推、図的表現、思考実験を通じて対象を理解し、仮説を作る推論である。リファクタリングにおける状態遷移図、依存グラフ、テストケース、プロトタイプは、コードそのものとは異なるモデルとして機能する。開発者はそれらを操作することで、コードだけを読んだ場合には見えにくい関係を発見する。
Gentner(1983)の構造写像理論も、設計理解の更新に関係する。リファクタリングでは、あるドメインの構造を別のコード構造へ写像することが多い。たとえば、業務上の承認フローを状態遷移として写像する、契約上の責任分界をモジュール境界として写像する、価格体系の例外を方針オブジェクトとして写像する。この写像が妥当であれば、コードは対象領域を理解しやすくする。妥当でなければ、コードは現実を歪めて見せる。
Dijkstra(1974)は、科学的思考を、複数の関心を同時に意識しながら、ある観点を一時的に分離して考える能力として論じた。この「関心の分離」は、リファクタリングの認識論にも当てはまる。設計者は、正しさ、性能、利用者の理解、将来の変更、組織上の責任を同時に扱う必要があるが、それらを混同してはならない。よいリファクタリングは、対象をよりよく分節し、必要な関心を必要な表現へ割り当て直す実践である。
本章は、リファクタリング教育の焦点を、技法適用の成否から、抽象化の根拠を説明し検証する過程へ拡張する。リファクタリングを抽象と具体の往復運動として捉えるなら、教育課題は、重複除去、長い関数の分割、命名改善、コードスメル修正といった個別技法だけでなく、学習者がどの具体例からどの抽象を導き、その抽象をどの反例やテストで確かめたのかを扱う必要がある。
この問題設定は、リファクタリング教育に関する先行研究を否定するものではなく、その射程を拡張するものである。Tan and Poskitt(2024)は、初学者向けの一部のリファクタリング教育では、動作する講師提供コードを対象にコードスメルを修正する課題が用いられることに着目し、学習者自身が書いたコードをリファクタリング対象にする mistake-based familiarisation1 を提案した。AlOmar, Mkaouer, and Ouni(2024)は、アンチパターン、すなわち反復的に現れるが望ましくない設計・実装上の解決形式の検出とリファクタリング修正を、統合開発環境のプラグインを用いた教室内実験として扱った。これらの研究が扱う教育場面でも具体的技法は重要である。しかし、教育課題が技法の適用結果だけに閉じるなら、学習者は「どの抽象が妥当なのか」を判断する過程を十分に説明し、検証する訓練を受けにくい。
Wing(2006)は、計算論的思考を、問題解決、システム設計、人間行動の理解を、計算機科学の基本概念にもとづいて行う思考として論じた。本稿の立場からは、リファクタリング教育は計算論的思考の具体的訓練として位置づけられる。学習者は、具体例を集め、共通性と差異を観察し、抽象名を与え、境界を引き、型、API、テスト、図、ドキュメントに落とし、反例によって抽象を壊し、修正する。この往復こそが、設計を学ぶ中核である。
教育課題として重要なのは、完成後の差分だけを評価しないことである。単に「重複をなくした」「関数を短くした」「クラスを増やした」という結果は、抽象化の質を十分に示さない。本稿の提案としては、どの具体例を観察したのか、どの差異を保持すべきと判断したのか、どの変更シナリオを想定したのか、どの反例で抽象を検証したのかを説明させる課題が考えられる。これは、設計判断を暗黙のセンスから、共有可能な根拠を持つ推論へ移すための課題設計であり、その教育効果を経験的に検証することは今後の課題である。
コードレビューも、教育的には抽象化の妥当性を共同で検証する場として再定義できる。レビューは、バグの発見や規約違反の指摘だけではない。命名が対象を正しく表しているか、境界が変更理由に沿っているか、テストが抽象の不変条件を表現しているか、図やコメントが実装と矛盾していないかを検討する場である。本稿の枠組みからは、レビューアが単に好みを述べるのではなく、具体的な変更履歴、要求、反例、利用者の語彙を根拠として抽象の妥当性を問うように課題を設計することが望ましい。
チーム学習の観点では、リファクタリングは設計言語を共同で更新する機会である。ある開発者が命名を変更したとき、それは個人のコード改善にとどまらず、チームがその対象をどう呼ぶかを変える。あるモジュール境界を変更したとき、それは責務と変更理由の共有理解を変える。したがって、リファクタリング教育では、個人の技法だけでなく、チームが言語を共有し、修正する過程を扱う必要がある。
コード生成支援に関する実証研究も、この論点と接続できる。ここでいう人工知能(Artificial Intelligence: AI)支援とは、主として GitHub Copilot などのコード生成・編集支援を指し、人工知能一般の能力を指すものではない。Peng ら(2023)は、GitHub Copilot を用いた特定の JavaScript 実装課題における統制実験により、コード生成支援が開発作業の速度に影響しうることを示した。一方で、経験あるオープンソース開発者が成熟したリポジトリ上の実課題に取り組むランダム化比較試験では、AI 利用を許可された条件で完了時間が増加したことも報告されている(Becker et al., 2025)。この実験では、開発者は主に Cursor Pro と Claude 3.5/3.7 Sonnet を用いていたが、この事実は当該実験条件を示すものであり、同種の製品一般の効果を直接に示すものではない。ただし、この結果は2025年初頭のツールと対象課題に関するプレプリントであり、同じ研究グループは2026年2月24日の後続報告で、以後の調査では参加者選択やタスク選択の偏りが大きく、現在の効果量を安定して推定しにくいとも述べている(Becker et al., 2026)。さらに Vaithilingam, Zhang, and Glassman(2022)は、大規模言語モデルにもとづくコード生成ツールの利用経験を調査し、生成結果を理解し、修正し、統合する負荷が残ることを報告している。したがって本稿の文脈では、AI 支援の効果は課題、コードベース、開発者経験、ツールの世代、評価指標に依存し、AI が提示するコード片や抽象候補を、具体的要求、既存設計、テスト、チーム語彙に照らして検証する課題が残る、と限定して解釈できる。
したがって、リファクタリングを学ぶとは、単に「きれいなコード」を書くことではない。具体から抽象を作り、抽象を具体に戻して検証し、その過程を他者に説明できるようになることである。教育においては、完成したコードだけでなく、抽象化に至る証拠、検討した反例、捨てた抽象案、修正した名前や境界を記録させる課題が、本稿の教育的提案として導かれる。
本章では、小さなリファクタリングから設計言語が生まれる過程を、単純化した例で示す。題材は、注文の状態に応じて通知文を生成する処理である。最初の実装では、複数の箇所に次のような条件分岐があるとする。
if (order.status === "paid" && order.shippedAt == null) {
return "発送準備中です";
}
if (order.status === "paid" && order.shippedAt != null) {
return "発送済みです";
}
if (order.status === "canceled") {
return "キャンセル済みです";
}最初に見える具体は、重複した条件分岐である。Fowler(2018)のカタログに従えば、関数抽出(Extract Function)、すなわちまとまった処理を名前付き関数として取り出すリファクタリングによって、deliveryMessage(order) のような関数を抽出できる。これにより、同じ判定が複数箇所に散らばることは避けられる。しかし、この段階の抽象はまだ弱い。なぜその関数名が妥当なのか、どの変化に耐えるのかが明確でないからである。
次に、条件分岐の背後にある概念を考える。ここで扱っているのは、単なる注文状態ではなく、顧客へ提示される配送上の状態である。支払い状態、配送状態、キャンセル状態が一つの文字列 status と shippedAt の組み合わせで表現されているため、コード上では業務上の状態が明示されていない。そこで、Evans(2015)の値オブジェクトの考え方を踏まえ、DeliveryState という型または値オブジェクト、すなわち識別子よりも値そのものによって意味を表す小さなオブジェクトを導入し、preparing、shipped、canceled などの状態を表すことを検討できる。この変更は、単なる関数抽出ではなく、新しい語彙を導入するリファクタリングである。
ただし、ここで抽象を固定してはならない。将来、「入金済みだが在庫不足」「一部発送済み」「配送停止中」「返品受付中」という要求が追加されるかもしれない。これらのシナリオに対して、DeliveryState が妥当な境界であるかを検証する必要がある。「配送停止中」は、配送業者、倉庫、出荷指示、再開条件と関係するため、配送文脈の状態として DeliveryState に含める候補になる。一方で、「返品受付中」は発送後の顧客サポート、返金、在庫戻し、監査証跡と関係するため、配送状態に混ぜると異なる変更理由を一つの型に押し込める危険がある。この場合、返品は ReturnState や、より広い顧客向け進行表示を担う CustomerOrderProgress として扱うべきかを検討する必要がある。
この検討を支えるために、テストケース表を作ることができる。
| 入力条件 | 顧客表示 | 概念候補 |
|---|---|---|
| paid, not shipped | 発送準備中 | preparing |
| paid, shipped | 発送済み | shipped |
| canceled | キャンセル済み | canceled |
| paid, out of stock | 入荷待ち | awaiting_stock |
| paid, shipment paused | 配送停止中 | paused |
| shipped, return requested | 返品受付中 | DeliveryState から除外する反例 |
この表は、コードではないが設計言語の一部である。どの具体例を同じ状態として扱い、どの具体例を別の状態として扱うかを明示するからである。さらに、状態遷移図を描けば、preparing から shipped へ遷移できるが、canceled から shipped へ直接遷移できない、といった不変条件が見える。ここに「返品受付中」を置くと、shipped の後に返品プロセスへ分岐することは見えるが、その遷移は配送の進行ではなく返品受付の進行であることも見える。依存グラフを用いれば、この状態が通知、管理画面、配送指示、監査ログ、返金処理のどこに影響するかを確認できる。
この事例で重要なのは、リファクタリングが段階的に進む点である。最初の Extract Function は、重複を減らす局所的な改善である。次の型や状態名の導入は、業務概念をコード上に明示する改善である。さらに、テストケース表や状態遷移図は、その抽象が具体例に耐えるかを検証する表現である。DeliveryState は、「顧客に提示する配送上の状態」を表す限りでは有効である。しかし、「注文全体の顧客向け進行」や「返品処理の状態」まで表そうとした瞬間に、抽象は過大になる。したがって、リファクタリングは「コードを短くする」作業ではなく、具体的な条件分岐から設計言語を作り、反例によってその境界を調整する過程である。
この過程は、抽象と具体の往復である。具体的な条件分岐から共通性を見つけ、DeliveryState という抽象を導入する。次に、将来の要求、反例、状態遷移、テストケースへ戻し、その抽象が妥当かを確認する。必要ならば名前を変え、境界を変え、型を分ける。小さなリファクタリングが設計技能の訓練になるのは、抽象を作る経験だけでなく、作った抽象を捨てる条件をコードの中で具体的に経験できるからである。
本稿は、リファクタリングを単なるコード整理や保守作業ではなく、抽象と具体を往復する思考訓練として論じてきた。Fowler や Opdyke による定義が示すように、リファクタリングの基本条件は、外部から観察可能な振る舞い、あるいはプログラムの意味を保存しながら内部構造を変更することである。しかし本稿が強調したのは、振る舞い保存がリファクタリングの必要条件であっても、十分条件ではないという点である。設計上意味のあるリファクタリングは、既存の構造を壊さないだけでなく、対象をより正確に記述し、将来の変更をより理解しやすくし、共同作業の中で共有可能な表現を与えるものでなければならない。
この観点から、本稿ではリファクタリングを、具体から出発する設計実践として位置づけた。アーキテクチャ設計が複数の関心、制約、ステークホルダーを統合し、システム全体の構造を記述する活動であるとすれば、リファクタリングは既存コードという具体的制約の中で、同じ設計課題に局所的・漸進的に取り組むことがある。とりわけモジュール境界、変更可能性、複数の関心の分離に関わるリファクタリングは、アーキテクチャ設計と接続する。Parnas の情報隠蔽が示すように、よい分割は単に機能を並べ替えることではなく、変化しうる設計判断をどこに閉じ込めるかを決めることである。したがって、リファクタリングにおける関数抽出、クラス分割、モジュール境界の変更は、見た目の整理ではなく、変化の可能性に対する仮説の更新である。
その仮説は、具体的事例から導かれる。重複コード、条件分岐、命名の揺れ、責務の集中は、抽象化の出発点である。しかし、それだけでは十分ではない。論理結合やバージョン履歴マイニングの研究が示すように、変更履歴は、どの要素が繰り返し同時に変化するかを観察する手がかりになる(Gall, Hajek, and Jazayeri, 1998; Zimmermann, Weißgerber, Diehl, and Zeller, 2005)。ただし、同時変更は設計依存を直接証明するものではなく、境界を疑うための材料として扱うべきである。障害やインシデントは、実装や運用の問題に加えて、既存の抽象が現実を十分に記述できていない箇所を示すことがある。要求変更やレビューコメントは、現在の構造がどの程度説明可能で、どこで共同理解を妨げているかを明らかにする。リファクタリングにおける具体とは、コード片そのものだけではなく、コードを取り巻く実践の履歴全体である。
しかし、具体から抽象へ上昇することだけをリファクタリングの目的とするならば、議論は不十分である。抽象は常に誤りうる。ある時点で共通に見えた構造が、別の要求変更の下では重要な差異を隠していることがある。ある命名が概念を明確にしたように見えても、別のステークホルダーの語彙では異なる意味を持つことがある。したがって、抽象は具体へ戻されなければならない。テスト、シナリオ、反例、プロトタイプ、レビュー、図表、依存グラフは、抽象を検証するための装置である。よいリファクタリングは、抽象を作ることと、抽象化しないこと、あるいは抽象をいったん壊して作り直すことのあいだの往復によって成立する。
この往復運動を支えるのが、広い意味での言語である。本稿では、言語を自然言語やプログラミング言語に限定せず、命名、型、API、モジュール境界、テスト、図表、アーキテクチャビュー、依存グラフ、ドメインモデルを含む表現体系として捉えた。Larkin and Simon の図的表現に関する議論や、Blackwell らの表記法の認知的次元に関する研究が示すように、表現は対象を単に写すだけではない。表現は、何に注意を向け、どの関係を見つけやすくし、どの誤りを起こしにくくするかを変える。したがって、リファクタリングはコードの内部構造を改善するだけでなく、チームが対象を記述し、議論し、検証するための言語を作り替える実践である。
この意味で、本稿の概念枠組みから見れば、設計上の失敗の一部は、実装ミスだけではなく、対象の分節の誤りとして現れる。少なくとも業務アプリケーションや情報システムにおいて、ドメイン概念をコード、型、アプリケーションプログラミングインタフェース、状態遷移として表す場面では、業務上の概念、制度上の制約、ユーザー行動の一部、組織上の責任、計算機上の制約は、ソフトウェアの中で名前、型、境界、不変条件、依存関係として表現されることがある。現実の理解が深まったとき、あるいは現実そのものが変化したとき、既存の表現は不十分になる。リファクタリングは、その不十分さを、動作しているコードの中で修正する実践である。Schön のいう行為の中の省察や、モデルを用いた推論に関する議論と接続するならば、リファクタリングは、設計者が対象についての知識を作業の中で獲得し、検証し、更新する実践として解釈できる。
この見方は、教育にも含意を持つ。リファクタリングを重複除去やコード整形の技法としてだけ教えるならば、学習者は抽象を作る手続きは学べても、その抽象が妥当であるかを判断する技能を十分に身につけられない。教育において重要なのは、リファクタリング前後の差分を示すことだけではなく、どの具体例を観察し、どの共通性と差異を選び、どの境界を引き、どの反例で検証したのかを説明させることである。コード生成支援によって実装案や抽象候補を生成する費用が下がる場面では、この教育上の焦点はなお重要になる。単純な実装課題では速度向上が報告される一方、成熟した大規模コードベース上の実課題では減速も報告されており、2026年時点の後続調査でも参加者選択やタスク選択の偏りにより効果量の推定は難しい(Peng et al., 2023; Becker et al., 2025; Becker et al., 2026)。また、生成結果の理解・修正・統合には人間の判断が残る(Vaithilingam, Zhang, and Glassman, 2022)。したがって本稿の立場では、生成された候補を採用する前に、具体的要求、既存設計、テスト、チーム語彙に照らして検証する技能が必要になると考える。
以上より、本稿の結論は次のようにまとめられる。リファクタリングとは、コードの見た目を整える作業ではない。それは、具体的な変更経験から抽象を導き、その抽象を具体的状況で検証し、必要に応じて表現を修正し続ける実践である。この実践を通じて、エンジニアは対象をより正確に記述し、操作する言語を構築する。その言語は、自然言語、プログラミング言語、図表、グラフ、テスト、ビュー、境界、命名体系を含む。したがって、リファクタリングを通じて獲得すべき専門的技能とは、抽象化能力そのものではない。抽象と具体を往復しながら、表現を検証し、修正し、共同で使える設計言語へ鍛え続ける能力である。
アクセス日は、特に断りのない限り2026年6月4日である。ウェブ記事の公開日・更新日は、公開ページで確認できる範囲を記す。