BOOK Preview

Code Complete 第2版 上・下
― 完全なプログラミングを目指して ―

第24章 リファクタリング

マイクロソフトプレスの書籍紹介ページ
書籍情報のページ
2005/05/10


24.3.4 クラス実装のリファクタリング

 次のリファクタリングは、クラスレベルでコードを改良する。

■ 値オブジェクトを参照オブジェクトに変更する
 大きなオブジェクトや複雑なオブジェクトをいくつも生成して管理していることに気付いたら、マスタコピー(値オブジェクト)を1つ生成して、コードの残りの部分ではこのオブジェクトへの参照(参照オブジェクト)を使用するように、オブジェクトの使用法を変更する。

■ 参照オブジェクトを値オブジェクトに変更する
 小さいオブジェクトや単純なオブジェクトへの参照を管理する作業に追われていることに気付いたら、すべてのオブジェクトが値オブジェクトになるように、オブジェクトの使用法を変更する。

■ 仮想ルーチンをデータの初期化に置き換える
 サブクラスがいくつかあり、それぞれが返す定数値だけが異なる場合は、サブクラスでメンバルーチンをオーバーライドする代わりに、サブクラスを適切な定数値で初期化するようにし、それらの値を処理する汎用コードをスーパークラスに追加する。

■ メンバルーチンまたはメンバデータの配置を変更する
 継承階層全体の変更をいくつか検討する。これらの変更を行うと、通常はサブクラスから重複するコードがなくなる。

  • ルーチンをスーパークラスへ移動する。
  • フィールドをスーパークラスへ移動する。
  • コンストラクタの本体をスーパークラスへ移動する。

 サブクラスでの特化をサポートする場合は、通常は他にもいくつかの変更が行われる。

  • ルーチンをサブクラスへ移動する。
  • フィールドをサブクラスへ移動する。
  • コンストラクタの本体をサブクラスへ移動する。

■ 特殊なコードをサブクラスとして抽出する
 インスタンスの一部でしか使われないコードがクラスに含まれている場合は、その特殊なコードをサブクラスとして独立させる。

■ 同じようなコードをスーパークラスにまとめる
 2つのサブクラスに同じようなコードがある場合は、そのコードを合体させて、スーパークラスへ移動する。

24.3.5 クラスインターフェイスのリファクタリング

 次のリファクタリングは、クラスのインターフェイスを改善する。

■ ルーチンを別のクラスに移動する
 ターゲットクラスに新しいルーチンを作成し、ルーチンの本体をソースクラスからターゲットクラスに移す。そして、元のルーチンから新しいルーチンを呼び出す。

■ 1つのクラスを2つに分ける
 クラスにまったく異なる機能が2つ以上ある場合は、クラスを複数のクラスに分割して、それぞれに明確に定義された機能を割り当てる。

■ クラスを削除する
 クラスの機能がそれほどない場合は、そのコードをモジュール凝集度の大きいクラスへ移動して、元のクラスを削除する。

■ 委譲を隠ぺいする
 本来なら、クラスAがクラスBを呼び出し、クラスBがクラスCを呼び出さなければならないところを、クラスAがクラスBとクラスCを呼び出すことがある。このような場合、クラスAとクラスBのやり取りをどれくらい抽象化すればよいか検討する。クラスBがクラスCを呼び出すのが妥当であれば、クラスBからクラスCを呼び出すようにする。

■ 中間オブジェクトを削除する
 クラスAがクラスBを呼び出し、クラスBがクラスCを呼び出す場合、クラスAから直接クラスCを呼び出す方がうまくいく場合がある。クラスBに処理を委譲すべきかどうかは、クラスBのインターフェイスの整合性を維持するにはどうするのが最適であるかによって決まる。

■ 継承を委譲に置き換える
 クラスが別のクラスを使用する必要があるが、そのインターフェイスをさらに制御したいという場合は、スーパークラスをサブクラスのフィールドに書き換え、よりまとまった抽象化を提供するルーチンを公開する。

■ 委譲を継承に置き換える
 クラスが委譲クラス(メンバクラス)のパブリックルーチンをすべて公開する場合、単にクラスを使用するのではなく、委譲クラスを継承させる。

■ 外部ルーチンを導入する
 クラスにルーチンを追加する必要があるが、そのようにクラスを変更できないという場合は、クライアントクラスにその機能を提供する新しいルーチンを作成する。

■ 拡張クラスを導入する
 クラスにルーチンをいくつか追加する必要があるが、そのようにクラスを変更できないという場合は、変更できないクラスの機能と追加機能を組み合わせて新しいクラスを作成する。それには、元のクラスをサブクラス化して新しいルーチンを追加するか、クラスをラッピングして必要なルーチンを公開する。

■ 公開されているメンバ変数をカプセル化する
 メンバデータがパブリックである場合は、メンバデータをプライベートに変更して、その値をルーチン経由で公開する。

■ 変更できないフィールドのSet()ルーチンを削除する
 オブジェクトの生成時に設定され、後から変更できないフィールドがある場合は、誤解を招くSet()ルーチンを提供するのではなく、そのフィールドをオブジェクトのコンストラクタで初期化する。

■ クラスの外側で使用されないルーチンを隠ぺいする
 ルーチンがない方がクラスのインターフェイスの一貫性が強まるという場合は、ルーチンを隠ぺいする。

■ 使用されないルーチンをカプセル化する
 いつもクラスのインターフェイスの一部しか使用しないという場合は、必要なルーチンだけを公開する新しいクラスインターフェイスを作成する。新しいインターフェイスは一貫性のある抽象化を実現するものにする。

