switch(true) イディオム考察


# `switch(true)` イディオムとは

ここでは， ***switch(true) イディオム*** とは，以下のように `switch` 構文を用いて `if`, `else if` と意味的に等価なものを実現しようとするコードパターンを指すことにします．

```js:switch-true-idiom-example.js
function hoge(x) {
    switch (true) {
    case x < 0:
        console.log(x + " は自然数ではありません．");
        break;
    case x === 0:
        console.log("ここでは 0 は自然数です．");
        break;
    case x > 0:
        console.log(x + " は正の数です．");
        break;
    default:
        console.log(x + " は数ではないようです．");
    }
}
```

一部界隈（？）ではどうやら割と認識されているらしく，そこそこ使われているようです（記事末尾に `switch(true)` コードが載っているページなどを挙げています）．

念のため補足しておくと，上記のコードは `if`, `else if` を使う場合は以下のように書けます．

```js:if-and-else-if.js
function hoge(x) {
    if (x < 0) {
        console.log(x + " は自然数ではありません．");
    } else if (x === 0) {
        console.log("ここでは 0 は自然数です．");
    } else if (x > 0) {
        console.log(x + " は正の数です．");
    } else {
        console.log(x + " は数ではないようです．");
    }
}        
```

本稿では，`switch(true)` イディオムの是非について論じて行きたいと思います．なお，話をややこしくしないために，本稿では JavaScript における `switch(true)` イディオムのみについて考察します．（当然ですが，言語によって `switch` のシンタックスおよびセマンティクスは大きく異なります．例えば ruby の `case` はいわゆる C-like な `switch` とは全く異なり，フォールスルーがない点や `===` 演算子による柔軟性がある点などにおいて同列に語れるものではありません．）

# 賛否両論の `switch(true)`

`switch(true)` イディオムに関しては，様々な意見があります．記事の末尾に，各所から集めて来た意見をまとめておきますので参考にしてください．ここで私が述べたいことは，このイディオムに対しては **とにかく賛否両論ある** ということです．

いったいなぜこのイディオムには賛成・反対の意見が出ているのでしょうか．双方の意見を順に見てみましょう．

## 賛成意見: `switch(true)` の利点

まず，賛成派の方々の意見をまとめると，次のような利点があるようです．

 * 条件ブロック単位の切り貼りが容易．
 * **条件式のアラインメントが揃う**．
 * **フォールスルーを利用して OR などの条件を簡潔に書ける**．
 * スッキリして読みやすくなる．
 * 速い．{{要出典}}

## 賛成意見: `if`, `else if` の欠点

言い換えると，賛成派の方々は `if`, `else if` を以下のような点で鬱陶しく思っています．

 * コピペしづらい．
 * 条件式のアラインメントが揃わない．
 * 読みづらい．
 * 遅い．{{要出典}}
   * ※注: 速度差はありません． *[6]*

行の中で条件式がどの位置に書かれているか，その見た目をかなり重視しているようです．また，`case` を縦に並べてフォールスルーすることで OR 条件をスマートに記述できるところがクールだと考えているようです．

## 反対意見: `switch(true)` の欠点

それに対して，反対派の方々は ~~switch(true)イディオムに対して~~ 以下のような欠点を挙げています．

 * `switch` の **本来の意味** からかけ離れてしまっている．
 * 条件式が定数になっているのもおかしい．
   * ※注: JSLint でもこの点で怒られます． *[12]*
 * これらのために **コードの正しい意図を解釈しづらい**．
 * **`break;` 忘れフォールスルーによるバグの原因となり得る**．

バグの温床となるという主張は，`switch(true)` の欠点というよりは，一般の `switch` の欠点です．

## 反対意見: `if`, `else if` の利点

その点，`if`, `else if` には上述のような危険性がないというのが反対派の主張です．

 * `if`, `else if` は犬でも分かる．
 * `if`, `else if` は猫でも分かる．
 * スッキリして読みやすくなる．

これらのことから，反対派は，本来であれば `if`, `else if` で書けるところを敢えて欠陥のある `switch` で書こうとすることを非難しているということですね．

