Lindelin

高凝集で低結合のプログラミング(High Cohesion and Low Coupling)

理想なプログラムとして、 凝集度は高いほど良い、 つまり責任範囲が明確で軸ぶれないほどよいです。 結合度は低いほど、 つまりそっちはそっちで勝手にやっててねとできるほどよいです。 そもそも、高凝集で低結合とは何か?まだ理解できてない方が多くいらっしゃると思います。 この記事を読めば、すべて明らかになるでしょう。

凝集(ぎょうしゅう)

凝集とは、散らばったりしていたものが、一つに集まり固まること。「勢力を―させる」

凝集、簡単に言うと、それは、自分のものをちゃんと自分で管理することです。または、自分の仕事をちゃんと自分でやることです。 つまり、自分でやって、他人には迷惑をかけないようにすることです。

プログラムの 2 大要素として データ(Data)操作(Operation) があるという理論があります。 さらに、Pascal(パスカル) のクリエイター Niklaus Wirth は 「プログラム = データ構造 + アルゴリズム」 という有名な数式を作りました。 両者の違いが少しあるが、根本的な意味は同じです。前者はミクロな視点からの考えで、後者はミクロな視点とマクロな視点の間からの考えです。 しかし、マクロな視点から考えると、私は 「プログラム = オブジェクト + 情報」 だと思います。

オブジェクトとはなんですか?
オブジェクトとは、 自分のものがちゃんと自分で管理できる、 尚且つ、自分の仕事がちゃんと自分でできるプログラムモジュールです。それは凝集と呼ばれるものです。 伝統的なプロセス指向プログラミングはデータ構造アルゴリズムを分離することによって、ほとんど凝集が低く、 1 度ソフトウェアの危機(参考:ソフトウェア危機 – Wikipedia)を引き起こしたことがあります。考えてみると、皆さんは自分のものが自分で管理できなければ、 自分の仕事が自分でできなければ、危機を引き起こしてもおかしくないのではないですか。 もちろん、オブジェクトの凝集だけでなく、色んな条件下、メソッドも凝集しなければならないし、フレームワークも凝集しなければならないです。 『論語・憲問』の中、 「子路問君子。子曰、脩己以敬。曰、如斯而已乎。曰、脩己以安人。」に教わったのは我々は常に自分自身の凝集を高めたほうがいい、 できるだけ他人に迷惑をかけないようにすることです。そうすると、自分の身を修めて天下万民を安んずるのです。

凝集度

凝集度(ぎょうしゅうど、コヒージョン、cohesion)とは、 情報工学においてモジュール内のソースコードが特定の機能を提供すべく如何に協調しているかを表す度合いである。 IPA が実施する情報処理技術者試験では、強度(きょうど、ストレングス、strength)という言葉が使われる。 凝集度は順序尺度の一種であり、「凝集度が高い」とか「凝集度が低い」といった言い方で使われる。 凝集度の高いモジュールは、堅牢性、信頼性、再利用性、読みやすさなどの点で好ましく、 凝集度の低いモジュールは保守/評価/再利用/読解が難しいため好ましくないとされる。– Wikipedia

非常に低い凝集性

機能が大きく異なる複数の分野での多くの作業の責任を 1つ のクラスが単独で負う

低い凝集性

1つ の機能分野での複雑なタスクの責任を 1つ のクラスが単独で負う

高い凝集性(High Cohesion)

クラスが 1つ の機能分野での適度の責任を持ち、タスクを遂行するために他のクラスと協調する

中程度の凝集性

論理的にクラスの概念に関係するが、相互には関係していない少数の異なる分野において、クラスがライトウェイトで独占的な責任を負う

高凝集のメリット

凝集性の低いクラスは、関係性のない仕事をこなしたり、仕事が多すぎたりします。これは以下のような問題を発生させます。

  • 理解しにくい
  • 再利用しにくい
  • 保守しにくい
  • 脆弱で、変更による影響を絶えず受ける

逆に凝集性を高めると、以下のような利点があります。

  • 設計の明確さと理解のしやすさが高まる
  • 保守と拡張が容易である
  • 疎結合性も同時に促進されることが多い
  • 関係性の強い、適度に細分化された機能の再利用性が促進される。
  • 凝集性の高いクラスは限定された目的に使用出来るから。

結合と依存性

