悪しき構造の弊害
1-3

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

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

01 商品有高帳の残高クラスと散らばる金額計算

Bad

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

データとロジックが離れている(低凝集)
@dataclass
class InventoryBalance:
    product_code: str = ""
    quantity: int = 0
    unit_cost: Decimal | None = None

class InventoryReportService:
    def ending_amount(self, balance: InventoryBalance) -> Decimal:
        return balance.unit_cost * balance.quantity

class CostOfSalesService:
    # 別の担当者が「未実装だ」と思い込んで再実装した重複コード
    def ending_inventory(self, balance: InventoryBalance) -> Decimal:
        return balance.unit_cost * balance.quantity
Good

帳簿価額の計算をクラス自身に持たせる。サービス側は book_value() を呼ぶだけになり、重複も修正漏れも起こらない

データと計算ロジックを同居させる(高凝集)
@dataclass
class InventoryBalance:
    product_code: str
    quantity: int
    unit_cost: Decimal

    def book_value(self) -> Decimal:
        """商品有高帳の帳簿価額(計算ロジックの置き場はここ1か所だけ)"""
        return self.unit_cost * self.quantity

class InventoryReportService:
    def ending_amount(self, balance: InventoryBalance) -> Decimal:
        return balance.book_value()

class CostOfSalesService:
    def ending_inventory(self, balance: InventoryBalance) -> Decimal:
        # 同じメソッドを再利用するだけ。重複も再実装も起きない
        return balance.book_value()

データと計算ロジックが離れていると、既存実装の存在に気づけず同じロジックが量産される。データが持つべき計算はデータの近くに置く(凝集)と、仕様変更時の修正点が1か所に収まる。

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

Bad

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

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

# unit_cost は None のまま → ここで TypeError
ending_amount = balance.unit_cost * balance.quantity
Good

既定値を外して全フィールドを必須にする。引数なしの InventoryBalance() は型エラーで弾かれ、未初期化インスタンスがそもそも生成できない

生成時に完成形を強制する
@dataclass
class InventoryBalance:
    product_code: str     # 既定値なし → 必須引数
    quantity: int         # 必須
    unit_cost: Decimal    # 必須

# InventoryBalance()  # TypeError: missing 3 required positional arguments
balance = InventoryBalance(product_code="A-001", quantity=120, unit_cost=Decimal("850"))

# unit_cost は必ず初期化されている → 安全に計算できる
ending_amount = balance.unit_cost * balance.quantity

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

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

Bad

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

不正値を入れ放題
balance.quantity = -300              # 商品有高帳の残高数量がマイナス
balance.unit_cost = Decimal("-1200")  # 取得単価がマイナス

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

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

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

    def __post_init__(self) -> None:
        if self.quantity < 0:
            raise ValueError("残高数量は0以上であること")
        if self.unit_cost < 0:
            raise ValueError("取得単価は0以上であること")

    def book_value(self) -> Decimal:
        return self.unit_cost * self.quantity

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

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

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

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

01 商品有高帳の残高クラスと散らばる金額計算

Bad

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

データとロジックが離れている(低凝集)
@dataclass
class InventoryBalance:
    product_code: str = ""
    quantity: int = 0
    unit_cost: Decimal | None = None

class InventoryReportService:
    def ending_amount(self, balance: InventoryBalance) -> Decimal:
        return balance.unit_cost * balance.quantity

class CostOfSalesService:
    # 別の担当者が「未実装だ」と思い込んで再実装した重複コード
    def ending_inventory(self, balance: InventoryBalance) -> Decimal:
        return balance.unit_cost * balance.quantity
Good

帳簿価額の計算をクラス自身に持たせる。サービス側は book_value() を呼ぶだけになり、重複も修正漏れも起こらない

データと計算ロジックを同居させる(高凝集)
@dataclass
class InventoryBalance:
    product_code: str
    quantity: int
    unit_cost: Decimal

    def book_value(self) -> Decimal:
        """商品有高帳の帳簿価額(計算ロジックの置き場はここ1か所だけ)"""
        return self.unit_cost * self.quantity

class InventoryReportService:
    def ending_amount(self, balance: InventoryBalance) -> Decimal:
        return balance.book_value()

class CostOfSalesService:
    def ending_inventory(self, balance: InventoryBalance) -> Decimal:
        # 同じメソッドを再利用するだけ。重複も再実装も起きない
        return balance.book_value()

データと計算ロジックが離れていると、既存実装の存在に気づけず同じロジックが量産される。データが持つべき計算はデータの近くに置く(凝集)と、仕様変更時の修正点が1か所に収まる。

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

Bad

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

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

# unit_cost は None のまま → ここで TypeError
ending_amount = balance.unit_cost * balance.quantity
Good

既定値を外して全フィールドを必須にする。引数なしの InventoryBalance() は型エラーで弾かれ、未初期化インスタンスがそもそも生成できない

生成時に完成形を強制する
@dataclass
class InventoryBalance:
    product_code: str     # 既定値なし → 必須引数
    quantity: int         # 必須
    unit_cost: Decimal    # 必須

# InventoryBalance()  # TypeError: missing 3 required positional arguments
balance = InventoryBalance(product_code="A-001", quantity=120, unit_cost=Decimal("850"))

# unit_cost は必ず初期化されている → 安全に計算できる
ending_amount = balance.unit_cost * balance.quantity

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

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

Bad

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

不正値を入れ放題
balance.quantity = -300              # 商品有高帳の残高数量がマイナス
balance.unit_cost = Decimal("-1200")  # 取得単価がマイナス

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

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

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

    def __post_init__(self) -> None:
        if self.quantity < 0:
            raise ValueError("残高数量は0以上であること")
        if self.unit_cost < 0:
            raise ValueError("取得単価は0以上であること")

    def book_value(self) -> Decimal:
        return self.unit_cost * self.quantity

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

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