ソフトウェア設計

ディメーターの法則(Law of Demeter):メソッドチェーンが悪いコードになる瞬間

コードレビューをしていると、こんな指摘を一度は受けるものです。

読了 4 分
ディメーターの法則(Law of Demeter):メソッドチェーンが悪いコードになる瞬間のカバー画像

コードレビューをしていると、こんな指摘を一度は受けるものです。

「この行、ドット(.)が多すぎませんか?」

1行に続けて書くと、むしろすっきり見えます。何が問題なのでしょうか?

この話の核心が、ディメーターの法則です。

今日はディメーターの法則とは何か、そしてなぜ許容されるメソッドチェーンと悪いコードになるメソッドチェーンがあるのかを、例を交えて説明します。

まず結論からお話しします。

ディメーターの法則は「知らないオブジェクトに話しかけない」というルールです。

メソッドチェーン自体が悪いのではなく、チェーンを使って他人の内部まで踏み込むと悪いコードになります。

この一文だけ覚えておけば、今日の記事の半分は理解できたも同然です。


ディメーターの法則とは

ディメーターの法則(Law of Demeter)は「最小知識の原則」とも呼ばれます。

簡単に言うと、こういうことです。

あるオブジェクトは、自分が直接知っている「近しい友達」とだけ対話すべきだ、という意味です。

友達の友達、そのまた友達まで引き出して使い始めると、問題が起きます。

メソッド内で呼び出してよい対象は、通常次のように整理できます。

  • 自分自身(self)のメソッド
  • メソッドの引数として渡されたオブジェクト
  • メソッド内で直接生成したオブジェクト
  • 自分が保持しているプロパティ(メンバー)オブジェクト

この範囲内だけで対話しよう、ということです。

ここから「1行にドットは1つまで」という有名な経験則も生まれました。

もちろん、ドットの数はあくまでヒントであり、絶対的なルールではありません。


メソッドチェーンはなぜ悪いコードになるのか?

私が実際に経験したコードで説明します。

注文から顧客の都市名を取り出す場面でした。

// 注文 → 顧客 → 住所 → 都市まで次々に掘り下げる
let city = order.customer
                .address
                .city
                .name

一見、すっきり見えますよね。

しかしこの1行は、OrderがCustomerの内部、その中のAddress、さらにその中のCityまで、すべて知っていることを意味します。

問題はここで起きます。

Addressの構造が変わったり、Cityがなくなったりすると、このコードも一緒に壊れます。

他人の家の引き出しの中まで手を入れているので、その家の構造が変わると自分のコードも壊れるのです。

このようにオブジェクトグラフをたどっていくチェーンは、よく「列車事故(train wreck)」と呼ばれます。

客車のように連なった列車事故チェーン
客車のように連なった列車事故チェーン

客車が次々につながっているように見えるからです。

ドットが連続するこの1行こそが列車事故です
ドットが連続するこの1行こそが列車事故です

では、良いチェーンと悪いチェーンはどう見分ければよいのでしょうか?

ここが今日の記事で最も重要な部分です。

メソッドチェーンがすべて悪いわけではありません。

判断基準は1つです。

「チェーンしながら他人の内部オブジェクトを次々に取り出して使っているか?」

先ほどの例のように、customer → address → cityと別々の他人のオブジェクトを掘り続けるなら違反です。

一方、次のように同じ種類の対象を扱い、自分自身を返し続けるチェーンなら問題ありません。

// filter·map チェーンは毎回「同じ文脈」を返すため、違反ではない
let names = users
    .filter { $0.isActive }
    .map(\.name)

このような高階関数のチェーンやビルダーパターンは、ドットがいくつあっても他人の内部を荒らしません。

毎回、自分自身(同じ流れ)を返しながら続いているだけです。

2つのコードを表で比較すると、次のように整理できます。

区分 悪いチェーン 良いチェーン
対象 他人の内部オブジェクトを次々に取り出す 同じ流れ/型を返す
order.customer.address filter { }.map { }
結合度 高い(構造変更に弱い) 低い
判断 ディメーター違反 違反ではない

違反コードをこのように直します

解決方法は思ったより簡単です。

「聞くな、命じろ(Tell, Don’t Ask)」を思い出せばよいのです。

内部を取り出すのではなく、その仕事をしてくれるよう依頼します。

先ほどの都市の例なら、次のように変更できます。

// Order必要な結果だけを尋ねる
let city = order.shippingCity
聞くのではなく命じる。この1行で十分でした
聞くのではなく命じる。この1行で十分でした

Orderの内部でCustomerとAddressを経由し、都市を返すよう自動的に処理させるのです。

こうすればAddressの構造が変わっても、外側のコードはそのままです。

修正範囲がOrderの内部だけに収まるからです。

実際にこのようにリファクタリングしてみると、後で構造を変えるときに修正箇所が大幅に減りました。


最後にまとめると、こうなります。

ディメーターの法則はドットの数を数えるルールではなく、他人の内部にむやみに手を出さないという考え方です。

次に長いチェーンを見つけたら、1つだけ問いかけてみてください。

「これ、他人の引き出しを開けているのでは?」

この問いを1つ投げかけるだけでも、コードはずっとすっきりします。