イントロダクション:疎結合を一言で言うと?🔗
疎結合(そけつごう)とは、「お互いに強く依存しすぎないように部品同士をつなぐこと」です。
簡易まとめ
- 密結合:実装にベッタリ依存 → 変更が連鎖しやすい
- 疎結合:契約(インターフェース)に依存 → 影響範囲を局所化しやすい
- 近づけるコツ:IF(インターフェース)/責務で分ける、依存方向を揃える、DIで差し替える
- AI時代:境界が明確だと「ここだけ直して」が通り、手戻りが減る
あるクラスやモジュールを変更しても、他の場所への影響(影響範囲)ができるだけ小さくなるようにする考え方で、
「変更に強い設計」を支える、とても大事な基礎用語です。
密結合は「アロンアルファでガチガチに固めた模型」、疎結合は「必要な時にパーツを組み替えられるレゴブロックのようなもの」と言えば、イメージが湧きやすいかもしれません。
疎結合は、人間とAIの共通言語を整える作業でもあります。
AIで爆速実装したはいいけど、修正を頼むたびに別の機能まで巻き込んで壊れたり、
「ここだけ直して」のはずが関係ない場所まで書き換わってしまったり…そんな経験ありませんか?
原因は、部品同士が相手の内部(実装の詳細)にベッタリ依存している=密結合になっていることが多いです。
依存が絡み合うと、どこが境界か分からず、AIも人間も影響範囲を見積もれないまま修正してしまい、
意図しない修正の混入や“手戻り”が一気に増えます。
だからこそAI時代では、影響範囲が小さい設計ほど、変更依頼やコード生成の指示を安全に進めやすくなります。
必要な文脈を絞って渡せるため、意図しない修正の混入や“手戻り”も抑えやすくなります。
1. なぜ疎結合が大事なの?(現場でよく効く理由)
疎結合になっていると、システムをあとから変更したり、部品を差し替えたりするときに、とても動きやすくなります。
逆に、部品同士がベッタリつながった「密結合」のままだと、次のような困りごとが起きやすくなります。
-
ちょっとした修正で壊れやすい
あるクラスを直しただけなのに、思わぬ別の機能まで一緒に壊れてしまう。
-
テストしにくい
1つのクラスをテストしたいだけなのに、他のクラスや外部サービスが一緒に動いてしまい、単体テストが書きづらい。
-
置き換え・差し替えが難しい
DBや外部API、UIなどを変えたいときに、あちこちのコードを書き換えないといけなくなる。
実務でAIに改修を依頼する場面でも、密結合な構造ほど関連ファイルをまとめて読ませる必要が出やすくなります。
結果としてトークン消費が増え、関係ない箇所の修正案が混ざる傾向も出ます。
疎結合は、AIに渡す情報をダイエットさせ、精度を上げる設計としても有効です。
疎結合を意識して設計しておくと、
「変更しやすい」「テストしやすい」「長く育てやすい」
システムに近づいていきます。
より高度なモジュール設計については、こちらの『凝集度(高凝集)』も併せてご覧ください
2. 疎結合と密結合の違い(一瞬でわかる比較表付き)
ざっくりとしたイメージとして、まずは比較表で押さえると分かりやすくなります。
疎結合と密結合の違い(結論:実装ではなく「契約」に依存する)
| 観点 |
密結合 |
疎結合 |
| 依存しているもの |
相手の実装の詳細(内部構造・具体クラス) |
契約(インターフェース/入出力の約束) |
| 変更の影響 |
変更が連鎖しやすい(思わぬ場所が壊れる) |
影響を局所化しやすい(直す場所を絞れる) |
| 差し替え |
差し替えにくい(修正が広がりがち) |
差し替えやすい(実装を入れ替えやすい) |
| テスト |
単体テストしにくい(モックしづらい) |
単体テストしやすい(モック/スタブが作りやすい) |
| AI時代の修正依頼 |
境界が曖昧で、AIも影響範囲を見積もれない → 手戻り増 |
境界が明確で、「ここだけ直して」が通りやすい |
補足として、もう少し噛み砕くと次のように捉えると分かりやすいです。
-
密結合:
クラスAがクラスBの「具体的な実装」にベッタリ依存している状態。
Bの中身や使い方が変わると、すぐAも壊れてしまう。
-
疎結合:
AはBの「インターフェース(外から見える約束ごと)」だけを知っていて、
中身の細かい実装にはなるべく依存しないようにしている状態。
現場では、
「この2つ、ちょっと密結合すぎない?」
といった会話で使われることが多いです。
「お互いを知らなさすぎて困る」のではなく、
「ほどよい距離感でつながっている」
ことがポイントです。
3. 図解で見る「密結合」と「疎結合」
疎結合と密結合の違いを、UI・実装A / 実装B・DB の4つのモジュールで図解します。
同じ登場人物でも、結合のさせ方によって変更のしやすさ / 壊れやすさが大きく変わります。
疎結合は、モジュール同士がお互いの詳細をあまり知らず、
必要最小限の情報だけでゆるくつながっている状態です。
一方で密結合は、モジュール同士が直接ベタベタにつながり、
どこか1か所を変えると周りも巻き込んで壊れやすい状態を指します。
UI・実装A・実装B・DB が互いに直接参照し合い、矢印だらけでベタベタにつながった状態。
どこか1つを変えると、他のモジュールも連鎖的に修正が必要になりやすい構成です。
UI はインターフェースにだけ依存し、実装A/B と DB はインターフェース経由でゆるくつながる構成。
誰か1つの詳細を変えても、他のモジュールへの影響を小さく保ちやすくなります。
4. JavaScriptで見る「密結合」と「疎結合」の例
ユーザー登録時の「お知らせ送信」を題材に、フォームやメール送信の具体実装にベッタリ依存した密結合の例と、
インターフェースで責務を分離した疎結合の例を比較できるコードスニペットです。※コードのコメントを読むだけでも理解できるかと思います。
密結合の例:具体的なメール送信処理にベッタリ依存するコード
フォームDOMの扱い・ユーザー作成・メール文面の組み立て・送信APIの呼び出しまで、
1つの関数&特定のモジュールにくっついている例です。
通知方法を変えたいだけでも大きな修正が必要になります。
// 密結合の例:登録とメール送信がベッタリくっついている
// メール送信モジュール(具体的なAPIに直結している)
const emailSender = {
send(to, subject, body) {
// 実際には fetch や外部サービスSDKなどを叩く想定
console.log('メール送信:', { to, subject, body });
// fetch('/api/send-email', { ... }) のようなコードがここに来る
}
};
// フォーム送信イベントから直接呼ばれるハンドラ
function registerUserTightlyCoupled(formElement) {
// 1. フォームから直接DOM操作で値を取得
const name = formElement.querySelector('[name="name"]').value;
const email = formElement.querySelector('[name="email"]').value;
// 2. ユーザー作成ロジック(ここではローカルストレージに保存)
const users = JSON.parse(localStorage.getItem('users') || '[]');
const newUser = {
id: users.length + 1,
name,
email,
createdAt: new Date().toISOString()
};
users.push(newUser);
localStorage.setItem('users', JSON.stringify(users));
// 3. メール文面の組み立てもここで実施
const subject = 'ようこそ!' ;
const body =
newUser.name + ' さん、登録ありがとうございます!\n' +
'あなたの会員IDは ' + newUser.id + ' です。';
// 4. 特定のメール送信モジュールにベッタリ依存
emailSender.send(newUser.email, subject, body);
// 5. UI 更新もここでやってしまう
const message = document.getElementById('tightlyCoupledMessage');
message.textContent = '登録とメール送信が完了しました(密結合の例)';
}
BeforeをAIに渡したときの傾向:
影響範囲が広く、意図や修正箇所が曖昧になりやすいため、周辺コードまで触る提案が混ざりやすくなります。
疎結合の例:登録処理と通知処理をインターフェースで分離
「ユーザーを登録する処理」と「登録されたことを通知する処理」を切り離し、
通知側はインターフェース(契約)で受け取るようにした例です。
メール以外の通知方法に差し替えるのも簡単になります。
// 疎結合の例:登録処理と通知処理をインターフェースで分離
// ユーザーリポジトリ:保存だけに責務を限定
const userRepository = {
save(userInput) {
const users = JSON.parse(localStorage.getItem('users') || '[]');
const newUser = {
id: users.length + 1,
name: userInput.name,
email: userInput.email,
createdAt: new Date().toISOString()
};
users.push(newUser);
localStorage.setItem('users', JSON.stringify(users));
return newUser;
}
};
// 通知インターフェースを「満たす」オブジェクトの例1(メール通知)
const emailNotifier = {
notifyUserRegistered(user) {
const subject = 'ようこそ!';
const body =
user.name + ' さん、登録ありがとうございます!\n' +
'あなたの会員IDは ' + user.id + ' です。';
console.log('メール通知:', { to: user.email, subject, body });
}
};
// 通知インターフェースを「満たす」オブジェクトの例2(コンソール通知)
const consoleNotifier = {
notifyUserRegistered(user) {
console.log('コンソール通知: 新規ユーザー登録', user);
}
};
// 疎結合なユースケース:
// 「ユーザーを登録して、通知を飛ばす」という振る舞いだけを担当
function registerUser(userRepo, notifier, userInput) {
const user = userRepo.save(userInput);
// notifier が notifyUserRegistered(user) を持っていることだけを契約とする
notifier.notifyUserRegistered(user);
return user;
}
// UI 層:フォームとユースケースをつなぐ薄い部分
function handleRegisterUserLooselyCoupled(formElement) {
// フォームから値を取得
const userInput = {
name: formElement.querySelector('[name="name"]').value,
email: formElement.querySelector('[name="email"]').value
};
// ユーザーを登録して、通知を飛ばす ※ここで自由に通知機能を差し替え可能に
registerUser(userRepository, emailNotifier, userInput);
// 通知
const message = document.getElementById('looselyCoupledMessage');
message.textContent = '登録と通知が完了しました(疎結合の例)';
}
AfterをAIに渡したときの傾向:
境界(責務/IF)が明確で、局所修正・安全な差分提案・テスト生成が安定しやすくなります。
AIへの指示例:「このIFを実装する新クラスを追加して。呼び出し側は変更しないで。」
5. 疎結合に近づけるための工夫 (境界・依存・DI)🧱
疎結合にするためのテクニックはいくつかありますが、代表的なものをいくつか挙げます。
インターフェースや抽象クラスをはさむ
具体的なクラスどうしを直接つなぐのではなく、
「このメソッドが呼べればOK」という抽象的な窓口
を用意してつなぐ方法です。
実装を差し替えたいときも、そのインターフェースを満たすクラスを用意するだけで済みます。
実務でAIを使うなら、インターフェースは「AIとの契約書」としても機能します。
「このインターフェースを実装するクラスを新規追加して」「既存の呼び出し側は触らず、差分は実装クラス側に閉じる」
と依頼しやすくなり、DI/IFの手間を安全な機能追加のしやすさで回収しやすくなります。
依存の向きをそろえる(依存方向を意識する)
ビジネスロジックが、UIやDBなどの「外側の都合」に引きずられないように、
中心にあるルールほど、外側の詳しい仕組みに依存しない ようにします。
依存の向きをそろえておくと、設計が整理されて見通しも良くなります。
コンストラクタやDIコンテナで依存を渡す
クラスの中で直接 new してしまうのではなく、
必要な相手は外から注入してもらう(依存性の注入:DI)
ようにすると、テスト用のダミーや別実装に差し替えやすくなります。
どのテクニックも共通して、
「片方を変えても、もう片方への影響は最小限にしたい」
という目的に向かっています。
通信層を分離する(htmxでHTMLを共通言語にする)
フロント側で複雑な状態管理を抱え込みすぎると、UIとロジックが密結合になりやすくなります。
htmxのようにサーバーから受け取ったHTMLを差し替える設計へ寄せると、
フロントとバックは「HTML」という共通言語でつながりやすくなり、通信層の疎結合に近づけます。
AI視点:
実務でAIを使うなら、インターフェースは「AIとの契約書」としても機能します。
差分を“実装側に閉じる”指示が通りやすくなり、既存コードを壊さずに機能追加しやすくなります。
- 『このインターフェースを実装するクラスを新規追加して』
- 『既存の呼び出し側は触らず、差分は実装クラス側に閉じて』
DI/IFが面倒に感じる場面でも、「AIへの指示が楽になる」メリットで回収しやすくなります。
実装パターンは、関連記事の
htmx逆引きレシピ
も参考にしてください。
6. 疎結合は“正義”ではない:やりすぎると起きる副作用
疎結合は「変更に強い設計」を作るうえで強力ですが、やりすぎると逆に
読みにくい・追いにくい・直しにくいコードになります。
大事なのは、必要な場所だけに効かせる“使い分け”です。
-
過剰な抽象化/DIで読みにくい
インターフェースやクラスが増えすぎると、処理の流れが追いにくくなります。
「結局どれが実体?」となると、理解コストが上がります。
-
境界が増えて追跡が難しい
責務分割が細かすぎると、修正時に複数ファイルをまたいで追跡する必要が出ます。
ログやデバッグの“入口”が分かりにくくなるのも典型です。
-
「変えない所」まで疎結合にしてしまう
変化が少ない領域まで抽象化すると、得られるメリットよりも、
間接化による複雑さ(学習コスト)の方が勝ちやすくなります。
使い分けのコツ:疎結合にする場所/しない場所
目安はシンプルです。「変える可能性が高い場所」だけ疎結合に寄せると、費用対効果が高くなります。
-
疎結合に寄せると得:外部API/DB/決済/通知など、差し替えや仕様変更が起きやすい所
-
寄せすぎなくてOK:小さく閉じた計算ロジックなど、変更頻度が低く見通しが良い所
AI視点:AIは「抽象化しておけば安全」と判断しがちなので、
“どこを疎結合にするか(境界)”を先に決めてから依頼すると、設計が暴れにくくなります。
より高度なモジュール設計については、こちらの『凝集度(高凝集)』も併せてご覧ください
7. AIに渡す「疎結合リファクタリング」プロンプト(1行)
疎結合は「影響範囲を小さく保つ」設計です。AIに依頼するときも、変更点を境界(IF/責務)に閉じるように伝えると、安全に進めやすくなります。
指示テンプレ(コピペ用):
影響範囲を最小にしたいので、(境界:インターフェース/責務)を守って、(変更対象)だけを修正してください。呼び出し側は変更せず、差分は実装側に閉じてください。
- 例:影響範囲を最小にしたいので、PaymentGatewayInterface を守って、Stripe 実装だけを追加してください。呼び出し側は変更せず、差分は実装側に閉じてください。
- 例:影響範囲を最小にしたいので、UserRepository の契約を守って、DB実装だけを差し替えてください。ユースケース層は変更しないでください。
ポイントは「境界(契約)」と「触っていい場所」を先に固定することです。AIの提案がブレにくくなり、意図しない修正の混入も減らせます。
よくある質問(FAQ)
Q. 疎結合と密結合の一番の違いは?
A. 依存の仕方が違います。密結合は「相手の内部(実装の詳細)」に依存しがちで、変更が連鎖します。
疎結合は「契約(インターフェース/入力と出力の約束)」に依存し、影響範囲を小さくしやすいのが特徴です。
Q. 疎結合にすると何が嬉しい?(メリット)
A. 主に3つです。①変更の影響範囲が小さくなる、②差し替えや機能追加がしやすい、③テストしやすい。
結果として、修正の手戻りやレビューコストを減らせます。
Q. どこまで疎結合にすべき?やりすぎの判断は?
A. 目安は「変える可能性が高い場所だけ」です。外部API/DB/通知/決済など“差し替えが起きやすい所”は疎結合の効果が大きいです。
一方、変化が少なく小さく閉じた処理まで抽象化すると、間接化が増えて読みにくくなる副作用が出やすいです。
Q. 疎結合にするにはDIやインターフェースが必須?
A. 必須ではありません。まずは「責務を分ける」「依存方向を揃える」「境界をまたぐI/Oを明確にする」だけでも疎結合に近づきます。
DIやインターフェースは“差し替え需要がある場所”で導入すると費用対効果が高いです。
Q. 疎結合にすると追跡が難しい・遅くなるって本当?
A. 一面では本当です。抽象化や境界が増えると、処理の流れを追うためのファイル数が増えたり、通信が増えると遅くなることもあります。
対策としては「境界を増やしすぎない」「ログの入口を用意する」「依存の向きを固定する」などで“追える疎結合”にします。
Q. 「影響範囲が読めない」状態を減らすには、まず何を直す?
A. まずは境界を1つ決めて、そこを越えるやり取りを“契約”に寄せます。
具体的には「I/Oを引数・戻り値で明確化」「外部アクセス(DB/API)を1箇所に寄せる」「直参照をやめて受け渡しにする」など、
変更が連鎖しやすい依存を切るのが最優先です。
Q. AIに修正を頼むとき、疎結合の観点で伝えるコツは?
A. 「どこが境界か」「何が契約か」を先に固定するとブレにくいです。
たとえば「この関数のI/Oは変えない」「このモジュールの責務はここまで」「この依存は増やさない」など制約を短く指定し、
差分が小さい単位で依頼すると“関係ない場所まで書き換わる”事故を減らせます。
まとめ:AI時代に疎結合が効く理由
- 疎結合は、変更の影響範囲を局所化し、AIの差分提案を安全に適用しやすくします。
- 責務・IF・入出力の境界が明確だと、AIに渡す文脈が絞れ、生成精度が安定します。
- 実装詳細から呼び出し元を切り離す設計は、人間にもAIにも保守しやすい土台になります。
一言でいえば、疎結合は「変更に強さ」と「AI活用の安定性」を同時に高める設計原則です。
おまけ:AIを迷わせない疎結合チェックリスト
-
影響範囲が小さい(変更点が局所化されている)
-
境界(責務、IF、入出力)が明確
-
依存方向が一方向(呼び出し元が実装詳細を知らない)
-
UI/状態管理を抱え込みすぎない(宣言的/通信層の分離)
-
AIに渡す情報を絞れる(必要なファイル/関数だけで説明できる)