密結合
8-7

巨大データクラスを分割する

あらゆる項目を1つに詰め込んだデータクラスは、無関係な処理からも参照・変更されるグローバル変数の親戚になる。どこで何が書き換わるか追えず、一部だけ更新された中途半端な状態も生まれやすい。関心事ごとに小さなクラスへ分割し、関連するロジックも一緒に持たせる。

確定申告の案件管理データ

Bad

申告進捗・請求・面談予約という別々の関心事が1つのデータクラスに詰め込まれている

なんでも入りの巨大データクラス
@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 だけ見ればよい」と影響範囲が閉じる。進捗判定のような小さなロジックも、データと同じクラスに置けば散らばらない。巨大データクラス+手続きの山は、密結合が極まった「神クラス」への入り口。

参考: 『良いコード/悪いコードで学ぶ設計入門』(ミノ駆動 著、技術評論社)第8章。コード例は原則を自分の題材で表現し直したオリジナル。
8-7

巨大データクラスを分割する

あらゆる項目を1つに詰め込んだデータクラスは、無関係な処理からも参照・変更されるグローバル変数の親戚になる。どこで何が書き換わるか追えず、一部だけ更新された中途半端な状態も生まれやすい。関心事ごとに小さなクラスへ分割し、関連するロジックも一緒に持たせる。

確定申告の案件管理データ

Bad

申告進捗・請求・面談予約という別々の関心事が1つのデータクラスに詰め込まれている

なんでも入りの巨大データクラス
@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 だけ見ればよい」と影響範囲が閉じる。進捗判定のような小さなロジックも、データと同じクラスに置けば散らばらない。巨大データクラス+手続きの山は、密結合が極まった「神クラス」への入り口。

参考: 『良いコード/悪いコードで学ぶ設計入門』(ミノ駆動 著、技術評論社)第8章。コード例は原則を自分の題材で表現し直したオリジナル。