# リファクタリング 第7章、第8章 ## 第7章 カプセル化 カプセル化とは、変数やデータ構造等のプライベートな性質のものを隠蔽し、外部から変更不可能にする仕組みである。 予期せぬ変数やデータ構造の変更を防ぐために必須となるリファクタリングである。 ### レコードのカプセル化 レコードは、関連するデータをグループ化したものである。意味のあるデータ単位を渡すことができる。 こうしたレコードには、格納されている値と計算値の明確な区別が難しい。 カプセル化を行う事で、データの予期せぬ変更を防ぐほか、各値すべてに対するメソッドを用意できるメリットがある。 #### 手順 1. レコードを保持する変数に「**変数のカプセル化**」を施す。 2. 変数の中身を、レコードをラップする簡単なクラスに置き換える。 3. レコードをそのまま返すアクセサを追加する。 4. 呼び出し側にアクセサを設定し、テストする。 5. オブジェクトを返すアクセサを追加する。 6. 呼び出し側のアクセサをオブジェクトを返すものに置き換えてテストする。 7. レコードをそのまま返すアクセサと、生データのアクセサを削除してテストする。 8. (+α)レコードのフィールド自体が構造体の場合は、「レコードのカプセル化」と「コレクションのカプセル化」を再帰的に施すことを検討する。 ### コレクションのカプセル化 コレクションをgetterでそのまま返してしまうと、クラスを介さずにデータが変更される恐れがある。 そこで、コレクションをそのまま返すのではなく、そのクラスの特定のメソッドを通じて行う。 #### 手順 1. コレクションの参照がまだカプセル化されていなければ「**変数のカプセル化**」を施す。 2. 要素を追加・削除するための関数をコレクションに追加する。 3. 静的チェックを実行する。 4. 全てのコレクションの参照を探す。変更メソッドの場合、新たな追加・削除メソッドを使うようにすべて置き換える。 5. getterを変更して「保護されたビュー」を返すようにする。 6. テストする。 ### オブジェクトによるプリミティブの置き換え プリミティブ(基本データ型)は、データ中に何度も型が変わることが多い。 急速に重複していく傾向があるため、こうしたデータはオブジェクトで保存するのが良い。 値の振る舞いを定義するオブジェクトを作ることで、型や数値にとらわれない呼び出しが可能となる。 #### 手順 1. 「**変数のカプセル化**」ができていないならば施す。 2. データ値のための単純な値クラスを作る。既存の値をコンストラクタで受け取り、値を返すgetterを用意する。 3. 静的チェックを実行する。 4. 値クラスのインスタンスを作るようにsetterを変更し、インスタンスフィールドに値を格納する。必要に応じてその型を変える。 5. getterを変更して、値クラスのgetterを呼び出し結果を返すようにする。 6. テストする。 7. (+α)処理内容がより分かるように、元のアクセサに「関数名の変更」の適用を検討する。 8. (+α)新たなオブジェクトの役割が値オブジェクトなのか、参照オブジェクトなのかを明確にすることを検討する。 ### 問い合わせによる一時変数の置き換え 一時変数を利用した式を、関数呼び出しに変更するリファクタリングである。 値の計算の繰り返しを防ぎ、抽出されたロジックと元の関数との間に明確な境界が築かれる。 厄介な依存性や副作用を特定でき、避けられるようになる。 #### 手順 1. 一時変数の値は、利用される前に確定していることを確認する。また、それを計算するコードはいつ使っても常に同じ結果になることを確認する。 2. 一時変数を可能な限り読み取り専用(const)にする。 3. テストする。 4. 一時変数への代入を関数として抽出する。副作用がある場合は「問い合わせと更新の分離」を施す 5. テストする。 6. 「変数のインライン化」を実施し、一時変数を取り除く。 ### クラスの抽出 クラスは処理を追加するごとに大きくなりがちである。 こうしたクラスを役割ごとに切り出すのがクラスの抽出である。 #### 手順 1. クラスの責務をどのように切り出すかを決める。 2. 切り出した責務を記述するための新たな子クラスを作る。 3. 元の親クラスのインスタンスを生成する時に、新たな子クラスのインスタンスも作り、親インスタンスから子インスタンスへのリンクを加える。 4. 移動したい各フィールドに「フィールドの移動」を施す。移動の度にテストする。 5. メソッドを子クラス側に移動するために「関数の移動」を施す。低レベルのメソッドから着手する。移動の度にテストする。 6. 両クラスのインターフェースを見直す。不要なメソッドを取り除き、新たな状況にふさわしい名前に変更する。 7. 新たな子クラスのインスタンスを公開するかどうか決める。公開するなら、「参照から値への変更」を新たなクラスに施すことを検討する。 ### クラスのインライン化 クラスの抽出の逆。責務がほとんど残っていない場合、その責務を別のクラスに畳み込んでクラスを消滅させるのが良い。 また、リファクタリングしてクラスの特性の配置を変えたい場合には、一度インライン化でまとめてから抽出する方が良い。 #### 手順 1. インライン先のクラスに、元のクラスのすべてのpublic関数に対応するクラス委譲メソッドを作る。 2. 元のクラスのメソッドへの参照を全て変更し、インライン先のクラス委譲メソッドを使うようにする。 3. 全ての関数とデータを元のクラスからインライン先に移す。一つ移すごとにテストする。 4. 元のクラスを削除する。 ### 委譲の隠蔽 カプセル化はフィールドだけでなく、オブジェクトを隠蔽することもできる。 委譲用のメソッドを作り、委譲先のオブジェクトを隠蔽することで、委譲先のオブジェクト変更の影響を抑えることができる。 #### 手順 1. 委譲先のオブジェクトの各メソッドに対応する、単純な委譲用メソッドをサーバオブジェクトに作る。 2. クライアントを修正し、サーバオブジェクトの委譲メソッドを呼ぶようにする。変更の度にテストする。 3. クライアントで委譲先のオブジェクトへのアクセスが必要な個所が無くなったら、移譲先のオブジェクトのアクセサをサーバオブジェクトから取り除きテストする。 ### 仲介人の除去 隠蔽のための委譲用のメソッドに任せすぎてしまうと、サーバオブジェクトがただの仲介人になってしまう。 こうした場合は仲介人を除去し、移譲先のオブジェクトが直接呼ぶ方が良い。 #### 手順 1. 委譲先のオブジェクトを取得するgetterを作る。 2. 委譲メソッドを使用するクライアントごとに、委譲メソッドの呼び出しを、getterから始まるメソッドチェーンで置き換える。変更の度にテストする。 ### アルゴリズムの置き換え 冗長なアルゴリズムをより簡単にしたり、重複が見られたりする時に用いるリファクタリングである。 この処理を行う前に、なるべくメソッドを分解しておくことが必要である。 #### 手順 1. 完結した機能だけを果たすように置き換え前のコードを整える。 2. この機能だけを使ったテストを用意して、その振る舞いを把握する。 3. 代わりのアルゴリズムを用意する。 4. 静的チェックを実行する。 5. テストを実行して、古いアルゴリズムと新たなアルゴリズムの出力結果を比較する。それらが同じであれば終了する。違えば、古いアルゴリズムを比較対象としてテストとデバッグを行う。 ## 第8章 特性の移動 ### 関数の移動 関数の移動は、関連するソフトウェア要素をグループにまとめて、それらの結びつきを簡単に特定して把握するために用いられる。 また、呼び出し元に移動させたり、次の拡張時に呼び出したい場所に移動することもある。 #### 手順 1. 移動対象の関数が、現在のコンテキストで使用しているプログラム要素を全て調べる。これらも移動するべきかを検討する。 2. 選択した関数がポリモーフィズム系メソッドかを確認する。 3. 関数を移動先のコンテキストにコピーする。移動先に関数が適合するように調節する。 4. 静的チェックを実行する。 5. 元のコンテキストから移動後の関数を参照できるようにする。 6. テストをする。 7. 元の関数に対して「**関数のインライン化**」の適用を検討する。 #### 例 * 入れ子の関数の、トップレベルへの移動 * クラス間での移動 ### フィールドの移動 データ構造はコードの簡潔さを決める重要なテーマである。 しかし、ある時点で優れたデータ構造であっても、拡張後のある時点では不適切ということもある。 データ構造が正しくないと分かった時点で、すぐに変更することが重要である。 #### 手順 1. 移動元のフィールドをカプセル化してテストする。 2. 移動先にフィールド(およびアクセサ)を作成する。 3. 静的チェックを実行する。 4. 移動元のオブジェクトから移動先のオブジェクトを参照できるようにする。 5. 移動先のフィールドを使うようにアクセサを調整する。 6. テストをする。 7. 移動元のフィールドを削除してテストする。 ### ステートメントの関数内への移動 特定の関数を呼び出すたびに同じコードが実行されている場合、反復コードを関数自体に組み込む方が良い。 関数の呼び出し先の一部とみなす方が理解しやすい場合に実行される。 #### 手順 1. 反復コードが移動先の関数呼び出しに隣接していない場合は、「ステートメントのスライド」を行って隣接させる。 2. 移動元の関数の呼び出し元が、移動先の関数だけであった場合は、移動元の関数から切り取り、移動先の関数に張り付けてテスト行い完了となる。 3. 2.でない場合(複数の呼び出し元がある場合)は、いずれかの呼び出しに対して「関数の抽出」を行い、移動先の関数呼び出しと移動したいステートメントを抽出する。名前は簡潔かつ一時的なものにする。 4. 他のすべての呼び出しを変更して、新しい関数に置き換える。変更の度にテストをする。 5. 元の呼び出しのすべてで新しい関数を使うようになったら、「関数のインライン化」を施す。 6. 元の関数を削除する。 7. 「関数名の変更」を行って、新しい関数の名前を元の関数と同じ名前に変更する。 ### ステートメントの呼び出し側への移動 関数の抽象化の境界線が、いつの間に変わっていたり、異質なものが混ざっている場合がある。 小さな変更に対して、境界線を変化させ、呼び出し側で実行したい場合に用いる。 #### 手順 1. 呼び出し元1つor2つで、呼び出される関数が単純な場合は、その関数から最初の行を切り取り、呼び出し側に張り付けてテスト行い、完了となる。 2. 1.でない場合(呼び出し側が多い場合)は、移動したくないすべてのステートメントに「関数の抽出」を行う。名前は簡潔かつ一時的なものにする。 3. 元の関数に対して、「関数のインライン化」を施す。 4. 「関数名の変更」を行って、名前を元の名前に変更する。 ### 関数呼び出しによるインラインコードの置き換え 関数呼び出しにより、インラインコードと同じ内容の処理を置き換える。 #### 手順 1. 既存の関数呼び出しで、インラインコードを置き換える。 2. テストする。 ### ステートメントのスライド 同じデータ構造にアクセスするコードが複数行ある場合、他のデータ構造にアクセスするコードと混在させずにまとめるべきである。 アクセスするコードから離れた位置にある場合に、スライドさせるリファクタリングである。 #### 手順 1. コードの断片先を特定する。 2. 移動元と移動先の間のステートメントを調べて、コード断片により干渉が起きないかを確認する。 3. ソースからコード断片を切り取り、移動先に張り付ける。 4. テストする。 #### 例 * 関数の抽出を行う場合 * 条件文コードの重複を取り除く場合 ※次の場合はスライドできない。 * コード断片が参照する要素の宣言より前にスライドする場合。 * コード断片が参照する要素より後にスライドする場合。 * コード断片が参照する要素を変更するステートメントを超えてスライドする場合。 ### ループの分離 1つのループ内で複数の異なる処理を行っている場合、変更時は異なる処理全てに影響を及ぼす。 こうした場合、ループを分離し、1ループ1処理を実現すべきである。 リファクタリングと最適化は分離して考え、パフォーマンスに影響が出る場合は後で戻せばよい。 #### 手順 1. ループをコピーする。 2. 重複による副作用を特定して排除する。 3. テストする。 4. (+α)ループ部分に「関数の抽出」を施した上で、「**パイプラインによるループの置き換え**」の実施を検討する。 ### パイプラインによるループの置き換え **パイプライン**を利用することで、繰り返しを排除し、上から下にコードを読むことが可能になる。 JavaScriptの一般的なパイプラインとして、**.map(型変換)** と、**.filter(条件指定)** がある。 #### 手順 1. ループ処理のコレクション用に、新しい変数を作成する。(既存の変数をコピーする) 2. ループ内の各処理を上から一つずつ取り出し、コレクションのパイプライン操作に置き換えて、ループ処理のコレクション変数を加工するパイプラインに付け加える。 3. 変更の度にテストする。 4. 全ての処理をループから削除できたら、ループを取り除く。 ### デッドコードの削除 不要になったコードや、定義したが用いられていないコードは、コンパイルの手間がかかるだけで邪魔なため削除する。 #### 手順 1. 外部から参照できる場合は、その呼び出しが残っていないことを確認する。(独立した関数の場合等) 2. デッドコードを削除する。 3. テストする。 ###### tags: `読書`