トップQs
タイムライン
チャット
視点

ヨーダ記法

ウィキペディアから

Remove ads

ヨーダ記法(ヨーダきほう、Yoda notation)またはヨーダ条件式(ヨーダじょうけんしき、Yoda conditions)とは、プログラミングにおけるジャーゴンのひとつで、If文などの条件式において比較するふたつの要素を一般的な順序と逆に記述するプログラミングスタイルをいう。ヨーダ記法では条件式の左辺(比較演算子の左オペランド)に定数を配置する。

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

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

例えばC言語C++において、int型の変数valueの値を調べて分岐するif文は、多くの場合、以下のように記述される。

if ( value == 42 ) { /* ... */ }
// 上記は「もし value が 42 と等しければ、…」と読める。

ヨーダ記法では同様の式を逆に記述する。

if ( 42 == value ) { /* ... */ }
// 上記は「もし 42 が value と等しければ、…」と読める。

等値比較の演算子==の左オペランドと右オペランドは入れ替えても結果に変わりはない[注釈 1]ことを利用している。

定数42[注釈 2]比較演算子==の左側に書かれ、中身を調べようとしている変数valueは右側に書かれるという順序を、ヨーダの独特のしゃべり方(目的語-主語-動詞、OSV型[8])に見立てている。(例: “When nine hundred years old you reach, look as good you will not."[9][10]

Remove ads

利点

要約
視点

式における定数の位置はプログラムの挙動を変更しない[要校閲](ただし後述[どれ?]のようにfalseに評価される場合[要説明]や、ユーザー定義の演算子オーバーロードが存在する場合を除く)。

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

例えばC/C++では下記コードは合法であり、コンパイルエラーにならない。代入演算子=は左辺に格納されたものと同じ値もしくは参照(左辺値)を返すからである[11][12]。結果として、プログラムの実行段階になってようやく動作異常に気づき、記述ミスの発覚が遅れる可能性がある。

int myNumber = 0;
/* ... */
if (myNumber = 42) { /* ... */ }
// まず myNumber に 42 が代入され、代入後の myNumber が持つ値すなわち 42 が if 文の条件式として評価される。
// 結果として、myNumber の代入前の値が何であっても、if 文の条件式は非ゼロすなわち真となる。

上記をヨーダ記法で書き換えると、以下のようになる。

int myNumber = 0;
/* ... */
if (42 = myNumber) { /* ... */ }
// シンタックスエラーとなりコンパイルされない。

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

Java/C#のブーリアン

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

例えば下記はJavaでも合法であり、コンパイルエラーにはならないが、本来プログラマーが意図していたコードはflag == trueである。

boolean flag = false;
/* ... */
if (flag = true) { /* ... */ }
// 条件式は常に真と判定されてしまう。

このようなミスを防ぐためには、flag != falseと書くか、あるいはヨーダ記法を使ってtrue == flagと書く方法もあるが、そもそもboolean型の変数をtruefalseと比較するのは冗長である[注釈 3][注釈 4]。真を期待する場合は条件式にそのまま使用し、偽を期待する場合は論理否定演算子!で反転してから条件式に使用すればよい。

boolean flag = false;
/* ... */
if (flag) { /* ... */ }

暗黙変換の回避

下記はJavaにおいて、プリミティブラッパークラスjava.lang.Booleanからプリミティブ型booleanへの暗黙的なボックス化解除(オートアンボクシング)が実行されることで異常動作を引き起こす例である。

Boolean myBoolean = true;
/* ... */
if (myBoolean = null) { /* ... */ }
// まず java.lang.Boolean 型の変数に null が代入された後、java.lang.Boolean.booleanValue() が暗黙的に呼ばれる。
// コンパイルは通るが、実行時に Java ランタイム環境によって例外 NullPointerException がスローされる。

ヨーダ記法では:

Boolean myBoolean = true;
/* ... */
if (null = myBoolean) { /* ... */ }
// シンタックスエラーとなりコンパイルされない。

Java 8以降はjava.util.Objects.isNull(Object)を使う方法もある。

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

nullの回避

また、null値のデリファレンスによる危険な振る舞いを回避するのに使える場合もある。

String myString = null;
/* ... */
if (myString.equals("foobar")) { /* ... */ }
// myString が null だった場合、Java ランタイム環境によって例外 NullPointerException がスローされる。

ヨーダ記法では:

String myString = null;
/* ... */
if ("foobar".equals(myString)) { /* ... */ }
// 文字列リテラルは常に non-null であり、NullPointerException は発生しないことが保証される。
// また、Object.equals(Object) メソッドの引数に null が渡された場合は false が返却される。
// そのため、myString が null だった場合、期待通りに false と判定される。

Java 7以降はjava.util.Objects.equals(Object, Object)を使う方法もある。ただし文字列の大文字・小文字を区別せずに比較する場合、インスタンスメソッドString.equalsIgnoreCase(String)を使う必要がある。

C#の場合は文字列の比較にはインスタンスメソッドのObject.Equals(Object)ではなく、両辺にnullを許可する==演算子オーバーロードを使い[18][注釈 5]、また前述のように条件式の型はboolでなければならないため、少なくとも文字列の比較に関してヨーダ記法を使う必要はない。大文字・小文字を区別せずに比較する場合、インスタンスメソッドだけでなくnullを許可する静的メソッドも用意されている[21]

string myString = null;
/* ... */
if (myString == "foobar") { /* ... */ }
// myString が null だった場合、false と判定される。
if (myString = "foobar") { /* ... */ }
// シンタックスエラーとなりコンパイルされない。

見切れた場合の可読性向上

正確にはヨーダ記法固有の利点ではないが、比較演算子を左側に寄せることにより人によっては可読性が増すという長所もある。特にこの長所は略語を使わない文化や型無しのオブジェクト指向言語で顕著となる。(型が無い場合は名前の重複を避けるためメソッドにつける名前が長くなりやすい)[独自研究?]

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

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

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

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

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

"字句解析中に'/*'があるか判"

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

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

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

上記のように比較演算子を左に寄せている場合(結果的にヨーダ記法)は、定数と演算子により各行が何をしているか推測できるが、比較演算子を右に寄せた場合、重要な情報がすべて見切れてしまい、各行が何をしているか推測が困難となる。

"字句解析中に'/*'があるか判"

contextOfParser tokenStack
    ifFalse:
    [
        ^ self.
    ].

contextOfParser tokenStack
    ifFalse:
    [
        ^ self.
    ].

contextOfParser tokenStack
    ifFalse:
    [
        ^ self.
    ].
Remove ads

PowerShell

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

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

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

$ary = @('abc', '123', 'ABC', $null, $null)
if ($ary -eq $null) { 'Array is null.' } else { 'Array is non-null.' }
# → 比較演算子によって返却される値は、配列 {$null, $null} となるため、条件式の評価結果は真となってしまう。
if ($null -eq $ary) { 'Array is null.' } else { 'Array is non-null.' }
# → 期待通り、条件式の評価結果は偽となる。

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

批判

要約
視点

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

PythonKotlinSwiftなどいくつかの後発プログラミング言語では、=による代入が式ではなく文であったり、あるいは代入演算子=が値を返さないようにしていたりすることで、条件式中の代入を許容しない設計になっており、そういった言語を用いる場合はこの種の不具合を作りこむことがそもそも不可能になる[25][26][27][注釈 6]。そのため、わざわざヨーダ記法を用いる理由はどこにもない。

またC/C++のように潜在的に問題を抱えた言語であっても、多くのコンパイラはif (myNumber = 42)のようなコードに対して、記述ミスのおそれがあるとして警告を表示する[注釈 7]。ただし、コンパイラ警告は無視することも可能であるため、不注意なプログラマーは警告に気づかずミスを犯すかもしれない。警告をエラーとして扱うように設定して無視させないように構成することもできるが、コードの書き方によっては警告を出さなくなるコンパイラもある[注釈 8]。そのようなコードであっても問題を検出できるようにするために、静的コード解析ツールが併用されることもある[34]

SEI CERT C Coding Standardではかつて「EXP21-C. Place constants on the left of equality comparisons」という項目をレコメンデーション(推奨事項)としていたが、独立した項目としては2014年に削除され[35][36]、EXP45-Cなどにおける限定的な解決策のひとつとして例示されるだけとなっている[37]。なお、JPCERT/CCによる日本語版は更新されておらず、項目の削除が反映されていない[38]

nullの振る舞いを回避できる利点についても、ヌルポインタエラーを隠蔽し発覚が遅れるという意味では欠点と考えることができる。入力としてnullを許可しないコードであった場合、本来は事前のnullチェック処理を明示的に記述し、実際に入力を使用する箇所ではnon-nullになることを保証すべきだが、ヨーダ記法で書くと事前のnullチェックを省略できてしまい、意図せずnullを許可するコードになってしまう。

そのほか、C++において非基本型を == 演算子で比較する際、適切な演算子オーバーロードが存在しない場合があることも欠点に挙げられる。例えば、ATLCComBSTRを文字列リテラルと比較するとき、if (L"Hello" == cbstrMessage)と記述すると、CComBSTRによるメンバー演算子オーバーロードCComBSTR::operator ==は使用されない[39]。代わりに暗黙の型変換演算子オーバーロードCComBSTR::operator BSTRが使用され[40]、結果として文字列ではなくポインタ同士の比較が実行されてしまう[注釈 9]_bstr_tにも同様の問題がある[42][43]。もっとも、この点に関してはヨーダ記法の問題というより、演算子オーバーロードの定義の仕方に問題がある。等値比較演算子==は本来オペランドの交換法則を満たすべきであり、このような組み込み型に対する通常の演算子の意味から逸脱するような振る舞いをする演算子オーバーロードは、便利というより混乱を招くだけである[44][注釈 10][注釈 11]

Remove ads

関連項目

脚注

外部リンク

Loading content...
Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads