条件を組み合わせる
このレッスンで分かること
- 条件が入れ子になると読みにくくなり、抜け漏れも起きやすくなります
&&と||を使うと、入れ子をフラットなelse ifの並びに書き換えられます- 入れ子とフラット、どちらが読みやすいかは条件の性質で決まります
条件を組み合わせる とは
公開状態とカテゴリという 2 つの情報から、作品に付けるラベルを決める関数を書きます。入れ子を論理演算子でフラットにする書き換えが主役です。
2 つの情報で判定する
山田 太郎さんのプロフィールサイトの作品データは、次の形をしていました。
const work = { title: "プロフィールサイト", year: 2026, views: 120, published: true };一覧画面に出すラベルを、公開状態とカテゴリの 2 つから決めたいとします。仕様は次のとおりです。
| 公開状態 | カテゴリ | ラベル |
|---|---|---|
| 公開 | "web" | "注目作品" |
| 公開 | "web" 以外 | "公開中" |
| 非公開 | どれでも | "下書き" |
判定に使う値が 2 つになりました。ここからが今回のテーマです。同じ仕様でも、書き方は何通りもあります。
まず入れ子で書いてみる
素直に考えると、まず公開状態を見て、公開ならさらにカテゴリを見る、という順になります。if の中に if を書く、いわゆる入れ子 (ネスト) の形です。
function pickupLabel(published, category) {
if (published) {
if (category === "web") {
return "注目作品";
} else {
return "公開中";
}
} else {
return "下書き";
}
}正しく動きます。仕様の考え方の順序がそのままコードの形になっているので、書いた本人には読みやすく感じるはずです。
問題は、条件が増えたときです。ここに「閲覧数が 100 以上なら人気作品」という条件を足し、さらに「今年の作品なら新着」を足していくと、次のような形に育ちます。
function pickupLabel(published, category, views, year) {
if (published) {
if (category === "web") {
if (views >= 100) {
if (year === 2026) {
return "新着の人気 Web 作品";
} else {
return "人気 Web 作品";
}
} else {
return "注目作品";
}
} else {
return "公開中";
}
} else {
return "下書き";
}
}インデントが右へ右へと伸びていきます。この形にはいくつも欠点があります。
- どの
elseがどのifに対応しているのか、目で追わないと分からない - 条件の組み合わせのうち、どれが抜けているのか見つけにくい
- 1 つ条件を足すたびにインデントが 1 段深くなる
- 画面の右端をはみ出して、横スクロールが必要になる
深いネストは「読み手の頭の中に、いま成立している条件を全部覚えさせる」構造です。3 段目にいるとき、読み手は「公開されていて、web で、閲覧数が 100 以上」という 3 つを同時に覚えていなければなりません。人間の短期記憶はそんなに強くありません。
論理演算子でフラットにする
第 3 章で学んだ && と || を使うと、入れ子をやめて 1 段の else if の並びに書き換えられます。&& は「かつ」、|| は「または」でした。
function pickupLabel(published, category) {
if (published && category === "web") {
return "注目作品";
} else if (published) {
return "公開中";
} else {
return "下書き";
}
}インデントが 1 段に収まりました。ビフォーアフターを並べて比べてみます。
| 観点 | 入れ子 | フラット |
|---|---|---|
| インデントの深さ | 3 段 | 1 段 |
| 条件と結果の対応 | 離れていて追いにくい | 1 行で対になっている |
| 枝の数の確認 | else を数える必要がある | 上から数えるだけ |
| 条件を足すとき | 深くなる | 行が増えるだけ |
フラットな形のうれしいところは、上から 1 行ずつ読めば、条件と戻り値が対になって並んでいることです。「公開かつ web なら注目作品」「公開なら公開中」「それ以外は下書き」と、日本語の仕様書をそのまま読んでいる感覚に近づきます。
2 番目の枝の else if (published) に「かつ web ではない」を書き足す必要はありません。else if は前の条件が false だったときにしか来ないからです。前のレッスンで見た性質が、ここでも効いています。
書き換えるときは、条件の順番が変わっていないかを必ず確かめてください。入れ子の内側にあった条件は、フラットにすると外側の条件と
&&でつながって、より狭い条件になります。狭い条件が先、広い条件が後、という並びが崩れると、前のレッスンで見たデッドコードが生まれます。
入れ子のままがよい場合もある
いつでもフラットが正解というわけではありません。次のような場合は入れ子のほうが素直です。
- 外側の条件が「そもそも処理する対象か」を決めていて、内側の分岐と性質が違う
- 内側の分岐が 5 つも 6 つもあり、
&&でつなぐと同じ条件を何度も書くことになる - 外側の条件が長い式で、繰り返し書くと 1 行が読めない長さになる
判断の目安は「同じ条件を 3 回以上書くことになるならフラットにしない」です。また、条件式そのものに名前を付けて短くする手もあります。
function pickupLabel(published, category) {
const isFeatured = published && category === "web";
if (isFeatured) {
return "注目作品";
} else if (published) {
return "公開中";
} else {
return "下書き";
}
}isFeatured という名前が付いたことで、if の行を読むだけで意図が分かるようになりました。条件式が長くなってきたら、変数に切り出すことを検討してください。真偽値の変数名は is や has や can で始めるのが慣習です。
ネストを浅くするもう 1 つの定番として、関数の先頭で対象外の値をはじいてしまう書き方があります。これは第 6 章の関数のレッスンで扱います。ここでは論理演算子による書き換えに集中してください。
よくある間違い
&&と||を取り違える — 「公開されていて、かつ web」は&&です。||にすると、非公開の web 作品まで注目作品になってしまいます。
// NG。published が false でも category が web なら通ってしまう
if (published || category === "web") {
// OK
if (published && category === "web") {- 比較を省略して書く — 数学の書き方をそのまま持ち込むと、意図しない結果になります。
// NG。category === "web" || "frontend" は常に truthy
if (category === "web" || "frontend") {
// OK
if (category === "web" || category === "frontend") {"frontend" という文字列は空文字ではないので、それだけで true 扱いになります。|| の左が false でも右が true になるため、この条件はどんなカテゴリでも成立してしまいます。比較演算子は省略できません。
- フラットにしたのに順番を直さない — 入れ子を機械的に開いただけで、広い条件を先に書いてしまうパターンです。書き換えたら、必ず代表的な入力を 3 つほど手で流して、元のコードと同じ答えになるか確かめてください。今回なら公開かつ web、公開かつ design、非公開の 3 つで足ります。
要件
- 関数名は pickupLabel、引数は真偽値 published とカテゴリ名の文字列 category の 2 つ
- published が true かつ category が "web" なら "注目作品" を返す
- published が true でカテゴリが "web" 以外なら "公開中"、published が false なら "下書き" を返す
- if の入れ子ではなく && を使い、インデント 1 段の else if の並びで書く
入出力例
pickupLabel(true, "web") → "注目作品"
pickupLabel(true, "design") → "公開中"
pickupLabel(false, "web") → "下書き"
pickupLabel(false, "design") → "下書き"
pickupLabel(true, "") → "公開中"