コンテンツにスキップ

ヨーダ記法

出典: フリー百科事典『ウィキペディア(Wikipedia)』

ヨーダ記法(ヨーダきほう、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]

利点

[編集]

式における定数の位置はプログラムの挙動を変更しない[要校閲](ただし後述[どれ?]のように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.
    ].

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コーディネーションセンター (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]

関連項目

[編集]

脚注

[編集]

注釈

[編集]
  1. C/C++では、一部の例外[5]を除き、演算子のオペランドの評価順序は未規定[6][7]であり、評価順序に依存するようなコードを書いてはいけない。左オペランドと右オペランドのうち、少なくとも一方が副作用を持つ場合、評価順序に依存するコードは未定義動作を引き起こす。
  2. コード例に使われている定数42については、生命、宇宙、そして万物についての究極の疑問の答えを参照のこと。
  3. boolean型リテラルとの冗長な比較をするコードに対して、警告を出力する静的コード解析ツールもある[13]
  4. C/C++においては、ポインタとNULLあるいはポインタとnullptrを関係演算子で比較するようなコードも同様に冗長である。
  5. C#の演算子オーバーロードは静的(static)でなければならないという規則がある[19][20]
  6. Python 3.8では値を返す代入式が導入されたが、Pascalと類似の代入演算子:=が使われるため、通常は比較演算子の==と混同するようなことはない[28]。なお、Pythonにおけるこの演算子は、その見た目から「セイウチ演算子」(walrus operator) とも呼ばれているが、コードの複雑さを低減したり、可読性を向上したりできるケースにのみ限定して利用することが推奨されている[29]
  7. 例として、GCCおよびClang-Wparentheses オプション(-Wallに含まれる)は suggest parentheses around assignment used as truth value と警告する[30][31]Microsoft Visual C++ (MSVC) はC4706の警告[32]を出力する。コンパイラとは別にMicrosoft Visual Studioに統合されたC/C++コード分析ツールは、C6282の警告[33]を出力する。
  8. GCC/Clangでは代入式を括弧()で囲むことによって-Wparenthesesの警告を抑制することができるため、if ((x == 0) || (x = 1))のようなコードに対しては警告を出さなくなってしまう。一方、MSVCはそのようなコードに対してもC4706の警告を出す。ただし、std::shared_ptrstd::functionstd::atomicのような、「boolへの変換演算子」または「何らかの組み込み型(基本型)Tへの暗黙変換演算子」をオーバーロードした型を持つ変数への代入をする場合、MSVCはC4706の警告を出さなくなってしまう。
  9. このとき、Microsoft Visual C++コンパイラはC4130の警告[41]を出力する。
  10. 標準C++ライブラリstd::basic_stringはこの問題を避けるため、非メンバー関数として比較演算子オーバーロードを提供している[45]。ATL/MFCCStringTも同様である[46]
  11. 演算子オーバーロードについては賛否両論ある。Javaでは濫用による混乱を避けるために、ユーザー定義の演算子オーバーロードの機能は実装されなかった[47]

出典

[編集]
  1. PHP Coding Standards | Coding Standards Handbook | WordPress Developer Resources
  2. PHP コーディング規約 – Japanese Team – WordPress.org 日本語
  3. Coding Standards (Symfony Docs)
  4. コーディング規約 - 4.x
  5. 厳密な式の評価順 - cpprefjp C++日本語リファレンス
  6. 評価順序 (C) - cppreference.com
  7. 評価順序 (C++) - cppreference.com
  8. Yoda's Syntax the Tribune Analyzes; Supply More Details I Will!”. http://itre.cis.upenn.edu/~myl/languagelog/. Language Log (2005年5月18日). 2014年12月22日閲覧。 “One way to look at Yoda's syntax is that it shows signs of favoring OSV syntax (Object-Subject-Verb) as the basic order in the simple clause.”
  9. The StarWars.com 10: Best Yoda Quotes”. starwars.com. Lucasfilm, Ltd. (2013年11月26日). 2014年12月22日閲覧。 “When nine hundred years old you reach, look as good you will not.”
  10. Quotes for Yoda (Character)”. imdb.com. Amazon. 2014年12月22日閲覧。 “When nine hundred years old *you* reach, look as good *you* will not, hmm?”
  11. 代入演算子 (C) - cppreference.com
  12. 代入演算子 (C++) - cppreference.com
  13. Java static code analysis: Boolean literals should not be redundant
  14. User-defined explicit and implicit conversion operators - provide conversions to different types | Microsoft Learn
  15. true and false operators - treat objects as Boolean values | Microsoft Learn
  16. Equality operators - test if two objects are equal or not equal | Microsoft Learn
  17. New Features in C# 7.0 - .NET Blog
  18. String.Equality(String, String) Operator (System) | Microsoft Learn
  19. Operator overloading - Define unary, arithmetic, equality, and comparison operators. | Microsoft Learn
  20. Why are overloaded operators always static in C#? | Microsoft Learn
  21. String.Equals Method (System) | Microsoft Learn
  22. Everything you wanted to know about arrays - PowerShell (-eq and -ne) | Microsoft Learn
  23. Everything you wanted to know about arrays - PowerShell ($null or empty) | Microsoft Learn
  24. Everything you wanted to know about $null - PowerShell (Checking for $null) | Microsoft Learn
  25. 7. Simple statements — Python 3.11.4 documentation
  26. Statements - Kotlin language specification
  27. Basic Operators | Documentation | The Swift Programming Language
  28. 6. Expressions — Python 3.11.4 documentation
  29. What’s New In Python 3.8 — Python 3.11.4 documentation
  30. Warning Options (Using the GNU Compiler Collection (GCC))
  31. Diagnostic flags in Clang — Clang git documentation
  32. Compiler Warning (level 4) C4706 | Microsoft Learn
  33. Warning C6282 | Microsoft Learn
  34. clang-tidy - bugprone-assignment-in-if-condition — Extra Clang Tools git documentation
  35. VOID EXP21-C. Place constants on the left of equality comparisons - CERT C Coding Standard - CERT Secure Coding Standards, Internet Archive
  36. 6 The Void - CERT C Coding Standard - CERT Secure Coding Standards, Internet Archive
  37. EXP45-C. Do not perform assignments in selection statements - SEI CERT C Coding Standard - Confluence
  38. EXP21-C. 等価比較の左側に定数を配置する
  39. CComBSTR Class §CComBSTR::operator == | Microsoft Learn
  40. CComBSTR Class §CComBSTR::operator BSTR | Microsoft Learn
  41. Compiler Warning (level 4) C4130 | Microsoft Learn
  42. _bstr_t relational operators | Microsoft Learn
  43. _bstr_t::wchar_t*, _bstr_t::char* | Microsoft Learn
  44. General Rules for Operator Overloading | Microsoft Learn
  45. std::basic_string - cppreference.com
  46. CStringT Class §CStringT::operator == | Microsoft Learn
  47. Javaに演算子オーバーロードを導入すべきときが来たのか | Oracle Technology Network Japan Blog

外部リンク

[編集]