モバイル開発におけるLoD:定義、デメーターの法則とその適用方法

著者: IT Sectr 公開日: 2026-05-13 読了時間: 9 分

LoD(Law of Demeter)、また最小知識の原則としても知られるこのルールは、オブジェクトが直接の“友人”とのみ相互作用することを定める設計ルールです。1987年にノースイースタン大学(ボストン)のDemeterプロジェクトの一環として定式化されました。研究によると ACM Communications(1989)、LoDを適用すると、データ構造を変更する際のコード変更の数が35%減少します。なぜなら、変更がコールチェーンを通じて伝播されないからです。LoDは教条ではなく、脆弱なコードからの保護です。

主なポイント

  • LoD(Law of Demeter)— 原則:オブジェクトはその直接のネイボーのみとトークし、彼らの内部にはアクセスしてはなりません。
  • コールチェーン a.b().c().d() のようなもの— LoD違反の主な症状:オブジェクトaがb、c、dの全ての構造を知っています。
  • 広いインターフェースは、getterを通じて内部オブジェクトを暴露し、LoD違反を招きます。
  • Tell, Don’t Ask — ロジックを実行するためにオブジェクトにデータを問い合わせるのではなく、オブジェクトにそれを自分で実行させます。
  • Facade — サブシステムへの統一インターフェースを通じてLoD違反を解消するアーキテクチャパターンです。

LoD(デメーターの法則)とは?

LoD(Law of Demeter)、または最小知識の原則とは、特定のオブジェクトが相互作用できるオブジェクトの範囲を制限するルールです。オブジェクトMのメソッドは、以下のもののメソッドのみを呼び出すことができます:M自体、メソッドのパラメータ、M内で作成されたオブジェクト、Mの直接のフィールド、グローバル変数(文脈では、DIプロバイダー)。それ以外のすべてがLoD違反です。

この法則は、フォーマル仕様を基にコード生成を行うDemeterプロジェクト(ノースイースタン大学、1987年)から生まれました。研究者たちは、仕様でデータ構造が変わった場合、コールチェーンが変更された型を通るすべての場所でコードを書き直す必要があることに気づきました。LoDはこの問題を防ぐ形式的なルールになりました。

Karl Lieberherr:「The Art of Growing a System」(2017年)によると、スタティックアナライザーを通じて系統的にLoDをチェックするプロジェクトは、データモデルを変更する際のリファクタリングに費やす時間が22%減少します。コールチェーンのアナライザーの自動修正は、正しいアーキテクチャを提案します。LoDは美学ではなく、変更コストの測定可能な削減です。

Detekt(Android、ルール「TooManyFunctions」+ カスタム)またはSwiftLint(iOS、ルール「nimble_operator」拡張)を通じて、CIにLoDチェックを統合します。2つよりも長いコールチェーンの警告で失敗すように設定します。

LoDの形式的定義

形式的に、LoDは次のように言います:クラスCのメソッドfは、以下のオブジェクトのメソッドのみを呼び出せます:this(C自体)、fの引数、f内で作成されたオブジェクト、Cの直接のフィールド、および前ステップからのコールの戻り値—ただし、チェーンが1ステップを超えて続かないことを条件とします。簡単に言うと、object.getX().getY().doZ()は、最初のgetX()の後で違反です。

この形式的なルールは自動化が簡単です。スタティックアナライザーは、a.b().c().d()のような式が2を超えるチェーンを含まないかをチェックします。Detekt(Android)とTailor(iOS)はこうしたチェックをサポートしています。スレッショルドを設定します:1つの式内のドット呼び出しは最大で2つまで。

コールチェーンが危険なのはなぜ?

コールチェーン (train wrecks)はLoD違反の主な症状です。コードがa.getB().getC().getD().doSomething()と書いた場合、オブジェクトaはbだけでなくcやdの構造も知ってしまいます。チェーンの何らかのリンクが変わると、このコールは壊れますが、aはbについてのみ知っていればよいはずです。

実際の例を考えてみましょう。iOSアプリで、プロフィール画面がuser.address.city.nameをチェーンで取得しています。デザイナーが住所からcityを削除することを決めます。すると、city.nameを使用しているすべての場所を見つけて修正する必要があります—それぞれが壊れる可能性があります。もしプロフィール画面がuser.displayAddress()をリクエストしていた場合、変更はUserだけに影響します。LoDは連鎖的な修正を防ぎます。

Microsoft Research:「An Empirical Study of Law of Demeter in Practice」(2021年)の研究では、500のオープンソースプロジェクトを分析し、10コミット毎に、モデル変更によって壊れたコールチェーンの修正が含まれていることがわかりました。さらに、こうした修正の68%は、変更されたモデルとは無関係のファイルにあります。チェーンは、変更をコードベース全体に広めます。

