リファクタリングとは「ソフトウェアの外部的振る舞いを保ったままで、内部の構造を改善していく作業」をいいます。と、こんな説明は世の中腐るほど出ています。僕のおすすめの「リファクタリング プログラミングの体質改善テクニック」という本もあります。
ここでは、プロジェクト中のちょっとしたときに、リファクタリングを行えるような覚書きをまとめておこうかと思います。すべてのリファクタリングに先立って自動テストを行うことがリファクタリングの最低条件である。
コードの嫌な匂い
- 重複したコード
- 長すぎるメソッド
- 大きすぎるクラス
- 多すぎる引数
- 変更クラスが複数
- 変更箇所が多い
- 他クラスの属性ばかり使っている
- まとまったデータ
- 基本データ型よりオブジェクト
- スイッチ文
- 継承する毎に変更が入る
- 無駄なクラス
- 不要な一般化
- 一時属性の多用
- 過剰なメッセージチェーン
- 過剰な委譲
- 相互リンク
- 処理は同じで名前が違う
- 未熟なクラスライブラリ
- データクラス
- 親クラスがほとんど不要
- コメント
リファクタリングカタログ
- メソッドの抽出
- 一時変数の置き換え
- 説明用変数の導入
- 複数の意味を持つ変数は避ける
- 引数でわたされた変数への代入を避ける
- メソッドの移動
- クラスの分割
- デメテルの法則
- 自己カプセル化
- オブジェクトによるデータの置き換え
- コレクションのカプセル化
- 条件記述の分解
- ガード節による条件記述の置き換え
- ポリモーフィズムによる条件記述の置き換え
- ヌルオブジェクトの導入
- メソッドに関して
- メソッドの名前変更
- 問い合わせと更新の分離
- 引数オブジェクトの導入
- setメソッドの削除
- Factoryメソッドによるコンストラクタの置き換え
- ダウンキャストの隠蔽
- メソッドの引き上げ
- インターフェースの抽出
- 委譲による継承の置き換え
メソッドの抽出
- 長すぎるメソッド
- コメントがないと処理が理解できない部分
メソッド抽出できる合図です。ただし、メソッドに良い名前が付けられる場合だけ抽出します。メソッドには、どんな処理をするかではなく、何をするかによって命名する。メソッド名と本体との意味的な距離が重要であって、メソッド名の長さは関係ない。明快さが向上するなら、メソッド名がコードよりも長くても抽出すべきである。
一時変数の置き換え
一度だけ代入される一時変数がある。そんな時、メソッドの問い合わせによる置き換えを検討する。ただ、パフォーマンスなど考慮する場合もあるので、ほとんどの場合無視できる。他のリファクタリングの邪魔になったときだけ実行する。
説明用変数の導入
長くて、よく考えないとわからないような評価式があった場合、一時変数を使って意味のわかるようにする。条件ロジックにおいて、各条件記述の意味を適切な名前の一時変数を使って説明するのは有効です。ただし、メソッドの抽出を行える場合は、そちらを優先する。メソッドにすることで他の場所でも使える可能性ができる。
複数の意味を持つ変数は避ける
ループ変数や、値を集める変数でもなく、複数回代入されるようなものは、意味ごとに変数を分けるべき。その際、適切な名前を付けること。
引数でわたされた変数への代入を避ける
メソッドの引数に渡された変数に、値を代入することはさける。「値渡し」と「参照渡し」の混乱をさけるために、代入はしない。結果用の別の変数を用意して、そっちをつかう。
メソッドの移動
メソッドの定義されたクラスの属性よりも、他のクラスの属性のほうをよく使っている場合、属性を良く使うクラスの方にメソッドを移動させたほうがよい。クラス間の結合度を下げるために。逆に、属性を移動させる方法もある。どちらにするかは、やってみてぴったりくる方にする。
クラスの分割
ひとつのクラスに、メソッドが多くなりすぎた場合や、責務が複数ある場合は、別々のクラスに分割する。責務で分割する粒度を決める。
デメテルの法則
あるオブジェクトから取得したオブジェクトには、直接アクセスしてはいけない。デメテルの法則について、詳しくは達人プログラマーをご覧あれ。
自己カプセル化
自分のクラスの属性であっても、アクセサメソッドを使ってアクセスする。サブクラスが情報の取り出し方をメソッドによってオーバーライドできること、遅延初期化のようにデータ管理面で、 柔軟性を持たせられることがその理由。逆に、変数に直接アクセスすると、コードが読みやすくなる利点がある。
オブジェクトによるデータの置き換え
引数で渡されたデータだけを使って処理をしたり、他のクラスの属性を使って処理をしたりしている場合、それらのデータをクラスとして切り出し、振る舞いを持たせることができる。
配列の各要素がそれぞれ別々の意味を持つようなデータ構造を考えた場合も、オブジェクトとして切り出したほうがすっきりする。配列や、リストなどのコレクションを使う場合は、同じ種類のオブジェクトを保持するときだけにする。
コレクションのカプセル化
getアクセサメソッドで、直接コレクションオブジェクトを返すのはやめにしましょう。コレクションの中身を直接書き換えられてしまう可能性があります。コレクションを隠蔽し、getメソッドでは、読み取り専用のオブジェクトを、コレクションに値を加えたい場合は、クラスにaddメソッドを用意しましょう。コレクションにsetメソッドは必要ありません。自らが仲介役を果たしましょう。
条件記述の分解
複雑な条件記述がある場合、その条件部をメソッドとして切り出します。抽出するメソッドは小さくても、メソッドのほうが条件式よりもコメントのように楽に読めます。
条件記述の抽出は、何を行っているかの記述をなぜ行っているかの文で置き換えることになるため、非常に読みやすくなります。
ガード節による条件記述の置き換え
if-then-else構造が使われるときには、if部にもelse部にも同じウェイトが置かれています。
これは、読み手に、両方とも等しく起こる、等しく重要であるということを伝えます。ガード節は「めったに起きないが、起きたときは何もしないで出て行く」ということを伝えます。ガード節はreturnするか例外を投げるかのどちらかです。
ガード節による入れ子条件の置き換えを行う場合のヒントは、条件記述を逆にすることでしばしば行える。
ポリモーフィズムによる条件記述の置き換え
オブジェクトのタイプや状態でのswitch文が存在する場合、その部分をオブジェクトのポリモーフィズムに置き換えることができます。ポリモーフィズムの真髄は、オブジェクトの振る舞いが型によって変わるとき、明示的な条件記述を書かなくてもすむようにすることです。
このリファクタリングを行う前に、適切かつ必要な継承関係を持っていることが必要です。
ヌルオブジェクトの導入
null のチェックが繰り返し行われる場合、ポリモーフィズムを利用した、nullオブジェクトを導入するとよい。Null Objectパターン
メソッドに関して
優れたオブジェクト指向のソフトウェアを開発するには、理解が容易で使いやすいインターフェースを提供することが秘訣となります。状態を更新するメソッドと、状態を問い合わせるメソッドを明確に分離することは非常に良いことである。よいインターフェースは、何をすべきかだけを示し、それ以上は何も語らない。クラスのユーザにダウンキャストを強要するのはできるだけさける。
メソッドの名前変更
メソッドの名前は、そのメソッドの処理にコメントを付けるとどうなるかを考え、それをそのまま名前にしてしまうとよい。名前だけでなく、シグネチャの部分(引数)の順序を変更することで、メソッドの意図が明確になるなら、変更するべきです。
問い合わせと更新の分離
値を返す関数は、クラスの状態を変更するなどの副作用がないのが好ましい。副作用のあるメソッドと、ないメソッドを明確に分離するのは、非常によいことです。問い合わせと更新は、明確に分離するべきです。
ファンクション(関数)とプロシージャ(手続き)を明確に区別したほうがプログラムは分かりやすくなります。ファンクションとは、戻り値を記述して値の取得を意図するもの。プロシージャは、オブジェクトに対して何かしらの処理を行うもの。
ファンクション名には、戻り値を記述するものをつけると良い。例えば、cos()、sin()、currentPenColor()などは良い例です。プロシージャ名には、動詞 + 目的語を記述する良い。例えば、calcMoney()、printReport()、formatDocument()などは良い例です。
引数オブジェクトの導入
本来一緒に扱うべきデータを引数としてたくさん渡している場合、引数オブジェクトの導入を検討する。長い引数リストは理解しづらいものです。引数オブジェクトを導入することで、振る舞いもそのクラスに移動できる場合があります。クラスの責務を単純明快にするのは、オブジェクト指向の原則です。
setメソッドの削除
setメソッドを提供するというのは、フィールドが変更される可能性があるということを示しています。オブジェクトを生成した後、フィールドを変更したくないのであれば、setメソッドを削除し、フィールドをfinalに設定するべきです。
Factoryメソッドによるコンストラクタの置き換え
このリファクタリングは、主にサブクラス化することによって、タイプコードを置き換えたい場合に利用されます。また、単に引数の数値や型の違いではすまない生成時におけるさまざまな振る舞いを実現できます。
ダウンキャストの隠蔽
クラスのユーザにダウンキャストを強要するのはなるべくさけたほうがよい。それは、変更が分散してしまうからです。メソッドが返すオブジェクトは、メソッド内でキャストしてやり、できるだけ特化された戻り値を使用するほうが好ましいです。ただし、インターフェースで返す場合は別の話です。そもそも、インターフェースを使う場合はダウンキャストを使う必要はほとんどありません。
メソッドの引き上げ
まったく同じ処理を行っている2つのクラスがあった場合、共通のクラスを作り、そこにメソッドを移動してやる。オブジェクト指向では、重複するコードは書かないという原則がある。
2つのメソッドが非常によく似ているが、まったく同じではない場合は、Template Method パターンが利用できるかもしれない。
インターフェースの抽出
ひととまとまりのクライアントが、あるクラスの責任のうち一部だけを利用することがあります。その場合、責任をインターフェースとして抽出することで、責務を明確にすることができます。
インターフェースが機能を追加するように働く場合は、名前に able と付けるとわかりやすい。
委譲による継承の置き換え
継承を行ったはいいが、スーパークラスの多くの操作がサブクラスではふさわしくないことに気づくことがあります。この場合、多くは正しくインターフェースが切り出せていないのが原因です。これらの状況をすっきりさせるには、継承ではなく、メッセージの委譲を行うことで、必要な機能だけを利用している状況をはっきりさせることができます。
逆に、すべてのインターフェースに対して委譲を行っているような場合は、委譲をクラスの継承で置き換えるべきです。
参考
C言語によるリファクタリングの例です。 Refactoring C-code
リファクタリングのWebページ版 リファクタリング勉強ノート
コードの嫌な匂いのリファクタリング例が載ってます。(PDF) リファクタリング
J2EEのリファクタリング例が載ってます。Tokenの導入などあります。(PDF) J2EE リファクタリング
すばらしき良書。この本に出会って、変わった気がする。
- 「リファクタリング」をワークブック形式で学べます。
- 洋書ですが、リファクタリングをデザインパターンで行うといった趣旨の本です。