この記事のポイント

  • ABCを使った契約設計の正しい責務整理技法を学ぶ
  • インターフェース設計の「強制する責任」をレビューアー視点で読み解く
  • 実務レビューでABC肥大・設計破綻の兆候を見抜くポイントを整理する

そもそも抽象基底クラス(ABC)とは

Pythonでは標準ライブラリの abc モジュールを用いて、抽象基底クラス (Abstract Base Class, ABC) を定義できます。
ABCは以下の責務を担います。

  • 実装者にメソッド実装を強制する「契約」
  • サブタイプ多態の実装責任を明確化
  • 不完全な実装を事前検出(インスタンス化禁止)
最小例
from abc import ABC, abstractmethod

class DataLoader(ABC):

    @abstractmethod
    def load(self, source):
        pass

abstractmethod を付与することでサブクラスは必ず load() を実装しなければなりません。

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

ABCは強力な構造整理手段ですが、以下の設計ミスが頻発します。

  • 契約粒度の不統一(責務肥大)
  • 実装責任の混在(部分実装設計崩壊)
  • 継承強制の濫用(多重継承肥大)
  • 抽象契約で例外設計が未整理

レビューアーは「契約設計は責務分離である」という立場で読解します。

レビューアー視点

  • 契約対象は単一責務・明確な行動単位になっているか
  • 実装者が何を実装すべきか迷わず分かる構造か
  • デフォルト実装が不要に混在していないか
  • 例外通知・エラー契約が明文化されているか

開発者視点

  • ABCは「実装強制=最小構成責任」と捉える
  • 実装猶予がある責務はABCへ押し込めない
  • 継承階層を深くせず、インターフェース分割を意識する

良い実装例

正常設計例:責務分離された契約
from abc import ABC, abstractmethod

class DataLoader(ABC):

    @abstractmethod
    def load(self, source: str) -> dict:
        """データを読み込み辞書化して返す"""
        pass

class CsvDataLoader(DataLoader):

    def load(self, source: str) -> dict:
        with open(source, "r") as f:
            return self._parse(f)

    def _parse(self, file_obj):
        # CSV解析処理
        return {}

良い理由

  • loadのみ契約対象とし責務明確
  • 実装補助メソッドはサブクラス側に閉じ込め
  • 継承者が迷わず負うべき責任が見える

レビュー観点

  • 契約責務は最小単位に整理されているか
  • サブクラスが実装漏れを起こしにくい構造か
  • 実装補助責任をABCが背負い過ぎていないか
  • エラー発生時の契約行動(例外・戻り値)が整理されているか

良くない実装例: ケース1

問題例: 複数責務肥大化ABC
from abc import ABC, abstractmethod

class ComplexLoader(ABC):

    @abstractmethod
    def load(self, source: str) -> dict:
        pass

    @abstractmethod
    def validate(self, data: dict) -> bool:
        pass

    @abstractmethod
    def transform(self, data: dict) -> dict:
        pass
@Reviewer
1つのABCに複数の実装責任を押し込めすぎています。単一責務インターフェースへ分割整理してください。

問題点

  • load・validate・transformの責務混在
  • 継承者は常に全機能実装を強制される
  • 利用シナリオ毎の柔軟性喪失

改善例

改善後: インターフェース分割整理
class Loader(ABC):
    @abstractmethod
    def load(self, source: str) -> dict:
        pass

class Validator(ABC):
    @abstractmethod
    def validate(self, data: dict) -> bool:
        pass

class Transformer(ABC):
    @abstractmethod
    def transform(self, data: dict) -> dict:
        pass
設計補足
  • 責務単位ごとに契約分離
  • 複合的な実装は複数インターフェース実装で柔軟組合せ可能

良くない実装例: ケース2

問題例: 部分実装を強制混入
from abc import ABC, abstractmethod

class BaseLoader(ABC):

    @abstractmethod
    def load(self, source: str) -> dict:
        pass

    def validate(self, data: dict) -> bool:
        return True
@Reviewer
validateに具体実装を持たせています。デフォルト実装が契約設計と混在し、実装側の責任境界が曖昧化しています。

問題点

  • 部分的にデフォルト実装混入
  • サブクラス責任が読みにくくなる
  • オーバーライド責任が不明確

改善例

改善後: デフォルト責任を明確分離
class Validator(ABC):

    @abstractmethod
    def validate(self, data: dict) -> bool:
        pass

class AlwaysValidValidator(Validator):
    def validate(self, data: dict) -> bool:
        return True
設計補足
  • デフォルト方針を別クラス実装に切り出し
  • 継承側のオーバーライド判断が明確化

良くない実装例: ケース3

問題例: 例外契約未整理
from abc import ABC, abstractmethod

class DataLoader(ABC):

    @abstractmethod
    def load(self, source: str) -> dict:
        pass

class CsvLoader(DataLoader):

    def load(self, source: str) -> dict:
        with open(source, "r") as f:
            raise IOError("読み込み失敗")
@Reviewer
例外契約がABCで規定されていません。呼び出し元は何をcatchすべきか読めずレビュー不能です。契約仕様へ明示してください。

問題点

  • 例外契約が設計仕様に未明示
  • 呼び出し側がハンドリング方針を立てられない

改善例

改善後: 例外契約仕様明文化
class DataLoader(ABC):

    @abstractmethod
    def load(self, source: str) -> dict:
        """
        読み込み失敗時はDataLoadErrorを送出する
        """
        pass

class DataLoadError(Exception):
    pass

class CsvLoader(DataLoader):

    def load(self, source: str) -> dict:
        try:
            with open(source, "r") as f:
                return self._parse(f)
        except IOError as e:
            raise DataLoadError("読み込み失敗") from e
設計補足
  • 例外型も契約範囲に含め明文化
  • 呼び出し側の共通処理設計が安定

PlantUML設計イメージ

UML Diagram
UML Diagram

観点チェックリスト


まとめ

ABC設計は「型」ではなく契約管理の設計技法です。
レビューアーは常に「実装者は何を強制され、何を任意で決められるのか」を読み解き、
肥大化・責務混在・例外未整理を静かに整理していく技術が求められます。
契約整理は、構造レビューの中でも極めて中核のスキルとなります。