LoginSignup
32
22

More than 5 years have passed since last update.

自分は switch(true) イディオムを使ってなぜクソコードを書くのか

Posted at

前書き

今更ながら
switch(true) イディオム考察 - Qiita
を読み、読む前からたまに使っていたswitch(true)をなぜ使うのかを考えました。

賛否両論あることを知らずに、適切に使えばいいものだと思っていて、
賛否両論見た後も自分は使うだろうな、と感情的に思ったので、なぜそう思ったのかを書き出します。

自分の頭の整理が目的なので、とっちらかった文になるかとは思います。

記事リンクを貼ると通知が飛ぶかもしれないので、煽りと受け取られるかもしれないと思い余計な予防線を張りますが、
元記事自体は非常に参考になり、反対される理由も理論整然とされて納得できる内容でした。
いろいろな方の意見も集まっていましたし。
多分、switch(true)をこの記事で知っていたら一度も使う機会はなかった可能性が高いと思います。
自分でもなぜわざわざ反対派が居るイディオムを自分が良しとしているのか簡単に述べられないし、自分でもはっきりとわかっていません。
なので、文章にちょっとずつ書いて、良いと思う自分から何故良いと思うかわからない自分へ言い聞かせてみます。

再度書きますが、論理的なものでなく、自分が感覚的に使うと思った理由的なものです。

switch(true) イディオムへの反応 - Togetterまとめ

Tomoki UDA @t_uda
2014-04-25 23:59:50
switch(true) はすばらしいとか言ってるやつはコードの質を考えないクソ

自分もいつも脳内でコードの質を考えろよと悪態をついているのに、まさか言われる側に立って、更に今後も考えないクソで居続けると表明するとは思ってもいませんでした。

今は、クソじゃねえ!と言うわけでもなく、クソをやめると言うわけでもなく、今後もクソとしてコードを書いていくのか…という暗澹たる複雑な気分です。

書く前に注意したいこと

気をつけますが、どうにもならないことについて。
本来はis else ifswitch(true)を比較してみたいのですが、switch(true)について考えると、度々switchそのものへの言及となってしまいそうです。
switch(true)switchは分けて考えてみたいのですが、
どうやら自分では不可能に近いので、本心としてはswitch(true)のみを考えたい、ということを書き残しておきます。

書いている途中に思ったこと

反対意見に対してはswitchへの批判なのでswitch(true)に言うなと言っているのに、switch(true)の利点を説明するのにswitchの利点を説明してますわ。
これがダブルスタンダード、二枚舌ってやつでしょうか。
ずるいですね。
まぁ皆さんへの説得などではないので、卑怯者の思考回路とでもいうんでしょうか…感情論で書き続けます。

筆者知識

PHPJavaScriptの、難しいものは使わない基礎的な知識のみです。
なので、switchC言語の頃からの流れを汲んでいる?などの歴史背景とか
Rubyswitchが優れているとか、Pythonにはそもそもswitchがないなども知りません。
また、PHPJavaScriptがたまたまswitch(true)が使える言語であったことも知りませんでした。
初心者によく教えるswitch文のイディオム - Qiita - コメント

caseに式が使えるかどうかも言語依存ですね。定数しかcaseに書けない言語もあるし、整数しか取れない言語もありますね。

また、他人にコードを読まれることもあまりないので、わりかし好き勝手に書き散らしています。

どこでswitch(true)を知ったのか

すみません、忘れました。
もしかしたらPHPのswitchを厳密な型比較するテクニックとして
case $hoge === 'huga'
みたいなもので知ったのかもしれません。

引っ掛かりを覚えたか

switch(true)は、やはり一瞬「ん?」となりましたが、理解すればすんなり受け入れた記憶があります。
初見のイディオムへのひっかかりはswitch(true)のみならず、
do-while(false)!!hogeでも感じましたし、知らないものへの共通反応なのかと思っています。
do-while(false) を見つけたときの雑感 - Qiita

一瞬意味がわからない
break出来ることに気づく

また、理解自体はすぐにでき、意図がわからないコードだと思った記憶はありません。
繰り返しますが、引っ掛かりを覚えたのは(true)の部分のみで短い時間でした。

賛成反対の利点・欠点を読んで

switch(true) イディオム考察 - Qiita
から
それぞれ自分はどう思うか

賛成派

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

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

切り貼りについては言われればはぁそうですねと。

