Upgrade to Pro — share decks privately, control downloads, hide ads and more …

統合強度: 現代的な結合度の評価モデル勉強スライド

Avatar for nnc_5522 nnc_5522
March 04, 2026
21

統合強度: 現代的な結合度の評価モデル勉強スライド

References
- [1] Vlad Khononov, "Balancing Coupling in Software Design", Addison-Wesley, 2024
- 邦訳: 島田浩二 訳 『ソフトウェア設計の結合バランス』 インプレス, 2025
- [2] Meilir Page-Jones, "What Every Programmer Should Know About Object-Oriented Design", Dorset House, 1996
- [3] Glenford J. Myers, "Composite/Structured Design", Van Nostrand Reinhold, 1978
- [4] #link("https://coupling.dev/posts/dimensions-of-coupling/integration-strength/")[Integration Strength | coupling.dev]
- [5] #link("https://coupling.dev/posts/related-topics/connascence/")[Connascence | coupling.dev]
- [6] #link("https://connascence.io/")[connascence.io]

Avatar for nnc_5522

nnc_5522

March 04, 2026
Tweet

Transcript

  1. Agenda 1. はじめに p.3–5 2. 従来の結合度メトリクス p.6–9 3. 統合強度 p.10–19

    4. 結合の 3 次元モデル p.20–24 5. 補足: 設計パターンと DDD 用語 p.25–29 6. 事例集 — Web Backend 編 p.30–45 7. 事例集 — 機械学習編 p.46–50 8. まとめ p.51–55 9. References p.56–57 2
  2. 背景 • このスライドは勉強用にまとめたものを社内共有したのを手直しして公開してます ‣ 書かせる内容の指示を与えて Claude Code で typst を書かせてます

    ‣ レビューはしてますが、不正確な部分があるかもしれません • 詳細は References [1]の邦訳版をお読みください ‣ これを読むことを強く推奨します • 例えば ‣ DB にカラムを 1 つ追加しただけで 3 つのサービスが壊れた ‣ 前処理のパラメータを変えたら 推論スコアが再現不能 に ‣ 「誰もこの API 使ってないはず」→ デプロイ直後に 障害アラートの嵐 ‣ チーム間で同じバリデーションルールが 微妙に違って 不整合が発生 これらは全て 意図しない結合 が原因。 → この問題を体系的に分析するフレームワークの 1 つが 統合強度。 4
  3. なぜ結合度を議論するのか • ソフトウェアの変更コストの大部分は 意図しない依存関係 に起因する • 「疎結合・高凝集」は目標だが、何をもって結合が強い/弱いと判断する? • 従来のメトリクスには課題がある: ‣

    モジュール結合 (Myers, 1970s): 構造化設計時代の分類。物理的な接続前提 ‣ Connascence (Page-Jones, 1990s): OOP 時代に拡張。静的/動的の区別を導入 ‣ いずれも 物理的な接続がないと検出できない • 統合強度 (Khononov, 2024) はこれらを統合し、現代の分散システムにも対応する 5
  4. モジュール結合 (Myers, 1970s) 構造化設計における結合度の古典的分類(弱い → 強い): 種類 説明 データ結合 単純なデータのみ受け渡し

    スタンプ結合 構造体(の一部)を受け渡し 制御結合 制御フラグを受け渡し 外部結合 外部フォーマットやプロトコルを共有 共通結合 グローバルデータを共有 内容結合 他モジュールの内部に直接アクセス • 課題: 物理的な接続 を前提としており、暗黙的な依存を捉えられない 7
  5. Connascence (Page-Jones, 1990s) 「2 つのコンポーネントの一方を変更したとき、もう一方も変更しないと正しさが保てない」関係 静的 Connascence (コンパイル時に検出可能、弱い → 強い):

    CoN 名前 同じ名前を参照する CoT 型 同じ型に依存する CoM 意味 値の意味の取り決めに依存する(例: 0=男, 1=女) CoA アルゴリズム 同じアルゴリズムに依存する(例: ハッシュ) CoP 位置 要素の順序に依存する(例: 引数の順番) 8
  6. Connascence (続き) 動的 Connascence (実行時にのみ検出可能、弱い → 強い): CoE 実行順序 特定の実行順序に依存する

    CoTm タイミング 実行のタイミングに依存する(例: 競合状態) CoV 値 複数の値が整合性を保つ必要がある(例: トランザクション) CoI 同一性 同じインスタンスを参照する必要がある 重要な性質: 最も弱い動的 Connascence (CoE) > 最も強い静的 Connascence (CoP) • 課題: 物理的な参照関係が存在しない 暗黙の重複 を捉えられない 9
  7. 統合強度とは • Vlad Khononov が “Balancing Coupling in Software Design”

    (2024) で提案 • 結合を 「共有される知識の種類と量」 として再定義 • 物理的な接続の有無に関係なく結合を評価できる • Connascence と従来のモジュール結合を統合し、4 段階に整理 弱い ← 統合強度 → 強い コントラクト モデル 機能 侵入 11
  8. 共有される知識の範囲 — 段階的に広がる 契約のみ コントラクト 契約 1. ドメインモデル モデル 契約

    + モデル 1. ビジネスロジック 機能 契約 + モデル 1. ロジック + 実装詳細 侵入 レベルが上がるほど、相手について知る必要がある知識が 累積的に増える 12
  9. 1. コントラクト結合 (Contract Coupling) 最も弱い(望ましい)結合 • コンポーネント間の通信に 統合専用の契約 を使う •

    内部モデル・機能要件・実装詳細を隠蔽する • 契約は「モデルのモデル」, 内部モデルから 統合に必要な最小限 だけを抽出した専用の抽象層 ‣ 内部構造の変更が契約の外に漏れないことを保証する 設計パターン: Facade, DTO, Anti-Corruption Layer (腐敗防止層), Published Language 対応する従来の結合: データ結合を精緻化したもの Connascence との関係: 静的 Connascence (CoN〜CoP) が契約内の結合度を示す 注意: 内部モデルをそのまま写しただけの DTO は コントラクト結合ではない → 設計パターンのコード例: 補足 p.26 13
  10. 2. モデル結合 (Model Coupling) • コンポーネント間で同じ ビジネスドメインモデル を共有する • ドメインモデルが変更されると、依存する全コンポーネントに変更が波及する

    例: 複数の Bounded Context (境界づけられたコンテキスト)が同じエンティティ構造を使用 対応する従来の結合: スタンプ結合 Connascence との関係: 静的 Connascence (特に CoP 以上) がモデル結合内の結合度を示す コントラクト結合との違い: • コントラクト結合: 統合専用に設計された契約を共有 • モデル結合: ドメインモデルそのものを共有 → 変更の波及リスクが大きい → Bounded Context の詳細: 補足 p.28 14
  11. 3. 機能結合 (Functional Coupling) • コンポーネント間で 機能要件の知識 を共有する • 3

    つのパターン: ‣ 逐次的: 他コンポーネントのビジネスロジックを呼び出す ‣ トランザクション的: 同じトランザクションに参加する必要がある ‣ 対称的 (Symmetric): 同じビジネスルールが複数箇所に重複 対応する従来の結合: 共通結合、外部結合、制御結合 Connascence との関係: 動的 Connascence (CoE〜CoI) が機能結合内の結合度を示す → 3 パターンのコード例: 補足 p.29 15
  12. 対称的機能結合の危険性 最も検出が難しく、最も危険なパターン • 物理的な接続が 一切ない のに結合している • 同じバリデーションルールをフロントエンドとバックエンドで重複実装 • 要件が変わったら両方を同時に更新しないと不整合が発生する

    // Frontend (TypeScript) const isValid = amount > 0 && amount <= 10000; # Backend (Python) is_valid = amount > 0 and amount <= 10000 → 上限が変わったら? 両方変更が必要! • 従来の Connascence では捉えられない -> 物理的な参照がないから • 統合強度では「共有される知識」として明確に検出対象になる 16
  13. 4. 侵入結合 (Intrusive Coupling) 最も強い(最も危険な)結合 • 公開インターフェースを迂回し、非公開の実装詳細 に依存する • 例:

    ‣ 他サービスの内部 DB に直接クエリ ‣ リフレクションで private フィールドにアクセス ‣ ドキュメント化されていない内部動作に依存 対応する従来の結合: 内容結合 最大のリスク: 侵入されている側のコンポーネントの開発者が 依存関係の存在に気づかない → 実装を変更するだけで予測不能なカスケード障害が発生する 17
  14. Connascence と統合強度の関係 統合強度の各レベルの 内部の結合度 を Connascence が示す: 統合強度レベル Connascence 種類

    コントラクト結合 CoN, CoT, CoM, CoA, CoP 静的 モデル結合 CoP 以上 静的 機能結合 CoE, CoTm, CoV, CoI 動的 侵入結合 (公開 API を迂回) — • 統合強度 = マクロな結合の分類(境界間の関係) • Connascence = ミクロな結合の分類(同レベル内の度合い) • 同じ統合強度レベル内で迷ったら、Connascence の弱い方を選ぶ 18
  15. 結合メトリクスの進化 モジュール結合 Connascence 統合強度 Myers (1970s) Page-Jones (1990s) Khononov (2024)

    構造化設計 OOP 分散システム 物理的接続前提 物理的参照前提 共有知識ベース 6 段階 9 種類 (静的 5+動的 4) 4 レベル × 3 次元 • 各メトリクスは 置き換え ではなく 層を積み重ねる 関係 • 統合強度が全体像、Connascence が詳細度、従来分類が歴史的文脈を提供 19
  16. 結合の 3 次元モデル 統合強度は結合評価の 3 次元のうちの 1 つ: 次元 説明

    スケール例 統合強度 共有される知識の種類と量 1 (契約) 〜 10 (侵入) 距離 変更を協調するのに必要な労力 1 (同一メソッド) 〜 9+ (分散) 変動性 コンポーネントの変更頻度 1 (レガシー) 〜 10 (コアドメイン) 結合は 1 つの次元だけでは判断できない — 3 つを 総合的に 評価する。 判断の順序: 変動性 → 強度 × 距離 の順で評価する。 21
  17. 判断の優先順位 — まず変動性を評価する 変動性は 3 次元の中で 最も優先すべき 次元。 変動性が低い場合 (stdlib,

    安定した OSS, 枯れたレガシー) • 依存先が変わらないなら、結合が強くても 壊れない • 改善の優先度は低い → リソースを変動性の高い箇所に振り向ける 変動性が高い場合 (コアドメイン, 頻繁に変わるビジネスルール) • 些細な変更でも連鎖的に壊れるリスクがある • 強度 × 距離マトリクス で具体的な対応を判断する(次ページ) → まず変動性でフィルタリングし、高い箇所から着手 する 22
  18. 強度 × 距離マトリクス — 変動性が高い場合の判断 変動性が高いコンポーネントについて、強度と距離で対応を判断する: 距離が近い 距離が遠い 強度が低い 問題なし

    — ローカルに完結 理想的な分散設計 — 目指すべき状態 強度が高い 高凝集として許容 — モジュール内で活用 危険 — 強度の弱化が必要 • 右下(強度高 × 距離遠)→ 最優先の改善対象。コントラクト結合へ弱化する • 左下(強度高 × 距離近)→ 高凝集として活用。無理に弱化しない • 目標: 遠い距離では常に右上の状態を維持する 23
  19. 距離と変動性 — 具体シナリオ 距離: 変更を協調するのに必要な労力 シナリオ 説明 評価 同一チーム内のモデル結合 共有ライブラリで型を共有。PR

    レビューで即座に気づける 許容 マイクロサービス間のモデル結合 別チームの DB 構造に依存。変更通知が Slack 頼み 危険 変動性: コンポーネントの変更頻度 シナリオ 説明 評価 stdlib / OSS 依存 年単位で安定。API が変わる頻度は極めて低い 強い結合でも OK コアドメインロジック依存 要件変更で頻繁に更新。週次で変わることもある 弱い結合が必須 24
  20. 補足: Facade & DTO Facade — サブシステムへの簡素化された窓口 # inventory/facade.py class

    InventoryFacade: """呼び出し側は内部構造を知らない""" def check_stock( self, product_id: int ) -> StockInfo: product = self._repo.get(product_id) wh = self._warehouse.locate(product) return StockInfo( product_id=product.id, available=wh.has_stock(product), ) DTO — データ転送専用、ロジックなし @dataclass(frozen=True) class StockInfo: # ← DTO product_id: int available: bool # 内部モデル Product の # warehouse_id, supplier 等は # 含めない Facade が 窓口、DTO が 受け渡す荷物。 どちらも内部モデルを外に漏らさないための仕 組み。 境界層では pydantic 等でバリデーション付き にすると契約がより堅牢になる。 26
  21. 補足: Anti-Corruption Layer (腐敗防止層) & Published Language Anti-Corruption Layer /

    腐敗防止層 (ACL) — 外部との変換層 # acl/legacy_adapter.py class LegacyInventoryACL: """レガシーAPIの形式を 内部モデルに変換""" def check_stock( self, product_id: int ) -> StockInfo: raw = legacy_api.get_zaiko( product_id ) return StockInfo( product_id=product_id, available=raw["在庫数"] > 0, ) Published Language — 合意されたデータ 形式 # events/schemas.py @dataclass(frozen=True) class OrderPlacedEvent: """各サービスの内部モデルとは 独立して定義""" order_id: str product_id: str quantity: int timestamp: datetime ACL は 外部の都合から内部を守る壁。 Published Language は 境界間の共通言語。 27
  22. 補足: ドメインモデルと Bounded Context (境界づけられたコンテキスト) 同じ「ユーザー」でも、コンテキストによって必要な情報が異なる: # 認証コンテキストのドメインモデル @dataclass(frozen=True) class

    AuthUser: # ← エンティティ id: int # 一意のID email: str password_hash: str # 請求コンテキストのドメインモデル @dataclass(frozen=True) class BillingUser: # ← エンティティ id: int # 同じユーザーでも name: str # 必要な属性が違う billing_address: str Bounded Context (境界づけられたコンテキ スト) = ドメインモデルが一貫した意味を持つ境 界 モデル結合の問題: # ❌ 両コンテキストが同じモデルを # 直接共有してしまう from shared.models import User # → 認証側が password_hash の # 暗号方式を変えると # 請求側のマイグレーションが壊れる 各コンテキストは 自分に必要なモデルだけ を持 つべき 28
  23. 補足: 機能結合の 3 パターン — コード例 逐次的 — 他サービスのロジックを直接呼び出し from

    payment.core import charge from inventory.core import reserve def place_order(order): # 全ステップを制御 reserve(order.product_id) charge(order.total) # 順序に依存 → import/呼び出しで 検出可能 トランザクション的 — 同一トランザクションに参 加 with db.transaction(): reserve_stock(order) # 在庫引き当て create_payment(order) # 決済 # 片方失敗 → 両方ロールバック → トランザクション境界で 検出可能 対称的 — 物理的接続なしで同じ知識を重複 // Frontend (TypeScript) const isValid = amount <= 10000; # Backend (Python) is_valid = amount <= 10000 → 互いを参照せず 検出が極めて困難 逐次的・トランザクション的はコードで追跡可能だ が、対称的は コードを読むだけでは発見できない 29
  24. 実践ガイドライン 1. 境界ではコントラクト結合を目指す → 事例 1, 2 • サービス/チーム境界には必ず専用の契約を設計する •

    内部モデルをそのまま公開しない 2. 統合強度と距離を合わせる → 事例 6 • 強い結合は近い距離でのみ許容する(同一チーム、同一サービス内) • 遠い距離で強い結合 → アーキテクチャ上の重大リスク 3. 変動性で優先順位をつける → 事例 7 • 変更頻度の高いコンポーネントから先にモジュール化する • 安定したレガシーは多少の結合を許容してもよい 4. 対称的機能結合を検出する → 事例 3, 4, 8 • コード検索や Architecture Fitness Function で重複ルールを発見する • 最も検出困難で最も危険な結合パターン 5. 同じレベル内では Connascence で判断する → 事例 5, 9 • CoP (位置) → CoN (名前) — 弱い Connascence に変換する 31
  25. 事例 1: 侵入結合 — 他アプリの内部 ORM を直接参照 (Django) 侵入結合 公開インターフェースを迂回し、非公開の実装詳細

    に依存する結合。 侵入された側の開発者は依存の存在に気づけない。 # orders/views.py (注文アプリ) from inventory.models import Product # 在庫アプリの内部モデル def check_stock(product_id: int) -> bool: product = Product.objects.get(id=product_id) return product._internal_stock > 0 # 非公開フィールド なぜ侵入結合か: • inventory.models.Product は在庫アプリの 内部実装 — 公開 API ではない • _internal_stock は非公開フィールドであり、外部から触れる想定がない • 在庫チームはこの依存の存在を知らず、リファクタリングで 無警告で壊す 32
  26. 事例 1: → 改善後 — サービス層 + dataclass (Django) 侵入結合

    → コントラクト結合 へ。公開サービス層を設け、専用の dataclass で返す: # inventory/services.py(公開API) @dataclass(frozen=True) class StockInfo: product_id: int available: bool def check_availability( product_id: int, ) -> StockInfo: product = Product.objects.get( id=product_id ) return StockInfo( product_id=product.id, available=product._internal_stock > 0, ) # orders/views.py(注文アプリ) from inventory.services import ( check_availability, ) status = check_availability(product_id) 何が治ったか: • 注文アプリは StockInfo という 契約 のみに依 存 • 在庫アプリは内部を 自由にリファクタリング可 能 • 依存関係が公開 API で明示され、変更の影響 範囲が予測できる 33
  27. 事例 2: モデル結合 — fields = "__all__" の罠 (DRF) モデル結合

    コンポーネント間で ドメインモデルそのもの を共有する結合。 モデル変更が依存先すべてに波及する。 class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = "__all__" # ORMの全フィールドをAPI公開 なぜモデル結合か: • API レスポンスが ORM モデルの構造に 直接依存 している • 統合専用の契約(Serializer)を設計せず、内部モデルをそのまま公開している 破綻シナリオ: ORM に is_staff, salary を追加 → 意図せず API 経由で内部情報が漏洩 34
  28. 事例 2: → 改善後 — 明示的な Serializer (DRF) モデル結合 →

    コントラクト結合 へ。統合専用の契約を設計する: class UserResponseSerializer(serializers.Serializer): id = serializers.IntegerField() username = serializers.CharField() display_name = serializers.CharField() # is_staff, salary 等は公開しない 何が治ったか: • ORM モデルと API レスポンスの間に 専用の契約層 が入った • 公開フィールドが明示的 → ORM にフィールドを追加しても API に波及しない • API 仕様が ORM の都合に振り回されない → セキュリティリスク低減 35
  29. 事例 3: 対称的機能結合 — ロジック重複 (Django) 対称的機能結合 同じビジネスルールが複数箇所に重複する結合。 物理的な参照が 一切ない

    ため、最も検出が難しい。 # views.py def order_view(request): if amount >= 10000: discount = amount * 0.1 else: discount = 0 # tasks.py @shared_task def batch_calc(amount): if amount >= 10000: discount = amount * 0.1 else: discount = 0 なぜ対称的機能結合か: • 2 つのコードは 互いを知らない が、同じ割引ルールという知識を共有している • import 関係がないため、grep や IDE の参照検索では 検出不可能 破綻シナリオ: 割引率 0.1 → 0.15 に変更 → View 側だけ更新 → バッチ経由の割引額が異なる 36
  30. 事例 3: → 改善後 — ドメインロジック層に抽出 (Django) 対称的機能結合 を解消し、暗黙の知識共有を 明示的な依存関係

    に変換する: # domain/pricing.py — 唯一の割引ルール定義 def calc_discount(amount: int) -> int: return amount * 0.15 if amount >= 10000 else 0 # views.py from domain.pricing import calc_discount def order_view(request): discount = calc_discount(amount) # tasks.py from domain.pricing import calc_discount @shared_task def batch_calc(amount): discount = calc_discount(amount) 何が治ったか: • 暗黙の知識共有が 明示的な import に変わった — IDE で参照追跡可能 • ルール変更が pricing.py の 1 箇所で完了、View/バッチの不整合が 構造的に不可能 37
  31. 事例 4: 対称的機能結合 — 言語をまたぐバリデーション重複 対称的機能結合 同じルールが 異なる言語 で独立実装されている結合。 同一言語なら

    grep で見つかるが、言語が違うと 検索すら困難。 # backend/validators.py VALID_STATUSES = ( "pending", "confirmed", "shipped" ) def validate_order(order): return ( 1 <= order["amount"] <= 1_000_000 and order["status"] in VALID_STATUSES ) // frontend/validation.ts const VALID_STATUSES = ["pending", "confirmed", "shipped"]; const isValid = (order) => order.amount >= 1 && order.amount <= 1000000 && VALID_STATUSES .includes(order.status); 破綻シナリオ: ステータスに "cancelled" を追加 → バックエンドだけ更新 → フロントが未知のステータ スを不正値として弾く 38
  32. 事例 4: → 改善後 — JSON Schema で言語非依存の契約を定義 対称的機能結合 →

    コントラクト結合 へ。ルールを 言語非依存のスキーマ で一元定義: // schemas/order.json — 唯一の定義 { "properties": { "amount": { "minimum": 1, "maximum": 1000000 }, "status": { "enum": ["pending", "confirmed", "shipped"] } } } # backend from jsonschema import validate validate(order, load("order.json")) // frontend import Ajv from "ajv"; const validate = new Ajv().compile(orderSchema); 何が治ったか: • ルールが JSON Schema という言語非依存 の契約に集約 • 各言語は同じスキーマを読んでバリデーション → 乖離が 構造的に不可能 • スキーマ = Published Language(境界間 の共通言語) 39
  33. 事例 5: Connascence — 戻り値の位置依存 (Django) コントラクト結合内の Connascence 事例 1

    で侵入結合をコントラクト結合に改善した。だが 同じコントラクト結合内 でも、 Connascence の強弱で保守性は大きく変わる。 もし事例 1 の check_availability が dataclass ではなく tuple を返していたら? # inventory/services.py — 事例1の改善後コードのtuple版 def check_availability(product_id: int) -> tuple[int, bool, int]: ... return product_id, is_available, remaining # 位置で意味が決まる # orders/views.py — 呼び出し側 pid, available, remaining = check_availability(product_id) なぜ悪いか (CoP — 静的 Connascence の中で最も強い): • 戻り値の順序を入れ替えると、呼び出し側が 無警告で壊れる • 要素が増えると分割代入の対応関係が崩れやすい • コードレビューで「3 番目は何? 」と毎回確認が必要 40
  34. 事例 5: → 改善後 — dataclass で名前依存に変換 CoP (位置) →

    CoN (名前) へ。事例 1 で導入した StockInfo と同じ発想で、Connascence を弱 化する: # inventory/services.py @dataclass(frozen=True) class StockInfo: product_id: int available: bool remaining: int def check_availability( product_id: int, ) -> StockInfo: ... return StockInfo( product_id=product_id, available=is_available, remaining=remaining, ) # orders/views.py info = check_availability(product_id) if info.available: reserve(info.remaining) 何が治ったか: • フィールド順序の変更が呼び出し側に 影響しな い • info.available と名前でアクセスするため意味 が自明 • フィールド追加時も既存コードが壊れない 41
  35. 事例 6: 逐次的機能結合 — サービス間の直接オーケストレーション 逐次的機能結合 他コンポーネントのビジネスロジックを 直接呼び出して制御 する結合。 呼び出し先の実装詳細(関数シグネチャ、実行順序)に依存する。

    # order_service/views.py — 注文サービスが全ステップを直接制御 from payment.core import charge_credit_card from inventory.core import reserve_stock from notification.core import send_order_email def place_order(request): order = create_order(request.data) reserve_stock(order.product_id, order.quantity) charge_credit_card(order.user.card_token, order.total) send_order_email(order.user.email, order.id) return Response({"order_id": order.id}) なぜ逐次的機能結合か: • 注文サービスが決済・在庫・通知の ビジネスロジックの実行順序と詳細 を知っている • 強い結合(機能) + 遠い距離(サービス間) → アーキテクチャ上の重大リスク 42
  36. 事例 6: → 改善後 — イベント駆動で疎結合化 逐次的機能結合 → コントラクト結合 へ。イベントスキーマを契約とし、直接呼び出しを排除:

    # order_service/views.py from events import publish def place_order(request): order = create_order(request.data) publish("order.placed", { "order_id": order.id, "user_id": order.user.id, "product_id": order.product_id, "total": order.total, }) return Response( {"order_id": order.id} ) # payment/handlers.py — 独立して購読 @subscribe("order.placed") def handle(event): charge(event["user_id"], event["total"]) 何が治ったか: • 注文サービスは他サービスのロジックを 一切知 らない • 新ステップ追加(例: 不正検知)が注文サービス の 変更なし で可能 • 強度が機能→コントラクトに弱化し、遠い距離に 適合 43
  37. 事例 7: 変動性の評価 — 同じモデル結合でも優先度が違う モデル結合 同じモデル結合でも、依存先の 変動性 によってリスクが大きく異なる。 変動性が高い結合から先に対処する。

    # order/service.py — 2つのモデル結合 from auth.models import User # 認証モデル(年1回の変更) from pricing.models import PriceTable # 価格テーブル(月2-3回の変更) def create_order(user_id: int, items: list[dict]): user = User.objects.get(id=user_id) prices = PriceTable.objects.filter(active=True) total = sum(p.unit_price * item["qty"] for item in items for p in prices if p.sku == item["sku"]) なぜ優先度が違うか: • User: 変動性 低 → 年に 1 回程度。壊れることは稀 • PriceTable: 変動性 高 → 月に 2-3 回変更。スプリント毎に壊れるリスク 44
  38. 事例 7: → 改善後 — 変動性の高い依存を優先的にモジュール化 モデル結合 → PriceTable のみ

    コントラクト結合 に変換: # pricing/services.py — 契約 @dataclass(frozen=True) class OrderQuote: total: int breakdown: list[LineItem] def calculate_total( items: list[CartItem], ) -> OrderQuote: ... # PriceTableの変更が # 外に漏れない # order/service.py from auth.models import User # ← 据え置き from pricing.services import ( calculate_total, # ← 契約に変更 ) def create_order(user_id, items): user = User.objects.get(id=user_id) quote = calculate_total(items) 何が治ったか: • 変動性の高い PriceTable を 優先的に 弱化 • User は変動性が低いため 意図的に据え置き • 3 次元モデルで優先順位 をつける 45
  39. 事例 8: 対称的機能結合 — 前処理の乖離 対称的機能結合 同じ前処理ロジックが訓練と推論に重複する結合。 物理的な参照がないため、乖離に気づけない。 # train.py

    img = cv2.resize(img, (224, 224)) img = img / 255.0 img = (img - 0.485) / 0.229 # inference.py img = cv2.resize(img, (256, 256)) img = img / 255.0 img = (img - 0.485) / 0.225 なぜ対称的機能結合か: • train.py と inference.py は互いを参照しないが、 「同じ前処理」という 知識を暗黙に共有 • 片方を修正してももう一方には何の通知もない 破綻シナリオ: リサイズ 224 vs 256、std 0.229 vs 0.225 がサイレントに不一致 → スコア再現不能の原 因特定に 数日を浪費 47
  40. 事例 8: → 改善後 — 共通前処理モジュール 対称的機能結合 を解消し、前処理を 1 箇所に集約:

    # common/preprocess.py @dataclass(frozen=True) class PreprocessConfig: size: tuple[int, int] = (224, 224) mean: float = 0.485 std: float = 0.229 def preprocess( img: np.ndarray, cfg: PreprocessConfig, *, augment: bool = False, ) -> np.ndarray: img = cv2.resize(img, cfg.size) img = img / 255.0 img = (img - cfg.mean) / cfg.std if augment: img = apply_augmentation(img) return img # train.py / inference.py preprocess(img, cfg, augment=is_training) 何が治ったか: • 暗黙の知識共有が 明示的な import に変わっ た • パラメータが dataclass に集約され、同一の 値が 保証 • augmentation のみフラグで切り替え → 前 処理の乖離が 構造的に発生し得ない 48
  41. 事例 9: モデル結合 — 特徴量の暗黙的依存 モデル結合 コンポーネントがデータの 内部構造(カラム位置) に直接依存する結合。 構造変更が依存先に波及する。

    # 特徴量抽出: カラム位置でアクセス features = df.iloc[:, 3:10].values # 学習コード: 同じ位置前提でアクセス X_train = train_df.iloc[:, 3:10].values なぜモデル結合か: • DataFrame の カラム順序 という内部構造に暗黙的に依存している • 前処理や EDA でカラムを追加・並び替えしただけで 違うカラムがサイレントに使われる 破綻シナリオ: 前処理で新カラムを挿入 → iloc[:, 3:10] が指す列がズレる → モデルが無関係な特徴 量で学習 49
  42. 事例 9: → 改善後 — Pandera でスキーマを契約として定義 モデル結合 内の Connascence

    を弱化し、位置依存(CoP)を 名前依存(CoN) に変換: # features/schema.py — 契約 import pandera as pa feature_schema = pa.DataFrameSchema({ "age": pa.Column(float, pa.Check.ge(0)), "bmi": pa.Column(float, pa.Check.gt(0)), "blood_pressure": pa.Column(float), }) FEATURE_COLS = list( feature_schema.columns.keys() ) # train.py / inference.py df = feature_schema.validate(df) X = df[FEATURE_COLS].values # np.ndarray 何が治ったか: • 位置依存(CoP)が名前依存(CoN)に変わり、 Connascence が弱化 • スキーマが 契約 として型・制約を含めて定義 される • validate() で契約違反を即座に検出 • 出力は np.ndarray のまま — 後段のモデルに そのまま渡せる 50
  43. まとめ • 統合強度は結合を「共有される知識」として再定義した現代的メトリクス • 4 段階: コントラクト < モデル <

    機能 < 侵入 • 物理的接続がない 対称的結合 も捉えられる点が最大の進化 • 距離・変動性と組み合わせた 3 次元で結合を評価する • Connascence は統合強度の 内部の度合い を示す補完的なメトリクス 実践ガイドライン(再掲 p.31) 指針 事例 境界ではコントラクト結合を目指す 事例 1 (ORM 隠蔽), 事例 2 (Serializer) 統合強度と距離を合わせる 事例 6 (サービス間オーケストレーション) 変動性で優先順位をつける 事例 7 (変動性の優先順位) 対称的機能結合を検出する 事例 3 (割引), 事例 4 (言語横断), 事例 8 (前処理) 同レベル内では Connascence で判断 事例 5 (戻り値), 事例 9 (特徴量) 結合は排除するものではなく、バランスするもの 52
  44. 設問: この結合は何か? 以下のシナリオについて、統合強度のレベル と 改善の方向性 を考えてみてください。 Q1. あるマイクロサービスが、別チームのサービスの DB テーブルを直接

    SELECT している。 Q2. 5 つのサービスが共有ライブラリの User 型を利用している。 User に電話番号フィールドを追加したい。リスクは? Q3. フロントエンド (TypeScript) とバックエンド (Python) で、 同じ日付フォーマットのバリデーション (YYYY-MM-DD) を独立して実装している。 Q4. チーム内モノリスの 2 つのモジュール間でモデル結合がある。 今すぐコントラクト結合に改善すべきか? 53
  45. 設問: 解説 Q1. 侵入結合 公開 API を迂回し内部実装に直接依存。DB 所有チームはこの依存に気づけない。 → API

    経由に変更し コントラクト結合 へ。 Q2. モデル結合 5 サービス全てに変更が波及する。電話番号が不要なサービスにも影響。 → 各サービスが必要なフィールドだけを持つ 専用の DTO を定義する。 Q3. 対称的機能結合 物理的な接続がなく検出困難。フォーマット変更時に片方だけ更新するリスク。 → バリデーションルールを API の 契約 (JSON Schema 等) として一元定義する。 Q4. 必ずしも改善不要。 距離が近い(同一チーム・同一リポジトリ)ため、モデル結合でも 変更の協調コストは低い。変動性が低ければなおさら許容できる。 → 3 次元モデルで総合判断。距離 × 変動性が低いなら 他の改善を優先 すべき。 54
  46. 適用時の注意点 1. 全てをコントラクト結合にしようとしない • 契約層の設計・維持にもコストがかかる • 距離が近く変動性が低い場所は、強い結合のままで十分 2. 対称的結合の検出には工夫が要る •

    grep や IDE の参照検索では検出不可能 • 設計レビュー と ドメイン知識 を組み合わせる必要がある • Coding Agent に統合強度を基準としたコードレビューを skill 化 して組み込む • Architecture Fitness Function(自動テスト)で継続的に監視する 3. 改善は段階的に • 侵入 → モデル → コントラクトと 一段ずつ 弱化すればよい • 一気に理想形を目指すより、まず最も危険な結合から対処する 4. まず現状を分類することから始める • 測定できないものは改善できない • 既存のシステムを 3 次元モデルで棚卸しするだけでも価値がある • 最もスコアが高い(強度 × 距離 × 変動性)箇所から着手する 55
  47. References • [1] Vlad Khononov, “Balancing Coupling in Software Design”,

    Addison-Wesley, 2024 ‣ 邦訳: 島田浩二 訳 『ソフトウェア設計の結合バランス』 インプレス, 2025 • [2] Meilir Page-Jones, “What Every Programmer Should Know About Object- Oriented Design”, Dorset House, 1996 • [3] Glenford J. Myers, “Composite/Structured Design”, Van Nostrand Reinhold, 1978 • [4] Integration Strength | coupling.dev • [5] Connascence | coupling.dev • [6] connascence.io 57