オブジェクト指向プログラミングにおいて、オブジェクト自分自身は、 自分のものがちゃんと自分で管理できる、尚且つ、自分の仕事がちゃんと自分でできるプログラムモジュールです。 そして、外側に自分の状態や行為を示します。絶対的自立はありません。 1つ のオブジェクトは、常に他のオブジェクトとやり取りしなければならないです。 相手の状態を知るまたは、相手と連携するという場合があります。 その際、私たちは、オブジェクトが他のオブジェクトに依存していることを言うのでしょう。 2つ のオブジェクト間に依存関係が存在する限り、その 2つ のオブジェクト間の結合があると言えます。 例えば、赤ちゃんとお母さん、お母さんは常に赤ちゃんの面倒を見ないといけないです。 赤ちゃんはお母さんを依存していると言えます。そのため、プログラムの意味では、お互いに依存しているので、結合が存在します。 先に結論を言うと、結合が必要不可欠です。結合をなくすことができません。

結合度と低結合

結合度(けつごうど、カップリング、coupling)とは、コンピュータープログラミングで用いられる(機械よりは)人間寄りの尺度。 ソフトウェア測定法の一種。利用者またはメンテナンスをする者にとって対象を利用、 保守しやすいように対象の内容が整理、分割できているかどうかを、その状態によって段階に分けて表現する。– Wikipedia

簡単に言うと、結合度とは、お互いに依存している程度と言うことです。 上記の例で、赤ちゃんとお母さんは高結合です。なぜなら、お母さんがいないと、赤ちゃんは生きていけないです。 最近、私たちもスマホと高結合になりましたね。逆に、Amazon ショピングサイトとの結合が低結合です。

結合・依存性が高くなるほど、自由がなくなります。例えば、IT 会社に勤めているあなたは、 常に上司から進捗を促され、新人の指導も求めらていて、上司に怒られないように気をつけないといけなくて、 妻の気分も懸念しないといけなくて、さらに、BUG、障害、アラームにも気をつけないといけなくて、さらにさらに、 目、頚椎など身体状態に注意を払うようにしなければならなくて、まさに超高結合度になっていますね。 そのため、結合度を低減することが必要であり、このプロセスはデカップリングと呼ばれています。

依存関係反転(Dependence Inversion Principle)

デカップリングにおいて、最も重要な原則は:

高レベルのモジュールは、抽象に依存する必要があり、基本的なモジュールに依存してはいけません。 いわゆる、具体的なものが抽象に依存しなければならない、細部に依存すべきではありません。

『資本論』は、依存関係反転原理を説明していた。「商品経済の初期に、物々交換が現れた」と書かれています。 例えば、あなたは IPhone が欲しいです。IPhone を持っている方は黒毛和牛が欲しいです。 しかし、あなた黒毛和牛を持っていないので、IPhone と交換できないです。IT 会社に勤めているあなたはシステムしか作れないですね。 そのため、黒毛和牛を持っている方のところに辿り着き、APP を作ってあげて、黒毛和牛と交換したいが、黒毛和牛を持っている方は APP は要らないが、 車が欲しいそうです。こうして循環すると、従って、たくさん依存関係が生まれ、結合度が高くなりました。 この問題を解決する最善の方法は、抽象に依存することです。つまり、貨幣です。貨幣はとても抽象的なもので、色んなものと交換できます。 そうすると結合度が低くなるのです。

プログラミング上でどうなるでしょうか?

まず、一緒にキャッシュ処理を実現する高結合のソースを見てみましょう。

<?php

namespace App\Orders;

class Repository
{
    /**
     * キャッシュインスタンス
     */
    protected $cache;

    /**
     * 新しいリポジトリインスタンスの生成
     *
     * @param  \SomePackage\Cache\Memcached  $cache
     * @return void
     */
    public function __construct(\SomePackage\Cache\Memcached $cache)
    {
        $this->cache = $cache;
    }

    /**
     * 注文をIDから取得
     *
     * @param  int  $id
     * @return Order
     */
    public function find($id)
    {
        if ($this->cache->has($id))    {
            //
        }
    }
}
?>

このクラスのコードは、使用しているキャッシュの実装ときつく結合しています。 つまりパッケージベンダーの具象キャッシュクラスに依存しているために、結合が強くなっています。 パッケージの API が変更されたら、同時にこのコードも変更しなくてはなりません。

キャッシュの裏で動作している技術(Memcached)を別のもの(Redis)へ置き換えたくなれば、 リポジトリーを修正する必要があるというのは起こり得ます。 リポジトリーは誰がデータを提供しているかとか、どのように提供しているかという知識を沢山持っていてはいけません。