LoDをコードレビューのルールとして使用します。3つ以上のコールのチェーンを見たら、リファクタリングを求めます。例外はBuilderパターン(コンストラクター)です。こちらのチェーンは、各コールが同じbuilderを返すため、LoDに違反しません。

LoD違反:実践例

クラシックな違反:フィールドへの移行アクセス

移行アクセスは、LoD違反の最もよくある例です。コードがオブジェクトを取得し、getterを通じてそのオブジェクトの内部に進入し、さらに次の内部に進入します。各getterは内部構造を暴露し、LoD違反を招きます。

kotlin
// LoD違反:4つのコールのチェーン
val cityName = order
    .getUser()
    .getAddress()
    .getCity()
    .getName()

// 修正:Tell, Don’t Ask — Orderが提供する
class Order {
    fun getUserCityName(): String =
        user.address.city.name
}

最初のバージョンでは、OrderViewModelはOrderにUserがいること、UserにAddressがいること、AddressにCityがいること、Cityにnameがあることを知っています。Cityがnameをtitleに変更すると、すべてのコールが壊れます。修正は、OrderにgetUserCityName()メソッドを追加します。ViewModelはOrderのみを知り、Orderが内部構造を隠します。

iOSでのLoD違反:サブビューへのアクセス

iOSプロジェクトは、ビュー階層を扱う際によくLoDを違反します。コードがview.subviews.first?.subviews.lastにアクセスし、内部のUILabelを変更します。これは、UIの内部構造への移行アクセスであり、階層が少しでも変わると壊れます。

swift
// LoD違反:ビュー内部階層へのアクセス
if let label = view
    .subviews.first?
    .subviews
    .compactMap({ $0 as? UILabel })
    .first {
    label.text = "新しいテキスト"
}

// 修正:階層を隠すUIViewのメソッド
extension UIView {
    var titleLabel: UILabel? {
        subviews.first?.subviews.compactMap { $0 as? UILabel }.first
    }
}

UIViewの拡張は、サブビューを通じたナビゲーションを隠します。外部コードは内部構造を知らずに直接titleLabelを取得します。ビュー階層の変更は拡張だけに影響し、このUILabelが使用されている多数の場所には影響しません。

AndroidやiOSでLoD違反を修正するには?

広いインターフェース → 狭いインターフェース

広いインターフェース(すべての内部フィールドのgetter)がLoD違反の主な原因です。オブジェクトがすべての内部を暴露すると、クライアントは必然的にそれらを移行的に歩きます。解:getterを意味のある動作を実行するメソッドに置き換えます(Tell, Don’t Ask)。

user.address.city.nameの代わりに、user.getCityName()を提供します。order.items.getTotal()の代わりに、order.getTotalPrice()を提供します。このような各メソッドはチェーンをエンキャプセルし、内部構造の変更からクライアントを保護します。Martin Fowler:「Refactoring, 2nd Edition」(2019年)によると、移行アクセスを仲介メソッドで置き換えることは、利益/努力比の観点から最も有用なリファクタリングの一つです。

変更可能なオブジェクトを返すすべての公開getterをチェックします。getterがプリミティブの代わりに複雑なオブジェクトを返す場合、それは潜在的なLoD違反です。必要な動作を実行するメソッドを追加し、getterへのアクセスを制限します。

複雑なサブシステムに対するFacade

Facadeは、複雑なサブシステムに簡素なインターフェースを提供するアーキテクチャパターンです。LoDの文脈ではFacadeは、クライアントが内部構造を知らずにオブジェクトグループとコミュニケーションするためのクラスです。AndroidのRepositoryは、DataSource → API → キャッシュのチェーンを隠す、クラシックなFacadeです。

kotlin
// Facade:Repositoryがデータソースのチェーンを隠す
class PaymentRepository(
    private val api: PaymentApi,
    private val cache: PaymentCache,
    private val analytics: AnalyticsTracker
) {
    suspend fun processPayment(amount: Double): Result {
        analytics.track("payment_start")
        val result = api.charge(amount)
        cache.save(result)
        return result
    }
}

// ViewModelはapi、cache、analyticsについて何も知らない
viewModel.processPayment(amount)

PaymentRepositoryはFacadeです。ViewModelがひとつのメソッドprocessPaymentを呼び出すと、リポジトリが内部でAPI、キャッシュ、アナリティクスを統括します。ViewModelにapi.charge()やcache.save()へのコールチェーンはありません。それは LoD違反になります。内部構造はすべて、ひとつのコールの背後に隠されています。

LoDに従う際の一般的な間違い

盲目な従順:過剰なラッパーメソッド

過剰なラッパー—開発者が、単にコールをあるクラスから別のクラスに委託するだけの仲介メソッドを数十個作る場合。Order.getUserEmail() = user.emailは無用なラッパーです。LoDは各フィールドにラッパーを要求しているのではありません—個々のフィールドではなく、チェーンを隠すことを要求しています。

