デメテルの法則って何?従うべき?
概要
保守性の高いコードを書くための法則集の中に、時たま出てくる「デメテルの法則」
この法則とは何者なのか?従うメリットはあるのか?などをここで記載しておく
デメテルの法則ってなに?
あるオブジェクト/関数は、直接の友達以外のメソッド・プロパティを知るべきではないという法則
以下のようなパターンはダメだよって意味
code: sample1.py
def sample(ban):
# banがhogeAを参照するまでは良いが、そのhogeAのメソッドやプロパティを知るのはダメ
ban.hogeA.baba()
ban.hogeA.name
code: sample2.py
def samle(ban):
# メソッドチェーンになってないからといって良いわけではないよ。念の為。
# とにかく友達の友達とは話さないほうがいい。
hogeA = ban.getHogeA()
hogeA.baba()
何がいいの?メリットは?
保守性が向上する(= 結合度が下がる)
内部の情報を知り過ぎないことにより、利用されてる側のクラスの内部実装を変更しやすくなる。
もし、友達の友達と関わるようになってしまうと、そこ周辺の結合度が上がってしまい変更しにくいコードになる。
例えば、上記のコード例を説明に用いると...
本来sample関数はbanオブジェクトと話しておけばいいだけなのに、hogeAとも話してしまうことでhogeAの変更がやりにくくなる。
もし、hogeAがbanの中に隠されてる(カプセル化)状態なら、hogeAの変更時にsample関数のことを気にする必要はなくなる。banだけ気にしておけばOKになる。
テストが容易になる
対象クラスのテストをする際、デメテル違反をしてる分だけモックオブジェクトを用意する羽目になる。
また、依存が多くなるので、テストの変更頻度が多くなっちゃう...
逆にデメリットは?
ラッパーメソッドが増えて、直接の友達のインターフェースが巨大になる
デメテルに従うために、直接の友達が必要なメソッドを全て用意することになる。
そうなると、場合によってはメソッドがめちゃくちゃ増えてしまう。
「これはこれでどうなの?」てなっちゃう。
そういう場合は大抵、設計が不十分になってる場合が多いとのこと。
これは、LoD自体の結果ではなく、設計が不十分なためです。ラッパーメソッドが使用されている場合、ラッパーを介して呼び出されるオブジェクトは、呼び出し元のクラスの依存関係である必要があることを意味します。
結局従うべきなの?従うとしたらどう従う?
基本的には従っておくといい。従えるように設計を工夫したりすること。
従うことで、結果的にシステム全体の結合度を下げることができる。
ただ、全部が全部従う必要もない。
本当にオブジェクトを返さないといけない時は返せばいい。
また、変更範囲が広がらない程度の波及に抑えれるなら、その箇所では従う必要なし。
感想
内部を知れば知るほど、全体の結合度が上がるリスクが増える。
ただ、割り切って内部を知るってのもありっちゃありかなと。
影響範囲が小さく絞れてることが確約されてる状態で、内部に依存するとかならまだマシかなと思う。
止むに止まれん理由があるなら、そういう対処もあるんだよと。
ここに書くことじゃないけど、変更しやすいコード書く際に意識しておくことは...
この2つがとても大事だと思う
特に「1」が一番大事。ここができてないと、リリース後のコード変更時に思わぬバグが発生する。
「1」ができてる上で、より影響範囲が小さくなるように作っていく。
ここができると、変更スピードがより上がっていく。
参考
hr.icon
この記事が一番デメテルの法則に関して理解しやすいので要約しておくonigiri.w2.icon
はじめに
デメテルの法則は、コード中のオブジェクト間の「結合度」を下げるための便利なイディオムである。
1. 例示
ある新聞配達員が顧客から支払いをもらう状況を考える。
具体的には、新聞配達員が顧客の家に行き、新聞を顧客に渡して代金をもらう場面。
この状況下でデメテルの法則違反の支払いはどうなるか?
新聞配達員は顧客のポケットから財布を取り出して、その財布にお金があることを確認し、財布から2ドル抜き出す。
現実的に考えると、これはとても不自然な状況であるのはわかる。
コード的な問題は「新聞配達員が必要以上の情報を知ってしまっている」ということである。
新聞配達員は顧客の情報も知りつつ、顧客の財布の情報まで知ってしまっている。
これは密結合な状態といえる。
財布クラスを変更したら、他のクラスも変更する可能性が出てきてしまう。
また、財布がNullだった場合にもエラーが起きてしまう。
誰かが顧客のポケットから財布を盗んでしまった場合、配達員はそれに気づかずに財布を使おうとしてエラーが起きる。
まあ、isNullなどで実行前にNullチェックとかできるけど、それは配達員側のコードが乱雑になり始める。
これはダメ。
2. どう改良する?
結論から言うと、配達員が財布を直接触るのではなく、顧客に支払いを求めるのが良い。
これの何がいいのか
1. こっちの方が現実により近くモデリングできてる。
財布を直接取るとかいう不自然な行動になってない
2. 財布クラスを簡単に変更できるようになる。
配達員が財布の構造などを気にする必要が無くなる
結合度が下がってる。
3. より「オブジェクト指向」的である。
支払い方法を内部に隠せてる。
支払いさえすれば、どんなやり方でも良くなる。
3. 欠点は何か?
顧客クラスにラッパーメソッドが増えてしまう
財布にメソッドを持たせないとなると、その分顧客に持たせるメソッドが増えてしまう恐れがある
ラッパーメソッドが増えることで、顧客クラスがどんどん複雑に見えるようになる
ただ...
この複雑さは元々顧客周りのドメインで内包されてたものであると言える。
その複雑さを顧客クラスに閉じ込めている。
変更の影響自体は小さく収めれてることに変わりはない。
コラム:デメテルの法則を全てに適用するのはナンセンス
例えば、警察官と免許証というオブジェクトで考えてみる。
警察官が運転手に免許証を提示しろと言ってくる場合、オブジェクトをそのまま渡したほうが都合がいい。現実で考えても財布の時よりも至極自然な状況になってる。
このように、内部のオブジェクトを外にそのまま露出させるほうがいい場合もある。
4. デメテルを適用する基準
原則、以下に当てはまる以外のオブジェクトのメソッドは利用してはダメ(例外はある)
1. そのオブジェクト自身のメソッド
2. 引数で渡されたオブジェクトのメソッド
3. 同スコープ内でインスタンス化されたオブジェクトのメソッド
4. そのオブジェクトのインスタンス変数としてあるオブジェクトのメソッド
例外として...
利用者側が本当に内部のオブジェクトを必要としてる場合のみ、内部オブジェクトをクライアントに露出させてもいい
非公式ではあるが。
例えば、免許証というオブジェクトを警察官に渡したい場合、それは渡すしかない。
わざわざラッパー関数を作る必要はない。そのまま渡せばいい。