アライメント(こちらで書かせてください)が揃うのは嬉しいですね。

フォールスルーについては、簡潔に書ける、と同意義だと思いますが、アライメントの揃えと合わさってグループ化しやすいな、とは感じます。もちろん簡潔で意図が読めることが前提ですが。(あるいは// no breakとか)
でもifなら||を改行して揃えればいいので大きな利点とは言えないですね。

読みやすさは優っていると思います。むしろ自分は読みやすくするためにswitch(true)を使っているのでは、と思っています。

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

コピペしづらい.
条件式のアラインメントが揃わない.
読みづらい.
遅い.{{要出典}}

コピペは同様に言われればはぁそうですねと。

アライメントは結構気になります。

読みづらさは感じます。もちろんすべてがすべてなわけではありませんし、ごくごく個人的なものだと思っていました。
だからswitch(true)に書き換えたいという衝動が生まれるのかもしれません。

反対派

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

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

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

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

本来の意味、を自分が正しく理解していないと思いますが、、switch(hoge)のhogeが重要と考えると、switch(true)は離れていると思います。
(後で書くと思いますが、自分はswitch(true)でもhogeが大事だと思います)

条件式が定数、は見慣れたという意味では無限ループで受け入れられましたね。
値が変わらない?という意味だとswitch(hoge)もまぁ一緒だと思いましたし。
もちろん前述の通り、初見時はおかしいと思いました。
(無限ループwhile(true)についてはJavaScript switch (true)-ウンコード・マニアのコメントで言われている通り意図と利点が明確かの問題も絡むのでそこは避け、条件に定数、というところのみを切り出しての発言です)

break忘れはswitch自体なので一旦スルーで

本来の意味をあまり意識せず、なんとなくメリットも感じてしまったので、欠点を理解しつつ使っている、という感じなのかもしれません。

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

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

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

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

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

ifelse ifは今までもこれからもお世話になるもので、理解しやすいし危険性が少ないものだと思います。
この利点だけ他と書き方が違ってどう書けばいいのか難しいです。
バグの予防という言葉はとてもいいんですが、
意図が明確でなく変更時にバグを生みやすいコード = switch(true)
ということが自分の中ではあまりピンと来ないです。(読み間違えているのかも)
自分がまだswitchで手痛いバグに遭遇していないだけなのもあるんだと思いますが。
自分はそこまでswitchに悲観的ではありません。
無知から来る楽観でしょうけど。

switch(true) の解釈

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

true の評価結果(??)によって場合分けする.
x < 0 の場合は,hoge する.
x === 0 の場合は,fuga する.
x > 0 の場合は,piyo する.

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

これは直下にかかれていますが、自分の直感で見慣れてないだけだろ、と思ってしまいました。
それに対する意見も見慣れない以外のダメな理由もちゃんと読みました。

引き合いには本当に出したくないのですが、while(true)も「trueの…間…?」となりましたけど今ではwhile(true)に無限ループ以外の情念?を覚えませんし、
switch(true)もはじめは書かれている通り「trueによって場合分け??」となりましたが、今では「case見んのね」としか思いませんし。

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

自分もこれを読んで、switch(true)は糾弾され続けるだろうなと思いました。
全くもってこの主張は納得できます。
わかりやすいですよね。
正しいかどうかわかりませんが(正しいのでしょうが)。

じゃあなんで使うねんと納得している自分からツッコミがくるのですが…
明快にして唯一無二の利点がなくても自分は読みやすいから使うし、
break;忘れがバグの原因になり得るという 致命的な欠点があるのはswitch自体の話だからswitch(true)そのものではないし、
switchとはうまく付き合っていけるよと楽観的に思っているので「じゃあ使うのやめよう!」とすんなりいかないんじゃないかなぁ…

再度言いますが、この記事からswitch(true)を知れば使わなかった可能性が高いです。
なぜ納得しない。自分。

if else ifswitch(true)の翻訳の違い。

翻訳、というか脳内での解釈ですね。読み方というか

自分だけの感覚だと思うのですが、if else ifは読むのがしんどいです。
多分その理由は、elseが強すぎるんじゃないかなぁと
今回、改めて考えて気付きました。

if(A) {
  // Aの場合の処理
} else if(B) {
  // Bの場合の処理
}

というものの場合、else if(B)を読むとき、自分の中では
「AではなくBの場合」と読んでしまいます。
elseがあるのでif(A)ありきのif(B)
else if(C)まであると、
「AでなくBでなくCの場合」、と、ネストの様にどんどんと状態がスタックして読むことがすごくしんどいです。

コードを調査して、Bの場合の条件と処理が気になったとしても、自分は必ずif(A)の条件と、軽くあるいは完全に処理を確認します。
それからelse if(B)の条件と処理を読みます。
elseということはifが前提としてある、と強く意識してしまいます。

対してswitch(true)の場合、

switch(true){
    case A:
        // Aの場合の処理
        break;
    case B:
        // Bの場合の処理
        break;
}

これは自分の中ではAとBのパターンは完全に独立しています。
(フォールスルーがあればまた別ですが)
もちろん並び順に意図があること、持たせることは意識しますが。
Bの場合の条件と処理が気になったとき、case Aの条件と処理は読まないと思います。
case Bcase Aを前提としている、とはなりません。

何故かは説明できませんが、ひとまず自分は毎回無意識に両者をそう読んでしまうということで。

だから、switchはどんなにパターンが増えても脳内のネストは深くなりませんが、else ifは増えてしまうんですよねぇ。
それで条件式が単純であればあるほどネスト部のオーバーヘッドが気になり、switchで書き直したくなるのかもしれません。

if else ifは自由だ

if else ifの条件式を読み解くとき、自分は身構えます。
何が来るかわかりませんから。
特に困った記憶はないのですが、可能性として

if(hogeが100なら){
} else if(hugaが平日なら){
} else if(xが地球なら){
} else if(yが田中さんなら){
}

…別にif else ifをdisりたいわけでは決してないのですが、極端に表現するとこんなんも(想像の上では)ありうるわけで、あるifelse ifの条件式にはまったく関連性がありません。(しつこいですが現実にはほぼほぼないのですが)
なので、毎回else ifを読むときに、前のifの否定を前提にしながらも今回はどんな条件が来るのか、まったく今までと関係がない条件がくるのではないかと身構えます。
本当に無意識に近いものですが、よくよく考えるとこういったことがあるな、と思いました。

対してswitch(x)はその時点で、すべてのcasexに対する条件式であることが確約されるように感じます。
なので、同じ変数を何度もelse ifでチェックしているものを見ると、switchで書き変えたくなります。

じゃあswitch(true)とどう関係があるかというと…
今のswitch(x)でなんかわかった気がします。

switch(true){ case x < hoge:}switch(x)の変形だ

改めて自分がswitch(true)を使っているところを探すと、普段全然書いてないことと合わさって実は全然使ってなかったんですよね。
で、使っているものは投稿では2個

初心者がSlack+Hubot(+Heroku)でPHPマニュアルをスクレイピングする(後編) - Qiita

switch true
    when res.headers.location.indexOf('/book.') != -1
    when res.headers.location.indexOf('/function.') != -1
    when res.headers.location.indexOf('/class.') != -1
.
.
.


Qiitaのフィードのコメント数ごとに背景色を追加するユーザースクリプト - Qiita

  switch (true) {
    case commentsNum == 0:
      bgcolor = HEAT_MAP.NO_COMMENT
      break;
    case commentsNum == 1:
      bgcolor = HEAT_MAP.COMMENT
      break;
    case 2 <= commentsNum && commentsNum <= 5:
      bgcolor = HEAT_MAP.DIALOGUE
      break;
    case 6 <= commentsNum && commentsNum <= 9:
      bgcolor = HEAT_MAP.CONVERSASTION
      break;
    case 10 <= commentsNum:
      bgcolor = HEAT_MAP.DISCUSSION
      break;
    default:
      alert("想定外のコメント数です:" + commentsNum)
      break;
  }

でした。
どちらも一つの要素、というか複雑な条件ではなく、res.headers.locationcommentsNumを調べたかっただけなんですよね。
だからできればswitch(commentsNum)とか書きたかったわけです。
そうすればcommentsNumだけを見て条件分岐することが宣言できるわけですから。
でもswitch(commentsNum)では値の範囲を表現することができず、caseは一致する値ぐらいしか書けないように見えます。
だからswitch(true)で、
引き合いに出されるのもこのようなパターンが多いのかもしれません。

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)を使うときは、本当はswitch(x)としたいんだけど、
それだとうまくcaseが書けないからswitch(true) { case x < hoge:}のイディオムを使うよ。caseの式は単純で、少ない事柄についての場合分けだよ、ということを暗に自分に示しているような気がします。

