Java | オブジェクト指向:if 文だらけのコードの問題点

Java Java
スポンサーリンク

if 文だらけのコードが「なぜツラくなる」のか

if 自体は悪ではありません。
条件分岐はどの言語にもある基本機能だし、使う場面ももちろんあります。

問題になるのは、「if 文がコードのあちこちに増えすぎて、ロジックが条件だらけの迷路になる」ことです。
そうなると、読みにくい・直しにくい・バグが入りやすい、という三拍子が揃ってしまいます。

ここから、具体例を交えながら「if 文だらけの何が問題なのか」「どういう状態が危険なのか」を、初心者向けにかみ砕いて話していきます。


問題点 1: 読みにくくて意図がわからなくなる

条件と処理が混ざって「ストーリー」が見えない

まずは、ちょっと極端ですがよくある形の例です。

final class OrderService {

    String place(String userType, int amount) {
        if (userType == null) {
            return "NG";
        }
        if ("guest".equals(userType)) {
            if (amount > 10_000) {
                return "NG";
            } else {
                if (amount <= 0) {
                    return "NG";
                } else {
                    return "OK";
                }
            }
        } else if ("member".equals(userType)) {
            if (amount <= 0) {
                return "NG";
            } else {
                return "OK";
            }
        } else {
            return "NG";
        }
    }
}
Java

条件だらけで、「このメソッドの目的」が一瞬でつかめません。

本当はビジネスルールはこうです。

ユーザー種別が null なら NG
ゲストなら 1〜10,000 の範囲で OK
会員なら 1 以上なら OK
それ以外は NG

ですが、コードを読むときにその「ストーリー」が見えてこない。
if が何段にもネストしていて、頭の中で追いかける負荷が高すぎます。

if 文だらけになると、
「このメソッドは何をしたいのか?」よりも
「この条件は true か false か?」を追いかける作業に脳のリソースが奪われてしまいます。


問題点 2: 条件の追加・変更で壊れやすくなる(重要)

新しい条件追加のたびに if が増殖する

例えば、さっきの OrderService に「VIP ユーザ」を追加したくなったとします。

ルールはこう変えたいとしましょう。

VIP は金額に関係なく常に OK

if だらけのコードにこれを足そうとすると、たいていこうなります。

else if ("vip".equals(userType)) {
    return "OK";
}
Java

一見簡単に見えますが、実際には各条件を全部検証し直さないといけません。

「guest の条件に影響してないか?」
「member の条件に漏れがないか?」
「最後の else に落ちるパターンは何か?」

条件が増えれば増えるほど、「全パターンを頭の中で再シミュレーション」が必要になります。

if 文は「追加するとき楽そうに見える」けれど、
「増えれば増えるほど全体が壊れやすくなる」構造を持っています。

条件に穴が開いてもコンパイラは教えてくれない

if ベースの条件は、「どのパターンを扱い忘れているか」をコンパイラが教えてくれません。

例えば enum を使っていたとしても、

if (type == UserType.GUEST) { ... }
else if (type == UserType.MEMBER) { ... }
Java

ここに新しい enum 値 VIP を追加しても、コンパイラは何も言いません。
開発者が「修正漏れ」に気づかなければ、バグになります。

if だらけの構造は「抜け漏れに気づきにくい」という意味で、とても危険です。


問題点 3: 同じ if ロジックがコピペされて増殖する

あちこちに “似たような if” が散らばる

例えば、ユーザの種別によって「割引率」と「ポイント付与率」が変わるとします。

まず割引のコードを書くとき、こうなりがちです。

int discountRate(String userType) {
    if ("guest".equals(userType)) {
        return 0;
    } else if ("member".equals(userType)) {
        return 10;
    } else if ("vip".equals(userType)) {
        return 20;
    } else {
        return 0;
    }
}
Java

次にポイントのコードを書くとき、

int pointRate(String userType) {
    if ("guest".equals(userType)) {
        return 1;
    } else if ("member".equals(userType)) {
        return 2;
    } else if ("vip".equals(userType)) {
        return 3;
    } else {
        return 0;
    }
}
Java

というように、「UserType ごとの判定」が丸ごと二重に存在する形になります。

ここで「vip の扱いを変えたい」となったとき、
両方の if を探して修正しないといけません。

一箇所修正して、もう一箇所を忘れたら矛盾が生じます。
テストで偶然気づければ良いですが、そうでなければ本番で事故になります。

