第6章 テスト不足に気づいたら
この章でやったこと
2 つのクラスで重複していたメソッドを、新しく定義した親クラスのメソッドに引き上げた
このリファクタリング手法は Martin Fowler の書籍にも出てくる:Pull Up Method
リンク先は radish-miyazaki.icon が以前に読んだ リファクタリング:Rubyエディション の読書メモ
Before
code:mermaid
classDiagram
class Dollar {
+equals(other) bool
}
class Franc {
+equals(other) bool
}
After
code:mermaid
classDiagram
class Money {
+equals(other) bool
}
class Dollar {
}
class Franc {
}
Money <|-- Dollar
Money <|-- Franc
テスト不足に気がついた場合
リファクタリング の前に本来あるべきテストを追加すべき
サンプルコードでは Franc クラスの equals メソッドが不足していた
テストが無ければリファクタリングは不可能
結果として次第に設計が腐っていく
気になったこと: 継承 を安易に選択している
継承より委譲
今回のケースでも一見すると継承でも良さそう
is-a の関係としては自然
親クラスの実装変更がサブクラス全体に影響する問題 も現時点では起こり得ない
しかし、継承はやっぱりデメリットが多すぎ
is-a の関係ということは、高結合 で変更にも弱い ということ
カプセル化 の破壊
サブクラスは、親クラスの protected や private フィールドにアクセスできる(言語による)
リスコフの置換原則 違反
これ避けるの無理ゲー
結果として利用者が想定していない挙動によるバグが発生する可能性
オープン・クローズドの原則 にも違反しやすくなる
∵ 呼び出し元でクラス判定が必要になるため
コンパイル時に静的に定義される(静的型付け)
また、特に今回のケースだと、Effective Java でも論じられているような以下の問題も発生
[* 継承可能なクラスで equals を実装すると、対称性・推移性・リスコフの置換原則 のいずれかを必ず壊す]
対称性: x.equals(y) が true ならば y.equals(x) も true
推移性: x.equals(y) かつ y.equals(z) が true ならば x.equals(z) も true
∵ 等価性という概念は 二項関係 であり、両方の オペランド のうち「どちらの型を基準とするか」を一意に決められないため
親クラス視点では、サブクラス特有のフィールドは無視すべき余分な情報
サブクラス視点では、上記のフィールドは等価性の本質的な一部
上記の 2 つを同時に満たそうとすると、矛盾が生じる
まぁ、あくまで設計の過程でしょ
通貨の種類ごとにクラスを作成するのも大変だしね(サブクラスの爆発)
最終的に値の合成(has-a, Record<int amount, int currency>)に置き換わった気がする(忘れた)
#読書メモ