「新装版リファクタリング - 既存のコードを安全に改善する」を読んだ

f:id:ryskit:20190604221313j:plain

今、仕事で参加しているプロジェクトのJavaのコードは数年前のもので、昔に書かれた部分は、メソッド名が分かりづらかったり、処理を一つのメソッドに詰め込み過ぎていて可読性が低い状況である。(テストは結構書かれているから修正できる)

また、最初にアプリケーションを書いたエンジニアはすでにプロジェクトからは抜けており、どうしてそのコードを書いたのか意味を読み取れないこともあったりする。

僕自身、恥ずかしながら前職で汚いコードを書いてきた方だと思うし、今もキレイなコードを書けているかと言われればそうではないだろう(キレイに書く努力はしているつもりで、先輩方にPRで指摘もいただけるので助かっている)

そんな僕でさえ、ウッとなるコードがところどころ見かける。

この読みづらい・修正しづらいコードをリファクタリングしたいと思っていて、今後、プロジェクトの機能開発や修正スピードを上げたり、新しくプロジェクトに入ってくるメンバーが気持ちよくプロジェクトに参加してもらうためには、率先して僕がリファクタリングをしていくべきだと考えている。

そのリファクタリングの作業を良いものにしたくて、この本を手に取った。

最初の5章までは、リファクタリングについての説明から始まり、原則であったり、どういったコードが不吉な臭いを発しているのかなど、細かく説明している。

また、リファクタリングにはテストコードが不可欠であるということも、まるまる1章割いて説明している。

6章からはリファクタリングのカタログを眺めるように読むことができる。どういったときに、どのようなリファクタリングを行うのが良いのか、UMLの図を見て作業手順を読みながら学ぶことができる。

400ページ以上ある本ではあるが、サクッと読めるし、もしリファクタリングの際にヒントが欲しい場合はなんとなく目を通してからリファクタリングするのも良いかもしれない。

すぐに効き目が出るような本ではないと思うが、まだ読んだことがない人はぜひ一度読んでみて、リファクタリングのパターンを頭に入れておくのは良いと思う。

ノート

リファクタリングとは

  • リファクタリングとは、ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改善していく作業を指す
  • 実装したあとで、設計を改善する
  • リファクタリングによって、仕事の作業配分が変わってくる
    • 設計の作業が、最初の工程で集中的に発生するのではなく、全行程を通じて継続して行われるようになる

第1章 リファクタリング - 最初の例

  • 構造的に機能を付け加えにくいプログラムに新規機能を追加しなければならない場合には、まず機能追加が簡単になるようにリファクタリングをしてから追加を行うこと
  • リファクタする前にテストを用意せよ
  • コード内の論理的な固まりを見つけ出して「メソッドの抽出(p.110)」リファクタリングを適用する
  • リファクタリングをするときには、失敗するとどうなるかを常に把握しておく必要がある
    • 最初に、抜き出そうとする部分でローカルなスコープを持つ変数に着目し、それらが新規に作られるメソッドの一時変数かパラメータにならないか検討する
    • 変更されない変数については、パラメータとして渡すことができる
    • 変更される変数にはもっと注意を払う必要がある
  • リファクタリングでは小さなステップでプログラムを変更していく。そのため誤ったことをしても、バグを見つけるのは簡単である。
  • 名前変更の後には、何以下おかしなことを指定内科、コンパイルしてテストをして確認する
  • コンパイラが理解できるコードは誰にでも書ける。優れたプログラマは、人間にとってわかりやすいコードを書く
  • 仕様するデータを持つオブジェクトにメソッドは定義されるもの
  • 古いメソッドを、新しいメソッドへの委譲として残しておくこともある。これはメソッドがpublicで、’他のオブジェクトに対するインターフェースを変えたくないと金役立つ
  • 一時変数をできる限り取り除くようにする
    • 一時変数は、不必要なパラメータをたくさん受け渡してしまう原因になりがち
    • パラメータ数が多いと、なんのためのものなのかすぐにわからなくなる
  • switch分は他のオブジェクトの属性を調べるのではなく、自分自身について行うべき
  • Stateパターン
  • まとめ
    • メソッドの抽出
    • メソッドの移動
    • ポリモーフィズムによる条件記述の置き換え

