凝集度と結合度について

 凝集度と結合度という概念は、コンスタンチンとヨードンが、その共著である「構造化設計」において提案した関数の尺度です。言い換えれば、これらは構造化設計の中心的テーマで、構造図を書くのも、設計時にこの尺度で判断して品質を織り込むためなのです。以下に、これらの尺度について簡単に説明します。

 またこれらの尺度は、オブジェクト指向の時代に入って、残念ながらあまり省みられなくなりましたが、メソッド内で関数が階層構造になる場合の関数の尺度には、そのまま有効ですし、表現はちがっても、クラスやオブジェクトの関係や、適切な大きさを判断する際にも有効です。

 保守作業に伴って品質の低下を招く危険は、構造化の言語であろうと、オブジェクト指向の言語であろうと同じです。


    凝集度(コヒージョン)

 これは、プログラムのひとつのコンポーネント(以下、関数と呼ぶ)の中に含まれる機能の純粋度を表す尺度です。保守を考えたとき、ひとつの関数の中で、いくつもの機能が混ざり合っているよりも、上手く機能分割されて、機能と関数が1対1になっている方が理解しやすいし、修正の際に他の部分に悪い影響を与える機会は確実に減ります。

 コンスタンチンとヨードンは、凝集度を以下のように7つのレベルに分類し、その危険性を指摘しています。なお、凝集度はレベルの高い方を良しとします。

 レベル7:機能的凝集

 ここでは関数はひとつの機能を実行するようになっています。すぐ下の階層に機能的凝集度をもつ幾つかの関数を管理するだけの関数もまた、機能的凝集度をもつと認定されます。

 できるだけ、機能的凝集になるように設計するのですが、システムに於て、全ての関数を機能的凝集にすることは出来ないし、また必ずしも適切とはいえません。

 レベル6:逐次的凝集

 これは、ひとつの関数の中に2つ以上の機能が含まれていますが、それらの機能は関連性が高く、またその順に処理していくことに意味があり、それが理由でひとつの関数の中に収まっている状態です。それらの機能を分割しても、特段に利用価値が高まるわけではないし、そのことに意味も認めにくいものです。ただし、だからといって理解度を妨げる程の大きさになってしまえば、障害の方が大きくなるので注意が必要です。

 レベル5:通信的凝集

 これは逐次的凝集と良く似て、あるデータの処理をする機能が集まっているのですが、処理の順序性には強い制限はない状態です。通信的凝集と逐次的凝集は、厳密に分ける意味は小さいかも知れません。ちなみに、マイヤーが提唱している『複合設計』では、この二つは、「連絡的強度」としてひとつに扱われています。

 レベル4:手順的凝集

 これは、外見上は通信的凝集に似ていますが、共通するデータを扱っているわけではなく、単に処理の順番になっているものを幾つかまとめてひとつの関数の中に収めた状態です。その中に含まれる機能も、別にひとつの関数の中に含まれる必要はありません。この状態は、上位の関数が一連の処理を実行するために、複数の関数を呼びだすのが面倒になったとき、幾つかの機能を、処理の順番にひとつの関数に収めたようなときに起きます。

 この辺りから、関数の再利用性は非常に悪くなってしまい、保守作業に支障を来すようになるので注意が必要です。

 レベル3:一時的凝集

 これは、初期処理とかエラー処理などのように、ある時点だけ実行する関数の中に、幾つかの機能が含まれている状態です。システムに於ては、このような凝集度を持つ関数は避けられないかも知れませんが、関数の再利用性も悪く、気をつけておかないと、同じような処理の関数を別に作ってしまう危険があります。凝集度のレベルが低く扱われているのは、そのためでです。

 レベル2:論理的凝集

 これは、そこに含まれる幾つかの機能が、外から(上位関数から)の指示で選択されるのが特徴です。つまり、ひとつの関数の中に含まれている幾つかの機能が、一回の呼び出しでシーケンスに処理されるのではなく、選択された機能だけが実行される状態です。もちろんこの種の処理も、システムにおいては無くすことは出来ないかも知れませんが、一旦、不用意に関連する機能を集めて、この凝集にしてしまったとき、以後の修正作業において、まるで“磁石のように”類似の選択機能を呼び集めます。また処理の選択方法がちょっとでも分かりにくいと、一気に理解度を低下させることになります。

 この型の凝集をもつ関数は、意外と頻繁にかつ不用意に使われており、実際に、保守の度に品質を低下させていることも確かです。

 レベル1:偶発的凝集

 これは、全くいい加減な関数で、そこに含まれる幾つかの機能には何の脈略もありません。メモリー・サイズを縮める必要から、単に共通するパターンを集めてひとつの関数としたときに起きるものです。

 今日では、殆ど問題にする必要はないかも知れません。



    結合度(カップリング)

 これは、プログラムの中で呼びだし関係にある2つのコンポーネント(以下、関数と呼ぶ)の関係を表す尺度で、保守に伴う修正作業による副作用などを未然に防ぐために、情報の受け渡しが出来るだけ「疏結合」の状態を目指します。