もちろん複数人で行うOSS活動などには通用しないと捉えられる考え方でしょうが。

switchを書くときは自分が読みやすい単純なswitchにすることを心がけているので(複雑なものはswitchにしない)、他人のswitchswitch(x)であろうとswitch(true)であろうと、読みやすいものであるなら許容すると思います。
だから無意味なswitch(true)なんてのはNGですよね。どんなものかはパッと浮かびませんが。

switchと三項演算子は似ていると感じる

無駄に広げるとそれは違う、となりそうですが。
自分は三項演算子を使います。一部では使うべきではないとかもありそうですが。
リーダブルコードでも、注意して使うように、的なことが書いてあったと思います。
とかく、使える状態ではあるのですから(じゃあgotoも使えるじゃないかというとダメなんですが…)
自分が三項演算子を使うときは、新規であろうとif elseからの書き換えであろうと、慎重に吟味します。
本当に三項演算子で書いてもいいか?わかりやすいか?と。
だから三項演算子中に三項演算子がでるようなものはif elseで書きますし、できるだけ簡潔なものの場合にのみ三項演算子を使います。

同様にswitchを使う時も、switchで書いてもわかりやすいかを慎重に考えます。
ちょっとでもダメそうなら素直にif else ifで書きます。
安全に書けるんですから。
でもswitchでわかりやすくなると思うなら、自分は読みやすさを優先して三項演算子のようにswitchを使います。
このときにbreak忘れたら~とかは特に考えません。

