三項演算子を使おう(推奨)

に公開

免責事項

要約

  • const 変数の初期化に使おう
  • return の値に使おう
  • 値がいらないなら if 文を使おう

三項演算子とは?

以下のようなものです。

x ? y : z

三項演算子は、式 x が真(しん)なら式 y を、偽(ぎ)なら式 z を評価した結果を与えます。
if 文や switch 文と同じく、条件分岐のために使います。

条件演算子じゃないの?

三項演算子(ternary operator)というのは通称で、正式名称としては条件演算子(conditional operator)と呼ばれます。以下では上記の演算を三項演算子とは呼ばず、条件演算子と呼ぶことにします。

if 文との違いは?

if 文との違いは、

  • 偽の場合の記述が必須
  • 式である

という点です。

偽の場合の記述が必須になることは、局所的に邪魔になり得ますが、ほとんどの場合は後でどちらも必要なことが分かったり、if文や条件演算子以外の選択肢があったりするため問題にはなりません。

式であるという点は重要です。なぜならこれにより、条件演算子は、

  • 値を利用できる
  • 変数宣言文や for 文などの文を含めることはできない
  • 変数の初期化や代入の右辺に置くことができる

という特徴を持つからです。

if だけではだめか?

だめでないです。

条件演算子は、結果の値を使う必要がなければ、制約の強い if 文に過ぎません。

ただし、条件演算子が光る場面が2つあります。それは、

  • 条件付きで const 宣言された変数を初期化したい
  • 条件付きで return の値を指定したい

場合です。

const 宣言した変数の初期化

const 宣言された変数は再代入ができない定数として扱われます。if 文は文であるため、その結果を変数の初期値として使うことができません。

これは動かない
//  Error:
//  右辺には if 文を書けない
const c = if (x) { y; } else { z; };

const c;
if (x) {
  //  Error: c は定数のため再代入できない
  c = y;
} else {
  //  Error: 同上
  c = z;
}

しかし、条件演算子は式であるために、const 変数の初期化に用いることができます。

これは動く
const c = x ? y : z;

return 文の値を設定する

if 文で複数の戻り値を扱うには、if文と else 文の中に return 文を書く必要があります。

if文による戻り値の指定
if (x) {
  return y;
} else {
  return z;
}

一方で条件演算子を使うと、return 文の値として条件演算を書けるため、return の記述は一度で済みます。

条件演算子による戻り値の指定
return x ? y : z;

でもフットガンなんでしょう?

はい。しかし、いいえ。

あなたがtruthyやfalsyの概念を学んだとき、あるいは論理和演算 || の結果がbooleanに限らないことを知ったとき、非自明な振る舞いに恐れ慄いたはずです。しかし慣れてしまえば、それらは空気のように自明なものになってしまいます。あなたの手は自らの血で汚れています。

気をつけるべきは少なく、その振る舞いは周辺のコードから察することができます。

条件演算子に関して気をつけたい点は2点です

  • 自動セミコロン挿入
  • 演算の優先順位

自動セミコロン挿入に気をつけろ

自動セミコロン挿入(auto semicolon insertion; ASI)は、構文上セミコロン(;)が必須となる個所にセミコロンが置かれていない場合に、セミコロンが置かれているものと解釈する言語仕様です。

細かい仕様については他の解説に譲りますが、おおむね以下のような挙動をします:

  • 式の直後にある改行やブロック終端(})の直前にセミコロンを挿入する
  • returnyield などの直後に改行がある場合、その改行の直前にセミコロンを挿入する

大抵の場合、自動セミコロン挿入は簡潔さと見やすさをもたらします。しかし、意図しない結果をもたらすこともあります。例えば、return の直後に改行があると、戻り値には undefined が設定され、その後に記述された文は実行されません。

改行を入れただけなのに
//  以下のコードは return; 42; と同じ
return
42

世の中の多くの出来事が複雑な過程の上に成り立っているように、条件演算子に与える条件式は複雑で長いものになりがちです。
プログラマが持つ72文字より長い行を避ける習性から、そういった条件式の周囲には改行が入れたい誘惑が充満しています。そのときに、return の直後に改行を入れると……

