疎結合の正体見たり

モジュールが疎結合になっているとか密結合になっているとか、業界にいますとよく聞きます。モジュール間の結合度の定義を発見したのでメモしておきます。

モジュール A の モジュール B に対する結合度 C_{AB} は以下の式で定義できます。

C_{AB} =  - \sum_{h  \in H_{AB}} \log  \big( 1 - P_h \big)

ここで、 H_{AB}AB に対して持つ仮定の集合、P_h は仮定 h が成立しなくなる確率です。

要するに、これは情報エントロピーを用いて結合度を定義しようとしていまして、C_{AB} は0以上の値を取り、結合度の値が大きいほどモジュール間の結合が密となります。

そもそも、モジュール間の結合度というものが定義されていなかったので、その定義を発見したことに意味があります。

さらに、この定義が便利なのは有名な設計原則を説明できてしまうことです。以下のようなものを聞いたことがあると思います。

  • デルメル原則
  • リスコフの置換原則
  • ハリウッド原則
  • 驚き最小の原則

これらはだいたい同じことを言っています。依存する側が持つべき仮定のことを仕様といいまして、依存する側はできるだけ少なく、覆ることがなさそうな仮定の上でシステムを作れと言っています。結合度 C は、仮定の数が少なく、仮定が成立し続ける蓋然性が高いほど、小さくなります。

簡単のために原則でない例から挙げていきますと、REST API でシステム間を疎結合にしたマイクロサービスとか良くいいますよね。API 仕様が安定で、HTTP プロトコルが廃れる可能性が低ければ、結合度 C は小さくなります。逆に、システム間で、データベースやソースコードを共有していたりすると、それらが変わる可能性は高いので、C が大きく密結合になります。

他にも、Java の標準ライブラリに密結合しているとは誰も言わないのは、Java はサービスの寿命が続く限り使い続けることができて、Java の標準ライブラリは安定していて変わる心配をする必要がないからです。一方で、 Kotlin が廃れる可能性は高いので、Kotlin で作ったアプリは Kotlin と密結合だと言ってよいでしょう。

原則に戻りますと、デルメル原則は別名で最小知識の原則といいまして、結合度 C の定義でいうところの、仮定の数を少なくしましょうという法則です。仮定の数が少なければそれだけ疎結合になります。

リスコフの置換原則・ハリウッド原則についても、これらはインターフェース仕様にのみ依存して、実装が変わっても動くように作れといっています。インターフェース仕様は安定していて、変わる可能性は低いので、 P が小さくなり、疎結合が実現できます。一方で、不安定なインターフェース仕様だと、余計に密結合になってしまうでしょう。

驚き最小の原則も、要するにびっくりしない仕様は普通の仕様なので、安定しているから、それに依存すれば疎結合だよねという意味です。

他の例を上げれば、バグがあって、バグを回避するように奇妙な細工をしたら、バグを改修された動かなくなります。バグが直らないという不確かな仮定の上に作られているので、結合度は大きいです。あるいは、他のライブラリの private な変数をリフレクションで覗いて、それにロジックに組み込むという正気を疑うコードを昔見たことがあります。当然のように相手のバージョンが上がったら動かなくなってましたが、これも不確かな仮定の上に作られた密結合な作りです。

設計を評価するのに、今回発見した結合度の定義は大変有用性が高いように思います。