イントロダクション:結合度を一言で言うと? 🔗
結合度(Coupling)とは、
「モジュール同士がどれくらい強く依存し合っているか」
を表す指標です。
ここでの「モジュール」は、クラス・関数・ファイル・パッケージなど
「ひとまとまりの単位」
だとイメージしてください。
あるモジュールを変えたときに、
他のモジュールまで次々と直さないといけない状態が「結合度が高い」
と言い、
影響を受ける範囲が小さく済む状態を
「結合度が低い」
と言います。
設計の世界では、
「高凝集・低結合」
が理想とされます。
1. なぜ「結合度」が大事なの? 🧯
モジュール間の結合度が高いと、次のような「現場あるある」が起きやすくなります。
-
ちょっとした修正が連鎖する
Aモジュールを直したら、BもCも動かなくなり、あちこちを連鎖的に修正する羽目になる。
-
影響範囲が読みにくい
「この変更はどこまで影響するのか?」が分かりづらく、
レビューやテストの工数が膨らむ。
-
置き換え・再利用がしにくい
別の実装に差し替えたいだけなのに、細かい前提条件までベッタリ結びついていて、
差し替えが大工事になってしまう。
逆に、結合度が低い設計では、
「ここを変えたら、主にここだけが影響を受ける」
と考えやすくなり、
変更やリファクタリングがかなり楽になります。
疎結合なアーキテクチャを目指すうえで欠かせない観点です。
2. 結合度のレベル(強い → 弱い)
結合度にも、いくつかの段階があります。
ここでは、クイズで使っているレベル分けに沿って、
レベル1(最悪)からレベル6(望ましい)
までを整理します。
レベル1:内容結合(最悪)
他モジュールの内部に直接アクセスしたり書き換えたりする状態(最悪) です。
片方を直すともう片方も壊れやすく、
モジュールの「中身」にべったり依存しているため、変更に極端に弱くなります。
レベル2:共通結合
グローバル変数(どこからでも見える変数)を一緒に使っている状態 です。
どのモジュールがいつ値を変えたのか追いにくく、
予期しない副作用の温床になりやすい結合です。
レベル3:外部結合
外部宣言(publicな変数・関数など)を共有している状態 です。
インターフェースは表向きに見えているものの、
まだ結びつきは強く、
「相手の公開メンバーの細かい都合」に引きずられやすい段階です。
レベル4:制御結合
フラグ(0/1などの切り替え変数)で相手の振る舞いを細かく指示している状態 です。
「このフラグが1の時はこう動いて、0の時はこう動く」といった前提で組んでいるため、
相手モジュールの中身をよく知っている必要があり、結局べったりな関係になりがちです。
レベル5:スタンプ結合
構造体やクラスなどの複合データを丸ごと渡してしまう状態 です。
呼び出し側はそのうちの一部の項目しか使わないのに、
実際には使わない項目まで含まれてしまっています。
「何を本当に必要としているのか」が曖昧な結合です。
レベル6:データ結合(望ましい)
本当に必要な単体の値(スカラ値)だけを引数として渡す状態 です。
モジュール間でやり取りする情報が最小限でハッキリしており、
もっとも望ましい結合度とされています。
実務では、常にデータ結合だけにできるわけではありませんが、
「内容結合や共通結合は避ける」「スタンプ結合を減らし、できるだけデータ結合に寄せる」
といった意識が設計の質を大きく左右します。
3. 結合度を下げる(疎結合に近づける)ための考え方 🪄
結合度を下げるポイントは、
「相手モジュールの中身・事情をできるだけ知らなくて済むようにする」
ことです。
-
インターフェースでやり取りする
クラスや関数の「中身」ではなく、
「こう呼べば、こう振る舞う」
という外側の契約(インターフェース)を決めて、その経由でやり取りするようにします。
-
本当に必要なデータだけ渡す
「とりあえず全部渡しておく」ではなく、
「この処理に必要な値」だけ
を引数にすることで、スタンプ結合をデータ結合に近づけます。
-
フラグで振る舞いを細かく指示しすぎない
「このフラグが0の時はこっち、1の時はあっち」のような制御結合が増えてきたら、
ポリモーフィズムや戦略パターン などを検討し、
「相手に任せる設計」に変えられないか考えます。
-
依存の向きを統一する(DI / DIP)
具象クラスに直接依存せず、
抽象(インターフェースや抽象クラス)
に依存するように設計すると、
実装の差し替えがしやすくなり、結合度も下げやすくなります。
図解で見る「高結合度」と「低結合度(疎結合)」
モジュール同士が「グローバル変数や内部実装にベッタリ依存している状態(高結合)」と、
「インターフェースと最小限のデータだけでゆるくつながっている状態(低結合)」をイメージできる図解です。
高結合度:相手の中身や共通状態にベッタリ依存している例
モジュール同士が同じグローバル変数を共有し、
フラグで細かく振る舞いを指示し合い、
相手の内部実装まで前提にしているイメージです。
1つを変えると他も連鎖的に壊れやすくなります。
モジュールA
Bの内部フィールドを書き換えたり、
debugフラグを直接切り替えたりしている。
共通グローバル変数
フラグで細かく指示
内部フィールドへの直接アクセス
モジュールB
外から直接いじられてしまう前提で作られており、
内部構造を変えにくい。
「相手の事情をよく知っていないと動かない関係」
になっていて、変更のたびに双方の調整が必要になるイメージです。
低結合度(疎結合):契約と最小限のデータだけでつながる例
モジュール同士が「こう呼べば、こう返す」というインターフェースだけを共有し、
本当に必要なデータだけを受け渡しするイメージです。
内部実装はお互いに知らなくてもよくなります。
モジュールA(データ取得)
ユーザー情報を取得し、インターフェースが約束する形
(例:id と displayName)で返すだけを担当する。
インターフェース / 契約
最小限のデータ
id・名前など明示的な引数
モジュールB(表示)
渡されたデータをどう表示するかだけに集中し、
「どこから来たデータか」や「内部の保存形式」は気にしない。
「相手の中身を知らなくても使える関係」
になっているため、片方を差し替えたり、内部実装を変えたりしても、
インターフェースさえ守れば影響を最小限にできます。
JavaScriptで見る「強い結合」と「弱い結合(疎結合)」の例
ユーザー一覧の表示を題材に、「グローバル状態や内部実装にベッタリ依存した強い結合」と、
「インターフェース越しに最小限のデータだけやり取りする疎結合」の違いを示すコード例です。※コードのコメントを読むだけでも理解できるかと思います。
強い結合の例:相手モジュールの中身とグローバル状態に依存
グローバル状態(共通結合)とビューの内部フィールド(内容結合)を直接書き換え、
フラグで振る舞いを細かく指示する(制御結合)など、結合度が高い例です。
// 強い結合の例:モジュールAがモジュールBの中身やグローバル状態に依存している
// 共通結合:複数モジュールから直接触れるグローバル状態
const appState = {
users: [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'member' }
],
debugMode: true
};
// モジュールB:ユーザーリストを表示するビュー
const userListView = {
elementId: 'highCouplingList',
// 本来は外から触られたくない内部情報
_internalTemplate: '<li>{id}: {name} ({role})</li>',
render(users, showIdFlag, showRoleFlag) {
const ul = document.getElementById(this.elementId);
ul.innerHTML = '';
// グローバル状態に依存
if (appState.debugMode) {
ul.style.border = '1px dashed red';
}
users.forEach(user => {
let label = user.name;
// 制御結合:フラグで細かく振る舞いを指示
if (showIdFlag) {
label = user.id + ': ' + label;
}
if (showRoleFlag) {
label += ' (' + user.role + ')';
}
const li = document.createElement('li');
li.textContent = label;
ul.appendChild(li);
});
}
};
// モジュールA:ビューの内部事情まで知っているコントローラ
function showUsersHighCoupling() {
// 内容結合:ビューの内部フィールドを書き換えている
userListView.elementId = 'highCouplingList';
userListView._internalTemplate = '[DEBUG] {id}: {name}';
// グローバル状態もここで直接変更
appState.debugMode = document.getElementById('debugFlag').checked;
const showIdFlag = true;
const showRoleFlag = document.getElementById('showRoleFlag').checked;
// users 配列をそのまま渡していて、「何が必要なのか」が曖昧(スタンプ結合寄り)
userListView.render(appState.users, showIdFlag, showRoleFlag);
}
弱い結合(疎結合)の例:契約と最小限のデータだけでやり取り
「ユーザー情報をどう取得するか」と「どう表示するか」を分け、
必要な情報だけを引数で渡すことで、モジュール間の結合をゆるくした例です。
// 弱い結合(疎結合)の例:モジュール間のやり取りを「契約」と最小限のデータに絞る
// データ取得モジュール:公開関数の戻り値が契約になる
function loadUserSummaries() {
const users = [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'member' }
];
// ビューに本当に必要な情報だけを渡す形に変換しておく
return users.map(user => ({
id: user.id,
displayName: user.name
}));
}
// 表示モジュール:必要な情報だけを引数でもらう
function renderUserList(element, summaries, options) {
element.innerHTML = '';
summaries.forEach(summary => {
const li = document.createElement('li');
li.textContent = options.showId
? summary.id + ': ' + summary.displayName
: summary.displayName;
element.appendChild(li);
});
}
// UI をつなぐ薄い層:依存の向きをまとめる
function showUsersLowCoupling() {
const ul = document.getElementById('lowCouplingList');
const showId = document.getElementById('showIdFlagLow').checked;
// データ取得 ※中身(どこから取っているか)はここでは気にしない
const summaries = loadUserSummaries();
// 表示
renderUserList(ul, summaries, { showId });
}
まとめ:結合度を「モジュール間のつながり」の物差しにする
-
結合度は、
モジュール同士がどれくらい強く依存し合っているか
を表す指標で、低いほど疎結合な設計に近づきます。
-
内容結合・共通結合のような強い結合は避け、
できるだけデータ結合に寄せていく
ことが、変更に強いコードへの第一歩です。
-
「相手の中身を知らなくても使えるか?」
を基準に設計を見直すと、
インターフェース設計・依存関係・引数の取り方などが自然と整理されていきます。
-
凝集度と合わせて、
「高凝集・低結合」
をモジュール設計のゴールラインとして意識することで、
長く保守しやすいシステムに近づけます。
まずは、
「このモジュール同士、本当にここまでくっついている必要がある?」
と問いかけるところから、
結合度の感覚を育てていきましょう。