第2章 リファクタリングの原則

  • リファクタリング(名詞)は、外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること
  • リファクタリングする(動詞)は、一連のリファクタリングを適用して、外部から見た振る舞いの変更なしに、ソフトウェアを再構築すること
  • リファクタリングの定義として強調する点
    • あくまでソフトウェアを理解しやすく、変更を用意にするために行う
    • 外的振る舞いを保ったまま、ソフトウェアに多くの変更を加えることは可能
    • 対照的なのは、パフォーマンスの最適化
      • リファクタリング同様に、大抵は振る舞いの仕方を返ることはなく、内部構造が書き換えられる
      • パフォーマンスチューニングでは、コードは理解しにくくなるのが普通
      • 必要なパフォーマンスを得るためには、そうせざるを得ない
    • ソフトウェアの外的振る舞いを保つ
  • ソフトウェア開発でリファクタリングを行うときには、2つの活動に区分すべき
    • 機能追加
      • 機能追加を行うときには、既存のコードを変更してはいけない
      • 機能拡張に専念する
      • 作業の進度は、テストの追加とそれが正常に終了したことによって測ることが可能
    • リファクタリング
      • リファクタリングをしているときには、機能追加は行わないようにする
      • コードの再構築のみ
      • 原則として、テストの追加をしてはいけない(機能追加の段階で漏れていた場合は例外)
  • 3度目の法則
    • 最初は単純に作業する
    • 2度目に以前と似たようなことをしていると気がついているときがついたときは、重複や無駄を意識しつつ作業を続行。
    • 3度目に同じことをしていると気づいたなら、そこでリファクタリング
  • 機能追加のときにリファクタリングを行う
  • バグフィックスのときにリファクタリングを行う
  • コードレビューのときにリファクタリングを行う
  • 何がプログラムを難しくするか
    • 読みにくいプログラムが変更しにくい
    • ロジックが重複しているプログラムは変更しにくい
    • 機能追加に伴い、既存のコード修正が必要になるプログラムは変更しにくい
    • 複雑な条件分岐の多いプログラムは変更しにくい
  • 間接層(indirection)
    • ロジックの共有を可能にする
    • 意図と実装を独立して説明できる
    • 変更を分離できる
    • 条件分岐をポリモーフィズムで表現する
  • オブジェクト指向の利点
    • ソフトウェアの実装とインターフェースを独立して変更できる
    • インターフェースを守ることは重要で、変更すれば何らかの影響が及ぶことになる
  • あまり早急にインターフェースを公布しないこと。スムーズなリファクタリングのために、時にはコードの所有権のポリシーを変えることも必要
  • リファクタリングには、設計を補完する役割がある
  • 速いソフトウェアを作る、一般的な3つの方法
    • 最も厳しい要求を実現するのが、時間分割を行うやり方
      • ハードリアルタイムシステムでよく使われる
      • 時間とメモリ使用量というリソース観点から、設計を細かなコンポーネントに分割する
        • 各コンポーネントは予め与えられたリソースの消費量を超えてはならない
        • 与えられた時間を交換するメカニズムが許されていても。
        • 心臓のペースメーカーなどのように、タイミングが遅れたらデータが役に立たないシステムにおいては不可欠な考え方
    • パフォーマンスを常に意識しておくやり方
      • すべてのプログラマがパフォーマンスを常に意識しておくというやり方
      • パフォーマンスで興味深いのは、プログラムを解析してみると、ほとんどの時間がごく一部の処理で集中的に消費されていること
    • パフォーマンスチューニングに、前述の90%の法則を使う
      • このやり方では、まずプログラムを整理された形で作り上げる
      • チューニングの段階で、一定の手順に従い、最適化を行う