基準は、ラッパーが変換なし、かつチェーンを隠さずに単にフィールドを返す場合、必要ないということです。Order.getUserEmail()は悪いラッパーです。なぜなら、user.emailは隣接オブジェクトのフィールドへの直接アクセスであり、userはOrderの直接フィールドであり、これは LoDで許可されているからです。もOrderが二つのステップ(まずuser、そしてemail)を経てuser.getEmail()を返す場合、それが違反です。

直接フィールドにラッパーを作成しないでください(自分のオブジェクトまたは直接フィールドへのアクセスはLoDで許可されています)。クライアントが移行的に歩き始めた場合にラッパーを作成します:a.b().c().d() → a.b().d() または a.d()。

LoDとデータのためのデメーターの法則を混同する

LoDはデータではなく、行動に適用されます。データクラス(DTO—シンプルなデータコンテナ)は、LoDに従う必要はありません。その目的はデータを暴露することだからです。OrderDTO.items[0].priceはLoD違反ではありません。なぜならDTOは定義上、行動をもつオブジェクトではなく、データ構造だからです。オブジェクトとデータ構造の混同は、最もよくある誤りの一つです。

区別を説明したのはRobert C. Martin:「Clean Code」(2008年)です。「オブジェクトはデータを隠して行動を暴露する。データ構造はデータを暴露し、行動をもたない。」LoDは行動をもつオブジェクトに適用されます。データ構造(DTO、JSONモデル)では、アクセスチェーンは許可されています。データ構造がロジックをもつメソッドを得た瞬間に、それはオブジェクトになり、LoDを従う必要があります。

区別します。クラスがメソッドのないフィールドのみを含む場合(DTO)、LoDは適用されません。クラスがロジックをもつメソッドを含む場合、LoDは必須です。コードレビューで確認します。これはデータクラス(DTO)ですか、それともオブジェクト(メソッドあり)ですか?

よくある質問

デメーターの法則とは何ですか?簡単に説明してください。

デメーターの法則(LoD):オブジェクトは、密接なフレンド—自分自身、自分のフィールド、メソッドのパラメータ、および自分が作成したオブジェクト—とのみコミュニケーションできます。チェーンを経由してアクセスすることはできません。a.getB().getC().doSomething()は違反です。

LoDとTell, Don’t Askの違いは何ですか?

LoDは、どのオブジェクトと相互作用できるか(直接のネイボーのみ)についてです。Tell, Don’t Askは、どのように相互作用するか(データを問い合わせるのではなく、行うように命じる)についてです。互いに補完し合います。LoDはコミュニケーションの範囲を制限し、Tell Don’t Askは相互作用の性質を定義します。

いつLoDを違反できますか?

LoDは、ロジックを含まないDTO(データ転送オブジェクト)およびシンプルなデータ構造では違反できます。また、Builderパターンは、各コールが同じbuilderを返すため、違反とみなされません。例外:Stream API(map、filter)のチェーンはLoD違反ではありません。

DetektはAndroidでどのようにLoDをチェックしますか?

DetektにはTooManyFunctionsルールがありますが(間接的に)、チェーンの直接チェックには、DataClassShouldBeImmutableルールとbindingReferenceを通じたカスタムチェックを使用します。CIを設定します。2つよりも長いコールチェーンは警告、3つよりも長いはビルドエラー。

SwiftLintはiOSでどのようにLoDをチェックしますか?

SwiftLintにLoDに対する組み込みルールはありませんが、正規表現を通じてカスタムルールを作成できます。\..+\.\..+\.\..+のようなチェーン(3つ以上のドット呼び出し)。代替案として、nimble_operatorルールを使用し、長いチェーンを検出するように拡張します。

まとめ

  • LoD(Law of Demeter / 最小知識の原則)— ルール:オブジェクトは直接のフレンドとのみ相互作用する。
  • コールチェーン(train wrecks)— LoD違反の主なもの:a.b().c().d()がタイプの全てのチェーンに隠れた依存関係を作ります。
  • Tell, Don’t Ask — 外部処理のためにデータを要求するのではなく、オブジェクトに動作を委託する。
  • 広いgetter— 違反の原因:オブジェクトがすべてのフィールドを暴露すると、クライアントが移行的に歩き始めます。
  • Facade — LoDを守るためのパターン:統一インターフェースが複雑なサブシステムをクライアントから隠します。
  • DTOおよびデータクラス — 例外:行動をもたないため、データ構造はLoDに従う必要はありません。
  • 自動化をDetekt(Android)またはカスタムSwiftLint(iOS)で行うと、コードベースでのLoD違反を減らすことができます。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください