モジュール性
結合度
結合度 (coupling) とは、コンポーネントやモジュール内の依存関係の強さを表す指標である。結合度の高いモジュールは密結合 (tight coupling) と呼ばれ、他のモジュールに依存する度合いが高く影響を受けやすいことから変更やテストが困難になる傾向がある。反対に結合度の低いモジュールは疎結合 (loose coupling) と呼ばれる。
- データ結合
-
モジュール間の依存関係が必要不可欠なデータのみを共有する通信に基づいている。例えばハッシュ化するメッセージのみをパラメータとして取るハッシュ関数を呼び出しているケースはデータ結合である。
- スタンプ結合
-
モジュールが複合データ構造を共有し、その複合データ構造には機能に不要なデータが含まれている。例えばあるソフトウェア機能のためにデータベースから読み込んだ完全なレコードを渡しているが、実際にはその中のいくつかのフィールドのみしか使用しないケースが該当する。データのカプセル化や処理効率の理由でこの結合を選択することがある。
- 制御結合
-
モジュールが何をすべきかに関する動作情報を共有する。特に挙動の全く異なる複数の機能を制御パラメータを渡すことによって切り替える設計は問題となる。ただし、制御パラメータが機能の分解と再利用を促進するのであれば良い場合もある。例えば比較関数を渡すソート機能がその例である。
- 外部結合
-
モジュールがソフトウェアやシステムの外部の機能に依存している。システム外部の機能とは、特定の場所にあるファイルや、外部ライブラリ、API サーバ、データベース、OS やハードウェアなどが該当する。外部結合を持つモジュールは実行環境に対する制約が存在するため変更やテストが困難になる傾向がある。
- 共有結合
-
モジュールがグローバルな状態を共有している。例えばすべてのモジュールが同じグローバル変数や共通のデータベースを参照しているケースが該当する。この結合は、問題や変更の影響を評価するためにはそのデータにアクセスするすべてのモジュールを追跡する必要があり保守性と再利用性が低下する。
- 内容結合
-
モジュールが他のモジュールの内部実装や内部状態に依存している。例えばあるモジュールが他のモジュールの内部データ構造を直接参照したり変更するケースが該当する。この結合はモジュールの内部実装に対する依存関係が強く、モジュールの変更によって他のモジュールに影響を与えるため保守性が著しく低下する。
凝集度
凝集度 (cohesion) とは、コンポーネントやモジュール内の要素がどれだけ関連する責務でまとまっているかを表す指標である。これはモジュールの品質を表し、凝集度の高いモジュールは関連性の高い責務がまとめられているため変更やテストが容易になる傾向がある。
- 機能的凝集
-
単一の機能に必要な関連する要素だけでモジュールが構成され、モジュールが機能するために必要不可欠なものが全て含まれている。理想的な状態。
- 逐次的凝集
-
モジュール内のある要素は、他の要素への入力となるデータを出力する。つまりモジュール内の要素が部品化されている。これは関数型プログラミングでは自然に発生する構成である。
- 通信的凝集
-
モジュール内の複数の要素が通信の連鎖を形成し、それぞれで情報を操作したり何らかの出力に貢献する。例えば、ある要素がデータベースのレコードを追加し、別の要素がその情報に基づいて電子メールを生成するなど。
- 手続き的凝集
-
モジュールに含まれる要素に機能的な関連はないが特定の順序で実行する必要がある。例えば売り上げを集計し、店舗ごとの記録を保存し、今月の営業利益を計算し、全体のレポートを印刷するといった一連の手順に相当する機能がまとめられているケースなど。
- 時間的凝集
-
モジュール内の処理に機能的な関連はないが時間的関連がある。例えば多くのシステムにはシステム起動時に初期化しなければならない、一見無関係な処理群が存在している。これらの処理には時間的な凝集があるといえる。
- 論理的凝集
-
モジュール内のデータは論理的に関連しているものの、機能的には関連していない。例えばテキストやシリアライズされたオブジェクト、またはストリームを情報へ変換するモジュールでは、これらは操作の上では関連があるものの、機能的には全く異なる。この種の凝集の典型的な例がほぼすべてのプロジェクトに存在する StringUtils モジュールである。殆どの場合、StringUtils モジュールは String を操作するということでは関連しているものの、互いには関連していない静的メソッドの集まりである。
- 偶発的凝集
-
モジュール内の要素は、同じディレクトリやソースファイル上にある以外には関連がない。これは無造作に処理が集められたモジュールであり、最も最悪な形の凝集を表している。
凝集度の指標は評価者の裁量に依存する要因が大きく一般に客観性は薄い。
コナーセンス
コナーセンス (connascence) はコンポーネント間の合意や接続方法の変更に対する影響を表す指標である。ある要素を変更したとき、ソフトウェア全体が正しく機能するために他の要素の変更を必要とする場合、それらの要素の間にはコナーセンスがあるという。コナーセンスの度合いが低いほど変更に対する難易度は低くなる。
- 名前のコナーセンス
-
エンティティの名前に合意する。例えばあるモジュールが
Customer
という名前のクラスを定義している場合、他のモジュールはCustomer
という名前でそのクラスを参照することになる。このときCustomer
という名前に対するコナーセンスがあると言う。一般に名前の合意は最もコナーセンスが低い。現代的な IDE のリファクタリング機能を利用して簡単に識別子を変更することができる。また静的型付け言語では変更を漏れた名前はコンパイルエラーとして検出できる。
- 型のコナーセンス
-
エンティティの型に合意する。例えばある関数が
Account
型のパラメータを取る場合、他のモジュールはAccount
型のデータを渡す必要がある。このときAccount
という型に対するコナーセンスがあると言う。 - 意味のコナーセンス
-
エンティティの表す意味に合意する。例えば数値 0 を
false
(偽) として扱うという合意がある場合、0 に対する意味のコナーセンスがあると言う。 - 位置のコナーセンス
-
エンティティの現われる順序に合意する。例えば関数呼び出し時のパラメータの順番が位置のコナーセンスに相当する。
- アルゴリズムのコナーセンス
-
使用するアルゴリズムに合意する。例えばサーバが SHA3-256 ハッシュアルゴリズムを使用してデータの破損を検出するためクライアントは SHA3-256 でハッシュ値を送信しなければならないという合意がある場合、クライアントとサーバにはアルゴリズムのコナーセンスがあると言う。
- 実行順序のコナーセンス
-
あるモジュールを利用するときに、その要素の実行順に合意がある。例えばあるモジュールが
exec()
関数を提供している場合、そのexec()
関数を呼び出す前にinit()
関数を呼び出す必要があるという合意がある場合、実行順序のコナーセンスがあると言う。 - タイミングのコナーセンス
-
あるモジュールを利用するときに、その要素の実行タイミングに合意がある。例えば時間のかかる集計の後に印刷するシステムで、印刷前であれば印刷をキャンセルできる場合、キャンセル機能の実行に対してタイミングのコナーセンスがある。非同期環境やイベント駆動設計でのテストの困難さは、このようなタイミングのコナーセンスが頻出することが大きな要因である。
- 値のコナーセンス
-
複数のモジュールが同じエンティティを参照しているケースで、その操作に対して合意がある。例えばメモリ上で共有しているデータ構造の排他制御や処理の順序など。
- アイデンティティのコナーセンス
名前、型、意味、位置、アルゴリズムのコナーセンスは静的、実行順序、タイミング、値、アイデンティティのコナーセンスは動的という分類がある。ソフトウェア設計は静的なコナーセンスに傾倒すべきである。コナーセンスが低くなる方向に向けて設計やリファクタリングを進めることで変更に対する耐性を上げることができる。
参考文献
- RICHARDS, Mark; FORD, Neal. ソフトウェアアーキテクチャの基礎 ―エンジニアリングに基づく体系的アプローチ. O'Reilly Media, 2020.