8-7
巨大データクラスを分割する
あらゆる項目を1つに詰め込んだデータクラスは、無関係な処理からも参照・変更されるグローバル変数の親戚になる。どこで何が書き換わるか追えず、一部だけ更新された中途半端な状態も生まれやすい。関心事ごとに小さなクラスへ分割し、関連するロジックも一緒に持たせる。
確定申告の案件管理データ
✕ Bad
@dataclass
class TaxReturnCase:
case_id: int
client_id: int
client_name: str
fiscal_year: int
progress: str # 申告の進捗
received_documents: list[str]
fee_amount: int # 請求
invoice_issued: bool
meeting_at: datetime # 面談予約
meeting_place: str
# 進捗更新も請求も面談変更も、全部このクラス越しに行われる✓ Good
@dataclass(frozen=True)
class FilingProgress:
status: FilingStatus
received_documents: tuple[str, ...]
def is_ready_to_file(self) -> bool:
return self.status is FilingStatus.REVIEWED
@dataclass(frozen=True)
class Billing:
fee_amount: int
invoice_issued: bool
@dataclass(frozen=True)
class Meeting:
held_at: datetime
place: str
@dataclass(frozen=True)
class TaxReturnCase:
case_id: int
client_id: int
fiscal_year: int
progress: FilingProgress
billing: Billing
meeting: Meeting分割すると「請求の修正は Billing だけ見ればよい」と影響範囲が閉じる。進捗判定のような小さなロジックも、データと同じクラスに置けば散らばらない。巨大データクラス+手続きの山は、密結合が極まった「神クラス」への入り口。