Python|dataclassの__post_init__で発生する責務肥大の整理法
この記事のポイント
- __post_init__の責務設計をレビューで整理できる
- 初期化後ロジックの肥大化パターンを見抜ける
- 構造的に安全な責務分離設計技法を学べる
そもそも__post_init__とは
Pythonのdataclassでは、__init__は自動生成されます。
追加の初期化処理が必要な場合、__post_init__メソッドを定義できます。
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
def __post_init__(self):
if self.age < 0:
raise ValueError("年齢は0以上でなければなりません")特徴は以下です。
- __init__実行後に自動呼び出し
- 初期化検証・補完処理向き
- 可読性と責務整理を意識しないと崩壊しやすい
なぜこれをレビューするのか
現場では以下の失敗が頻発します。
- __post_init__が肥大化し過ぎる
- バリデーションと依存初期化が混在
- 外部I/O・副作用処理が混入
- 可読性・テスト性・保守性崩壊
レビューアーは「これは本当に初期化責務か?」を常に読み取ります。
レビューアー視点
- __post_init__の責務範囲が整理されているか
- バリデーション・依存初期化・副作用処理が分離されているか
- 外部アクセスが混入していないか
- テスト可能性が維持されているか
- データクラス本体の責務肥大化が発生していないか
開発者視点
- __post_init__は「データ整合性検証」に限定
- 外部依存初期化はファクトリ・サービス層へ分離
- 複雑ロジックは専用メソッドへ抽出
- 単一責務原則(SRP)を強く意識
- 無理な状態自動修正を行わない
良い実装例
なぜこの実装が良いのか
- __post_init__責務は整合性検証のみ
- 外部依存は完全に分離
- データ構造の単一責務性が明確
- テスト性・再利用性が高い
# user.py
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
def __post_init__(self):
if not self.name:
raise ValueError("名前は必須です")
if self.age < 0:
raise ValueError("年齢は0以上でなければなりません")補足
「純粋な入力値整合性チェックのみ」に限定するのが安定設計です。レビューアーは副作用有無を即座に確認します。
レビュー観点
- __post_init__がバリデーション専用になっているか
- 外部依存処理が混入していないか
- 複雑ロジックが専用関数化されているか
- 自動補正・補完が暴走していないか
- 責務が他クラスへ分離可能になっているか
良くない実装例: ケース1(外部依存初期化混入)
# bad_external_dependency.py
from dataclasses import dataclass
@dataclass
class Report:
file_path: str
def __post_init__(self):
self.content = open(self.file_path).read()
@Reviewer__post_init__で外部ファイル依存処理が混入しています。依存注入かファクトリ分離を検討してください。
問題点
- I/O処理がデータ構造初期化に混入
- テスト困難
- 保守性崩壊
改善例
# good_external_injection.py
@dataclass
class Report:
file_path: str
content: str# 別責務層で注入処理
def load_report(file_path: str) -> Report:
content = open(file_path).read()
return Report(file_path=file_path, content=content)外部依存は明示注入原則。レビューではファイル・DBアクセス混入を即指摘します。
良くない実装例: ケース2(データ補完処理の混入)
# bad_auto_correction.py
from dataclasses import dataclass
@dataclass
class Config:
timeout: int
def __post_init__(self):
if self.timeout < 0:
self.timeout = 0
@Reviewer自動補完による状態修正が混入しています。例外化または事前補正へ分離検討してください。
問題点
- サイレント修正による不具合誘発
- 入力値不整合が隠蔽される
- バグ診断困難化
改善例
# good_validation_only.py
@dataclass
class Config:
timeout: int
def __post_init__(self):
if self.timeout < 0:
raise ValueError("timeoutは0以上である必要があります")サイレント補完ではなくバリデーション例外設計が原則。レビューでは補完動作有無を確認します。
良くない実装例: ケース3(重複ロジック肥大)
# bad_business_logic_mixed.py
from dataclasses import dataclass
@dataclass
class Order:
quantity: int
unit_price: int
def __post_init__(self):
self.total_price = self.quantity * self.unit_price
@Reviewerビジネス計算ロジックが__post_init__に混入しています。専用計算責務に分離してください。
問題点
- __post_init__に計算責務混入
- 単一責務崩壊
- 再計算・再利用困難化
改善例
# good_separate_calculation.py
@dataclass
class Order:
quantity: int
unit_price: int
def total_price(self) -> int:
return self.quantity * self.unit_price計算責務は専用関数へ分離が設計原則。レビューでは計算混入有無を重点確認します。
良くない実装例: ケース4(複合責務肥大)
# bad_mixed_responsibilities.py
from dataclasses import dataclass
@dataclass
class Account:
user_id: str
balance: int
overdraft_allowed: bool
def __post_init__(self):
if not self.user_id:
raise ValueError("user_id必須")
if self.balance < 0 and not self.overdraft_allowed:
raise ValueError("残高不足")
self.is_active = self.balance >= 0
@Reviewer検証・規約・状態派生処理が混在しています。責務ごとに整理分離してください。
問題点
- バリデーション・ビジネス規約・状態派生が混在
- 読み手負荷増大
- 保守困難化
改善例
# good_responsibility_split.py
@dataclass
class Account:
user_id: str
balance: int
overdraft_allowed: bool
def __post_init__(self):
if not self.user_id:
raise ValueError("user_id必須")
if self.balance < 0 and not self.overdraft_allowed:
raise ValueError("残高不足")
def is_active(self) -> bool:
return self.balance >= 0純粋バリデーションと派生状態計算は分離が設計安定策。レビューでは肥大混在を即検出します。
観点チェックリスト
まとめ
__post_init__レビューは「初期化後ロジックの責務線引き訓練」です。
レビューアーは常に
- これは純粋な整合性検証か?
- 外部依存を持っていないか?
- 派生処理が混入していないか?
を読み取り、安全で明確な初期化設計を導くレビュー習慣を形成します。
__post_init__責務整理は現場設計育成教材として非常に有効です。