下位の関数が処理を始めるとき、何らかのデータを渡す必要がありますが、それをどのような形(方法)で受け渡しするかが問題なのです。

 コンスタンチンとヨードンは、結合度を以下のように6つのレベルに分類し、その危険性を指摘しています。なお、結合度はレベルの低い方を良しとします。

 レベル1:データ結合

 2つの関数は、相互にデータの「要素」だけで交換している状態です。ポインタで渡すときも、要素のポインタであれば、データ結合と見做します。

 この方法で受け渡しされている関数は、ソースを見たときに、そこに受け渡しされるデータの全てが現れており、それ以外には存在しないことで、関数の処理の理解性が高くなり、それだけ間違いにくくなります。とは言っても、全ての関数の関係をデータ結合にすることは必ずしも適切ではありませんので注意して下さい。

 レベル2:スタンプ結合

 これは、受け渡しされているデータの中に、使用されないものが含まれている状態です。下位の関数が論理的凝集度を持つ関数などの場合、データ要素で渡していても、条件によってはダミーのパラメータが存在したりします。その意味で、構造体で渡している時も、それだけでスタンプ結合と判断されます。構造体渡しの場合、渡された構造体の要素が、呼び出された関数の中で全が処理の対象とは限らないからです。もちろん、この場合も、善玉のスタンプ結合と悪玉のスタンプ結合があります。極端な例として、“万能”の構造体を作って、何時でも誰でも、それを渡せば、必要なデータはそのなかの何処かに入っているというような、最悪な構造体を見ることもあります。この場合の問題は、修正の際にデータが“そこ”にあるために、不適切な場所で処理を変更してしまうことに繋がりやすいことです。

 したがって構造体で渡すときには、ハンドリングしやすく、また変更に強いように構成要素を吟味する必要があります。

 レベル3:制御結合

 これは、下位の関数に対して、処理を分ける為のデータ(制御データ)を渡している状態です。したがって、このとき、下位の関数は「論理的凝集度」を持つことになります。

 システムにあっては、制御結合は必ずしも避けることは出来ません。したがって、「論理的凝集度」を持つ関数になるとしても、そこに集まっている機能などを慎重に検討して、少しでも“ましな”「論理的凝集度」にすることです。

 レベル4:ハイブリッド結合

 これは、関数間で受け渡しされるデータに、状況によって異なる意味をもたせたり、ハイブリッド構成で複数の意味を持たせている状態です。メモリやCPUなどのリソースの制限が厳しかった時代には、やむを得なかったかも知れませんが、今日では修正時に混乱を招くだけです。

 エラーなどの時に関数のリターン値を「マイナス値」にすることも、厳密な意味ではハイブリッド結合になります。その後の仕様の変更で、関数の正しいリターン値として、最上位ビットが「1」の値が許されるように変更されたとき、間違いなく混乱します。したがって、そのような可能性があるか/ないかを、十分に検討する必要があります。

 レベル5:共通結合

 これは、グローバル・データで関数間の受け渡しをしている状態です。パラメータ渡しと混じっていても、共通結合と見做されます。特に、異なるプライリティを持つタスク(スレッド)間での共通結合は最悪な結果を招きやすいので注意が必要です。同一タスク内にあっても、グローバル・データを直接操作することは、思わぬ操作をしてしまったり、また問題の発見を遅らせる原因になります。

 また、プログラム自身も、理解性を大きく損ねるため、是非とも避けたいものです。オブジェクト指向言語にあっても、この事に起因する問題は同じです。

 レベル6:内容結合

 これは、自身の所有するデータが、他の関数から直接操作される状態です。アセンブラの時代には、しばしば見られましたが、今日の言語ではあまり見かけなくなりました。しかしながら「C言語」などで、自身のローカル変数のアドレスを下位の関数に渡し、そこからポインタを介して直接データを返してもらうような処理は珍しくはありません。その場合、そのための専用のローカル変数を用意すれば問題ないのですが、それが面倒とばかり、直接アルゴリズムに関わっているローカル変数のアドレスを渡してしまうケースは、「内容結合的」でもありますので、注意して下さい。


「キーワード・リスト」にもどる