リファクタリング
14-4

ロジックを値オブジェクトへ移す

貸出判定が読みやすくなると、次に目につくのは画面表示用・督促メール用とあちこちにコピペされた延滞料の計算。金額のような業務概念は、計算ロジックごと値オブジェクトに集約すれば重複が消え、ルール変更が1か所で済む。移し替えの間はテストで挙動を固定し、壊していないことを常に確認する。

01 呼び出し側にベタ書きされた延滞料計算

Bad

「1日10円」のルールが画面側にベタ書きされ、督促メール側にも同じ式のコピーが存在する

計算ロジックが呼び出し側に散在
def show_member_page(member: Member) -> None:
    # 督促メール側にもこれと同じ計算式のコピーがある
    fee = 0
    for loan in member.loans:
        days = (date.today() - loan.due_date).days
        if days > 0:
            fee += days * 10
    render_member_page(member, late_fee=fee)
Good

延滞料を LateFee 値オブジェクトに昇格させ、延滞日数の計算は Loan 自身に持たせる

計算ロジックを値オブジェクトへ集約
@dataclass(frozen=True)
class Loan:
    book_id: str
    due_date: date

    def overdue_days(self, today: date) -> int:
        return max((today - self.due_date).days, 0)


@dataclass(frozen=True)
class LateFee:
    FEE_PER_DAY: ClassVar[int] = 10
    amount: int

    @classmethod
    def of(cls, loans: tuple[Loan, ...], today: date) -> "LateFee":
        total_days = sum(loan.overdue_days(today) for loan in loans)
        return cls(amount=total_days * cls.FEE_PER_DAY)


# 呼び出し側は1行になる
fee = LateFee.of(member.loans, today=date.today())

Java の final フィールドによる不変クラスは、Python では @dataclass(frozen=True) で表現できる。today を内部で date.today() せず引数で受け取ることで計算が純粋関数になり、テストで任意の日付を差し込める。

02 テストで挙動を固定してから移し替える

Good

期待する仕様を先にテストへ書き出し、移行中に挙動が変わったら即座に検知できる状態でロジックを動かす

移し替えを守るユニットテスト
def test_late_fee_is_10_yen_per_overdue_day() -> None:
    today = date(2026, 6, 11)
    loans = (
        Loan(book_id="978-4-00-000001-9", due_date=date(2026, 6, 8)),   # 3日延滞
        Loan(book_id="978-4-00-000002-6", due_date=date(2026, 6, 20)),  # 期限内
    )
    assert LateFee.of(loans, today=today).amount == 30


def test_late_fee_is_zero_when_nothing_is_overdue() -> None:
    today = date(2026, 6, 11)
    loans = (Loan(book_id="978-4-00-000003-3", due_date=date(2026, 6, 25)),)
    assert LateFee.of(loans, today=today).amount == 0

手順は「移行先のクラスのひな型を用意→テストを書く→いったん失敗させる→実装してテストを通す→呼び出し側を切り替えて古いベタ書きを消す」。テストという安全網があるからこそ、動いているコードに思い切って手を入れられる。

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

ロジックを値オブジェクトへ移す

貸出判定が読みやすくなると、次に目につくのは画面表示用・督促メール用とあちこちにコピペされた延滞料の計算。金額のような業務概念は、計算ロジックごと値オブジェクトに集約すれば重複が消え、ルール変更が1か所で済む。移し替えの間はテストで挙動を固定し、壊していないことを常に確認する。

01 呼び出し側にベタ書きされた延滞料計算

Bad

「1日10円」のルールが画面側にベタ書きされ、督促メール側にも同じ式のコピーが存在する

計算ロジックが呼び出し側に散在
def show_member_page(member: Member) -> None:
    # 督促メール側にもこれと同じ計算式のコピーがある
    fee = 0
    for loan in member.loans:
        days = (date.today() - loan.due_date).days
        if days > 0:
            fee += days * 10
    render_member_page(member, late_fee=fee)
Good

延滞料を LateFee 値オブジェクトに昇格させ、延滞日数の計算は Loan 自身に持たせる

計算ロジックを値オブジェクトへ集約
@dataclass(frozen=True)
class Loan:
    book_id: str
    due_date: date

    def overdue_days(self, today: date) -> int:
        return max((today - self.due_date).days, 0)


@dataclass(frozen=True)
class LateFee:
    FEE_PER_DAY: ClassVar[int] = 10
    amount: int

    @classmethod
    def of(cls, loans: tuple[Loan, ...], today: date) -> "LateFee":
        total_days = sum(loan.overdue_days(today) for loan in loans)
        return cls(amount=total_days * cls.FEE_PER_DAY)


# 呼び出し側は1行になる
fee = LateFee.of(member.loans, today=date.today())

Java の final フィールドによる不変クラスは、Python では @dataclass(frozen=True) で表現できる。today を内部で date.today() せず引数で受け取ることで計算が純粋関数になり、テストで任意の日付を差し込める。

02 テストで挙動を固定してから移し替える

Good

期待する仕様を先にテストへ書き出し、移行中に挙動が変わったら即座に検知できる状態でロジックを動かす

移し替えを守るユニットテスト
def test_late_fee_is_10_yen_per_overdue_day() -> None:
    today = date(2026, 6, 11)
    loans = (
        Loan(book_id="978-4-00-000001-9", due_date=date(2026, 6, 8)),   # 3日延滞
        Loan(book_id="978-4-00-000002-6", due_date=date(2026, 6, 20)),  # 期限内
    )
    assert LateFee.of(loans, today=today).amount == 30


def test_late_fee_is_zero_when_nothing_is_overdue() -> None:
    today = date(2026, 6, 11)
    loans = (Loan(book_id="978-4-00-000003-3", due_date=date(2026, 6, 25)),)
    assert LateFee.of(loans, today=today).amount == 0

手順は「移行先のクラスのひな型を用意→テストを書く→いったん失敗させる→実装してテストを通す→呼び出し側を切り替えて古いベタ書きを消す」。テストという安全網があるからこそ、動いているコードに思い切って手を入れられる。

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