この記事のポイント

  • dataclass(frozen=True)の活用ポイントをレビューできる
  • イミュータブル設計によるAPI契約の安全性向上を理解できる
  • 変更責務の分離整理をレビュー技法として学べる

そもそもdataclass(frozen)とは

Pythonのdataclassは柔軟なデータ構造を提供します。
さらにfrozen=Trueを指定することでイミュータブル(不変)なデータ構造が定義できます。

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

特徴は以下です。

  • 初期化後の属性変更禁止
  • __hash__自動実装 → set/dictのキーに利用可能
  • API契約の変更不可能性を型で保証
  • スレッドセーフ設計に有効

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

現場で頻発する失敗は以下です。

  • API契約に可変dataclassを使用
  • 内部状態保持も無条件でfrozen適用
  • 更新責務の整理不足
  • ハッシュ利用可否設計漏れ
  • 不変性必要箇所の見極めミス

レビューアーは「このオブジェクトは変更される設計責務か?」を常に読み取ります。

レビューアー視点

  • frozen指定が責務整理に一致しているか
  • API契約と実装責務の不変性整合が取れているか
  • 内部状態保持で誤ってfrozen適用されていないか
  • ハッシュ利用可能性が考慮されているか
  • 実行時変更漏洩リスクを事前に遮断できているか

開発者視点

  • API入出力・DTO用途はfrozen優先
  • 内部更新責務を持つモデルは非frozen
  • ハッシュキー候補はfrozen適用
  • 更新責務整理は設計段階で定義
  • frozen適用範囲は最小必要限に限定

良い実装例

なぜこの実装が良いのか

  • API層のDTOにfrozen適用
  • 内部処理層では柔軟dataclass利用
  • 更新責務を型に明確化
  • ハッシュ可能性も維持可能
# api_dto.py

from dataclasses import dataclass

@dataclass(frozen=True)
class ApiRequest:
    request_id: int
    endpoint: str
    client_ip: str
# domain_model.py

@dataclass
class ApiRequestLog:
    request_id: int
    endpoint: str
    client_ip: str
    archived: bool = False
補足

API契約は不変性が型に反映、内部状態は更新責務に応じ柔軟化されています。レビューアーはこの責務線引きを重点確認します。

レビュー観点

  • API契約で不変型が採用されているか
  • 内部状態でfrozen誤用が無いか
  • 更新責務が型定義に反映されているか
  • ハッシュ利用可能性設計が考慮されているか
  • frozen適用範囲が適切に限定されているか

良くない実装例: ケース1(API契約で可変dataclass)

# bad_mutable_api_dto.py

from dataclasses import dataclass

@dataclass
class ApiRequest:
    request_id: int
    endpoint: str
    client_ip: str
@Reviewer
API契約DTOにmutable dataclassを使用しています。frozen=Trueを検討してください。

問題点

  • 呼び出し側が意図せず値変更可能
  • バグ混入経路
  • API契約破壊リスク

改善例

# good_api_frozen_dto.py

from dataclasses import dataclass

@dataclass(frozen=True)
class ApiRequest:
    request_id: int
    endpoint: str
    client_ip: str

API契約は不変型優先が原則。レビューではAPI層型定義で即確認します。

良くない実装例: ケース2(内部状態管理でfrozen誤用)

# bad_internal_frozen.py

from dataclasses import dataclass

@dataclass(frozen=True)
class Session:
    user_id: str
    attributes: dict[str, str]
@Reviewer
内部状態保持にfrozenを指定し可変属性を内包しています。整合性崩壊リスクがあります。

問題点

  • dict型属性はmutable
  • 外部経由で実質更新可能
  • frozen指定の意図消失

改善例

# good_internal_non_frozen.py

from dataclasses import dataclass

@dataclass
class Session:
    user_id: str
    attributes: dict[str, str]

内部可変構造は非frozenで自然表現が基本原則。レビューでは属性型まで確認します。

良くない実装例: ケース3(ハッシュ利用検討漏れ)

# bad_missing_hashability.py

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

points = {Point(1, 2)}
@Reviewer
hash可能性が不定です。ハッシュ利用予定があるならfrozen指定を検討してください。

問題点

  • hashability未定義
  • 実行時TypeError可能性
  • 設計意図不明化

改善例

# good_hashable_point.py

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

points = {Point(1, 2)}

集合/辞書キー候補はhashable保証が原則。レビューではハッシュ利用想定有無を確認します。

良くない実装例: ケース4(frozen範囲の不必要拡大)

# bad_overfrozen.py

from dataclasses import dataclass

@dataclass(frozen=True)
class Config:
    timeout: int
    retries: int
    logger: object
@Reviewer
logger等の依存注入構造までfrozen化しています。必要最小限の凍結を検討してください。

問題点

  • 非データ属性まで凍結
  • DI柔軟性低下
  • 保守性悪化

改善例

# good_responsibility_split.py

from dataclasses import dataclass

@dataclass(frozen=True)
class RetryPolicy:
    timeout: int
    retries: int

@dataclass
class Config:
    policy: RetryPolicy
    logger: object

データ責務だけをfrozen化し依存性責務は柔軟化が安定設計。レビューでは責務ごとに凍結範囲を評価します。

観点チェックリスト

まとめ

dataclass(frozen)レビューは「変更責務と契約安全性を型で制御する訓練」です。
レビューアーは常に

  • どこが誰の責務で変更されるか?
  • API契約は守られる型構造か?
  • 不変保証が過剰/不足になっていないか?

を読み取り、安全な設計意図を型構造で自然に読み取れる状態をレビュー実践の中で作っていきます。
frozenレビューは現場設計育成に非常に有効です。