この記事のポイント

  • 循環依存の設計発生メカニズムを構造的に理解できる
  • 発生初期段階でのレビュー介入ポイントを学べる
  • 実務での整理アプローチを段階的に学習できる
  • PlantUMLで依存構造の可視化・整理イメージを掴める

そもそも循環依存とは

Pythonに限らずソフトウェア設計全般で発生する「循環依存(circular dependency)」は、2つ以上のモジュールが互いに直接・間接に依存し合う状態を指します。

典型的な循環依存例

# user.py
from order import Order

class User:
    def __init__(self, orders: list[Order]):
        self.orders = orders

# order.py
from user import User

class Order:
    def __init__(self, user: User):
        self.user = user
  • user.pyorder.pyに依存
  • order.pyuser.pyに依存

これによりインポート時の解決順序矛盾が発生し、ImportErrorNameErrorを誘発します。

なぜこれをレビューするのか

循環依存は「設計臭が顕在化したシグナル」です。
レビューアーは以下のような責任を持って読み解く必要があります。

レビューアー視点

  • 循環依存の「発生要因」を特定する
  • 依存整理・責務分離の切り口を提案する
  • 境界設計の整理不全を抽出する
  • 複雑化前の早期警戒信号を見逃さない
  • リファクタビリティを確保する整理案を提示する

開発者視点

  • モジュールを「意味的に」分割したつもりでも循環が発生
  • 関連ドメインが密結合なので依存が行き来しやすい
  • 初期実装時はエラーが出ないので気づきにくい
  • init.py利用が介在して更に隠れ循環になる

循環依存が発生する構造的要因

循環依存が発生しやすい設計臭
  • 相互参照を前提にする集約モデル
  • 値型・振る舞い型の混在整理不足
  • 参照先責務と所有先責務が混線
  • 補助ユーティリティの逆参照化
  • エラーハンドリング・通知責務の逆流
  • init.py経由のエクスポート拡張

発生構造モデル(崩壊前夜)

UML Diagram
  • User → Order → Payment → User で閉ループ完成
  • Utilは表面上補助に見えて逆参照を誘発

良い設計整理アプローチ

循環依存は「責務整理」と「依存方向整理」で解消できます。
レビューアーは以下の切り口で読み解きを行います。

① 値オブジェクト抽出(Value Object分離)

  • ID・Name・Datetimeなど参照は必要だが行動は不要な要素を分離

② 所有責務整理

  • 双方向所有を強制しない(外部ID・Lazy Loading等で片方向化)

③ コントロールレイヤ抽出

  • 管理統括クラスで依存集中し、個別ドメイン間は疎結合に整理

④ ユーティリティ逆参照排除

  • 共通ユーティリティがドメイン逆参照しないよう整理

改善例:責務整理パターン

改善版依存設計

# user.py
class User:
    def __init__(self, user_id: int):
        self.user_id = user_id

# order.py
class Order:
    def __init__(self, order_id: int, user_id: int):
        self.order_id = order_id
        self.user_id = user_id

# payment.py
class Payment:
    def __init__(self, payment_id: int, order_id: int):
        self.payment_id = payment_id
        self.order_id = order_id

改善ポイント

  • 外部ID参照で結合を弱くする
  • 振る舞いはコントロール層に委譲
  • ドメインモデルは相互依存しない構造に整理
循環依存対策は「片方向指向文化の定着」

レビューアーは「双方向参照していないか?」を常に確認すると崩壊を早期防止できます。

良くない実装例: ケース1(相互参照)

相互参照崩壊例

# user.py
from order import Order

class User:
    def __init__(self, orders: list[Order]):
        self.orders = orders

# order.py
from user import User

class Order:
    def __init__(self, user: User):
        self.user = user
@Reviewer
相互参照で循環依存が完成しています。ID参照による弱結合整理を検討してください。

良くない実装例: ケース2(ユーティリティ逆参照)

ユーティリティ逆参照例

# util.py
from user import User

def validate_user_active(user: User):
    return user.is_active
@Reviewer
共通ユーティリティがドメインを逆参照しています。呼び出し側依存に整理してください。

改善例:逆参照除去

責務整理後

# util.py
def validate_user_active(is_active: bool):
    return is_active
  • 値だけ受け取り責務を分離
  • 依存逆流が消失

PlantUML:整理後の依存構造

UML Diagram

観点チェックリスト

まとめ

循環依存は「設計臭の最終警報」とも言える現象です。
レビューアーは構造レベルでの臭い読みを養成し、依存方向の整理文化を継続教育することが設計健全性を守る鍵となります。