気をつけているから大丈夫とか子供かよって感じですが、今の所は自分が使う範囲では自分にOKを出してますね。

switch(true)を使うことへの弁明にはなりませんが、そもそも弁明でもないので。

全体としてはどうなの

一人で開発するなら自由にしてって一言に尽きるでしょうが。
言葉を費やしてもswitch(true)反対派を容認派に変えられるとは思ってませんし、ただただなぜ自分が使うかを述べてきました。
じゃあ周りが使わない!って考えなら自分はその中で断固としてswitch(true)を使うの?と聞かれたら…
規約に禁止とあれば従いますし、言語がswitchを廃止するって言っても特に反対しませんし、
賛否両論(強く否定する方が多いのでしょうか)の中でどうするのかと言われると…わかりません。
肯定派であることは言うかも知れませんが、雰囲気にまかせますかね。
じゃあ普段から使うな、というのには反対のガンコ者な心情です。
ですので、これからも禁止や極端な嫌悪の流れの中でない限り、使います。

再度引用ですが

第三者が読むことを前提としないなら,例えば少人数のチームで開発しているクローズドソースのような場合であれば,switch(true)を使うのは勝手にしてください.ただ,オープンな開発には持ち込まない方が良いでしょう.いつ,誰が,どのようにそのコードを改悪するかなんて我々には予期できませんから.

勝手にしてください、という部分には勝手にします。ということでしょうか。
(批難などではなく)余談ですが、if else ifではなくswitch(true)にしたことで生じうる固有の改悪というものが思いつきませんでした。break関係以外で。
イコール自分が見落としているってことなので、ちょっと怖いですね。
紹介されても気をつければええやんっていいそうですけど。

完璧な余談

これもdisりではなくif else ifswitchを使うときの頭の考えの書き出しなのですが、

if else ifは手続き的というか翻訳的というか、考えたままを書くことが多いです。

Aの場合はXしてBの場合はYして

ならまずは単純に

if(A) {
  // X
} else if(B){
  // Y
}

と書きます。
switchを使う場合、switchを使うと決めたときからまずは
手動やスニペットで

switch(){
  case :
  break;
  default :
  break;
}

まで書いてしまいます。箱物行政です。
何が言いたいかと言うと、if else ifでは最後のelse、想定外のパターンを書き忘れることがある、ということです。
最初からその他の場合が考えられていればいいのですが、

Aの場合はXしてBの場合はYして

を書くとAでもBでもない場合が頭から抜け落ちやすいです。(とにかく何かすることが前提として)
そもそも、その前のバリデーションなどで弾くのが普通なのかもしれませんが。
他人のコードを見ると、elseがないことがちょくちょくあったりして、
想定外の値が来たときにどうなるかわからないなーと思ったりします。

自分はswitchを書くときは最初にdefaultまで書くので、最低限ログに出すなりで取り逃しはなくなりそうだなーと思いました。

