はじめに

依存性注入(Dependency Injection:DI)は、テスト可能性や構造の柔軟性を高める設計技法として知られています。
しかし、PythonにおけるDIはJavaやC#と異なり、フレームワークによる強制がなく、開発者の裁量で「どこまでやるか」が大きく左右される構造となっています。

その結果、レビュー現場では以下のような問題に直面します:

  • DIが意図的な設計ではなく、ただの抽象のための抽象になっている
  • インターフェースと実装の整合性が崩れ、構造が形骸化している
  • テスト性向上を目的にしたはずが、むしろ読解性を悪化させている

本稿では、レビューアーの立場でDI構造を「設計意図の可視化」という観点から評価するための判断軸を整理します。

PythonにおけるDIの最低限の構造

インターフェースと実装の最小構成
class Notifier:
    def send(self, message: str):
        raise NotImplementedError()

class EmailNotifier(Notifier):
    def send(self, message: str):
        print(f"Email: {message}")

class App:
    def __init__(self, notifier: Notifier):
        self.notifier = notifier

    def run(self):
        self.notifier.send("Hello")
レビューコメント
@Reviewer: 構造としては DI の基本を踏まえていますが、`Notifier`が純粋なインターフェース(Protocol)として設計されているかを確認する必要があります。  
抽象を導入する目的が「拡張性」なのか「テスト性」なのか、意図が曖昧な場合は過剰設計の可能性があります。
DI設計レビューで確認すべき意図の可視性
  • 抽象の導入理由が記述やドキュメントで明示されているか?
  • テストコードから逆に「抽象の必要性」が読み取れるか?
  • 同一の抽象に対して複数の実装が存在しているか?
  • 実装差し替えの可能性が「将来の拡張」以外に明確にあるか?

実装先行で破綻したDI構造の例

DIの形骸化例
class UserRepository:
    def get_user(self, id): ...

class Service:
    def __init__(self, repo: UserRepository):
        self.repo = repo

この例では、一見DIの形を取っていますが、以下のような問題があります:

  • UserRepository が抽象インターフェースではなく、実装クラスそのもの
  • Service実装に依存しており、結合度が高い
  • 将来的な差し替えも想定されていない
レビュー指摘
@Reviewer: `UserRepository` が具象クラスのまま注入されているため、テストや拡張時の差し替えが想定されていません。  
抽象インターフェースやProtocolを定義し、実装との分離を図る必要があります。

DI構造の正しい抽象の流れ

UML Diagram

この図は、AppNotifierという抽象に依存し、具体的な実装(EmailNotifier, SlackNotifier)を切り替え可能な構造を示しています。
レビューアーは、このような依存の向き(依存の逆転)がコードに反映されているかを確認する必要があります。

:::warningning DI構造で見落とされがちなレビュー観点

  • 「インターフェース」と名付けていても、内部実装を含んだ設計になっていないか?
  • DI構造がモジュール間の境界設計として機能しているか?
  • 依存の向きが逆転していない(上位モジュール→下位モジュールのまま)になっていないか?
  • Depends()のようなフレームワークDIで、設計意図が隠蔽されていないか? :::

フレームワーク主導のDIと設計意図の非対称性

FastAPIなどでは以下のようにDIを実現できます:

FastAPIのDepends構造
def get_repo() -> Repository:
    return DBRepository()

@app.get("/users")
def get_user(repo: Repository = Depends(get_repo)):
    ...
フレームワーク主導DIの注意点
@Reviewer: `Depends()`を使ったDIは構造上は正しいですが、`get_repo()` が具象に依存している限り、抽象化の恩恵は得られません。  
DIを使う「意図」と「拡張の計画」が見えない場合、それは単なる技術的構文です。

意図の可視化と設計構造の言語化

設計意図がレビューアーに伝わる構造になっているかを確認するためには、以下のような補助要素が必要です:

  • 抽象クラスに コメントまたはdocstringで意図を明示
  • DI経由で注入されるクラスの 想定ユースケース(差し替え予定、モック用途)を設計資料に記載
  • テストコードにて、実装を差し替えた構造が 実際に使われている
意図の明文化を促す
@Reviewer: `Notifier` は拡張性確保のために抽象化された構造と思われますが、その用途(Slackとの切替など)が設計上明示されていないため、抽象の妥当性がレビュー上判断しづらくなっています。

結論:レビューアーは「抽象の存在理由」を読み取る構造を見る

DIは「使われていること」自体に意味があるのではなく、設計者がどのような意図で抽象と注入を導入したかが評価されるべきです。
レビューアーとして意識すべき判断軸は次の通りです:

  • 抽象が設計意図(テスト性・拡張性)と結びついているか?
  • 抽象が実装よりも「利用文脈」を表現しているか?
  • 抽象が形骸化していないか?(常に1実装しか存在しないなど)
  • DIによって構造が読みやすくなっているか、逆に複雑になっていないか?

Pythonは静的型の強制がないぶん、設計がすべて意図によって支えられる世界です。
レビューアーの役割は、「構文」よりも「構造」、「構造」よりも「意図」を正確に読むことにあります。