意図が明確でなく変更時にバグを生みやすいコードは，コードレビューにおいて真っ先に槍玉に挙げられます．こういった意見を挙げるプログラマーの多くは，バグの予防を念頭に置いているようです．また，こういったことを理由に，そもそもコーディング規約で `switch` 構文の利用を禁じている場合もあるでしょう．

# `switch` 本来の意味とは

ここで言う **意味** とは，ECMAScript 仕様に書かれているセマンティクスのように厳密なものではなく，我々がそのコードを見たときに **どのように解釈するか** をアバウトに **意味** と呼んでいます．

## `if` や `switch` の解釈

例えば `if (condition) { hoge; }` というコードなら，

```txt
もし条件 condition が成り立つならば hoge する．
```

という風に我々は解釈するでしょう．それと同じように `switch (expr) { case A: hoge; case B: fuga; ... }` を直訳すると，

```txt
expr の評価結果によって場合分けする．
A の場合は，hoge する．
B の場合は，fuga する．
```

となるでしょう．もちろん，このような **解釈** (あるいは翻訳) には個人差があるでしょう．しかしながら，JavaScript の `switch` が構文的にも意味的にも C 言語のそれに極めて似ていることを踏まえると，多くの人が上のような **直訳** をしても不思議ではありません．

## `switch(true)` の解釈

ここで，上の解釈に `switch(true)` イディオムをそのまま当てはめると，次のようになることが分かります．

```txt
true の評価結果（？？）によって場合分けする．
x < 0 の場合は，hoge する．
x === 0 の場合は，fuga する．
x > 0 の場合は，piyo する．
```

この不自然な直訳こそが，「`switch` 本来の意味からかけ離れているためにコードの意図が分かりづらくなっている」という主張の正体です．

実際，私が `switch(true)` のコードを何人か知人に見せてみたところ，まず誰もが「…え？switch... true...?????」と首をかしげて，そのあと10数秒してからコード本来の意図を察し，苦笑いしたり感動の声を上げたりしていました．`switch(true)` にはどうやら **初見のプログラマーの時間を10数秒無駄にする恐れがある** という欠点もあるようです．

これに対して， **"見慣れてないだけ"** という意見があります．確かに，一度見ればなかなか忘れないでしょうから，次からはすぐ正しい解釈を得られて時間の無駄はないかもしれません．しかし，反対派の人は **個々人が見慣れているかどうか** は些細な問題であると考えています．そのコードを **初めて読む第三者** が理解できるかどうかが重要なのであって，その都度プログラマーの思考を10数秒止めるようなコードであるからこそ邪悪だと考えているのです．

## 「見慣れないからダメ」ではない

もう一つ言及しておかなければならないことがあります．それは，反対派の主張は「見慣れないからダメ」だけではないということです．

例えば即時関数パターンは，JavaScript-er の間では非常に **見慣れた** パターンですが，逆に言うと JavaScript-er 以外は **見慣れていない** パターンです．

```js:即時関数パターン
(function () {
  // local scope
}());
```

しかしこのパターンは， **見慣れていない** 人々から（たとえ気持ち悪いと言われることがあっても）非難されることはありません．ローカルスコープを作るという欠かせない役割があり，即時関数パターンの **利点** を明快に説明できるからです．

残念ながら，`switch(true)` イディオムにはそのような **明快にして唯一無二の利点がない** ように思います．仮に全 JavaScript-er が `switch(true)` イディオムを見慣れているとしても，このイディオムへの根強い反発は消えないでしょう．なぜならば，JavaScript における `switch` 文にはフォールスルーがあり，`break;` 忘れがバグの原因になり得るという **致命的な欠点がある** からです．その上，本命の `if`, `else if` にはそのような欠点がないというアドバンテージもあります．この決定的な差を明快な根拠で以て埋めない限り，`switch(true)` は糾弾され続けることでしょう．

## ツールを使って意図しないフォールスルーを防ぐ [追記]

とても良い意見を頂いたので紹介させて頂こうと思います．

> あと fall through でバグの原因になるって言う人はただちにIDEかツール使えで解決する

-- *@y_imaya, [17]*

意図しないフォールスルーがあると色をつけたりして警告を出してくれる IDE や Lint ツールもあります．そういった環境で JavaScript を書いている限り，意図しないフォールスルーは起きず，上述の反対派の批判は当たらないという指摘ですね．

> @t_uda lint系のツールはあまり詳しくないのですが、WebStormというIDEを使っていると意図しないfall-throughになっているcaseは分かりやすく色が変わるようになっています。http://gyazo.com/422c66ac9d4435fddbcc74cb171dc9d6

-- *同上, [18]*

![WebStorm における意図しないフォールスルーへの警告](http://i.gyazo.com/422c66ac9d4435fddbcc74cb171dc9d6.png)

最初の `case` から次の `case` への暗黙のフォールスルーがあるため，2 つ目の `case` にハイライトが入っています．一方，明示的に `/* FALLTHROUGH */` コメントを置いた 3 つ目の `case` のところは意図的なフォールスルーと見なされ警告は出ていません．意図してフォールスルーを行いたいときはこのような開発環境を使うのが良さそうですね．

また，JSLint や JSHint のような Lint 系のツールを使っても `break;` 忘れは検出できます．例えば上と同じコードを JSHint に渡すと，次のような警告が出ます（ただし JSLint の場合は `switch(true)` の部分にもエラーを出します）．

> Two warnings
> 5	Expected a 'break' statement before 'case'.
> 7	Expected a 'break' statement before 'case'.

-- *JSHint, [19]*

コードレビューの作法でも「まず文法チェッカにコードを通せ」と言われます（文法チェッカに通らないとコミットできないようリポジトリに hook をしかけておくというのも一つの手でしょう）．人為的なミスはできるだけ機械的に修正しておきたいところです．

確かに，このような整った環境で作業していれば，`break;` 忘れによる意図しないフォールスルーバグを **人間が心配する必要はない** でしょう．ただし，当然ですが開発チーム内でこういった良い開発環境を共有できていることが要求されます．オレオレ環境にしがみつく人がたった一人でもいれば，また前の話に逆戻りしてしまいます．その辺りは，環境を整える労力が実際に対価に見合うかどうかのコストパフォーマンスの問題でもあり，ツールの利用を視野に入れると `switch(true)` の是非もケースバイケースと言えそうです．

# まとめと雑感

## まとめ

 * 賛成派は「アラインメントが揃っていて見やすいコード」の方が好き．
 * 反対派は「パッと見で意図が分かるコード」の方が好き．
 * 反対派はバグを予防したい．
 * 意味が明瞭でないイディオムはプログラマーの貴重な10数秒を犠牲にする．
 * [追記] 良い開発環境を使おう．

## 雑感

書き切れなかった様々な何か．

 * 第三者が読むことを前提としないなら，例えば少人数のチームで開発しているクローズドソースのような場合であれば， `switch(true)` を使うのは勝手にしてください．ただ，オープンな開発には持ち込まない方が良いでしょう．いつ，誰が，どのようにそのコードを **改悪** するかなんて我々には予期できませんから．
 * ~~これも散々言われていることですが，~~ そんなに `if`, `else if` のチェインが続くイヤな状況になっているということは，そもそも **設計ミス** の可能性も考えられます．他のデザインパターンを適用できないか思慮するべきではないでしょうか．
   * [2014-04-26 01:21] どこかで誰かが言っていた気がしましたが出典を思い出せなかったので前半を取り下げます．常に `if`, `else if` に代わる良いデザインパターンがあるとは限りませんが，それを試みてもよいのではという提案です．
 * コードの見た目に関する議論はその人の感性の問題なので，ここでは深入りしませんでした．他に挙げられそうな意見として，流儀や使っているフォーマッタにもよりますが，インデントが深くなることや `break;` を書く分下に伸びることを嫌う人もいるかもしれません．
 * そんなに性悪説的にコトに当たらなくても良いじゃないか仰る方もいるかもしれませんが，そういう人はちょっと ***JavaScript: The Bad Parts*** *[13]* をナメすぎだと思うんで勉強して出直してきてください．
   * `{baka: console.log("niha")} + "yomenai";`
 * Ruby の `case when` や coffeescript の `switch when` は極めて良いものだと思っています．ただ，他言語での事情を JavaScript に持ち込まないでください．
 * この記事では JavaScript に限定して扱っていたので書いていませんでしたが，他の言語 (例えば C や Java) との互換性がないのもこのイディオムの欠点といえると思います．まぁ，JavaScript のコードを C に移植したりする機会なんか滅多にないでしょうが．
 * これを書くにあたってクソコードを大量に読んだせいで胃がマッハなんですがどうすればいいんですかね．本文は中立の立場で書いたつもりだったんですが，今この感情をどこにぶつければいいんですかね．うぼあー あー発散させてくれー
 * "プログラマーの貴重な10数秒を犠牲にする" と書きましたが，これだけ賛否両論の議論が繰り広げられるということは，実際にはもっと時間が無駄に消費されるわけです（レビューのスレッドが縦に伸びて戦争する様が目に見えるようです）．これはホントに強調しておきたいんですが，"理解しがたい上にバグの温床となっているコード" は悪です，悪魔です，絶対悪です，クソです，コケです． ***頼む，やめろ，やめてくれ***．
 * [追記] 「ツールを使え」という意見を仰っている方は，（私を除くと）y_imaya さんぐらいしか見かけませんでした（4/28時点）．この記事でも最初から JSLint のことには言及していましたし，指摘してくれる人はもっといてもよかったと思うんですが，いったいなぜですかね．そういうところに普段から気が回っていない JavaScript-er が多いのではないか{{要出典}}という気がしてしまい，悲しい限りです．
   * 誰か JavaScript-er が使う文法チェッカ，フォーマッタ，IDE の統計とってくれ．
 * 議論は私の胃が痛まない範囲で大いに歓迎します．

# 附録. `switch(true)` への賛否それぞれの意見

参照先を明示していないコメントは，主にチャットや実世界で知り合いのプログラマーたちから頂いた意見です．また，コメントはいずれも一部抜粋および要約です．

## 賛成派

 * "仕様上は、`switch ( Expression )` のExpressionについての評価結果とCASE句の Expressionへのinput内容を!演算子で比較して交通整理するだけに思えるので、特に問題があるようには思えないですが、どうなんでしょう？ " *[1, toshirot]*
 * "これは大有りです。立派なテクニックの１つです。" *[4, Hikaru_oao]*
 * "`break` を毎回は使わないようなパターンで真価を発揮するものだと思います。例えば `if (a) A; if (a || b) B; if (a || b || c) C;` を `case a: A; case b: B; case c: C;` と書けます" *[16, 同上]*
 * "1つのオブジェクトに対する条件分岐にはよく使えそうだね。" *[4, sharo0331]*
 * "`if` より `switch` の方がほんのわずかだけ速い。バッドノウハウ云々言う奴は、見慣れてないだけ" *[5]*
   * ※注: 速度差はありません． *[6]*
 * "I think `switch(true)` is a good and useful idiom." *[8, JasonFruit]*

## 反対派

 * "ゆるされない"
 * "これぜんぜん許せねえぞ俺には…… `if` しろハゲとか言いたいけどMDNにのってると負けそうになる"
   * ※注: MDN にサンプルコードとして `switch(true)` イディオムが掲載されています．
 * "`switch(true)` を見てから精神が不安定になったんだが"
 * "`if` 文でいいんじゃないですかね……"
 * "`switch` 本来の意味に沿ってないからダメでしょう"
 * "`switch` 自体バグの温床になりやすいです,あまり初心者に勧められるイディオムとは言えない." *[1, ncaq]*
 * "お願いだから他人にこんな恐ろしい書き方教えないでください。というか`if else` 使いましょうよ。かっこいい書き方かもしれませんが想定されていない書き方は想定されていないバグの温床になります。" *[1, uten00]*
 * "速くなるなら正義だがそうじゃないなら害悪でしかない" *[@uwabami さん]*
 * "You should not be switching on a constant value." *[8, Oded]*
 * "This isn't really the same as a true `switch`.... in a `switch` statement there is a single evaluation and a table lookup directly to the correct case." *[9, Cory Gross]*
 * "It also looks... weird. `if ... else if` is shorter and much more conventional, which is a good thing. And with a `switch` you've got to beware of fall-throughs." *[10, Flambino]*
 * "Not really a fan of switching on expressions. I would normally only use a `switch` if I was matching a static(ish) value, like a string/character or number." *[11, bengourley]*
 * "Fall through is a bug-prone feature of `switch ... case`. It's too easy to forget a `break` statement, and if you use fall through intentionally, those forgotten `break` statements can be very hard to spot." *[15]*

## その他の意見

 * "（よく思いつくなぁ，と）逆に感動した"

## 最後に

> "Weird condition. -- line 2 character 13"

--- *JSLint [12]*

# 参考文献 / 関連リンク

リンクは順不同です．

1. [Coffescript - 初心者によく教えるswitch文のイディオム - Qiita](http://qiita.com/puriketu99/items/7ab216e64d522fe990c9#comment-34af33d94fd784886a28)
  * 私はこの記事で `switch(true)` イディオムの存在を知りました．CoffeeScript を念頭に置いていますが，JavaScript に関してもいくつかコメントがあります．
2. [JavaScript - if elseif が連続し ||(論理和)が頻出する時はswitchが使えるかも知れない - Qiita](http://qiita.com/yuku_t/items/ec82601c4d4b1969becb)
3. [PHP switch文の応用的な使い方 - Qiita](http://qiita.com/tkyk@github/items/ee0ab7bf502361f413d8)
4. [[JavaScript] switch (true)-ウンコード・マニア](http://unkode-mania.net/view/535678aae41bfe8422000007)
  * 今回記事を書くにあたって様々な意見を集められればと思って投稿しました．
5. [+ JavaScript の質問用スレッド vol.87 + - READ2CH](http://read2ch.net/hp/1306557072/)
6. [If-Else Chain VS Switch(true) · jsPerf](http://jsperf.com/if-else-chain-vs-switch-true)
  * `if-else` チェインと `switch` での速度差を比較しています．たいした差はないか，環境によっては `if-else` の方が速くなる（あるいは逆のこともある）ようです．
7. [switch - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch)
  * サンプルコードで `switch(true)` イディオムが使われていますが，引用元は Stack OverFlow の 2 つのスレッドです．読むと分かりますが，これらのスレッドに限らず，Stack OverFlow には的外れなコメントやバグを含んだコード，理解せずに書かれたコードも大量にあります．注意が必要でしょう．
8. [javascript switch(true) - Stack Overflow](http://stackoverflow.com/questions/2765981/javascript-switchtrue)
9. [Javascript: Example switch statement using a range of values. | SaintJohnShawn.com](http://saintjohnshawn.com/2007/05/24/javascript-example-switch-statement-using-a-range-of-values/)
10. [javascript - switch (true) as alternative to else if - Code Review Stack Exchange](http://codereview.stackexchange.com/questions/37424/switch-true-as-alternative-to-else-if)
11. [if-else Chain vs switch(true) · Issue #8 · bengourley/js-style-guide](https://github.com/bengourley/js-style-guide/issues/8)
12. [JSLint,The JavaScript Code Quality Tool](http://jslint.com/)
13. [JavaScript: The Good Parts: Douglas Crockford: 9780596517748: Amazon.com: Books](http://www.amazon.com/exec/obidos/ASIN/0596517742/wrrrldwideweb)
14. [Search · &quot;switch(true)&quot; NOT keep_continue NOT bool|tiny NOT TestLocalThrows](https://github.com/search?l=javascript&q=%22switch%28true%29%22+NOT+keep_continue+NOT+bool%7Ctiny+NOT+TestLocalThrows&ref=searchresults&type=Code)
  * Github で検索したら意味不明なコードが結構ありました　．`switch(true)` を好むプログラマーってなぜこんなに `switch`, `case` を理解していないんですかね．頭が頭痛で痛いよ．
15. [Switch statement multiple cases in JavaScript - Stack Overflow](http://stackoverflow.com/questions/13207927/switch-statement-multiple-cases-in-javascript)
16. [Twitter / Hikaru_oao: t_uda というよりむしろ、前のcaseから続けて処理 ...](https://twitter.com/Hikaru_oao/status/459667375193743360)
17. [Twitter / y_imaya: あと fall through ...](https://twitter.com/y_imaya/status/460656074027065344)
18. [Twitter / y_imaya: @t_uda lint系のツールはあまり詳しくないのですが、 ...](https://twitter.com/y_imaya/status/460801845246443521)
19. [JSHint, a JavaScript Code Quality Tool](http://www.jshint.com/)