第3章 コードの不吉な臭い

重複したコード

  • 同じようなコードが2箇所以上見られたら、1箇所にまとめることを考えると良い
  • 同一クラス内の複数メソッドに同じ式がある場合
    • 「メソッドの抽出」を行い、「メソッドの引き上げ」を適用すれば解決する
  • コードが完全に同じではなく似ている場合
    • 「メソッドの抽出」を使い、共通で使える部分とそうでない部分を分離する
    • 「Template Methodの形成(p.345)」に進んでいけることもある
  • 複数のメソッドが同じ処理を異なるアルゴリズムで実装している場合
    • 「アルゴリズムの取り替え(p.139)」を適用する
  • まったく関係ない2つのクラス間で重複したコードが見られるとき
    • 一方のクラスに対して「クラスの抽出」を行い、もう一方のクラスから新しいクラスへ処理を委譲するようにする
    • または、重複したコードを持つメソッドは、一方のクラスだけが本来持つべきであって、もう一方のクラスからはそれを呼び出すようにすべきかもしれない

長すぎるメソッド

  • メソッド名をわかりやすくする
  • コメントの必要を感じたとき、そうする代わりにわかりやすい名前をつけたメソッドに分割する
  • メソッド名には、内部でどのように処理しているかでなく、そのコードが何をするのかという意図を示すこと
  • 重要なのは、メソッド名の長さを切り詰めるのではなく、メソッド名とその実装との距離を埋めること
  • 「メソッドの抽出」は、メソッド名を短くするのに常に役に立つ
  • パラメータや一時変数が多すぎるメソッドは、メソッドの抽出を妨げる要因となる
  • 「問い合わせによる一時変数の置き換え(p.120)」を組み合わせて、一時変数を減らす必要がある
  • 長いパラメータには「パラメータオブジェクトの導入(p.295)」「オブジェクトそのものの受け渡し(p.288)」を使いスリム化すること
  • 「メソッドオブジェクトによるメソッドの置き換え」を試す

巨大なクラス

  • 1つのクラスが大きい場合、インスタンス変数を持ちすぎになっている
  • クラスがインスタンス変数すべて使っていない場合、「クラスの抽出」「サブクラスの抽出」の適用を何回か試す
  • コード量が多すぎるクラスは「重複したコード」の温床
    • クラスの重複部分を排除する

長すぎるパラメータリスト

  • メソッドの実行の必要なデータを、すべてパラメータで渡したりはしない
    • オブジェクトをそのまま渡し、メソッドがそこからさまざまなデータを取り出せば良い
  • パラメータの数が多いと、1つひとつが何を意味しているのか理解しづらくなる
    • パラメータの一貫性がなくなり、使いにくくなる

変更の偏り

  • 1つのクラスが別々の理由で何度も変更される状況では、「変更の偏り」が起こっている

変更の分散

  • 「変更の分散」は、「変更の偏り」に似ているが異なるもの
    • 変更を行うたびに、あちこちのクラスがすこしずつ書き換わるような場合、不吉な臭いと受け取った方が良い
    • 「メソッドの移動」や「フィールドの移動」を行い、変更部分を一つのクラスにまとめあげるようにする

特性の横恋慕

  • オブジェクト指向には、処理および処理に必要なデータを1つにまとめてしまうという重要な考え方がある
  • 通常、データとそのデータを扱う処理の変更の影響はいっしょに受ける
  • 例外としては、振る舞いのみの変更に対処するために外に出したほうが良い場合もある
    • Strategy、Visitorパターンを使うと振る舞いのみを簡単に変更していけるようになる

データの群れ

  • データの集まりから、ある要素を除外した場合を考えてみてください。
    • 残ったデータの集まりは意味をなすでしょうか。
    • 意味をなさない場合には、もとの集まりが一人前のオブジェクトの候補であったことを示しています。

