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";
}
}
JavauserType × 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();
Javaif 文だらけのコードでは、本来オブジェクトが持つべき「種類ごとの振る舞い」が、
一箇所の 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 が減っていた」が理想です。
