メソッド設計
12-3

尋ねるな、命じろ

呼び出し側がオブジェクトの状態をいちいち尋ね、判断し、外から書き換える構造は、判断ロジックを呼び出し側の数だけ複製させる。状態を持つオブジェクトに「こうしろ」と命じれば、判断はデータの持ち主に一元化される。あわせて、状態を変えるメソッド(コマンド)と状態を返すメソッド(クエリ)を1つに混ぜないことも大切。

01 督促判定を請求書自身に任せる

Bad

呼び出し側が期日と状態を尋ねて判断し、ステータスを外から書き換えている。督促画面とバッチで同じ判定が二重実装になる

尋ねて、判断して、外からいじる
def send_reminders(invoices: list[Invoice], today: date) -> None:
    for invoice in invoices:
        # 状態の判断が Invoice の外にある
        if invoice.status == "issued" and invoice.due_date < today:
            invoice.status = "overdue"
            mailer.send(invoice.customer_email, "お支払いのお願い")
Good

延滞判定は Invoice 自身のメソッドに命じる。呼び出し側は結果を使うだけ

判断はデータの持ち主に命じる
class Invoice:
    def update_overdue(self, today: date) -> None:
        # 期日と状態の判断は請求書自身が知っている
        if self._status is Status.ISSUED and self._due_date < today:
            self._status = Status.OVERDUE

    @property
    def is_overdue(self) -> bool:
        return self._status is Status.OVERDUE


def send_reminders(invoices: list[Invoice], today: date) -> None:
    for invoice in invoices:
        invoice.update_overdue(today)
        if invoice.is_overdue:
            mailer.send(invoice.customer_email, "お支払いのお願い")

Java の getter/setter だらけのクラスに相当するのが、Python では「公開属性を外から読んで書き戻すだけのクラス」。属性公開そのものは Python らしい書き方だが、判断を伴う変更はメソッドとして持ち主に置く。

02 コマンドとクエリを分離する

Bad

「加算して残高も返す」メソッドは、残高を見たいだけの呼び出しでも状態を変えてしまう

変更と取得を1つのメソッドが兼ねる
class ClientDeposit:
    """顧客からの預り金"""

    def __init__(self) -> None:
        self._balance = 0

    def deposit_and_balance(self, amount: int) -> int:
        self._balance += amount  # 変更(コマンド)
        return self._balance     # 取得(クエリ)も兼ねている


balance = deposit.deposit_and_balance(0)  # 照会のつもりの 0 円入金が履歴を汚す
Good

変更は deposit、取得は balance に分ける。照会だけなら状態は一切変わらない

コマンドとクエリを別メソッドに
class ClientDeposit:
    def __init__(self) -> None:
        self._balance = 0

    def deposit(self, amount: int) -> None:
        if amount <= 0:
            raise ValueError("入金額は正の値で指定してください")
        self._balance += amount

    @property
    def balance(self) -> int:
        return self._balance

コマンド・クエリ分離(CQS)と呼ばれる原則。「クエリは何度呼んでも安全」「コマンドは状態を変えるが値を返さない」と割り切ると、メソッドの役割が名前から読み取れるようになる。

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

尋ねるな、命じろ

呼び出し側がオブジェクトの状態をいちいち尋ね、判断し、外から書き換える構造は、判断ロジックを呼び出し側の数だけ複製させる。状態を持つオブジェクトに「こうしろ」と命じれば、判断はデータの持ち主に一元化される。あわせて、状態を変えるメソッド(コマンド)と状態を返すメソッド(クエリ)を1つに混ぜないことも大切。

01 督促判定を請求書自身に任せる

Bad

呼び出し側が期日と状態を尋ねて判断し、ステータスを外から書き換えている。督促画面とバッチで同じ判定が二重実装になる

尋ねて、判断して、外からいじる
def send_reminders(invoices: list[Invoice], today: date) -> None:
    for invoice in invoices:
        # 状態の判断が Invoice の外にある
        if invoice.status == "issued" and invoice.due_date < today:
            invoice.status = "overdue"
            mailer.send(invoice.customer_email, "お支払いのお願い")
Good

延滞判定は Invoice 自身のメソッドに命じる。呼び出し側は結果を使うだけ

判断はデータの持ち主に命じる
class Invoice:
    def update_overdue(self, today: date) -> None:
        # 期日と状態の判断は請求書自身が知っている
        if self._status is Status.ISSUED and self._due_date < today:
            self._status = Status.OVERDUE

    @property
    def is_overdue(self) -> bool:
        return self._status is Status.OVERDUE


def send_reminders(invoices: list[Invoice], today: date) -> None:
    for invoice in invoices:
        invoice.update_overdue(today)
        if invoice.is_overdue:
            mailer.send(invoice.customer_email, "お支払いのお願い")

Java の getter/setter だらけのクラスに相当するのが、Python では「公開属性を外から読んで書き戻すだけのクラス」。属性公開そのものは Python らしい書き方だが、判断を伴う変更はメソッドとして持ち主に置く。

02 コマンドとクエリを分離する

Bad

「加算して残高も返す」メソッドは、残高を見たいだけの呼び出しでも状態を変えてしまう

変更と取得を1つのメソッドが兼ねる
class ClientDeposit:
    """顧客からの預り金"""

    def __init__(self) -> None:
        self._balance = 0

    def deposit_and_balance(self, amount: int) -> int:
        self._balance += amount  # 変更(コマンド)
        return self._balance     # 取得(クエリ)も兼ねている


balance = deposit.deposit_and_balance(0)  # 照会のつもりの 0 円入金が履歴を汚す
Good

変更は deposit、取得は balance に分ける。照会だけなら状態は一切変わらない

コマンドとクエリを別メソッドに
class ClientDeposit:
    def __init__(self) -> None:
        self._balance = 0

    def deposit(self, amount: int) -> None:
        if amount <= 0:
            raise ValueError("入金額は正の値で指定してください")
        self._balance += amount

    @property
    def balance(self) -> int:
        return self._balance

コマンド・クエリ分離(CQS)と呼ばれる原則。「クエリは何度呼んでも安全」「コマンドは状態を変えるが値を返さない」と割り切ると、メソッドの役割が名前から読み取れるようになる。

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