「リファクタリング」の完全読破。その3
帰省などで間があきましたが、今回は第3章。8月中の読破を目標に。
第3章「コードの不吉な匂い」
テーマは、いつリファクタリングを始めていつ終えるか。
(P76) ここではリファクタリングの必要を示す不吉な兆候について説明していきます。 インスタンス変数はいくつ以上になれば多過ぎであり、メソッドは何行以上で長過ぎるかなどの感覚は、自ら養っていかねばなりません。
他に「経験で磨かれた人間の直感には、メトリックスをいくら集めてもかなわない」とも書かれており、経験の重要さが強調されている。
各項目はそれぞれ半ページから2ページで文量は少ないが、引用されている手法を確認しながら読むとかなり時間がかかる。
本エントリは後から見直せるように、見出しと簡単な説明を書いていく。
重複したコード
同一クラス内の複数メソッドに同じ式がある場合。
完全に同じでなく似通っている場合は、共通に使える部分とそうでない部分を分離し、TemplateMethodの形成(P345)を検討する。
複数のメソッドが、同じ処理を違うアルゴリズムで実装していた場合は、アルゴリズムの取り替え(P139)を適用する。
関係の無い二つのクラス間で、重複したコードがある場合は、クラスの抽出(P149)を行い処理を委譲する。
長すぎるメソッド
メソッドが長くて、ソースを追わないと何をしているかわからない場合。
メソッドの分割時に問題になるのは引数や一時変数が多いメソッドで、先に以下の手法を使ってスリム化を行う。
「問い合わせによる一時変数の置き換え(P120)」
「引数オブジェクトの導入(P295)」
「オブジェクトそのものの受け渡し(P288)」
「メソッドオブジェクトによるメソッドの置き換え(P135)」
条件分岐やループも「条件記述の分解(P238)」を使って抽出を検討する。
巨大なクラス
インスタンス変数を持ち過ぎたクラス、コード量の多いクラスは「重複したコード」の温床。
多すぎる引数
オブジェクトをそのまま渡し、メソッドがさまざまなデータをそこから取り出せば良い。
変更の発散、変更の分散、パラレル継承
設計は、一つの変更要求(DBの変更や商品追加など)に対して、1つのクラスの1箇所が常に修正対象となるようにする。
以下のような状態になったらリファクタリングを行うべき。
変更の発散:一つの変更に対して同一クラス内の複数のメソッドを少しずつ修正しなければならない。
変更の分散:一つの変更に対して複数のクラスを何度も修正しなければならない。
パラレル継承:一つの変更に対して複数の継承木にそれぞれサブクラスを作らなければならない。
属性、操作の横恋慕
見出しの翻訳が良いなと思う。
あるメソッドが他のオブジェクトのgetメソッドを何度も何度も呼び出している場合、メソッドの位置が悪い。
ただし、StrategyパターンとVisitorパターン(と、Self Delegationパターン)など例外はある。
上記のパターンは変更に必要な部分(この場合は振る舞い)を1か所にしているので、パターンを優先する。
データの群れ
「数個のデータがグループとなって」属性やシグニチャに頻繁に現れる場合は、オブジェクトとしてまとめてみる。
基本データ型への執着
基本データ型ではなくオブジェクトを使う。
例えば「電話番号」を最初に文字列で定義し、後から頻繁にフォーマット変更などの振る舞いが必要になった場合、オブジェクトにすると良い。
スイッチ文
スイッチ文を見たらポリモーフィズムを使って解決できないか考える。
1章で行った「State/Strategyによるタイプコードの置き換え(P227)」や、null値で分岐している場合の「ヌルオブジェクトの導入(P260)」など。
怠け者クラス
十分な仕事をしないクラスは排除する。「階層の平坦化(P344)」、「クラスのインライン化(P154)」など。
疑わしき一般化
「いつかこの機能が必要になるさ」と凝った仕掛けを作った場合。例えば、あるクラスやメソッドがテストケースでのみ利用されている場合など。
一時的属性
インスタンス変数の値が特定の状況でしか設定されない場合。クラスとして切り出すか、ヌルオブジェクトを使う。
メッセージの連鎖
クライアントがオブジェクトにメッセージを送り、そのオブジェクトが更に別のオブジェクトにメッセージを送り、更に……という場合。
getHoge()のような呼び出しが長い行となって連なっていたり、一時変数が非常にたくさん定義されているとあやしい。
仲介人
メソッドの大半が別のオブジェクトに委譲しているだけのクラスは除去する。
不適切な関係
これも翻訳が良い。見出しも本文↓も。
仲の良すぎるクラスは、いにしえの恋人たちのように、遠くに離してあげねばなりません。
この場合は「双方向関連の単方向への変更(P200)」、「以上による継承の置き換え(P352)」を検討する。
クラスのインタフェース不一致
処理が同じでシグニチャのみが異なるメソッドは「メソッド名の変更」を行う。
(見出しと説明文を読んでも、イマイチ何が言いたいのか理解できない。どういう場合?)
未熟なクラスライブラリ
再利用はオブジェクト指向の目的とよく言われるが、万能なクラスライブラリというのは非常に難しいもの。
既存のクラスライブラリに追加したいメソッドがある場合は「外部メソッドの導入(P162)」、「局所的拡張の導入(P164)」を行う。
データクラス
get,setメソッド以外に何も持たないクラスに、利用しているクラスから振る舞いを移せないか検討してみる。
接続拒否
サブクラスが親の属性と操作の一部しか利用していない場合、継承階層が間違っているというのが伝統的な見方になる。
兄弟となるクラスを新たに作成し、「メソッドの引き下げ(P328)」、「フィールドの引き下げ(P329)」を行う。
サブクラスが「振る舞いは継承するけどインタフェースは必要無い」場合は「委譲による継承の置き換え(P352)」を行う。
コメント
コメントは良いものだが、それが「わかりにくいコードを補うため」だとしたら修正する必要がある。「表明の導入(P267)」など。