条件分岐
6-4

条件をポリシーとして合成する

「優良取引先なら与信枠を増やす」のような判定は、複数の条件の組み合わせでできている。判定メソッドの中に条件をベタ書きすると、似たランク判定(優良・標準など)の間で同じ条件が重複し、基準変更のたびに全メソッドを探して直すことになる。条件を1つずつ独立したルールとして切り出し、ルールの組み合わせ(ポリシー)としてランクを表現すれば、条件の再利用と差し替えが自在になる。

取引先の与信ランク判定

Bad

優良と標準の判定メソッドに同じ条件が重複。遅延率の基準を変えるとき両方を直す必要がある

条件ベタ書きの判定メソッド
def is_prime_partner(history: TradeHistory) -> bool:
    if history.years_of_trade >= 3:
        if history.annual_amount >= 5_000_000:
            if history.late_payment_rate <= 0.01:
                return True
    return False

def is_standard_partner(history: TradeHistory) -> bool:
    if history.annual_amount >= 1_000_000:
        if history.late_payment_rate <= 0.01:  # 同じ条件のコピー
            return True
    return False
Good

条件を1つずつ関数に切り出し、ポリシーは「ルールの束」として組み立てる

ルール関数の合成でランクを定義
CreditRule = Callable[[TradeHistory], bool]

def long_term_trade(h: TradeHistory) -> bool:
    return h.years_of_trade >= 3

def large_annual_amount(h: TradeHistory) -> bool:
    return h.annual_amount >= 5_000_000

def low_late_payment(h: TradeHistory) -> bool:
    return h.late_payment_rate <= 0.01

@dataclass(frozen=True)
class CreditPolicy:
    rules: tuple[CreditRule, ...]

    def comply_with_all(self, history: TradeHistory) -> bool:
        return all(rule(history) for rule in self.rules)

PRIME = CreditPolicy((long_term_trade, large_annual_amount, low_late_payment))
STANDARD = CreditPolicy((large_annual_amount, low_late_payment))

原典の言語ではルールを interface+クラスで表すが、Python は関数が第一級なので関数そのものをルールにできる。「遅延率の基準を 0.5% に厳格化」は low_late_payment の1か所を直せば全ポリシーに波及し、新ランクの追加もルールの組み合わせを1行書くだけになる。

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

条件をポリシーとして合成する

「優良取引先なら与信枠を増やす」のような判定は、複数の条件の組み合わせでできている。判定メソッドの中に条件をベタ書きすると、似たランク判定(優良・標準など)の間で同じ条件が重複し、基準変更のたびに全メソッドを探して直すことになる。条件を1つずつ独立したルールとして切り出し、ルールの組み合わせ(ポリシー)としてランクを表現すれば、条件の再利用と差し替えが自在になる。

取引先の与信ランク判定

Bad

優良と標準の判定メソッドに同じ条件が重複。遅延率の基準を変えるとき両方を直す必要がある

条件ベタ書きの判定メソッド
def is_prime_partner(history: TradeHistory) -> bool:
    if history.years_of_trade >= 3:
        if history.annual_amount >= 5_000_000:
            if history.late_payment_rate <= 0.01:
                return True
    return False

def is_standard_partner(history: TradeHistory) -> bool:
    if history.annual_amount >= 1_000_000:
        if history.late_payment_rate <= 0.01:  # 同じ条件のコピー
            return True
    return False
Good

条件を1つずつ関数に切り出し、ポリシーは「ルールの束」として組み立てる

ルール関数の合成でランクを定義
CreditRule = Callable[[TradeHistory], bool]

def long_term_trade(h: TradeHistory) -> bool:
    return h.years_of_trade >= 3

def large_annual_amount(h: TradeHistory) -> bool:
    return h.annual_amount >= 5_000_000

def low_late_payment(h: TradeHistory) -> bool:
    return h.late_payment_rate <= 0.01

@dataclass(frozen=True)
class CreditPolicy:
    rules: tuple[CreditRule, ...]

    def comply_with_all(self, history: TradeHistory) -> bool:
        return all(rule(history) for rule in self.rules)

PRIME = CreditPolicy((long_term_trade, large_annual_amount, low_late_payment))
STANDARD = CreditPolicy((large_annual_amount, low_late_payment))

原典の言語ではルールを interface+クラスで表すが、Python は関数が第一級なので関数そのものをルールにできる。「遅延率の基準を 0.5% に厳格化」は low_late_payment の1か所を直せば全ポリシーに波及し、新ランクの追加もルールの組み合わせを1行書くだけになる。

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