持続可能なソフトウェアのコーディング(2)デメテルの法則

デメテルの法則

DRY原則が遵守されたコードは重複コードを最小限に抑え、直交性のあるクラスは互いに結合度を減少させます。それでは直交性のあるコードを作成するには、どうすればよいでしょうか?デメテルの法則を利用するとモジュール間の結合度を減らすことができます。デメテルの法則は、最小知識の原則(principle of least knowledge)と表現されることもあり、オブジェクト指向プログラミングのソフトウェア設計ガイドでもあります。デメテルの法則はクラス間の結合度を減らすための原則であり、これらの原則はオブジェクトのメソッドを使用する際に注意が必要です。

デメテルの法則は次のとおりです。(原文抜粋)

Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
– 各ユニットは、他のユニットに関する限られた知識のみを持つ:現在のユニットに「密接に」関連するユニットであるべき

Each unit should only talk to its friends; don’t talk to strangers.
– 各ユニットは、その友人にのみ話しかける:知らない人には話しかけないこと

Only talk to your immediate friends.
– あなたの親しい友人にだけ話しかけること

上記における「ユニット」はオブジェクトを意味し、「友人」はオブジェクトに関連する他のオブジェクトを意味します。互いに依存関係があるオブジェクトを指しています。そして「話しかける」とは、自分や他のオブジェクトのメソッドを呼び出すことを意味します。デメテルの法則は、いくつかのキーワードを用いて次のように表すことができます。

「近い」「親しい」オブジェクト、そして話しかけるのは非常に「慎重」である。

さて、ここでの近い親しいの基準は何でしょうか?また、話かけることについて非常に厳しく制限しているようです。例を見ながらもう一度確認しましょう。

オブジェクト指向プログラミングの場合

オブジェクト指向プログラミングにデメテルの法則を適用すると、次のように解釈できます。上述した近い親しいなどの言葉を思い出して読んでみましょう。

オブジェクトO(ObjectのO)のメソッドm(methodのm)は、次のような方法でメソッドを呼び出します。

1.オブジェクトO自身のメソッドは呼び出すことができる
2.メソッドmの引数のメソッドは呼び出すことができる
3.メソッドmの内部で生成/初期化したオブジェクトのメソッドは呼び出すことができる
4.呼び出し用のメソッドまたは属性として同じクラス内で宣言されたオブジェクトのメソッドは呼び出すことができる
5.オブジェクトOがアクセスできるメソッドmのスコープにある全域オブジェクトのメソッドは呼び出すことができる


デメテル法則が適用されたコード

さらに理解しやすいように、実際にコーディングして確認してみましょう。下記はデメテルの法則が適用されたコードです。まず、前述のキーワードは次のようにマッピングします。

– オブジェクトOは、ProductService オブジェクト
– メソッドmは、getProductByCode(String code) メソッド

下記コードでコメントしている説明と、上記のデメテルの法則を比較してみましょう。

public class ProductSerivce {

    private ProductRepository productRepository;

    private String purify(String code){
        String purifiedCode = code.subsctring(0, 10); 

        // ....

        return purifiedCode;
    }

    public ProductDetailResponse getProductByCode(String productCode){

        // ケース2 : メソッドの媒介変数Stringオブジェクトのメソッドtrim()を呼び出す。
        // 近い関係
        String trimedCode = productCode.trim();

        // ケース1 : オブジェクトProductService自身のメソッドpurifyを呼び出す。
        // 非常に親しい関係
        String code = this.purify(productCode);


       Decoder decorder = new ProductDecoder();

       // ケース3 : getProductByCode内で生成したDecoderオブジェクトget()を呼び出す。
       // これ以上近い関係があるだろうか?
       Long productId = decoder.get(code);

       // ケース4 : ProductServiceオブジェクト内で宣言されたproductRepositoryのfindByIdメソッドを呼び出す。
       // 同じクラスなので親友のようなものか?
       return  productRepository.findById(productId)
            .map(ProductDetailResponse::new)
            .orElseThrow(() -> {
                log.error("NotFound #{}",productId);
                throw new NotFoundException("Not Found!");
            });
    }

}

デメテルの法則に違反するコード

デメテルの法則が遵守されたコードを確認しました。上記は、非常に一般的で普遍的なコードだと思われます。
では次に、デメテルの法則が遵守されていないコードを見てみましょう。

public class ProductProcessor {

    // デメテルの法則以前に、このようなメソッド名は適切ではありませんね。
    // メソッド名に行為(behavior)が表されていません。
    public void process(ProductContext context){

        DeliveryLine deliveryLine = context.getDeliveryLine();
        // .... 省略.... //

        ProductCode code = context.getCode();

        // 引数で受け取ったcontextオブジェクトに含まれるProductCodeオブジェクトのisVarified()メソッドを呼び出す。
        // これは、ProductProcessorオブジェクトと1つ隔たっているオブジェクトではないでしょうか?
        // 「知らない人に話しかけない」の原則に違反しますね。
        if (!code.isVarified())
            throw new NotVarifiedException("Not varified");

        //.....
    }
}

上記のコードでは、ProductContextクラスは内部に数多くのオブジェクトを持っていそうな名前になっています。脈絡(Context)という名前を持つオブジェクトのほとんどがそうです。そして、私たちは当然のようにContextオブジェクトの内部にあるオブジェクトをget()メソッドで取り出し、内部オブジェクトのメソッドを自由に使用します。さらにset()メソッドを利用して、引数の値もいろいろと修正します。こうなると、ProductProcessorオブジェクトと関係のあるProductContextオブジェクトだけでなく、1つ隔たっているProductCodeオブジェクトにも結合が生じます。このようにオブジェクトの複雑度が増すと、最終的にメンテナンスが難しくなります。

デメテルの法則は、必ず遵守しなければならないのか?

デメテルの法則はうまく適用されているかを確認するのに有効ですが、ラムダ式やメソッドチェーンパターン(method chainning)に実装されたコードは、把握するのが困難です。チェーンで繋がったメソッドがどのようなオブジェクトを返すのか、一つ一つ確認するのが難しいためです。では、Javaのラムダ式は使用すべきではないでしょうか?モダンJavaが提供する流れるようなインターフェース(fluent interface)は放棄すべきでしょうか?答えは「いいえ」です。デメテルの法則を適用すると最近のトレンドとマッチしないように思われます。

では、なぜこの記事でデメテルの法則を取り上げたのでしょうか?しかも上記のサンプルでは、デメテルの法則と直交性とはあまり関連性があるように見えません。直交性はモジュール間の共通点を最小化するための内容ですが、デメテルの法則はオブジェクトの結合度を減らすための内容ですね。それにも関わらず、デメテルの法則と直交性を結び付けようとするのはなぜでしょうか?

デメテルの法則もオブジェクト指向プログラミングに役立ちます。結合度が削減され、開発者がコードを確認する際に意識の流れを読み取るのに便利だからです。

また、「The Pragmatic Programmer」の著者デイビット・トーマス、アンドリュー・ハントも、あまり多くを交流しない恥ずかしがり屋(shy)なコードを作成することを提案しています。

In their book The Pragmatic Programmer, Andrew and Dave suggest writing “shy” code that doesn’t interact with too many things.

デメテルの法則を実現するためには、まさにシャイコーディング(shy coding)が効果的です。これはオブジェクト内で宣言されたデータの結合度を増加させ、直交性とデメテルの法則がよく行われるためです。

TOAST Meetup 編集部

TOASTの技術ナレッジやお得なイベント情報を発信していきます
pagetop