54
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

複雑な if-else を解消するちょっとした心がけ

Last updated at Posted at 2018-12-27

背景

コーディング初心者のコードレビューをしていると、しばしば if-else の深いネストが散見されます。SonarQube などのメトリクスツールでも「cyclomatic complexity が高すぎる」と怒られるやつです。

条件分岐を徹底的に減らしていこうとすると Strategy パターンやら State パターンといったデザインパターンの話がよく出てきますが、そこまでいかずとも、ちょっとした心がけでコードが見やすくなるよ、というお話です。初心者向けです。

以下は商品が購入可能かどうかを判定するコードです。

public boolean canPurchase() {
    boolean result;

    // ログインしているか
    if (isLoggedIn()) {
        // 在庫があるか
        if (existsStock()) {
            // 代引きの場合
            if (isPaymentMethodCash()) {
                result = true;
            // クレジット払いの場合
            } else {
                // クレジットカードが登録されているか
                if (isCreditCardRegistered()) {
                    result = true;
                } else {
                    result = false;
                }
            }
        } else {
            result = false;
        }
    } else {
        result = false;
    }

    return result;
}

典型的な、ネストが深くなってしまった例で、非常に処理を追いにくいコードになっています。不慣れな人のコードを見ていると以下のように、ついつい正常系のワークフローを意識してしまっている気がします。

if (1_正常系) {
    if (2_正常系) {
        ...
        ...
        ... // 長い処理
        ...
        ...
    } else {
        // 2_異常系
        return null;
    }
} else {
    // 1_異常系
    throw new SomeException();
}

しかし、ログインしていない、在庫がないといった「特殊な」ケースにおいては、他の条件(支払い方法やカード登録の有無)はどうでも良く、無条件で返却値が決まります(あるいは、例外が発生します)。よってこれらのケースは、他のロジックから分離してあげましょう。

まずは、ログイン条件を分離してあげます。

public boolean canPurchase() {

    // ログインしていなければ無条件で false
    if (!isLoggedIn()) {
        return false;
    }

    // 在庫があるか
    if (existsStock()) {
        // 代引きの場合
        if (isPaymentMethodCash()) {
            return true;
        // クレジット払いの場合
        } else {
            // クレジットカードが登録されているか
            if (isCreditCardRegistered()) {
                return true;
            } else {
                return false;
            }
        }
    } else {
        return false;
    }
}

最初のコードではローカル変数 result に真偽値を詰めて最後に return result していましたが、今回は直ちに真偽値を return しています。このように一時変数を使わず、返り値が確定した時点で返却する方法を早期リターンと呼ぶようです。
また上記のコードのように、冒頭で特殊ケースの早期リターンを行い、以降はその特殊ケースを対象外とする方法をガード節と呼びます。こうすれば else 句を使わずに済み、ネストが解消されます。

在庫条件も分離してみましょう。

public boolean canPurchase() {

    // ログインしてなければ無条件で false
    if (!isLoggedIn()) {
        return false;
    }

    // 在庫がなければ無条件で false
    if (!existsStock()) {
        return false;
    }

    // 代引きの場合
    if (isPaymentMethodCash()) {
        return true;
    // クレジット払いの場合
    } else {
        // クレジットカードが登録されているか
        if (isCreditCardRegistered()) {
            return true;
        } else {
            return false;
        }
    }
}

ネストがだいぶ解消され、処理を追いやすくなったと思います。ちょっとした労力で相当コードの見通しが良くなるコスパのよい方法なので、ぜひ心がけてみてください。

※ ガード節の適用範囲
ここでさらに踏み込むと、代引きの場合は必ず true になっているので、ここにもガード節を適用するとさらにネストを減らせます。

public boolean canPurchase() {

    // ログインしてなければ無条件で false
    if (!isLoggedIn()) {
        return false;
    }

    // 在庫がなければ無条件で false
    if (!existsStock()) {
        return false;
    }

    // 代引きの場合無条件で true
    if (isPaymentMethodCash()) {
        return true;
    }

    // クレジットカードが登録されているか
    if (isCreditCardRegistered()) {
        return true;
    } else {
        return false;
    }
}

ただ、これは好みや方針の問題かもしれませんが、個人的にはやらない方が良いと思います。理由としては、ガード節はあくまで特殊な例をあらかじめ弾くものであり、代引きかクレジットカード払いか、はどちらも正常ケースにおける一般的な条件だからです。対等な条件であれば、if-else で並列に並べるべきだと思います。また以下のように、ガード節の順番が変わると問題が生じてしまいます。

/** まずい例。ログインしてなくても代引きなら true が返る **/
public boolean canPurchase() {

    // 代引きの場合無条件で true
    if (isPaymentMethodCash()) {
        return true;
    }

    // ログインしてなければ無条件で false
    if (!isLoggedIn()) {
        return false;
    }

    // 在庫がなければ無条件で false
    if (!existsStock()) {
        return false;
    }

    // クレジットカードが登録されているか
    if (isCreditCardRegistered()) {
        return true;
    } else {
        return false;
    }
}

ガード節は独立して(順番に依存せず)判定できる範囲に留めておくべきかな、と思っています。

54
40
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?