設計を蝕む悪魔たち
9-5

不正値はコンストラクタで遮断する

不正な値の混入をどこでも防げる唯一の関門が、インスタンス生成の瞬間。生成時にガードしておけば「存在するインスタンスはすべて正しい」と信頼でき、利用側のチェックが不要になる。逆に素通しのクラスは、不正値が帳簿の奥まで運ばれてから発覚する。

仕訳金額の検証をどこでやるか

Bad

無検証のクラスに外から値を流し込めるため、負の金額が月次集計まで素通りする

不正値が生成時を素通りする
class JournalEntry:
    def __init__(self) -> None:
        self.debit_account = ""
        self.credit_account = ""
        self.amount = 0

entry = JournalEntry()
entry.amount = -3_000  # 負の仕訳金額が誰にも止められない
entry.debit_account = "旅費交通費"
# 発覚するのは月次決算で貸借が合わなくなったとき。原因の特定は困難
Good

生成時に検証して例外を投げる。不正値は帳簿に入る前、発生したその行で止まる

__post_init__ で生成時に遮断
from dataclasses import dataclass

@dataclass(frozen=True)
class JournalEntry:
    debit_account: str
    credit_account: str
    amount: int

    def __post_init__(self) -> None:
        if not self.debit_account or not self.credit_account:
            raise ValueError("勘定科目は必須")
        if self.amount <= 0:
            raise ValueError(f"仕訳金額は正の値であること: {self.amount}")

entry = JournalEntry("旅費交通費", "現金", 3_000)
# 不正な金額はこの時点で ValueError になり、混入経路が即座に特定できる

frozen=True と組み合わせれば「正しく生まれ、その後変わらない」インスタンスになる。なお Python では object.__setattr__ や __new__ を使えば frozen もガードも貫通できてしまうが、これは検証を無効化するメタプログラミングであり、型と契約の信頼性を根こそぎ壊す。テストの特殊なセットアップ以外で使わないこと。

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

不正値はコンストラクタで遮断する

不正な値の混入をどこでも防げる唯一の関門が、インスタンス生成の瞬間。生成時にガードしておけば「存在するインスタンスはすべて正しい」と信頼でき、利用側のチェックが不要になる。逆に素通しのクラスは、不正値が帳簿の奥まで運ばれてから発覚する。

仕訳金額の検証をどこでやるか

Bad

無検証のクラスに外から値を流し込めるため、負の金額が月次集計まで素通りする

不正値が生成時を素通りする
class JournalEntry:
    def __init__(self) -> None:
        self.debit_account = ""
        self.credit_account = ""
        self.amount = 0

entry = JournalEntry()
entry.amount = -3_000  # 負の仕訳金額が誰にも止められない
entry.debit_account = "旅費交通費"
# 発覚するのは月次決算で貸借が合わなくなったとき。原因の特定は困難
Good

生成時に検証して例外を投げる。不正値は帳簿に入る前、発生したその行で止まる

__post_init__ で生成時に遮断
from dataclasses import dataclass

@dataclass(frozen=True)
class JournalEntry:
    debit_account: str
    credit_account: str
    amount: int

    def __post_init__(self) -> None:
        if not self.debit_account or not self.credit_account:
            raise ValueError("勘定科目は必須")
        if self.amount <= 0:
            raise ValueError(f"仕訳金額は正の値であること: {self.amount}")

entry = JournalEntry("旅費交通費", "現金", 3_000)
# 不正な金額はこの時点で ValueError になり、混入経路が即座に特定できる

frozen=True と組み合わせれば「正しく生まれ、その後変わらない」インスタンスになる。なお Python では object.__setattr__ や __new__ を使えば frozen もガードも貫通できてしまうが、これは検証を無効化するメタプログラミングであり、型と契約の信頼性を根こそぎ壊す。テストの特殊なセットアップ以外で使わないこと。

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