ヨーダ記法

ヨーダ記法またはヨーダ条件式とは、プログラミングにおけるジャーゴンのひとつで、If文などの条件式において比較するふたつの要素を一般的な順序と逆に記述するプログラミングスタイルをいう。ヨーダ記法では条件式の左辺に定数を配置する。

映画『スター・ウォーズ』シリーズの登場人物・ヨーダが、標準的ではない英語の文法で話すことに由来する。

ヨーダ記法はWordPressやSymfonyのPHPコーディング標準の一部に採用されている。一方、CakePHPのコーディング規約では、ヨーダ記法は避けるべきとされている。

1. 例
----

例えばC言語やC++において、int型の変数valueの値を調べて分岐するif文は、多くの場合、以下のように記述される。 if  { /* ... */ } // 上記は「もし value が 42 と等しければ、…」と読める。

ヨーダ記法では同様の式を逆に記述する。 if  { /* ... */ } // 上記は「もし 42 が value と等しければ、…」と読める。

等値比較の演算子==の左オペランドと右オペランドは入れ替えても結果に変わりはないC/C++では、一部の例外を除き、演算子のオペランドの評価順序は未規定であり、評価順序に依存するようなコードを書いてはいけない。左オペランドと右オペランドのうち、少なくとも一方が副作用を持つ場合、評価順序に依存するコードは未定義動作を引き起こす。}}ことを利用している。

定数42コード例に使われている定数42については、生命、宇宙、そして万物についての究極の疑問の答えを参照のこと。}}は比較演算子==の左側に書かれ、中身を調べようとしている変数valueは右側に書かれるという順序を、ヨーダの独特のしゃべり方に見立てている。

2. 利点
-----

date=2023-07。

Cに代表されるような、代入演算子に等号ひとつ  を使い、等値比較の演算子には==などの別のトークンを使うプログラミング言語では、比較式のつもりで代入式を書いてしまう間違いが見られる。

例えばC/C++では下記コードは合法であり、コンパイルエラーにならない。代入演算子=は左辺に格納されたものと同じ値もしくは参照を返すからである。結果として、プログラムの実行段階になってようやく動作異常に気づき、記述ミスの発覚が遅れる可能性がある。 int myNumber = 0; /* ... */ if  { /* ... */ } // まず myNumber に 42 が代入され、代入後の myNumber が持つ値すなわち 42 が if 文の条件式として評価される。 // 結果として、myNumber の代入前の値が何であっても、if 文の条件式は非ゼロすなわち真となる。

上記をヨーダ記法で書き換えると、以下のようになる。 int myNumber = 0; /* ... */ if  { /* ... */ } // シンタックスエラーとなりコンパイルされない。

42は定数であり、変更されないため、このエラーはコンパイラに捕捉される。結果として、コンパイル段階で記述ミスに気づくことができる。

2.1. Java/C#のブーリアン
------------------

C/C++とは異なり、JavaやC#の場合は、条件式の型はそれぞれbooleanやboolでなければならない仕様となっている。そのため、条件式を書くべきところに間違えて代入式を記述してしまっても、ほとんどのケースではコンパイルエラーになるが、例外もある。

例えば下記はJavaでも合法であり、コンパイルエラーにはならないが、本来プログラマーが意図していたコードはflag == trueである。 boolean flag = false; /* ... */ if  { /* ... */ } // 条件式は常に真と判定されてしまう。

このようなミスを防ぐためには、flag != falseと書くか、あるいはヨーダ記法を使ってtrue == flagと書く方法もあるが、そもそもboolean型の変数をtrueやfalseと比較するのは冗長であるboolean型リテラルとの冗長な比較をするコードに対して、警告を出力する静的コード解析ツールもある。}}C/C++においては、ポインタとNULLあるいはポインタとnullptrを関係演算子で比較するようなコードも同様に冗長である。}}。真を期待する場合は条件式にそのまま使用し、偽を期待する場合は論理否定演算子!で反転してから条件式に使用すればよい。 boolean flag = false; /* ... */ if  { /* ... */ }

2.2. 暗黙変換の回避
------------

下記はJavaにおいて、プリミティブラッパークラスname=java.lang.Booleanからプリミティブ型booleanへの暗黙的なボックス化解除が実行されることで異常動作を引き起こす例である。 Boolean myBoolean = true; /* ... */ if  { /* ... */ } // まず java.lang.Boolean 型の変数に null が代入された後、java.lang.Boolean.booleanValue が暗黙的に呼ばれる。 // コンパイルは通るが、実行時に Java ランタイム環境によって例外 NullPointerException がスローされる。

ヨーダ記法では: Boolean myBoolean = true; /* ... */ if  { /* ... */ } // シンタックスエラーとなりコンパイルされない。 Java 8以降はname=java.util.Objects.isNullを使う方法もある。

C#の場合はboolのような組み込みの値型もすべてSystem.ValueTypeから派生するオブジェクトであり、プリミティブラッパークラスのようなものは不要である。また、暗黙的なボックス化解除もされず、値を取り出すにはキャスト演算子を使って明示的に変換する必要があるため、上記のような問題は起きない。ただし、boolへの暗黙変換演算子をユーザー定義していたり、true/false演算子をユーザー定義していたりすると、上記のJavaにおける例のような間違ったコードを書くことができてしまう。とはいえ、2つの参照の等値性比較にはObject.ReferenceEqualsを使うことができるし、C# 7.0以降はis演算子を使ってif  {...}と書くこともできるので、問題を避けるためにヨーダ記法を使う必要はない。

2.3. nullの回避
------------

