doc.dev1x.org

判定関数パターン

1. 目的

複数の条件変数から構成される判定ロジックを簡潔に管理したい

2. 課題

if文がネストしたコードはメンテナンスが困難

# 3つの因子の組み合わせ毎に異なる値を返す関数
def example(value1, value2, value3):
    if value1 == 'xxx':
        if value2 == 'xxx':
            if value3 == 'xxx':
                # 数行から数十行のビジネスロジック
                return "Result08"
            else:
                # 数行から数十行のビジネスロジック
                return "Result04"
        else:
            if value3 == 'xxx':
                # 数行から数十行のビジネスロジック
                return "Result06"
            else:
                # 数行から数十行のビジネスロジック
                return "Result02"
    else:
        if value2 == 'xxx':
            if value3 == 'xxx':
                # 数行から数十行のビジネスロジック
                return "Result07"
            else:
                # 数行から数十行のビジネスロジック
                return "Result03"
        else:
            if value3 == 'xxx':
                # 数行から数十行のビジネスロジック
                return "Result5"
            else:
                # 数行から数十行のビジネスロジック
                return "Result01"

if __name__=='__main__':
    print(example("---", "---", "---"))     # => Result01
    print(example("xxx", "---", "---"))     # => Result02
    print(example("---", "xxx", "---"))     # => Result03
    print(example("xxx", "xxx", "---"))     # => Result04
    print(example("---", "---", "xxx"))     # => Result05
    print(example("xxx", "---", "xxx"))     # => Result06
    print(example("---", "xxx", "xxx"))     # => Result07
    print(example("xxx", "xxx", "xxx"))     # => Result08

3. 解決策

判定処理は判定処理のみを行う関数に分離する

# 因子1を判定する関数
def factor1(value):
    return value == 'xxx'

# 因子2を判定する関数
def factor2(value):
    return value == 'xxx'

# 因子3を判定する関数
def factor3(value):
    return value == 'xxx'

def decide(value1, value2, value3):
    result1 = factor1(value1)
    result2 = factor2(value2)
    result3 = factor3(value3)

    if all([not result1, not result2, not result3]):
        return "Pattern01"

    elif all([result1, not result2, not result3]):
        return "Pattern02"

    elif all([not result1, result2, not result3]):
        return "Pattern03"

    elif all([result1, result2, not result3]):
        return "Pattern04"

    elif all([not result1, not result2, result3]):
        return "Pattern05"

    elif all([result1, not result2, result3]):
        return "Pattern06"

    elif all([not result1, result2, result3]):
        return "Pattern07"

    elif all([result1, result2, result3]):
        return "Pattern08"

    else:
        raise ValueError()


    # ↓こういう書き方もあり
    # if all([not result1, not result2, not result3]):    return "Pattern01"
    # if all([result1, not result2, not result3]):        return "Pattern02"
    # if all([not result1, result2, not result3]):        return "Pattern03"
    # if all([result1, result2, not result3]):            return "Pattern04"
    # if all([not result1, not result2, result3]):        return "Pattern05"
    # if all([result1, not result2, result3]):            return "Pattern06"
    # if all([not result1, result2, result3]):            return "Pattern07"
    # if all([result1, result2, result3]):                return "Pattern08"
    # raise ValueError()



if __name__=='__main__':
    print(decide("---", "---", "---"))     # => Pattern01
    print(decide("xxx", "---", "---"))     # => Pattern02
    print(decide("---", "xxx", "---"))     # => Pattern03
    print(decide("xxx", "xxx", "---"))     # => Pattern04
    print(decide("---", "---", "xxx"))     # => Pattern05
    print(decide("xxx", "---", "xxx"))     # => Pattern06
    print(decide("---", "xxx", "xxx"))     # => Pattern07
    print(decide("xxx", "xxx", "xxx"))     # => Pattern08

判断ロジック内でビジネスロジックや入出力ロジックを実行しない

def main():
    # (1) 判断ロジック呼び出し
    result = decide(param1, param2, param3)

    # (2) 判定結果に応じたビジネスロジック実行
    if result == "Pattern01":
        # Pattern1 のビジネスロジック実行
        ...

    if result == "Pattern02":
        # Pattern2 のビジネスロジック実行
        ...
    ...

4. メリット

if文のネストを避けることができる

判断ロジックをビジネスロジックから分離できる

ディシジョンテーブルをそのままプログラムに落とし込める

5. デメリット

因子が増えるとコードが長くなりがち

6. 注意

関連文書

参考資料

Appendix-1 クラスによる実装

class Decision:

    def factor1(self, factor):
        return factor == 'xxx'

    def factor2(self, factor):
        return factor == 'xxx'

    def factor3(self, factor):
        return factor == 'xxx'

    def decide(self, value1, value2, value3):

        result1 = self.factor1(value1)
        result2 = self.factor2(value2)
        result3 = self.factor3(value3)

        if all([not result1, not result2, not result3]):
            return "Pattern01"

        elif all([result1, not result2, not result3]):
            return "Pattern02"

        elif all([not result1, result2, not result3]):
            return "Pattern03"

        elif all([result1, result2, not result3]):
            return "Pattern04"

        elif all([not result1, not result2, result3]):
            return "Pattern05"

        elif all([result1, not result2, result3]):
            return "Pattern06"

        elif all([not result1, result2, result3]):
            return "Pattern07"

        elif all([result1, result2, result3]):
            return "Pattern08"

        else:
            raise ValueError()


if __name__=='__main__':
    d = Decision()
    print(d.decide("---", "---", "---"))     # => Pattern01
    print(d.decide("xxx", "---", "---"))     # => Pattern02
    print(d.decide("---", "xxx", "---"))     # => Pattern03
    print(d.decide("xxx", "xxx", "---"))     # => Pattern04
    print(d.decide("---", "---", "xxx"))     # => Pattern05
    print(d.decide("xxx", "---", "xxx"))     # => Pattern06
    print(d.decide("---", "xxx", "xxx"))     # => Pattern07
    print(d.decide("xxx", "xxx", "xxx"))     # => Pattern08

Appendix-2 Go言語実装例

import (
    "errors"
)

func factor1(factor string) bool {
    return factor == "xxx"
}

func factor2(factor string) bool {
    return factor == "xxx"
}

func factor3(factor string) bool {
    return factor == "xxx"
}

func every(value1 bool, value2 bool, value3 bool) bool {
    return value1 && value2 && value3
}


func Decide(value1 string, value2 string, value3 string) (string, error) {

    result1 := factor1(value1)
    result2 := factor2(value2)
    result3 := factor3(value3)

    switch {
    case every(!result1, !result2, !result3):
        return "Pattern01", nil
    case every(result1, !result2, !result3):
        return "Pattern02", nil
    case every(!result1, result2, !result3):
        return "Pattern03", nil
    case every(result1, result2, !result3):
        return "Pattern04", nil
    case every(!result1, !result2, result3):
        return "Pattern05", nil
    case every(result1, !result2, result3):
        return "Pattern06", nil
    case every(!result1, result2, result3):
        return "Pattern07", nil
    case every(result1, result2, result3):
        return "Pattern08", nil
    default:
        return nil, errors.New("Error!")
    }
}