怠け者クラス

  • 一度作成したクラスは、理解や保守のためのコストがかかる。
    • 十分な仕事をせず、その見返りに合わないようなクラスは排除するべき

仲介人

  • オブジェクト指向の特徴にカプセル化がある
    • 内部の詳細を外部から見えないようにする
  • カプセル化は権限委譲をもたらす

第4章 テストの構築

  • リファクタリングに限らず、よいテストを書くとプログラミングが加速する
  • テストを完全に自動化して、その結果もテストにチェックさせること
  • テストをひとそろいにしておくと、バグの検出に絶大な威力を発揮する。
    • これによって、バグの発見にかかる時間は削減される
  • リファクタリングにはテストは必須
    • リファクタリングするにはテストを書かなければならない
  • テストフィクスチャ(test fixture)
    • テストデータとなるオブジェクトのこと
  • テストを頻繁に実行せよ。コンパイル時にはテストを局所化して、1日に最低一度はすべてのテストを実行せよ
  • テストしたくないという誘惑に屈してはいけない
    • 報いは必ず訪れる
  • テストを書く際は、はじめは失敗するようにしておく
    • 既存のコードであれば、失敗するように変更するか、予想される値として正しくないものを表明メソッドに与える
    • 本当にテストが実行され、期待通りのテストをしているか確認するため
  • バグレポートを受け取ったら、まずそのバグを明らかにする単体テストを書け
  • 実行されない完全なテストよりも、実行される不完全なテストの方がましである
  • 失敗すると予想されるときに、例外が上がることをテストし忘れないこと

第6章 メソッドの構成

  • メソッドを適切にパッケージ化されたコードとして構成すること
  • 問題を起こすのは、ほとんどの場合、長すぎるメソッドである
  • 「メソッドの抽出」では、ローカル変数の扱いが最大の問題であり、一時変数がこの問題の主たる発生源
    • あるメソッドについて作業するときは、「問い合わせによる一時変数の置き換え」によって取り除ける一時変数はすべて取り除いておきたいところ
  • 一時変数があまりにも絡み合っていて置き換えられない場合、「メソッドオブジェクトによるメソッドの置き換え」が必要

メソッドの抽出

  • コードの断片をメソッドにして、それに目的を表す名前を付ける
  • 長過ぎるメソッドやコメントがなければその木亭が理解できないメソッドを目にしたときは、その断片をメソッドにする

メソッドのインライン化

  • メソッドの本体が名前をつけて呼ぶまでもなく明らかである
  • わざわざ名前をつけて呼ぶほどでもないようなメソッドにリファクタリングしてしまうこともある
    • メソッドを取り除いてしまう
  • 手順
    • そのメソッドがポリモーフィックでないことを確認する
      • サブクラスでオーバーライドしているメソッドをインライン化しないこと。メソッドがなくなるとオーバーライドできなくなる
    • そのメソッドの呼び出しをすべて検索する
    • 各メソッド呼び出しをメソッド本体で置き換える
    • コンパイルしてテストする
    • メソッド定義を取り除く

一時変数の分離

  • 「パラメータに代入する」とは、パラメータとして渡ってきたオブジェクトを別のオブジェクトを参照させるように変更すること
    • 渡されたオブジェクトになにかをすることは問題ない
    • まるごと別のオブジェクトを参照させるように変更するのはだめ
  • 明確さの欠如および「値渡し」と「参照渡し」の間の混乱がこれを嫌う
  • 本質的に、オブジェクトへの参照は値渡し

メソッドオブジェクトによるメソッドの置き換え

  • 大きなメソッドから小さなメソッドを抽出することで、ものごとが把握しやすくなる

