リファクタリングとは
- プログラムの外部から見た動作を変えずにソースコードの内部構造を整理することです。また、いくつかのリファクタリング手法の総称としても使われます。
- 現時点で十分に確立された技術とはいえず、また「リファクタリング」という言葉に厳密な定義があるわけではありません。
- コンピュータプログラミングにおいてプログラムのソースコードに深刻な問題が存在することを示す何らかの兆候のことを「コードの臭い(Code smell)」と言います。コードの匂いが示す深刻な問題は、小さく管理された手順でリファクタリングする短いフィードバックルを廻し、それ以上のリファクタリングが必要なことを示すコードの臭いがないかどうか、設計を検査しなければなりません。リファクタリングを実施するプログラマの視点からは、いつリファクタリングするか、どのリファクタリング手法を用いるか、などをコードの臭いから計画します。このように「コードの臭い」とは、リファクタリングを後押しするものです。
- 詳細はリファクタリングカタログ(https://refactoring.com/)を参照してください。
# | コードの臭い | 概要 | リファクタリング例 |
---|---|---|---|
1 | 重複したコード | ・同じ処理が複数箇所に存在する。 ・同じようなコードが2箇所以上で見られたら、1箇所にまとめることを考えると良いプログラムになる。重複したコードが存在すると、その箇所を回収する時に同じ改修を重複コード分行う必要があるため、保守性が低くなりがちになる。 | メソッドの抽出、クラスの抽出、プルアップ、テンプレートメソッドの形成 |
2 | 長すぎるメソッド | ・1つのメソッドに多くの処理が記載されていること ・長いメソッドは理解するのが大変。長すぎるメソッドは小さいメソッドに分けていくと良い。ただ、小さいメソッドに分けることでコードを追っていくのが大変になるが、 適切なメソッド名を命名することで内部の実装を見なくても読み進めることが可能になる。メソッド名には、内部でどのような処理をしているかではなく、そのコードが何をするのかという意図を示す。こうした分割を行うことで以前よりもコードが長くなったとしても、意図が明確になっていればOK。メソッドの長さを切り詰めるのではなく、メソッド名とその実装との距離を埋めることが重要。 | メソッドの抽出、インライン化、パラメータオブジェクトの導入、条件記述の分解 |
3 | 巨大なクラス | ・あるクラスが責務を多く持ち過ぎている状態 ・1つのクラスがあまりに多くの仕事をしている場合は、たいていインスタンス変数の持ちすぎになっている。インスタンス変数が多すぎると、重複したコードが存在する可能性も高くなる | クラスの抽出、サブクラスの抽出、インターフェイスの抽出、オブジェクトによるデータ値の置き換え |
4 | 長すぎるパラメータリスト | ・パラメータがあまりに多いと、1つ1つが何を意味しているのか理解しづらくなる。それぞれのパラメータ間の一貫性がなくなり、使いにくくなる。何より、新たなデータが必要になった時にパラメータリストを変更しなければならない。 | メソッドによる引数の置き換え、パラメータオブジェクトの導入 |
5 | 変更の偏り | ・1つのクラスがさまざまな変更要求の影響を受ける状態 ・1つのクラスが別々の理由で何度も変更される状況では変更の偏りが起こっている。例えば、「データベースが新しくなるたびに、いつもこの3つのメソッドを変更しなければならないし、金融商品を追加するたびに毎回この4つのメソッドを修正している」等が同一クラスで見られるケースがこれにあたる。 | クラスの抽出(分類) |
6 | 変更の分散 | ・1つの変更要求によって、複数のクラスが影響を被る状態 ・変更を行うたびにあちこちのクラスが少しずつ書き換わる(1つの変更要求に対して複数のクラスが書き換わる)ケース。変更すべき箇所が全体に広がると、探すのが難しくなり重要な変更を実装し忘れる場合も出てくる。 | メソッドの移動、フィールドの移動、クラスのインライン化 |
7 | 特製の横恋慕 | ・自クラスより他クラスの属性、操作との関連が深い状態 ・オブジェクト指向には処理および処理に必要なデータを1つにまとめてしまおうという重要な考え方があるにもかかわらず、あるメソッドが自分のクラスよりも他のクラスに興味を持つようなケース。 | メソッドの移動、フィールドの移動、メソッドの抽出 |
8 | データの群れ | ・複数のデータがグループとなって各所に現れている状態 ・数個のデータがグループとなって、クラスのフィールドやメソッドのシグネチャなど、さまざまな箇所に現れることがある。 | クラスの抽出、おアラメータオブジェクトの導入、オブジェクトそのものの受け渡し |
9 | 基本データ方への執着 | ・基本データ型を使用し、複雑なロジックが記述されている状態 ・オブジェクトにちょっとした仕事をさせる場合には、小さなクラスを作ることが面倒に感じることがある。例えば、住所を表すために、県、市、町、番地などをそれぞれStringとして人クラスのフィールドに作成するようなケース。こうしたクラスが大きくなってくると、保守性が下がり将来の変更にも弱くなりやすい。 | クラスの抽出(分割) |
10 | Switch文 | ・Switch文により設計が複雑化、硬直化している状態 ・Switch文は重複したコードを生み出す問題児。コードのあちこちに同じようなSwitch文があると、新たな分岐を追加した時に全てのSwitch文を探して似たような変更をしていかなければならない。 | ポリモーフィズムの利用を特性 |
11 | パラレル継承 | ・あるクラスの継承ツリーと、他のクラスの継承ツリーに関連がある状態 ・新たなサブクラスを定義するたびに、 別の継承ツリーにもサブクラスを定義しなければならない状況。ある継承ツリー中のクラスに付けられたクラス名のプレフィックスが、別の継承ツリ中のクラス名のプレフィックスと同じ時はこの臭いを疑ったほうがよい。 | |
12 | 怠け者クラス | ・十分な仕事をせず、その見返りに合わないようなクラスは排除するべき。リファクタリングの結果クラスのダウンサイジングにより不要になる場合や、変更を予期して作成したクラスが実際には役に立っていない場合がある。 | クラスの排除 |
13 | 疑わしき一般化 | ・「いつか必要になりそうな機能だから」という理由で、現在は必要としていないのに凝った仕掛けや特殊な状況を考えているケース。いわゆるYAGNI。無用の長物なら削除したほうが良い。 | ロジックの排除 |
14 | 一時的属性 | ・インスタンス変数の値が特定の状況でしか設定されないオブジェクトがある。通常オブジェクトは、値を属性として常に保持するものなので、そうしたコードは非常に理解しづらい。原因として、複雑なアルゴリズムがいくつもの変数を必要としている場合がある。 | |
15 | メッセージの連鎖 | ・クライアントがあるオブジェクトにメッセージを送り、受け取ったオブジェクトがさらに他のオブジェクトに送り、それがまた別のオブジェクトに送る、というケース。オブジェクトをナビゲートする過程の構造にクライアントが強く依存することになる(中間のオブジェクトの関連が変わるたびに、クライアントが影響を受けてしまう)。 | |
16 | 仲介人 | ・カプセル化はしばしば権限の委譲をもたらすがこれが過剰となる場合もある。例えば、メソッドの大半が別のオブジェクト(仲介人)に委譲しているだけのクラス等がそれにあたる。こうした場合は、仲介人を除去して本当に仕事をするオブジェクトに直接処理させる。 | 仲介人メソッドがわずかであればメソッドをインライン化して呼び出し側にその部分を埋め込む |
17 | 不適切な関係 | ・クラス同士が必要もないのに密接に結びついているケース。 | |
18 | クラスのインターフェース不一致 | ・処理は同じでシグニチャのみが異なるメソッドがあるケース。 | |
19 | 未熟なクラスライブラリ | ・クラスライブラリの作成者も全能ではなく、それを非難することは意味のないこと。ソフトウエアの設計が正しいと確信できるのは、かなりの構築が進んでからがほとんどなので、万能なライブラリをはじめから作成しておくのは非常に困難。問題なのは、第三者によるクラスライブラリの修正が不可能であるか、可能であっても混沌とした形で行われるということ。 | |
20 | データクラス | ・属性とsetter/getterしか持たないクラスは単なるデータ保持用であるため、他のクラスからのアクセスを過剰に受けがちになる。 | 早い段階からのカプセル化 |
21 | 相続拒否 | ・サブクラスは親の属性と操作を継承するのが普通だが、ほんの一部しか利用していない場合がある。こうしたケースは継承階層が間違っている、というのが伝統的な見方。 | |
22 | コメント | ・コメントは悪い臭いではなくむしろいい香り。問題としているのは、コメントが消臭剤として使われているケースがあるということ。コメントが非常に丁寧に書かれているということは、実はコードの分かりにくさを補うため、ということがよくある。 | コメントがなくてもコードの意味がわかるように記述する |