結合の種類と結合度

先日に発見したモジュールの結合の定義を自身でも気に入ったので、よく知られている結合の種類をこの定義に当てはめてみようと思います。

モジュールの結合にはいくつかの種類がありまして、以下のものが知られています。

  • 内部結合 content coupling
  • 共通結合 common coupling
  • 外部結合 external coupling
  • 制御結合 control coupling
  • スタンプ結合 stamp coupling (data-structured coupling)
  • データ結合 data coupling

名前が変だし、それぞれの関係性も分かりづらいので、この分類はあまり好きではないのですが、他のもっと良い分類をしらないので、とりあえずこれを使います。

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

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

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

それぞれの結合の種類が、この結合度の値にどう影響するのか考察していきましょう。

内部結合 content coupling

あるモジュールと別のモジュールは、普通はインターフェースによって接しています。Java の interface だけでなくて、関数やクラス/メソッドといった、インターフェースを定義して外部に公開できる機能がプログラミング言語には用意されています。普通の人は、この機能を使って他のモジュールと自身のモジュールを結合させます。

しかし、インターフェースという概念すら理解していない人は、それを無視して、リフレクションで private な変数を取ってきて、使ったりします。こういう他のモジュールの内部に結合するような種類を内部結合と呼びます。

インターフェースは変更をすれば、呼び出し側も変更しなければならないので、あまり頻繁に変更しようとは思いません。しかし、内部は仕事をしていれば変更されます。モジュールの内部が変更されないという仮定は高い確率で成立しなくなるので、モジュール間の結合度 C は非常に大きくなります。

プログラミング言語の機能で普通すればできないようになっているので、意図して汚く脆弱に作ろうとでもしないかぎり、内部結合をつくってしまうことはないと思いますが、決してそんなことはしてはいけません。

共通結合 common coupling

いわゆるグローバル変数を共有している状態です。グローバル変数を使うときは、他のモジュールがこちらの意図どおりにグローバル変数を書き換えている/いないことを仮定しています。この仮定は壊れやすいので、グローバル変数を共有しているモジュールの結合度は高いです。

もし、グローバル変数が immutable であれば、意図しない書き換えが起こる可能性は低いので、疎結合は保たれます。例えば、設定変数はグローバルに持っていてもよいでしょう。

外部結合 external coupling

2つのモジュールが、ファイルやプロトコルといったもので結合している状態です。モジュールというかシステム/サービスの結合の話のようです。これだけ毛色が異なります。

なお、出典によって、例えば 『ずっと受けたかったソフトウェア設計の授業 』と wikipedia 、意味が違うのですが、wikipedia の方を採用しました。原典に当たるほどでもないし、正しいとかないので。

制御結合 control coupling

あるモジュールが他のモジュールの動きを制御している状態です。例えば、クラスに○○モードみたいな名前の変数があって、それを変えると動きが変わるような場合です。

制御する方は制御される方がどう動くのか知りすぎた状態になるので、この2つのモジュールの結合度はやや高くなります。抽象化が十分でなくて、中身が漏れているイメージですね。制御用の変数が増えて、変な if 文が追加されていきそうなのが、想像できます。この場合は、制御する方もされる方も不安定な I/F に依存していることになります。

インターフェースを抽象化して、ポリモーフィズムなどを使って、知り過ぎない状態にするのが解決方法です。

スタンプ結合 stamp coupling (data-structured coupling)

なぜスタンプなのかはよく分かりません。データ構造で結合している状態とは、引数や返り値に独自に定義したクラスなどを使って、それで2つのモジュールが通信している状態などです。必ずしも悪いわけではないが、不必要な型で結合するのは、要らない仮定を持ち込むことになります。仮定の数が多ければ結合度が高まります。

1つのフィールドしか要らないのに、データ構造を丸々渡してしまうのは、良くないでしょう。例えば、 this を引数に渡すのは乱暴さを感じませんか?

また、引数には複数の値が渡せますし、返り値としても Python などは多値を返せます。意味もなく型を共有せずに、基本的な型だけのインターフェースの方が、使いやすくなることもあるということは知っておいた方がよいでしょう。

データ結合 data coupling

必要なデータだけを交換して結合している状態です。要らない仮定がなくて、仕様が安定している状態なので、結合度が低いです。