イントロ:疎結合を一言で言うと?
疎結合(そけつごう)とは、「お互いに強く依存しすぎないように部品同士をつなぐこと」です。
簡易まとめ
- 密結合:実装にベッタリ依存 → 変更が連鎖しやすい
- 疎結合:契約(インターフェース)に依存 → 影響範囲を局所化しやすい
- 近づけるコツ: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. 実際にやって感じた「疎結合」の効きどころと、もちもちみかんの体験談
7-1. 最初から全部を疎結合にしようとすると、かえって重くなりやすい
実際に手を入れていると、最初から全部をきれいに疎結合へ寄せたくなることがあります。ですが、小さく閉じた処理や当面変わらない処理まで一気に抽象化すると、インターフェースやファイルだけが増えて、かえって流れを追いにくくなることがありました。
特に「疎結合にすること」自体が目的になると、読みやすさよりも構造の立派さが先に立ってしまいます。実務では、全部を最初から整えるよりも、まず今どこで変更がつらいのかを見るほうが、結果的に軽く進めやすいです。
7-2. まずは「差し替えが起きやすい場所」から境界を作ると進めやすい
疎結合が特に効きやすかったのは、通知、DB、外部API、保存先、Repository のような「後から差し替わりやすい場所」でした。こういう所は仕様変更や接続先変更が起きやすいので、先に契約を切っておくと影響範囲をかなり局所化しやすくなります。
逆に、最初から全体を大きく設計し直そうとすると止まりやすいです。まず変化点だけに境界を作る、次に必要なら周辺を整える、という順番のほうが、実際には前へ進みやすかったです。
7-3. AIには「契約を守る」「触ってよい場所」を先に固定すると安定しやすい
AI に改修を頼むときも、疎結合は人間向けのきれいさだけではなく、影響範囲を伝えるための道具として効きました。たとえば「このインターフェースは維持」「呼び出し側は触らない」「差分は実装側に閉じる」と先に決めておくと、提案がかなり安定しやすくなります。
逆に境界を示さないまま頼むと、AI が周辺コードまでまとめて触りにいきやすくなります。UI とロジックの境界、Repository と DB の境界、通知処理の境界のように、どこまで触ってよいかを先に言葉にしておくのが効きました。
7-4. もちもちみかんの体験談:疎結合は「変更点を閉じ込める道具」として使うとちょうどよかった
もちもちみかん.com の記事やコードを見直していると、最初は「もっときれいに分けたい」が先走りやすかったです。ですが実際には、全部に DI や IF を入れるより、変更が起きそうな所だけを分けたほうが、読みやすさと保守性のバランスがよくなりました。
特に AI と一緒に直すときは、境界があるだけで「ここだけ直して」がかなり通りやすくなります。通知だけ、DB だけ、外部 API だけ、と修正点を閉じ込めやすくなり、手戻りも減りました。
今の実感としては、疎結合は“全部を抽象化する思想”ではなく、“変更点を閉じ込めるための実務道具”として使うとちょうどよかったです。ほどよい距離感を作るための考え方として使うと、AI にも人間にもやさしい設計になりやすいです。
よくある質問(FAQ)
Q. 疎結合と密結合の一番の違いは?
A. 依存の仕方が違います。密結合は「相手の内部(実装の詳細)」に依存しがちで、変更が連鎖します。
疎結合は「契約(インターフェース/入力と出力の約束)」に依存し、影響範囲を小さくしやすいのが特徴です。
Q. 疎結合にすると何が嬉しい?(メリット)
A. 主に3つです。①変更の影響範囲が小さくなる、②差し替えや機能追加がしやすい、③テストしやすい、の3つです。
結果として、修正の手戻りやレビューコストを減らしやすくなります。
Q. どこまで疎結合にすべき? やりすぎの判断は?
A. 目安は「変える可能性が高い場所だけ」です。外部API、DB、通知、決済など、差し替えや仕様変更が起きやすいところは疎結合の効果が大きいです。
一方で、変化が少なく小さく閉じた処理まで抽象化すると、間接化が増えて読みにくくなる副作用が出やすくなります。
Q. 疎結合にするにはDIやインターフェースが必須?
A. 必須ではありません。まずは「責務を分ける」「依存方向をそろえる」「境界をまたぐI/Oを明確にする」だけでも、疎結合にはかなり近づけます。
DIやインターフェースは、差し替え需要がある場所で導入すると費用対効果が高いです。
Q. 疎結合にすると追跡が難しい・遅くなるって本当?
A. 一面では本当です。抽象化や境界が増えると、処理の流れを追うためのファイル数が増えたり、通信が増える構成では遅くなることもあります。
対策としては「境界を増やしすぎない」「ログの入口を用意する」「依存の向きを固定する」などで、“追える疎結合”にしていくことが大切です。
Q. 「影響範囲が読めない」状態を減らすには、まず何を直す?
A. まずは境界を1つ決めて、そこを越えるやり取りを“契約”に寄せるのがおすすめです。
具体的には「I/Oを引数・戻り値で明確にする」「外部アクセス(DB・API)を1か所に寄せる」「直参照をやめて受け渡しにする」など、
変更が連鎖しやすい依存を切るところから始めると進めやすいです。
Q. AIに修正を頼むとき、疎結合の観点で伝えるコツは?
A. 「どこが境界か」「何が契約か」を先に固定すると、AIの提案がブレにくくなります。
たとえば「この関数のI/Oは変えない」「このモジュールの責務はここまで」「この依存は増やさない」などの制約を短く明示し、
差分が小さい単位で依頼すると、“関係ない場所まで書き換わる”事故を減らしやすくなります。
まとめ:AI時代に疎結合が効く理由
- 疎結合は、変更の影響範囲を局所化し、AIの差分提案を安全に適用しやすくします。
- 責務・IF・入出力の境界が明確だと、AIに渡す文脈が絞れ、生成精度が安定します。
- 実装詳細から呼び出し元を切り離す設計は、人間にもAIにも保守しやすい土台になります。
一言でいえば、疎結合は「変更に強さ」と「AI活用の安定性」を同時に高める設計原則です。
おまけ:AIを迷わせない疎結合チェックリスト
- [ ] 変更の影響範囲が小さくなるように分けられているか?
ある処理を1か所直しただけで、関係ない画面や別の機能まで連鎖して直さないといけないなら、結びつきが強すぎるかもしれません。変更点を局所化できる構造にしておくと、人間にもAIにも安全に直しやすくなります。
- [ ] 境界(責務・インターフェース・入出力)がはっきりしているか?
「どこまでがこの関数やクラスの仕事なのか」があいまいだと、修正のたびに責務がにじみやすくなります。役割の線引きと、受け渡す値の形を明確にしておくと、疎結合に寄せやすくなります。
- [ ] 呼び出し元が実装の中身にベッタリ依存していないか?
利用する側が、具体的なクラス名や内部処理の流れまで強く知っていると、差し替えやテストがしにくくなります。呼び出し側は「何ができるか」だけを知り、実装の詳細は隠す形にすると、変更に強い構造を作りやすくなります。
- [ ] UI・状態管理・通信処理を1か所に抱え込みすぎていないか?
画面表示、状態更新、API通信が1つの場所に密集していると、少しの変更でも全体に影響しやすくなります。役割ごとに分けておくと見通しが良くなり、AIにも「どこを触ればよいか」を伝えやすくなります。
- [ ] 差し替えが起きやすい場所から、先に境界を作れているか?
最初から全部を疎結合にしようとすると、構造だけが増えて重たくなりがちです。通知、DB、外部API、決済のように、将来差し替えや仕様変更が起きやすいところから境界を作るほうが、実務では進めやすいことが多いです。
- [ ] AIに「この範囲だけ見て」と小さく切り出して依頼できるか?
疎結合な構造の強みは、AIに必要な範囲だけ渡して相談しやすいことです。「この関数」「このインターフェース」「この依存だけ見て」と説明できないなら、責務の分け方や依存関係を見直す余地があるかもしれません。
おみやげ:AIに疎結合を頼むときのプロンプト例
そのまま使える基本プロンプト
次のコードについて、影響範囲を最小限にしながら疎結合に寄せてください。
- 外部仕様と出力結果は変えない
- まず依存関係と責務の境界を整理する
- 呼び出し側の変更は最小限にする
- 可能ならインターフェースや受け渡しの形を整え、実装詳細への直依存を減らす
- どこをどう分離したか、理由を箇条書きで説明する
- 変更後に確認すべきテスト観点があれば最後に示す
[ここにコードを貼る]
実装は差し替えたいが、呼び出し側は変えたくないときのプロンプト
次のコードを、既存の呼び出し側をなるべく変えずに疎結合にしてください。
- この契約(インターフェース / 関数シグネチャ / 戻り値)は維持する
- 差分は実装側に閉じる
- 新しい実装を追加する場合は、どこに境界を置いたかを説明する
- 呼び出し元への影響が出る場合は、その理由を明示する
- 変更範囲は最小限にする
[ここにコードを貼る]
通知・DB・外部APIなどの依存を切り出したいときのプロンプト
次のコードについて、通知 / DB / 外部API などの外部依存をロジック本体から切り離してください。
- 画面の動作や外部仕様は変えない
- ロジックとI/Oの境界が分かるように整理する
- 依存先を差し替えやすい形に寄せる
- どの処理を「変わりやすい部分」とみなしたかを説明する
- 変更後に、どの単位ならテストしやすくなるかも示す
[ここにコードを貼る]
AIに頼むときの注意点
「疎結合にして」とだけ言うより、どの契約を守るかを先に示したほうが安定します。加えて、「ここは触ってよい / ここは触らない」を指定すると、関係ない場所まで巻き込んで書き換わりにくくなります。
一度に全部を直させるより、通知だけ、DB だけ、Repository だけのように依頼単位を小さくしたほうが安全です。AI は速いですが、どこに境界を置くかの判断軸は人間が握るほうが、最終的な品質は高くなりやすいです。