リファクタリング (プログラミング)
リファクタリング (refactoring) とは、コンピュータプログラミングにおいて、プログラムの外部から見た動作を変えずにソースコードの内部構造を整理することである。また、いくつかのリファクタリング手法の総称としても使われる。ただし、十分に確立された技術とはいえず、また「リファクタリング」という言葉に厳密な定義があるわけではない。
リファクタリングが登場する以前は、一度正常な動作をしたプログラムは二度と手を触れるべきではないと言われていた。なぜなら、下手に手を加えて動作が変わってしまうと、それに伴って関連する部分にも修正が加えられ、やがてその修正作業はプロジェクト全体に波及し対処しきれなくなる可能性があったからである。また、ソフトウェアテストを十分に行い、正常な動作が確認されたとしても、そのプログラムを少しでも改変してしまえば、その後バグ (欠陥) が見つかったときに、改変があったプログラムを疑わなければならない。
しかし、プログラムには必ず変更があり、プログラムはどうしても継ぎ接ぎだらけになることは避けられない。また、仕様が開発開始時から確定していることは少なく、開発をしている間にもソフトウェアに対する要求は日々変わり続けており、ソフトウェアには常に仕様変更に対応できる柔軟さが求められる。さらに、いくら厳密に設計しても実際に動作させないと分からない部分も多く、完璧な設計を行うことは不可能である。変更が必要になったとき、二度と手を触れられないほど煩雑になったソースコードを修正することは困難を極め、プログラマにも勇気が要求される作業になる。
そこで、Smalltalkプログラマなどの間で、常日頃からプログラムを整理し、仕様変更にも対応できる整理されたプログラムを書いていく考え方が生まれた。この過程では、ウォード・カニンガム、ケント・ベック、ラルフ・ジョンソン(GoFの一人)などの人々が大きな役割を果たした。この手法がリファクタリングと呼ばれている。また、リファクタリングは、プログラムの全容を捉えるためにも効果的である。例えば、バグが検出された場合でも、ソースコードが整理されているので、修正しやすい。また、プログラマとしても、普段から修正しているコードに手を入れるだけなので、修正にも積極的になれる。さらに、設計者も設計ミスによる心残りをなくすことができる。そのため、「リファクタリングは設計の代用にもなる」とする意見もあり、事前設計を非常に簡素化する役割も担っている。
リファクタリングは、オブジェクト指向設計と深く関係している。ほとんどのリファクタリングは、オブジェクト指向の性質に沿ったものであり、オブジェクト指向のコードの再利用性を最大限に引き出すことができる。また、オブジェクト指向プログラミングを行える言語であれば、プログラミング言語の種類に関わらず、リファクタリングを適用できる。
リファクタリングを行うことで、開発が停滞してしまうのではないか、という心配をされることも多い。たしかに、リファクタリングを行っている間は、何の機能追加も行われない。しかし、たいていの場合は、設計が向上することで機能追加やバグフィックスをしやすくなり、開発のスピードは安定するばかりか、速くなることもある。また、すでに機能しているコードを危険に晒すべきでない、とする意見もあるが、手順を守りテストを十分に行えば、ある程度危険を減らすことができる。
長すぎるメソッドは再利用性が低い。メソッドを抽出、細分化することで再利用性が高まり、呼び出し側メソッドの記述も読みやすくなる。処理の重複も減る。
不要な参照は管理のための手間を増やし、オブジェクトの破棄を失敗させる。不要になった関連は消す。
大きくなりすぎたクラスを分割する。クラスを小さくすることで、そのクラスの役目を明確にできる。
switch文をポリモーフィズムに置き換えることで、新たな条件が追加されても分岐部分には変更の必要がなくなる。
フィールドやメソッドが不適切なクラスにある場合、他のクラスとの余計な関連が増える。メンバを移動し、クラスの責任を整理する。
継承では基底クラスのすべてのメンバを、サブクラスに許さなければならない。基底クラスの一部だけの機能を利用する場合は、継承の代わりに委譲を使う。
ダウンキャストは互換性のない型に変換してしまう可能性があるが、それをコンパイル時に察知することは出来ない。総称型(テンプレート)がない言語では、カプセル化してクライアント側にダウンキャストの手間を減らすようにする。コレクションクラスなどでは特に必要。
コンストラクタをFactory Methodに置き換える
コンストラクタはそのクラスのオブジェクトを返すことしか出来ない。Factory Methodの導入によって柔軟なインスタンス化が可能になる。
たびたび一緒に受け渡しされる複数の値は、オブジェクトとしてまとめたほうが分かりやすい。
シンボルが指す対象をより適切に象徴するシンボル名に更新すること。
シンボル名(クラス名・関数名・属性名など)は対象がもつ役割を正確に象徴・説明すべきである(c.f. 命名規則 (プログラミング))。当初は適切であった名称も、プログラムの変更によって不正確・曖昧になりうる。この場合、シンボル名の変更(リネーム)がおこなわれる。いくつかのエディタではファイルを跨いだシンボル名の一括変更をサポートとしており(VSCode: F2で一括リネーム)、リネームに必要なリファクタリングコストは非常に小さくなっている。
マーティン・ファウラーなどの人々が著したリファクタリングの解説書、『リファクタリング プログラミングの体質改善テクニック』(以下『リファクタリング』)では、70種類ほどのリファクタリングが挙げられている。
いつでもなんでもリファクタリングをすればよいというものではない。例えば、納期がぎりぎりに迫った場合などにリファクタリングを行っている余裕はないし、リファクタリングは将来に備えて行うものであるため、そのリファクタリングが実を結ぶ可能性は少ない。また、リファクタリングといえども、やはりプログラミングであるので、常にミスをする危険性は拭えない。
『リファクタリング』では、機能追加するときと、リファクタリングするときをはっきり区別することを勧めている(このことを比喩して「実装の帽子をリファクタリングの帽子に被りなおす」という)。リファクタリングしてばかりいては開発は進まないし、どのリファクタリングをするべきかはある程度開発が進まないと分からない。リファクタリングを開始するタイミングとして、コードに「不吉なにおい」を感じ始めたら、と提案している。これは似たようなコードの重複や、長すぎるメソッド、ひとつの変更の度に複数のクラスが影響を受ける、などの症状が見つかったときを指している。また、機能追加の前、コードレビュー時、バグフィックス時にもリファクタリングを勧めている。
リファクタリングでは、プログラムの外観を変更してはならない。そのため、テストが非常に重要である。修正は段階的かつ小刻みに行い、わずかな変更であっても、その度にテストを行うことで、動作の異常をいち早く察知する。テストを行わずに一度にリファクタリングを行うと、プログラムの動作が気付かないうちに変わってしまい、その原因を突き止めることが難しくなる。プログラマにテストをサボらせないため、簡単にテストを実行できるツール (xUnit/JUnitなど) も必要である。また、テストを重要視することは、アジャイルソフトウェア開発のいくつかの開発手法(エクストリーム・プログラミングなど)における「テストファースト」や「テスト駆動開発」の考え方とも一致する。
リファクタリングには、いくつか課題が存在する。例えば、データベースに変更を加える場合、データを移行する必要がある。たしかに、中間層を挟むことで影響を緩和できるが、やはり時間が掛かることは否定できない。また、リファクタリングでは、従来のようにカプセル化されたクラス内だけでなく、インタフェースも変更することがある。それが広く公開されたインタフェースである場合、新しいインタフェースと古いインタフェースを両方保守しなければならない。また、修正するコードがあまりに酷い場合、新たに書き直したほうが早いこともある。リファクタリングは発展途上の技術であるため、これら以外の課題が見つかる可能性がある。
最近の統合開発環境には、リファクタリング機能が備わっていることが多い。リファクタリングでは、修正対象のメソッドやクラスがどのクラスから利用されているかを調べる必要が発生する。これを単なるテキストエディタで調べようとすると、かなり面倒な作業になる上、見落としをする可能性も高い。