第7章 オブジェクト間での特性の移動

  • オブジェクトの設計において根幹をなすのは、唯一とまではいかなくとも、責務をどこに配置するかについての判断である
    • Martin Fowlerでさへ、10年以上オブジェクトを生業としてきたが、責務をはじめから正しいところに配置できない
  • 責務が多すぎてクラスが膨張することもある
    • 「クラスの抽出(p.149)」を適用して、それらの責務を分離する
  • クラスの責務が希薄すぎる場合は、「クラスのインライン化(p.154)」を適用して、他のクラスに併合する
  • あるクラスが他のクラスを使っているとき、「委譲の隠蔽(p.157)」によって、そのことを隠すのが有用なことがある

メソッドの移動

  • あるクラスでメソッドが定義されているが、現在または将来において、そのクラスの特性よりも他クラスの特性の方が、そのメソッドを使ったり、そのメソッドから使われることが多い
  • あるクラスのメソッドをすべて眺めて、それが定義されているオブジェクトよりも、それ以外のオブジェクトを参照することが多いメソッドを探す
    • 特にフィールドを移動したあとは、これを行うほうが良い

フィールドの移動

  • あるクラスに定義されているフィールドが、現在または将来において、定義しているクラスよりも、他のクラスから使われることの方が多い
  • クラス間で状態や振る舞いを移動するのは、リファクタリングの本質
  • フィールドを移動すると考えるのは、そのクラスのメソッドよりも別クラスメソッドの方がそのフィールドを多く使っているとわかったとき

クラスの抽出

  • 2つのクラスでなされるべき作業を1つのクラスで行っている
    • クラスを新たに作って、適切なフィールドとメソッドを元クラスからそのクラスに移動する
  • 「クラスの抽出」は、並行プログラミングの実行性を向上するための一般的な技法

委譲の隠蔽(p.157)

  • クライアントがあるオブジェクトの委譲クラスを呼び出している
    • サーバにメソッドを作って委譲を隠蔽する
  • カプセル化は、オブジェクト指向技術の鍵となる
  • 委譲オブジェクトが変更されると、クライアントの変更も余儀なくされる
    • この依存関係を取り除くには、委譲を隠蔽するための単純なメソッドを配置する

仲介人の除去

  • クラスがやっているのは単純な委譲だけである場合
    • クライアントに委譲オブジェクトを直接呼ばせるように修正する

外部メソッドの導入

  • 利用中のサーバクラスにメソッドを追加する必要があるが、そのクラスを変更できない
    • クライアントクラスに、サーバクラスのインスタンスを第1引数に取るメソッドを作る

局所的拡張の導入

  • 利用中のサーバクラスにメソッドをいくつか追加する必要があるが、クラスを変更できない場合
    • それらの追加されるメソッドを備えた新たなクラスを作る。この拡張クラスは、元のクラスのサブクラスまたはラッパーである
  • メソッドを追加できるのであれば最善であるが、変更できない場合は、サブクラスやラッパークラスを作る
  • サブクラスかラッパーにするかは、martin fowlerはサブクラスを好む
    • 作業が少なくて済むから
  • ラッパーの場合は委譲を使う

データの再編成

自己カプセル化フィールド(p.171)

  • フィールドを直接アクセスしているが、そのフィールドとの結合関係が煩わしい場合
    • そのフィールドに対するgetメソッドとsetメソッドを作って、それだけを使ってアクセスするように変更する
  • 「自己カプセル化フィールド」を行う重要なタイミングは、スーパークラスのフィールドをアクセスしていて、その変数アクセスをサブクラス内の計算値で置き換えたいと思ったとき

オブジェクトによるデータ値の置き換え

  • 追加のデータや振る舞いが必要なデータ項目がある場合
    • そのデータ項目をオブジェクトに変える
  • コードが重複してきたり、特性の横恋慕が臭ってきた場合には、データ値をオブジェクトに変更する

値から参照への変更

  • 同じインスタンスが多数存在するクラスがある。それらを1つのオブジェクトに置き換えたい場合
    • そのオブジェクトを参照オブジェクトに変える
  • 参照オブジェクトと値オブジェクトを分けて考えることが役立つ
  • 参照オブジェクト
    • 顧客や勘定といったもので、実世界における1個のオブジェクトを表す
      • それらが同じかどうかはオブジェクト識別が用いられる
  • 値オブジェクト
    • 日付やお金のようなもの
    • それ自身のデータ値によって定義される
      • コピーはいくらあっても構わない