このようなアプローチを取る代わりに、ベンダーと関連がないシンプルなインターフェイスへ依存するコードにより向上できます。

<?php

namespace App\Orders;

use Illuminate\Contracts\Cache\Repository as Cache;

class Repository
{
    /**
     * キャッシュインスタンス
     */
    protected $cache;

    /**
     * 新しいリポジトリインスタンスの生成
     *
     * @param  Cache  $cache
     * @return void
     */
    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }
}
?>

これでコードは特定のベンダー、しかもフレームワークにさえ依存しなくなりました。

制御の反転(Inversion of Control)

制御の反転は根本的な意味として依存関係反転とほぼ同じです。依存関係反転が存在すると、制御の反転も存在すると考えられます。 しかし、制御の反転は特別な意味も持っています。

まず、私たちは ServerClient の意味を理解しないといけないのです。いわゆる、サービスする側と受ける側ですね。 それは、C/S 関係と呼ばれるものです。例えば、Laravel や CakePHP などのフレームワークを使う際、そのフレームワークはサービスする側または、 サービスを提供する側、利用者の私たちはサービス受ける側です。Client から Server にリクエストする場合は一般制御と言われています。 逆に Server から Client リクエストする場合は制御の反転と言われています。そのリクエストは Callback と呼ばれています。 制御の反転と依存関係反転は同じくプログラミング思想ですが、制御の反転はプログラムフローの制御を目的としたプログラミング思想です。 例えば、一般レストランに行く場合、注文して、店側は料理を作って、持ってくるのは一般制御です。 焼肉屋さんに行く場合、注文して、肉を自分で焼かないといけないのは制御の反転です。つまり、前者の場合、店側が料理作りの制御権を持っています。 後者の場合、料理作りの制御権は自分が持っています。

依存性注入(Dependency Injection)

依存性注入も根本的な意味として依存関係反転と制御の反転とほぼ同じです。ただし、依存関係反転と制御の反転は目的で、依存性注入は手段です。

A オブジェクトは B オブジェクトを依存していると、A オブジェクトの中に必ず B オブジェクトの変数やメソッドを呼び出していると考えられます。

A オブジェクトは B オブジェクトを利用する際、必ず下記のような手段を使っています。

  • 新規作成
  • コンストラクタ注入
  • プロパティ注入
  • メソッド注入

コンストラクタ注入、プロパティ注入、メソッド注入は依存性注入と呼ばれています。

普段、私たちは手動で新規作成や注入をしています。

<?php

class Csv {
    
    protected $library;
    
    // Library を依存している。
    public function __construct() 
    {
        // 新規作成
        $this->library = new Library();
    }
}
$csv = new Csv();
?>

この場合はクラスやインスタンスの制御はすべてプログラマが行なっています。つまり、プログラマはサービスする側です。 制御の反転による依存性注入はこうなります:

<?php
$this->app->bind(
        'App\System\SecuritySystem\Contracts\Security',
        'App\System\SecuritySystem\GmoSecurity'
    );
?>
<?php

namespace App\System\SecuritySystem;

use App\System\SecuritySystem\Contracts\Security;

class SecurityTool
{
    /**
     * Security インターフェース
     */
    private $security;

    /**
     * Security インターフェース
     * DI(依存性注入)
     *
     * SecurityTool constructor.
     * @param Security $security
     */
    public function __construct(Security $security)
    {
        $this->security = $security;
    }
}
?>
<?php
$securityTool = app('security');
?>

この場合はクラスやインスタンスの制御はすべて DI(依存性注入、IoC コンテナー) が行なっています。つまり、プログラマはサービス受ける側です。

依存性注入はデカップリングによく使われています。

インターフェース指向プログラミング

依存関係反転も制御の反転も依存性注入もインターフェース指向プログラミングの領域です。インターフェース指向、即ち抽象指向です。 哲学で考えると、決まりが少ない方は抽象的と呼ばれていて、決まりが多い方は具体的と呼ばれています。インターフェースは、典型的な「抽象的」形式のプログラムです。 振り返ってみると、プロセス指向プログラミングは低凝集で高結合になってます。オブジェクト指向プログラミングは高凝集で高結合になってます。 そして、インターフェース指向プログラミングは高凝集で低結合です。

※本記事は『日本語ドキュメント作成スタイル基準規約』に基づいて作成されています


TOP