💥KABOOOM!💥

すべてが水泡に帰するのです。うわー。

対策は簡単で、括弧(グループ化演算子)で条件式を囲い、括弧の中で改行することです。

とても簡単に行える対策
return (
  cond1 ||
  cond2 ||
  cond3
) ?
  ifTrue :
  ifFalse
;

しかし実際のところ、コーディング支援ツールを有効にしていれば、到達不可能なコードに対して警告してくれるので、自動セミコロン挿入で問題が生じることはまずないでしょう。

優先順位にも気をつけろ?

実際のところ、条件演算子の優先順位についてあまり悩む必要はありません(!)。
条件式については基本的にグループ化の必要がなく、偽の場合の式についても同様です。

だいたい大丈夫
//  条件式および代入式は期待通り評価される 
position += position < length - step ?
  step :
  step - length

//  上記は以下と等価
position += (
  (position < (length - step)) ?
    step :
    (step - length)
)

条件演算の条件式には括弧なしに条件演算子や代入演算子を含めることはできません。したがって、v = x ? y : z という式は、(v = x) ? y : z ではなく v = (x ? y : z) の意味で評価されます。

一方で後の2項にはカンマ演算子を除く演算子を括弧なしに含めることができます。逆に、カンマ演算子を含めるには括弧が必要です。

代入演算子は通す、だがカンマ演算子は通さない
//  OK
//  代入演算子は真の場合にも偽の場合にも含められる
x ? v = y : v = z;

// 上記は以下と等価:
x ? (v = y) : (v = z);

//  OK...?
//  偽の場合の式にカンマ演算子を含めるには括弧が必要
x ? y : z, w;

//  上記は以下と等価(構文エラーではない):
(x ? y : z), w;

//  Error
//  真の場合の式にカンマ演算子を含めるには括弧が必要
x ? y, w : z;

//  上記は以下と等価(構文エラー):
(x ? y), (w : z);

また、代入より優先される演算を条件演算の結果に対して行いたい場合、やはり括弧が必要になります

条件演算子を埋め込む際は括弧に入れよう
//  next に current に条件演算の結果を足したものを代入したい

//  OK
next = (Math.random() < 0.5 ? -1 : 1) + current;

//  Bad
//  (+ current) は偽の場合の式に含まれる
next = Math.random() < 0.5 ? -1 : 1 + current;

//  上記は以下と等価:
next = Math.random() < 0.5 ? -1 : (1 + current);

//  Bad
//  (current +) は条件式に含まれる
next = current + Math.random() < 0.5 ? -1 : 1;

//  上記は以下と等価:
next = (current + Math.random() < 0.5) ? -1 : 1;

(not)FAQ

(複数の処理を)書けないですよね

書けます。

そのためにはカンマ演算子と括弧を使います

できる
//  cond が真なら
//  ifTrue1, ifTrue2 の順に評価される。
//  偽なら
//  ifFalse1, ifFalse2 の順に評価される。
cond ? (
  ifTrue1,
  ifTrue2
) : (
  ifFalse1,
  ifFalse2
)

ただし制御構文(if-else, for, try-catch, etc.)は埋め込めませんし、変数宣言もできません。代入式は書けますが、そうするくらいなら if 文を書くべきです。そういう意味で、人間が読み書きするものとしては、実用性がありません。

分岐を増やせるの?

増やせます。

条件演算の偽の場合の式を条件演算子で書けばよいだけです。

増やせる
//  コロン(:)を末尾に置いていくスタイル
//  if 文と親和性が高い
cond1 ?
  ifCond1True :
cond2 ?
  ifCond2True :
  ifNoneTrue
;

//  コロンを先頭に置いていくスタイル
//  行数が少なく見た目がきれい
  cond1 ? ifCond1True
: cond2 ? ifCond2True
: ifNoneTrue
;

スタイルについては好みが分かれるところですが、いずれにせよ3つ以上の分岐をつくる場合、それぞれの条件と結果の組が分かるように改行を入れるべきです

参考文献

Discussion