参照から値への変更

  • 小さくて、不変で、コントロールが煩わしい参照オブジェクトがある場合
    • 値オブジェクトに変える
  • 参照オブジェクトを使った処理が煩わしくなってきたら、それは参照を値に変更するきっかけ
  • 値オブジェクトは、特に分散並行処理システムで有効
  • 値オブジェクトの重要な性質
    • 不変であること
    • そのオブジェクトについて問い合わせを行ったとき、常に同じ値が返されるべき

オブジェクトによる配列の置き換え

  • 配列の各要素がそれぞれ違う意味を持っている場合
    • その配列を、要素ごとに対応したフィールドを持つオブジェクトに置き換える

コレクションのカプセル化

  • メソッドがコレクションを返している
    • 読み取り専用のビューを返して、追加と削除のメソッドを提供する
  • getメソッドはコレクションオブジェクトそのものを返すべきではない
    • コレクションを所有するクラス側で、何が行われているのか知らないうちに、クライアントがそのコレクションの内容を操作できてしまう
  • 振る舞いをクラスに移動する

条件記述の分解

  • 複雑な条件記述(if-then-else)がある
    • その条件記述部とthen部およびelse部から、メソッドを抽出する
  • どんなに長いコードブロックでも、それを分解することで、そしてそのブロックにふさわしい名前を持ったメソッドを呼び出しで、一群のコードを置き換えることで、プログラマの意図を明確化できる

条件記述の統合

  • 同じ結果を持つ一連の条件判定がある場合
    • それらを1つの条件記述にまとめて抽出する
  • 一連の条件判定が全て異なっているのに、結果のアクションが同じである場合がときどきある
    • この場合は、andやorを使って結合して、同じ結果を持つ1つの条件判定にする

重複した条件記断片の統合

  • 条件式のすべての分岐に同じコードの断片がある場合
    • それを式の外側に移動する

ガード節による入れ子条件記述の置き換え

  • メソッド内に正常ルートが不明確な条件つき振る舞いがある場合
    • 特殊ケースすべてに対してガード節を使う

ポリモーフィズムによる条件記述の置き換え

  • オブジェクトのタイプによって異なる振る舞いを選択する条件記述がある場合
    • 条件記述の各アクション部をサブクラスでオーバーライドするメソッドに移動する。元のメソッドはabstractにする
  • ポリモーフィズムの真髄は、オブジェクトの振る舞いがその型によって変わるとき、明示的な条件記述を書かなくても良いようにする

第10章 メソッド呼び出しの単純化

  • 理解が容易で、使いやすいインターフェースを提供すること
  • 状態を更新するメソッドと、状態を問い合わせるメソッドは明確に分離すること
  • 良いインターフェースは、何をすべきかだけを示し、それ以上は何も語らない
    • 内部の詳細を隠蔽することで、インターフェースは改善される

メソッド名の変更

  • メソッドの名前がその目的を正しく表現できていない場合
    • メソッド名を変更する
  • 複雑な処理を小さく分解して、小さなメソッドの集まりにする
  • あなたの書くコードは、第一に人間のためのものであり、コンピューターはその次であることを忘れてはならない

パラメータの削除

  • あるパラメータが、もはやメソッド本体から使われていない場合
    • パラメータを削除する
  • パラメータは必要な情報を指示するもの
  • 値が異なれば、結果も変わるべき
  • 呼び出し側は、そのパラメータにどんな値を渡すのかを考える必要がある
    • パラメータを削除しなければ、そのメソッドを使うすべての人に余計な仕事をさせることになる

問い合わせと更新の分離

  • 1つのメソッドが値を返すと同時にオブジェクトの状態を変更している場合
    • 問い合わせ用と更新用の2つのメソッドをそれぞれ作成する
  • 値を返すメソッドはすべて、副作用を持たないと決めることが大切
  • 値を返すと同時に、副作用も伴うようなメソッドは分離するように努める

メソッドのパラメタライズ

  • 複数のメソッドがよく似た振る舞いをしているが、それはメソッド内部にもつ異なる値に基づいている場合
    • その異なる値をパラメータとして受け取るメソッドを一つ作成する

オブジェクトそのものの受け渡し

  • あるオブジェクトから複数の値を取得し、それらの値をメソッド呼び出しのパラメータとして渡している場合
    • 代わりにオブジェクトそのものを渡す
  • 依存関係の構造を乱すのであれば、「オブジェクトそのものの受け渡し」を適用すべきではない

メソッドによるパラメータの置き換え

  • あるオブジェクトがメソッドを呼び出し、その戻り値を別のオブジェクトのパラメータとして渡している。そのメソッドは受信側でも呼び出すことができる場合
    • パラメータを削除し、受信側にそのメソッドを呼び出させる
  • もしメソッドが、パラメータで渡される値を別の方法で取得できるならば、そのように修正すべき

パラメータオブジェクトの導入

  • 本来まとめて扱うべきひとかたまりのパラメータがある場合
    • それらをオブジェクトに置き換える
  • 開始日・終了日、上限値・下限値といった、範囲を示す一組の値は、Rangeパターン[Fowler AP]といったものを代わりに使うようにする

Factory Methodによるコンストラクタの置き換え

  • オブジェクトを生成する際に、単純な生成以上のことをしたい場合
    • ファクトリメソッドを使って、コンストラクタを置き換える
  • 「Factory Method によるコンストラクタの置き換え」に着手する最もわかりやすい動機は、 サブクラス化することによってタイプコードを置き換えたい場合

ダウンキャストのカプセル化

  • メソッドが返すオブジェクトを、呼び出し側でダウンキャストする必要がある場合
    • ダウンキャスト処理をメソッド内に移動する
  • ダウンキャストはやむを得ないかもしれないが、できる限り少なくするべき

例外によるエラーコードの置き換え

  • エラーを示す特別なコードをメソッドがリターンしている場合
    • 代わりに例外を発生させる

条件判定による例外の置き換え(p.315)

  • 例外を発生させているが、本来は呼び出し側が先にチェックすべきである場合
    • 最初に条件判定をするように呼び出し側を修正する

継承の取り扱い

フィールドの引き上げ

  • 2つのサブクラスが同じフィールドを持っている
    • そのフィールドをスーパークラスに移動する
  • 重複しているか調べる方法は、フィールドを調べて、他のメソッドからどのように使われているか理解すること
    • 同じように使われていれば、汎化できる

コンストラクタ本体の引き上げ

  • 複数のサブクラスに内容がほとんど同一のコンストラクタがある場合
    • スーパークラスのコンストラクタを作成して、サブクラスから呼び出す
  • このリファクタリングが複雑になる場合は、Factory Methodによるコンストラクタの置き換え(p.304)を検討する

サブクラスの抽出

  • あるクラスの特定のインスタンスだけに必要な特性がある場合
    • その一部の特性を持つサブクラスを作成する
  • サブクラスの抽出の有力な代替案としては、「クラスの抽出」がある
    • これは委譲と継承のどちらを使うかの選択
  • 一般的には「サブクラスの抽出」の方がより簡単で すが、これには制􏰂があります。オブジェクトがいったん生成されたあとは、クラスベース訳注(class-based)の振る舞いを変えることはできません。「クラスの抽出(p.149)」を行えば、異 なるコンポーネントを差し替えるだけで、クラスベースの振る舞いを簡単に変更できます。ま たサブクラスを使う場合は、1 種類のバリエーションだけしか表現できません。複数の異なる 方法でクラスを変化させたい場合には、1 つを除いた残りすべてに対して委譲を使う必要があります。

