Python|抽象基底クラス(ABC)の契約設計整理法
この記事のポイント
- ABCを使った契約設計の正しい責務整理技法を学ぶ
- インターフェース設計の「強制する責任」をレビューアー視点で読み解く
- 実務レビューでABC肥大・設計破綻の兆候を見抜くポイントを整理する
そもそも抽象基底クラス(ABC)とは
Pythonでは標準ライブラリの abc モジュールを用いて、抽象基底クラス (Abstract Base Class, ABC) を定義できます。
ABCは以下の責務を担います。
- 実装者にメソッド実装を強制する「契約」
- サブタイプ多態の実装責任を明確化
- 不完全な実装を事前検出(インスタンス化禁止)
最小例
from abc import ABC, abstractmethod
class DataLoader(ABC):
@abstractmethod
def load(self, source):
passabstractmethod を付与することでサブクラスは必ず 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
@Reviewer1つの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
@Reviewervalidateに具体実装を持たせています。デフォルト実装が契約設計と混在し、実装側の責任境界が曖昧化しています。
問題点
- 部分的にデフォルト実装混入
- サブクラス責任が読みにくくなる
- オーバーライド責任が不明確
改善例
改善後: デフォルト責任を明確分離
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設計イメージ
観点チェックリスト
まとめ
ABC設計は「型」ではなく契約管理の設計技法です。
レビューアーは常に「実装者は何を強制され、何を任意で決められるのか」を読み解き、
肥大化・責務混在・例外未整理を静かに整理していく技術が求められます。
契約整理は、構造レビューの中でも極めて中核のスキルとなります。