ififだけでelseが不要なパターンも多いので、switchdefaultに比べたらelseに気を使うことが少ないのかなーと思いました。
(空elseでも必ず書く文化もあるらしいですが、個人的には読みづらい)

完全に後から考えた蛇足なんですけどね。

まとめ?

なぜ使うの?

  • if else ifは、他分岐条件の考慮の脳内ネスト、条件の関連性のなさの可能性から身構えてしまい、読むことがしんどい
  • switchは各分岐が独立していると感じるから
  • switch(true) { case x ~}switch(x)と本質はあまり変わらないと思っている
  • switchを使うときは読みやすいこと、わかりやすいことを心がけて慎重に使っている(break含め)
  • したがって?switch(true)は各分岐が独立しており、特定のものの状態、条件だけを見て分岐していると読めるので、読みやすいと思っている
  • 明示的に禁止されてはいない

  • そもそもそんなに頻繁に使うものではないと思う
    • 大抵がxがとる値の(範囲)チェック+α程度の簡単なものだと楽観している
  • 変なわかりづらいswitchならif else ifに直すし
  • switchのクソコードを探してまだ胃を痛めていないので当事者意識が足りない
  • breakが~という言葉が実害をもって(まだ)痛感していない
  • switchそのものの欠点はうまく付き合って(使いこなせて)いけるよと今は思っている

書ききると、switch(true)は自分を随分と楽にしてくれていたイディオムだと気付きました。
楽になるものを積極的に手放したくはないわなぁ…と。
もちろん危険性はわかっているつもり風なので、同じような使いみちでもっと良い構造ができたら嬉しいですけど。
あと。自分の考え方に依存する部分が多いですね。
if else ifをもっと好きに、気楽に感じられれば手放せそうな気もします。

また、簡単にこれと一緒!という意味では、失礼ですがこのコメントだと思いました。
switch(true) イディオム考察 - Qiita -コメント2

うーん。
switch(true)の欠点は当に見慣れてないから、switch文の柔軟な使い方が馴染み薄いからであることに尽きると思うけどなあ。
breakを忘れるというのは100歩譲ってswitch文の欠点になり得ても、このテクニックの欠点ではないと思う。
もしくは、if elseはやめて、必ずこれを使おうというのなら、欠点になり得るが、そういうものじゃないと思う。

個人的な信条では、そもそも(式ではなく)制御文が多くなるのはスマートさに欠けると思ってる。
だから条件分岐でも、出来るだけ三項演算子や配列なんかを駆使して、混みあった条件文なんて絶対に置かないのが原則。
しかしどうしても単純なif elseのようなコードより良いものが出来ない場合に、たまにこのテクニックが面白く使える。

普段から好き好んで使うものではなくて、知っとけば年に1回くらい役立つ部類のテクニックだと自分は思ってる。
つまり『スタイル』ではなく『解決策』の1つであって、個人的には選べるカードは切り捨てないで多く持っておく方がいいと思ってる。
ただしそもそも臨機応変にトリッキーなことをすべきかどうかという、絶対に決着の付かない問題はふれないでおく。

breakswitchの欠点、個人的信条、一年に一度程度のテクニック、捨てないで持っておきたい。

で、結局は素晴らしいとまでは言わなくてもいいものだと思っているので、

Tomoki UDA @t_uda
2014-04-25 23:59:50
switch(true) はすばらしいとか言ってるやつはコードの質を考えないクソ

自分はクソとなりました。
ショックですが、こうやって整理したので受け入れられそうですし、クソ脱出の契機にもなるかもしれません。

まあそれ以前に

そんなに性悪説的にコトに当たらなくても良いじゃないか仰る方もいるかもしれませんが,そういう人はちょっとJavaScript: The Bad Parts[13] をナメすぎだと思うんで勉強して出直してきてください.

出直すべき案件なんですけどね。
かなり自分は楽観的すぎだなと思いました。

最後に

物事に中立の立場で挑むということはすごく大変なことだと思いました。
自分は中立に読もうとするも、早々にあきらめて肯定派からこうやってガンガン書きましたが、
クソコードを見て胃を痛めながらもしっかりと中立に書こうとした元記事
switch(true) イディオム考察 - Qiita
は凄いなと感じました。
できれば何時かは、自分は自分はではなく全体へいい影響を与えられる記事を書ければいいな。

32
22
1

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
32
22