■ スーパークラスとサブクラスの実装が非常によく似ている場合は1つにまとめる
 サブクラスがそれほど特化したものでない場合は、スーパークラスにまとめる。

24.3.6 システムレベルのリファクタリング

 次のリファクタリングは、システム全体のレベルでコードを改良する。

■ 制御できないデータについては、最も信頼のおけるデータソースを作成する
 システムが管理しているデータに、そのデータについて知る必要のあるオブジェクトから都合良くまたは常にアクセスすることが不可能な場合がある。一般的な例は、GUIコントロールによって管理されるデータである。その場合は、GUIコントロールのデータをミラー化するクラスを作成して、GUIコントロールと他のコードの両方に、そのクラスを最も信頼のおけるデータソースとして使用させる。

■ 一方向のクラス結合を双方向のクラス結合に変更する
 互いの機能を使用する必要がある2つのクラスがあり、一方のクラスだけがもう一方のクラスを知っているという場合は、互いを知るようにクラスを変更する。

■ 双方向のクラス結合を一方向のクラス結合に変更する
 互いの機能を知っている2つのクラスがあり、実際には一方のクラスしかもう一方のクラスについて知る必要がないという場合は、一方がもう一方を知っているが、その逆はないようにクラスを変更する。

■ 単純なコンストラクタではなくファクトリメソッドを提供する
 型コードに基づいてオブジェクトを生成する必要がある場合や、値オブジェクトではなく参照オブジェクトを使用したいという場合は、ファクトリメソッド(ルーチン)を使用する。

■ エラーコードを例外に置き換える、または例外をエラーコードに置き換える
 エラー処理戦略に応じて、コードで標準手法を使用する。

チェックリスト24-2 リファクタリングの概要

データレベルのリファクタリング
・
マジックナンバーを名前付きの定数に置き換える。
・
変数名をより明白なものにするか、その目的がわかりやすいものに変える。
・
式をインラインにする。
・
式をルーチンに置き換える。
・
中間変数を導入する。
・
多目的変数を複数の単一目的変数に変換する。
・
ローカルの目的には引数ではなくローカル変数を使用する。
・
データプリミティブをクラスに変換する。
・
一連の型コードをクラスまたは列挙に変換する。
・
一連の型コードをスーパークラスとサブクラスに変換する。
・
配列をオブジェクトに変更する。
・
コレクションをカプセル化する。
・
従来のレコードをデータクラスに置き換える。
ステートメントレベルのリファクタリング
・
論理式を分解する。
・
複雑な論理式をわかりやすい名前の付いた論理関数にする。
・
条件文に分散している重複するコードを1つにまとめる。
・
ループ制御変数でなくbreakやreturnを使用する。
・
ネストしたif-then-elseブロックで答えがわかったら、戻り値を代入せずに、すぐに制御を戻す。
・
条件文、特にcase文の繰り返しをポリモーフィズムで書き換える。
・
null値を評価するのではなくnullオブジェクトを生成して使用する。
ルーチンレベルのリファクタリング
・
ルーチンやメソッドを抽出する。
・
ルーチンのコードをインラインにする。
・
長いルーチンをクラスに変換する。
・
複雑なアルゴリズムを単純なアルゴリズムで代用する。
・
引数を追加する。
・
引数を削除する。
・
照会と変更を分離する。
・
同じようなルーチンを引数を介在させてまとめる。
・
入力引数によって振る舞いの異なるルーチンを分離する。
・
特定のフィールドではなくオブジェクト全体を渡す。
・
オブジェクト全体ではなく特定のフィールドを渡す。
・
ダウンキャストをカプセル化する。
クラス実装のリファクタリング
・
値オブジェクトを参照オブジェクトに変更する。
・
参照オブジェクトを値オブジェクトに変更する。
・
仮想ルーチンをデータの初期化に置き換える。
・
メンバルーチンまたはメンバデータの配置を変更する。
・
特殊なコードをサブクラスとして抽出する。
・
同じようなコードをスーパークラスにまとめる。
クラスインターフェイスのリファクタリング
・
ルーチンを別のクラスに移動する。
・
1つのクラスを2つに分ける。
・
クラスを削除する。
・
委譲を隠ぺいする。
・
中間オブジェクトを削除する。
・
継承を委譲に置き換える。
・
委譲を継承に置き換える。
・
外部ルーチンを導入する。
・
拡張クラスを導入する。
・
公開されているメンバ変数をカプセル化する。
・
変更できないフィールドのSet()ルーチンを削除する。
・
クラスの外側で使用されないルーチンを隠ぺいする。
・
使用されないルーチンをカプセル化する。
・
スーパークラスとサブクラスの実装が非常によく似ている場合は1つにまとめる。
システムレベルのリファクタリング
・
制御できないデータについては、最も信頼のおけるデータソースを作成する。
・
一方向のクラス結合を双方向のクラス結合に変更する。
・
双方向のクラス結合を一方向のクラス結合に変更する。
・
単純なコンストラクタではなくファクトリメソッドを提供する。
・
エラーコードを例外に置き換える、または例外をエラーコードに置き換える。
 

 INDEX
  Code Complete 第2版 上・下
  第24章 リファクタリング
    1.24.1 ソフトウェアの進化の種類
    2.24.2 リファクタリング概論
    3.24.3 リファクタリングの詳細(1)
  4.24.3 リファクタリングの詳細(2)
    5.24.4 安全なリファクタリング
    6.24.5 リファクタリング戦略
 
インデックス・ページヘ  「BOOK Preview」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間