悪しき構造の弊害
1-3

データクラスが不具合を呼び寄せる

フィールドを公開してデータを持つだけのクラスは、一見シンプルで無害に見える。しかしデータを使う計算ロジックが外に散らばるため、重複コード・修正漏れ・可読性低下を招き、さらに未初期化のまま使える「生焼けオブジェクト」や不正値の混入まで許してしまう。1つの悪しき構造が複数の不具合を連鎖的に呼び込む典型例。

01 保有銘柄クラスと散らばる評価額ロジック

Bad

クラスはデータの入れ物だけ。評価額の計算が複数のサービスに重複して住み着き、計算方法の変更時に修正漏れが起きる

データとロジックが離れている(低凝集)
@dataclass
class Holding:
    ticker: str = ""
    quantity: int = 0
    average_cost: float | None = None

class PortfolioReportService:
    def evaluate(self, h: Holding, price: float) -> float:
        return price * h.quantity

class RebalanceService:
    # 別の担当者が「未実装だ」と思い込んで再実装した重複コード
    def current_value(self, h: Holding, price: float) -> float:
        return price * h.quantity

データと計算ロジックが離れていると、既存実装の存在に気づけず同じロジックが量産される。評価方法の仕様変更時、全部を見つけて直せなければバグになる。

02 未初期化のまま使えてしまう(生焼けオブジェクト)

Bad

引数なしで生成できるため、取得単価が None のままのインスタンスが流通し、計算した瞬間に実行時エラーになる

初期化しないと使い物にならないクラス
holding = Holding()  # 全フィールドが既定値のまま生成できる

# average_cost は None のまま → ここで TypeError
profit = (1_850.0 - holding.average_cost) * holding.quantity

「初期化してから使う」というルールを利用側の記憶に頼る設計は、いつか必ず破られる。クラス自身が未初期化状態を許さない構造にすべき。

03 不正値の混入を構造で防ぐ

Bad

どこからでも代入できるので、ありえない値が混入する。防ごうとすると利用側ごとにバリデーションが重複していく

不正値を入れ放題
holding.quantity = -300         # 空売りしていないのに負の数量
holding.average_cost = -1200.0  # 取得単価がマイナス

# 利用側それぞれで防衛的チェックを書き始めると、
# 今度はバリデーションロジック自体が重複コードになる
Good

生成時に不正値を遮断し、計算ロジックをデータと同居させる。不変なので生成後に書き換えられる心配もない

不変+自己検証するクラスの予告編
@dataclass(frozen=True)
class Holding:
    ticker: str
    quantity: int
    average_cost: Decimal

    def __post_init__(self) -> None:
        if self.quantity <= 0:
            raise ValueError("数量は1以上であること")
        if self.average_cost <= 0:
            raise ValueError("取得単価は正の値であること")

    def market_value(self, price: Decimal) -> Decimal:
        return price * self.quantity

データを持つクラス自身が正しさを保証し、計算も自分で担う。この「値オブジェクト+不変」の設計は第3章で詳しく扱う。

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

データクラスが不具合を呼び寄せる

フィールドを公開してデータを持つだけのクラスは、一見シンプルで無害に見える。しかしデータを使う計算ロジックが外に散らばるため、重複コード・修正漏れ・可読性低下を招き、さらに未初期化のまま使える「生焼けオブジェクト」や不正値の混入まで許してしまう。1つの悪しき構造が複数の不具合を連鎖的に呼び込む典型例。

01 保有銘柄クラスと散らばる評価額ロジック

Bad

クラスはデータの入れ物だけ。評価額の計算が複数のサービスに重複して住み着き、計算方法の変更時に修正漏れが起きる

データとロジックが離れている(低凝集)
@dataclass
class Holding:
    ticker: str = ""
    quantity: int = 0
    average_cost: float | None = None

class PortfolioReportService:
    def evaluate(self, h: Holding, price: float) -> float:
        return price * h.quantity

class RebalanceService:
    # 別の担当者が「未実装だ」と思い込んで再実装した重複コード
    def current_value(self, h: Holding, price: float) -> float:
        return price * h.quantity

データと計算ロジックが離れていると、既存実装の存在に気づけず同じロジックが量産される。評価方法の仕様変更時、全部を見つけて直せなければバグになる。

02 未初期化のまま使えてしまう(生焼けオブジェクト)

Bad

引数なしで生成できるため、取得単価が None のままのインスタンスが流通し、計算した瞬間に実行時エラーになる

初期化しないと使い物にならないクラス
holding = Holding()  # 全フィールドが既定値のまま生成できる

# average_cost は None のまま → ここで TypeError
profit = (1_850.0 - holding.average_cost) * holding.quantity

「初期化してから使う」というルールを利用側の記憶に頼る設計は、いつか必ず破られる。クラス自身が未初期化状態を許さない構造にすべき。

03 不正値の混入を構造で防ぐ

Bad

どこからでも代入できるので、ありえない値が混入する。防ごうとすると利用側ごとにバリデーションが重複していく

不正値を入れ放題
holding.quantity = -300         # 空売りしていないのに負の数量
holding.average_cost = -1200.0  # 取得単価がマイナス

# 利用側それぞれで防衛的チェックを書き始めると、
# 今度はバリデーションロジック自体が重複コードになる
Good

生成時に不正値を遮断し、計算ロジックをデータと同居させる。不変なので生成後に書き換えられる心配もない

不変+自己検証するクラスの予告編
@dataclass(frozen=True)
class Holding:
    ticker: str
    quantity: int
    average_cost: Decimal

    def __post_init__(self) -> None:
        if self.quantity <= 0:
            raise ValueError("数量は1以上であること")
        if self.average_cost <= 0:
            raise ValueError("取得単価は正の値であること")

    def market_value(self, price: Decimal) -> Decimal:
        return price * self.quantity

データを持つクラス自身が正しさを保証し、計算も自分で担う。この「値オブジェクト+不変」の設計は第3章で詳しく扱う。

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