law-of-demeter について
コンポーネント間の結合を減らすため、デメテルの法則を解説するコーディングスキルです。オブジェクトのアクセス連鎖、API 境界、データ形状、コードレビューの実践を扱います。
主な機能
- 01直接の協力相手だけにアクセス
- 02深い連鎖をオブジェクト自身のメソッドに置換
- 03境界で外部 API をラップ
- 04利用側向けにデータをフラット化
- 05コードレビュー用チェックリスト
活用シーン
- 01ネストしたオブジェクトへの脆い依存を削減
- 02深いモックが必要なテストを簡素化
- 03サードパーティの応答構造を業務ロジックから隔離
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jordancoin/ codingskills --skill law-of-demeterインストール後、Claude Code で/law-of-demeterを入力して読み込まれたか確認します。
詳細
LoD — デメテルの法則(最小知識の原則)
適用する前に
.agents/stack-context.md が存在する場合は、まずそれを読んでください。検出されたスタックに適した慣用的なパターンを使って、この原則を適用してください。フレームワーク固有の詳細については、context7 MCP またはウェブ検索を利用してください。推測で判断しないでください。
原則
コンポーネントがやり取りするのは、直接の協力相手のみにするべきです。オブジェクトの内部や、その子オブジェクトの内部、さらに深い階層にアクセスするために、オブジェクトをたどってはいけません。
より正確には、メソッドが呼び出してよいのは、(1)自分自身、(2)自身のフィールド、(3)自身のパラメーター、(4)自身が作成したオブジェクト、(5)グローバル/シングルトンのサービスのメソッドのみです。
本番環境で重要な理由
アクセスの連鎖(order.customer.address.city)は、結合の連鎖です。その連鎖内のいずれかが変わると(顧客モデルの構造変更、住所が任意項目になる、市区町村がサブオブジェクトに移動するなど)、それを使うすべての箇所で問題が発生します。
LoD 違反は、些細に見えるリファクタリングが、無関係なコード全体に連鎖的な障害を引き起こす壊れやすいシステムを生みます。また、テストも難しくなります。4 階層をたどる関数をテストするには、4 階層すべてをモックしなければなりません。
本番環境では、LoD 違反は NullPointerException や undefined is not an object エラーの主な原因です。アクセスの連鎖にある . は、それぞれ null/undefined の参照解除が起きる可能性のある箇所です。
ルール
- オブジェクトを連鎖的にたどらない。 構造の深い階層にあるデータが必要な場合は、直接の所有者にそのデータを提供してもらいます。
order.customer.address.cityではなく、order.shipping_city()を使います。 - 問い合わせるのではなく、指示する。 オブジェクトの内部にアクセスしてデータを取得し、判断するのではなく、必要な処理をオブジェクトに指示します。オブジェクト自身の内部情報を使って処理させます。
- 外部 API は境界でラップする。 サードパーティの SDK や API レスポンスは、深くネストされた構造を公開していることがよくあります。薄いラッパーを作成し、コードが必要とするものだけを、必要な形で公開します。
- 利用側に渡すデータはフラットにする。 テンプレート、フロントエンド、ほかのサービスなどの境界を越えてデータを渡す場合は、内部モデルをそのまま公開するのではなく、目的に合わせた形にネスト構造をフラット化します。
- パラメーターを通じて知る範囲を限定する。 オブジェクトを受け取る関数は、直接必要とするフィールドのみを使います。ユーザーのメールアドレスだけが必要なら、ユーザーオブジェクト全体ではなく、メールアドレスを受け取ります。
アンチパターン
- トレインレック:
app.config.database.connection.pool.max_size— 設定構造の変更があると、これを使う箇所がすべて壊れます - フィーチャーエンヴィ: 自身のオブジェクトよりも別のオブジェクトのデータを多く使う関数 — その関数はおそらく、データを使われる側のオブジェクトに置くべきです
- 透過的なラッパー: ゲッターを通じて内部情報を公開し、すべての利用側を内部構造に依存させるオブジェクト
- テストでの深いモック: 注文処理のテストだけのために
order.customer.addressをモックする必要があるなら、コードは深くたどりすぎています
例
-- LoD violation: reaching through the chain
def get_shipping_label(order):
name = order.customer.name
street = order.customer.address.street
city = order.customer.address.city
zip = order.customer.address.zip_code
return format_label(name, street, city, zip)
-- LoD-compliant: ask the order for what you need
def get_shipping_label(order):
info = order.shipping_info() # order knows how to assemble this
return format_label(info)
-- LoD violation: reaching into config internals
timeout = app.config.services.payment.timeout_ms
-- LoD-compliant: config provides what you need
timeout = config.payment_timeout()
適用範囲
- 純粋なデータ構造には LoD は適用されません。 DTO、辞書、値オブジェクトのフィールドへのアクセスは問題ありません。これらはデータを運ぶために存在するものです。LoD が適用されるのは、振る舞いを持つオブジェクトです。
- LoD はすべてをラップするという意味ではありません。 あらゆるアクセスパターンにラッパーメソッドを作成しないでください。内部構造が変わる可能性の高い、意味のある境界で適用します。
- 流れるような API/ビルダー API は例外です。
query.select("name").where(active=true).limit(10)のような連鎖は、各メソッドがselfを返すため LoD 違反ではありません。同じオブジェクトとやり取りしているからです。 - KISS との兼ね合い: 中間メソッドを挟むより、直接連鎖させたほうが明確な場合もあります。結合のリスクが実際にある箇所(モジュール間の境界、不安定な構造を介する箇所)で LoD を適用し、密結合した内部には適用しないでください。
コードレビューのチェックリスト
- オブジェクトの境界を越えて、プロパティやメソッドに3つ以上連続してアクセスしていませんか?
- ある関数が、自身のオブジェクトよりも別のオブジェクトの内部に多くアクセスしていませんか?(フィーチャー・エンヴィ)
- 外部 API のレスポンスは、ビジネスロジックに漏れ出さず、境界でラップされていますか?
- 渡されたオブジェクトの内部構造を変更しても、このコードが壊れずに済みますか?
- 単純なロジックをテストするだけなのに、深いオブジェクトグラフの構築をテストで強いられていませんか?
関連スキル
- solid:依存性逆転とインターフェース分離(構造的な疎結合化)
- separation-of-concerns:各レイヤーの独立性を保つ場合
- kiss:ラップする処理がアクセスチェーンより複雑になる場合