if だらけのコードの怖さは、この「同じ条件判定の分散・コピペ」にあります。
ロジックが一箇所にまとまっていないので、変更のたびに“人力で同期”しないといけないのです。


問題点 4: テストが書きづらく、バグを見つけにくい

条件が絡み合うとテストパターンも爆発する

if が多くなればなるほど、「テストすべきパターン数」も増えていきます。

例えばこんなコードを考えます。

String result(String userType, int amount, boolean campaign) {
    if ("guest".equals(userType)) {
        if (campaign) {
            if (amount > 10_000) {
                return "A";
            } else {
                return "B";
            }
        } else {
            return "C";
        }
    } else if ("member".equals(userType)) {
        if (amount > 0 && amount <= 10_000) {
            return "D";
        } else {
            return "E";
        }
    } else {
        return "F";
    }
}
Java

userType × amount × campaign の組み合わせで、
どのパターンをテストしたか、どのパターンをまだ試していないかを整理するのは、
かなりしんどくなります。

if だらけは、「条件の組み合わせ」に対する意識を要求します。
それをきちんと整理してテストできるのは、正直なところ上級者でも大変です。

テストの観点から見ても、「条件分岐を減らす」「責務を分ける」ことは非常に重要です。


問題点 5: 本来オブジェクトが持つべき責務が if に吸い込まれる(重要)

「種類ごとの違い」を if が抱え込んでしまう

さっきの割引の例を、もう一度振り返ってみます。

int discountRate(String userType) {
    if ("guest".equals(userType)) {
        return 0;
    } else if ("member".equals(userType)) {
        return 10;
    } else if ("vip".equals(userType)) {
        return 20;
    } else {
        return 0;
    }
}
Java

本来これは、「UserType ごとに割引率を決める」という責務です。
Object 指向的には、「その違いは UserType 自身(またはそれに対応するクラス)が持つべき」です。

例えば、こういう形のほうが自然です。

enum UserType {
    GUEST {
        @Override int discountRate() { return 0; }
    },
    MEMBER {
        @Override int discountRate() { return 10; }
    },
    VIP {
        @Override int discountRate() { return 20; }
    };

    abstract int discountRate();
}
Java

呼び出し側はこう書けます。

int rate = userType.discountRate();
Java

if 文だらけのコードでは、本来オブジェクトが持つべき「種類ごとの振る舞い」が、
一箇所の if に吸い込まれてしまい、その if が神クラス化していきます。

オブジェクト指向は本来、
「データ」と「そのデータに対する振る舞い」を一緒に持たせる考え方です。
if だらけのコードは、これに真っ向から逆行してしまいます。


どういう if が危険で、どこから直すといいのか

危険サイン 1: 同じ条件が何度も出てくる

コード中に、同じ if ("guest".equals(userType)) のような条件が複数回出てきたら、
それは「何かが分離されていない」というサインです。

UserType の概念を enum やクラスにまとめて、その中に振る舞いを移せないか?
という視点で見直してみると良いです。

危険サイン 2: if の中でさらに if がネストしている

if の中で if が行列しているコードは、読み手にとって「条件の迷路」です。
多くの場合、

入力チェック
種別ごとの振る舞い
キャンペーンや日付など、別の軸の条件

がごちゃ混ぜになっています。

それぞれを別メソッド/別クラスに分けられないか?
「このメソッドは何の責任を持つべきか?」から逆算して、責務分割を考えると良いです。

危険サイン 3: 新しい種類追加のたびに既存の if に手を入れている

何か種類を足すたびに、既存 if 文をくり抜いて修正していないか?
それは、「分岐ロジックをポリモーフィズムや enum に移したほうがいいサイン」です。


まとめ:if 文だらけを「設計の匂い」として感じ取る

if 文が悪いわけではありません。
しかし、if 文だらけになっているコードは、次のような危うさを抱えています。

読むのが大変で、意図が見えにくくなる
条件追加・変更のたびに壊れやすくなる
同じ条件ロジックがコピペされて、修正漏れが起きやすくなる
テストすべきパターンが爆発して、抜け漏れの温床になる
本来オブジェクトが持つべき振る舞いを、if が丸飲みしてしまう

「if を減らしたい」はゴールではなく、「責務をいい感じに分解した結果、if が減っていた」が理想です。

タイトルとURLをコピーしました