また、null値のデリファレンスによる危険な振る舞いを回避するのに使える場合もある。 String myString = null; /* ... */ if ) { /* ... */ } // myString が null だった場合、Java ランタイム環境によって例外 NullPointerException がスローされる。

ヨーダ記法では: String myString = null; /* ... */ if ) { /* ... */ } // 文字列リテラルは常に non-null であり、NullPointerException は発生しないことが保証される。 // また、Object.equals メソッドの引数に null が渡された場合は false が返却される。 // そのため、myString が null だった場合、期待通りに false と判定される。 Java 7以降はname=java.util.Objects.equalsを使う方法もある。ただし文字列の大文字・小文字を区別せずに比較する場合、インスタンスメソッドのname=String.equalsIgnoreCaseを使う必要がある。

C#の場合は文字列の比較にはインスタンスメソッドのObject.Equalsではなく、両辺にnullを許可する==演算子オーバーロードを使いC#の演算子オーバーロードは静的でなければならないという規則がある。 string myString = null; /* ... */ if  { /* ... */ } // myString が null だった場合、false と判定される。 if  { /* ... */ } // シンタックスエラーとなりコンパイルされない。

2.4. その他
--------

"字句解析中に'/*'があるか判定する処理の一部"

2 = contextOfParser tokenStack size ifFalse: [ ^ self. ].

$* = contextOfParser tokenStack last ifFalse: [ ^ self. ].

$/ = contextOfParser tokenStack first ifFalse: [ ^ self. ].

<WtXmlEndTag /> 例えばSmalltalkによる上記の例を差分比較用のエディターや、参考用コードを表示するため横幅を縮めて画面端に置いたエディターなどで表示すると下記のように見切れてしまう。また入れ子の関係で字下げが深くなってしまった場合も同様に見切れてしまう。 "字句解析中に'/*'があるか判"

2 = contextOfParser tokenSt ifFalse: [ ^ self. ].

$* = contextOfParser tokenSt ifFalse: [ ^ self. ].

$/ = contextOfParser tokenSt ifFalse: [ ^ self. ].

<WtXmlEndTag /> 上記のように比較演算子を左に寄せている場合は、定数と演算子により各行が何をしているか推測できるが、比較演算子を右に寄せた場合、重要な情報がすべて見切れてしまい、各行が何をしているか推測が困難となる。 "字句解析中に'/*'があるか判"

contextOfParser tokenStack ifFalse: [ ^ self. ].

contextOfParser tokenStack ifFalse: [ ^ self. ].

contextOfParser tokenStack ifFalse: [ ^ self. ].

<WtXmlEndTag />

3. PowerShell
-------------

PowerShellでは、代入演算子に=を使い、比較演算子には別のトークンを使う仕様となっているが、-eq演算子の左オペランドに、配列のようなコレクションを指定すると、右オペランドと一致する要素を含むサブ配列が返却される。

$ary = @ $result = $ary -eq 'abc' $result.GetType.FullName # →「System.Object[]」が出力される。 $result # →「abc」と「ABC」が出力される。

そのため、コレクション変数自体を$nullと比較する場合、必ず演算子の左オペランドに$nullを指定するヨーダ記法を使わなければならない。

$ary = @ if  { 'Array is null.' } else { 'Array is non-null.' }

→ 比較演算子によって返却される値は、配列 {$null, $null} となるため、条件式の評価結果は真となってしまう。if  { 'Array is null.' } else { 'Array is non-null.' }

→ 期待通り、条件式の評価結果は偽となる。<WtXmlEndTag />

PowerShellは動的型付け言語であるため、変数は特定の型に束縛されず、プログラムの実行途中で型が変化する可能性がある。変数にコレクション型の値が格納されているか否かを問わず、nullチェックの際は常に$nullを比較演算子の左側に置くことがベストプラクティスとされている。

4. 批判
-----

ヨーダ記法の批判者は、可読性の欠如が上記の利点を上回っていると考えている。比較式の左辺に、本来の主語である変数が出現する順序のほうが、コードを自然言語に置き換えて読むときに理解しやすくなるからである。また、比較式の両辺のうち、少なくとも片方が再代入不可能な定数である場合はエラー捕捉の効果を発揮するが、ともに再代入可能な変数である場合は意味をなさない。

PythonやKotlin、Swiftなどいくつかの後発プログラミング言語では、=による代入が式ではなく文であったり、あるいは代入演算子=が値を返さないようにしていたりすることで、条件式中の代入を許容しない設計になっており、そういった言語を用いる場合はこの種の不具合を作りこむことがそもそも不可能になるPython 3.8では値を返す代入式が導入されたが、Pascalと類似の代入演算子:=が使われるため、通常は比較演算子の==と混同するようなことはない。なお、Pythonにおけるこの演算子は、その見た目から「セイウチ演算子」 とも呼ばれているが、コードの複雑さを低減したり、可読性を向上したりできるケースにのみ限定して利用することが推奨されている。}}。そのため、わざわざヨーダ記法を用いる理由はどこにもない。

またC/C++のように潜在的に問題を抱えた言語であっても、多くのコンパイラはif のようなコードに対して、記述ミスのおそれがあるとして警告を表示する例として、[[GNUコンパイラコレクション。ただし、コンパイラ警告は無視することも可能であるため、不注意なプログラマーは警告に気づかずミスを犯すかもしれない。警告をエラーとして扱うように設定して無視させないように構成することもできるが、コードの書き方によっては警告を出さなくなるコンパイラもあるGCC/Clangでは代入式を括弧で囲むことによって-Wparenthesesの警告を抑制することができるため、