スーパークラスの抽出

  • 似通った特性を持つ2つのクラスがある場合
    • スーパークラスを作成して、共通の特性を移動する
  • 代替案として「クラスの抽出(p.149)」もあります。これは本質的には、継承と委譲の選択 です。継承は、2 つのクラスがインタフェースに加えて振る舞いも共有している場合に簡単な 選択となります。間違った選択をした場合には、あとで「委譲による継承の置き換え(p.352)」 を適用できます。

インターフェースの抽出

  • 複数のクライアントが、あるクラスのひとかたまりのインターフェースを使っている。または2つのクラス間でインターフェースの一部が共通である場合
    • その共通部分をインターフェースとして抽出する
  • 「インタフェースの抽出(p.341)」は共通のインタフェースを抽出しますが、共通の コードは抽出しません。そのため「インタフェースの抽出(p.341)」は、「重複したコード」の 臭いを引き起こしがちです。この問題は、「クラスの抽出(p.149)」を適用し、振る舞いをコン ポーネント化して委譲することで緩和できます。もし共通の振る舞いがかなりの量になるなら ば「スーパークラスの抽出(p.336)」の方が簡単ですが、スーパークラスは 1 つだけしか宣言 できません。

階層の平坦化

  • スーパークラスとサブクラスにさほど大きな違いがない場合
    • それらを合わせてまとめる

Template Methodの形成(p.345)

  • 異なるサブクラスの2つのメソッドが、類似の処理を同じ順序で実行しているが、各処理は異なっている場合
    • 元のメソッドが同一になるように、各処理を同じシグネチャのメソッドにする。そしてそれらを引き上げる

委譲による継承の置き換え

  • サブクラスがスーパークラスの一部のインターフェースだけを使っている、あるいはデータを継承したくない場合
    • スーパークラス用のフィールドを作成して、メソッドをスーパークラスに委譲するように変更した上で、継承をやめる。

継承による委譲の置き換え

  • 委譲を使っていて、すべてのインターフェースに対する単純な委譲をたくさん書いている場合
    • 委譲元のクラスを、委譲先のクラスのサブクラスにする
  • まず、委譲先の クラスのメソッドをすべて使っているわけではない場合、「継承による委譲の置き換え」を適用すべきでない
    • サブクラスはスーパークラスのすべてのインタフェースには常に従うべきだから
  • 「仲介人の除去(p.160)」を適用して、クライアント自身に委譲呼び出しを行わせることもできる
  • また「スーパークラスの抽出(p.336)」を適用して、共通のイン タフェースを分離し、その新しいクラスから継承する方法もあります。同様に「インタフェー スの抽出(p.341)」も適用できる

第12章 大きなリファクタリング

継承の切り離し

  • 1つの継承階層で、2つの仕事をまとめて行っている場合
    • 2つの継承階層を作成し、委譲を使って一方から他方を呼び出す
  • 2つの仕事をしている1つの継承階層を特定することは容易
    • もし階層の特定レベルで、すべてのサブクラスが同じ形容詞から始まる名前のサブクラスを持っていたら、1つの階層で2つの仕事をしている可能性が高い
  • 一度に1ステップずつリファクタリングをするほうが、10ステップ先のすでに単純化されたデザインに飛びつくよりも安全

第15章 部品から全体へ

  • リファクタリングは学習可能な技法
    • 目標の選別に慣れる
      • 不吉な臭いの元を断ち、問題を取り除いて、目標に進む
      • プログラムを分かりやすくするもの
    • 自信がなくなったらやめる
      • 目標に進んでいくと、自分のしていることがプログラムのセマンティックを保っていると、自分や他人にきちんと説明できなくなる
        • そこがやめ時
    • 引き返す
      • 構成が正しいことがわかっている最新のところまで戻る
      • 再度、1つずつ変更し、変更が終わるたびにテストを行う
    • デュエットで
      • 誰かと組んでやってみる