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

null / None を構造で排除する

「未設定」「該当なし」を None で表現すると、None チェックがコードのいたるところに増殖し、1箇所でも漏れれば AttributeError で落ちる。「持っていない」も立派な状態なのだから、None ではなく専用の値として設計する。常にインスタンスが存在すれば、チェックも例外も構造ごと消える。

01 確定申告の控除合計が None チェックだらけになる

Bad

「適用なし」を None で表現したせいで、触る箇所すべてに None チェックが要る

None 前提のロジック
class TaxReturn:
    medical: Deduction | None
    donation: Deduction | None
    insurance: Deduction | None

    def total_deduction(self) -> int:
        total = 0
        if self.medical is not None:
            total += self.medical.amount
        if self.donation is not None:
            total += self.donation.amount
        if self.insurance is not None:
            total += self.insurance.amount
        return total

    def reset(self) -> None:
        # 「適用なし」状態を None で塗りつぶしている
        self.medical = None
        self.donation = None
        self.insurance = None
Good

「適用なし」を表す値を用意し、常にインスタンスが存在する構造にする

「適用なし」も状態として設計
from dataclasses import dataclass, field

@dataclass(frozen=True)
class Deduction:
    name: str
    amount: int

NO_DEDUCTION = Deduction("適用なし", 0)

@dataclass
class TaxReturn:
    medical: Deduction = NO_DEDUCTION
    donation: Deduction = NO_DEDUCTION
    insurance: Deduction = NO_DEDUCTION

    def total_deduction(self) -> int:
        return (self.medical.amount
                + self.donation.amount
                + self.insurance.amount)

    def reset(self) -> None:
        self.medical = NO_DEDUCTION
        self.donation = NO_DEDUCTION
        self.insurance = NO_DEDUCTION

02 None を返さない・渡さない

Bad

関数が None を返すと、呼び出し側すべてに分岐が伝播していく

None が呼び出し側へ漏れ出す
def find_donation(receipts: list[Receipt]) -> Deduction | None:
    total = sum(r.amount for r in receipts)
    if total == 0:
        return None  # 「寄附なし」を None で返してしまう
    return Deduction("寄附金控除", calc_donation(total))

# 呼び出し側は毎回 None を疑わないといけない
deduction = find_donation(receipts)
label = deduction.name if deduction is not None else "適用なし"
Good

「該当なし」も値として返せば、呼び出し側は分岐なしでそのまま使える

常に Deduction 型を返す
def find_donation(receipts: list[Receipt]) -> Deduction:
    total = sum(r.amount for r in receipts)
    if total == 0:
        return NO_DEDUCTION  # 「寄附なし」も立派な値
    return Deduction("寄附金控除", calc_donation(total))

deduction = find_donation(receipts)
label = deduction.name  # None チェック不要

Python に Kotlin のような null 非許容型はないが、型ヒントから `| None` を減らし、mypy / pyright の strict モードで検査すれば「None が紛れ込んだら型エラー」という null 安全に近い体制を作れる。Optional を返す関数を見たら、まず「該当なしを表す値で置き換えられないか」を考える。

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

null / None を構造で排除する

「未設定」「該当なし」を None で表現すると、None チェックがコードのいたるところに増殖し、1箇所でも漏れれば AttributeError で落ちる。「持っていない」も立派な状態なのだから、None ではなく専用の値として設計する。常にインスタンスが存在すれば、チェックも例外も構造ごと消える。

01 確定申告の控除合計が None チェックだらけになる

Bad

「適用なし」を None で表現したせいで、触る箇所すべてに None チェックが要る

None 前提のロジック
class TaxReturn:
    medical: Deduction | None
    donation: Deduction | None
    insurance: Deduction | None

    def total_deduction(self) -> int:
        total = 0
        if self.medical is not None:
            total += self.medical.amount
        if self.donation is not None:
            total += self.donation.amount
        if self.insurance is not None:
            total += self.insurance.amount
        return total

    def reset(self) -> None:
        # 「適用なし」状態を None で塗りつぶしている
        self.medical = None
        self.donation = None
        self.insurance = None
Good

「適用なし」を表す値を用意し、常にインスタンスが存在する構造にする

「適用なし」も状態として設計
from dataclasses import dataclass, field

@dataclass(frozen=True)
class Deduction:
    name: str
    amount: int

NO_DEDUCTION = Deduction("適用なし", 0)

@dataclass
class TaxReturn:
    medical: Deduction = NO_DEDUCTION
    donation: Deduction = NO_DEDUCTION
    insurance: Deduction = NO_DEDUCTION

    def total_deduction(self) -> int:
        return (self.medical.amount
                + self.donation.amount
                + self.insurance.amount)

    def reset(self) -> None:
        self.medical = NO_DEDUCTION
        self.donation = NO_DEDUCTION
        self.insurance = NO_DEDUCTION

02 None を返さない・渡さない

Bad

関数が None を返すと、呼び出し側すべてに分岐が伝播していく

None が呼び出し側へ漏れ出す
def find_donation(receipts: list[Receipt]) -> Deduction | None:
    total = sum(r.amount for r in receipts)
    if total == 0:
        return None  # 「寄附なし」を None で返してしまう
    return Deduction("寄附金控除", calc_donation(total))

# 呼び出し側は毎回 None を疑わないといけない
deduction = find_donation(receipts)
label = deduction.name if deduction is not None else "適用なし"
Good

「該当なし」も値として返せば、呼び出し側は分岐なしでそのまま使える

常に Deduction 型を返す
def find_donation(receipts: list[Receipt]) -> Deduction:
    total = sum(r.amount for r in receipts)
    if total == 0:
        return NO_DEDUCTION  # 「寄附なし」も立派な値
    return Deduction("寄附金控除", calc_donation(total))

deduction = find_donation(receipts)
label = deduction.name  # None チェック不要

Python に Kotlin のような null 非許容型はないが、型ヒントから `| None` を減らし、mypy / pyright の strict モードで検査すれば「None が紛れ込んだら型エラー」という null 安全に近い体制を作れる。Optional を返す関数を見たら、まず「該当なしを表す値で置き換えられないか」を考える。

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