🎄

if文とswitch文は使い分ける? / TypeScript一人カレンダー

2022/12/24に公開

こんにちは、クレスウェア株式会社の奥野賢太郎 (@okunokentaro) です。本記事はTypeScript 一人 Advent Calendar 2022の21日目です。昨日は『String Literal Typesとas const』を紹介しました。

ちょっとした疑問

本日はTypeScriptのAPIそのものの紹介ではなく、箸休め的にちょっとした周辺の話題を取り上げます。

今回の話題は『if文とswitch文は使い分ける?』について。この手の話題はC言語の時代からされており、数十年かけて世界中で様々な観点からあっちがいい、こっちがいいと言われています。その判断基準はおおよそ可読性や処理速度といったものに収束していき、最終的に筆者は「組織全員が納得する理由に従って好きな方を使えばいい」と諦観している部分があります。

今回は、可読性、処理速度とは別の観点からTypeScriptとしてはどう違うのかという部分を取り上げます。

Narrowingとエディタの挙動

TypeScriptでは、型を広く取りうる状態、たとえば無限長の配列であったり、候補の多いUnion Typesであったりする状況からif文やseitch文などの分岐によって型のとりうる可能性を狭めていくNarrowingの考え方があります。この話題はアドベントカレンダー内の過去の記事でも取り上げています。

このときif文とswitch文では絞り込みの挙動が少し異なりますので確認しましょう。現実的なサンプルではないですが、挙動確認用に次のようなUnionを扱うif文とswitch文を書いてみます。エディタの挙動の確認のため分岐後の処理が冗長であることは考慮しません。

type Union = "a1" | "a2" | "a3" | "b1" | "b2";

function withIf(v: Union): void {
  if (v === "a1") {
    console.log(`value: a1`);
    return;
  }
}

function withSwitch(v: Union): void {
  switch(v) {
    case "a1":
      console.log(`value: a1`);
      break;
  }
}

if文とswitch文でここまで書きました。このあとif (v === "a2")case "a2":を書き足していく状況を考えたときに、コードエディタがどういった補完を出すかについて検証します。

エディタが出す補完はLanguage Serviceによって提供されるものと、エディタが固有に補完情報を生成して提示してくるものと大きく2種類あります。Language Serviceとは言語固有の補完提示や構文解析を行えるようにエディタ側に情報提供するためのAPIで、主にエディタプラグインやIDE開発者などに向けて公開されています。

https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API

TypeScriptではNarrowingの機構に合わせて、コードエディタ上でも補完候補が絞り込めるようにデザインされていますが、Visual Studio CodeWebStormではすこし挙動が異なります。

Visual Studio Code

if

次の画像は、Visual Studio Codeにて、if文としてv === "a2"を追記しようとしている場面です。

v === ""の時点で、ここに来る値はUnion型であり、かつ"a1"ではないことが保証されていることから、Narrowingの作用とあわせて"a2", "a3", "b1", "b2"が候補にあがっています。

switch

次の画像は、Visual Studio Codeにて、switch文としてcase "a2":を追記しようとしている場面です。

こちらはif文とは異なりすでに"a1"でNarrowingされているというわけではなく、L11のswitch (v) {の時点で挙がっている候補がすべて表示されます。よって、Union型全体が候補となり"a1"も候補に含まれています。

WebStorm

if

続いては、WebStormにてif文としてv === "a2"を追記する場面を紹介します。

なんと、何もでません。WebStormでは値は1文字以上入力されてようやく補完を出すため、この時点では補完候補が表示されません。ここでaを入力すると"a2", "a3"が表示され、bを入力すると"b1", "b2"が表示されるようになります。

switch

次の画像は、WebStormにてswitch文としてcase "a2":を追記しようとしている場面です。

WebStormはswitch文の扱いが非常に強いです。この状況ではcasecの時点でもうcase "a2":を候補に挙げます。そしてもうひとつ強力な機能が、全分岐の自動生成です。

"Create missing branches"を選択すると、一斉に必要なcaseを自動生成します。安全のためにbreakもすべての分岐にセットになっている点が業務フローをよく理解していると思います。

筆者は業務ではWebStormを常用しているためVisual Studio Codeの機能やプラグインには疎く、もしかするとこういった機能はあるのかもしれませんが、いずれのエディタにせよif文ではこのような列挙を目的とした分岐機構ではないためUnion Typesにとってはswitch文との相性はとてもよいです。

Visual Studio CodeとWebStormに挙動差がある理由は、WebStormがTypeScriptのLanguage Serviceを利用する以外に独自の補完機構も併せ持っていることに由来します。WebStormが独自にどういった補完機能の追加を進めているかはThe WebStorm Blogにて読むことができます。

結論、やはりどっちでもいい

今回、TypeScriptのNarrowingの観点でみると、if文とswitch文でわずかに型の絞り込まれ方が異なることがわかりました。また、エディタの違いによって補完の出し方にも差があり、WebStormは標準でswitch文に対するユーティリティが充実していることが知られます。そのため、こういった違いを理解した上でswitch文を好む状況、if文を好む状況はあってよいと筆者は考えています。

なお、筆者が参加する案件ではWebStormユーザーが比較的多いという理由で、Union Typesを引数に取る分岐で全網羅をしたいようであればswitch文を積極的に書くようにしています。

現代のECMAScript処理系であれば5項目、10項目程度の分岐数では速度差は無視してよいですし、if文とswitch文の違いだけで明らかな速度差が出てしまうような状況であれば、構文が問題なのではなく分岐が多すぎる状況自体がまずいと感じます。そのため、やはり筆者としては「組織全員が納得する理由に従って好きな方を使えばいい」と結論付けています。

明日は『実例 mapOrElse()

TypeScriptの誕生によって、古典的なECMAScriptのみの開発に「補完を活用する」という概念が加わりました。今後はGitHub Copilotのような仕組みで、さらに快適な開発体験が得られるようになるかもしれません。またその時々で最適な判断をしたいところです。

明日もコラムとして、ジェネリクスはどんなときに使えばよいかという話題でmapOrElse()関数を紹介します。それではまた。

Discussion