ヨーダ記法とは 〜定数を左辺に記述するメリットと流行らない理由〜


ヨーダ記法とは

ヨーダ記法(Yoda notation)は、条件式の左辺に値や定数を記述するスタイルのことである。

// ヨーダ記法
if (NULL == value);

// 通常の記法
if (value == NULL);

従来「変数 is 定数」と書く所を、ヨーダ記法では「定数 is 変数」という形式で記述する。この記法は「ヨーダ条件式(Yoda conditions)」や「Left-hand comparisons(左辺比較)」と呼ばれることもある。


本記事ではヨーダ記法の概要とそのメリット・デメリットについて説明する。

左辺定数スタイルを考える上で、ヨーダ記法派の主張はもはやテクノロジーが発展した現代においては無意味な主張であると感じているのだが、スター・ウォーズ的にタイムリーな話題でもあるため、これを機会に紹介したい。後半ではヨーダ記法の意外なメリットについても触れる。

目次

なお本記事で言う「定数」とは、名前付き定数のことだけではなく、nullや、true、文字リテラル、数値リテラル等を含めたもののことを指す。
スポンサーリンク

ヨーダ記法の由来

処理の目的を左辺に記述するこのスタイルは、俗に「ヨーダ記法」と呼ばれているが、これは映画「スターウォーズ」に登場する仙人「ヨーダ」が倒置法に似た特殊な文法で話すことに由来している。

一般的な表現法我々で、シスを倒さねばならない。
倒置法シスを倒さねばならない、我々で。

ヨーダ記法の例

if (0 > less);
if (-1 != state);
if (UTF8 == enc);
if (this == *obj);
if ("Bob" == name);
if (NotFound != find());
if (false === this.isapen());
while (null != (line = read()));
if (Destroy == willOfForce(we)) free(sith);
return '\n' == ch || '\r' == ch;
return E == m * pow(c, 2);

ヨーダ記法のそれは要するに「B は A」という文法であり、日常生活で使われる「A は B」と大きく異なる。そのためヨーダ記法は不自然で直感的でないと批判されることも多い。

しかしヨーダ記法にはそれなりのメリットがある。

ヨーダ記法のメリット

ヨーダ記法には=演算子と==演算子の誤用による問題を回避できるという利点がある。

IF文内での誤った代入を回避できる

左辺派の主張でよく聞くのが、以下の様な問題である。

int const UTF8 = 4;
int encoding = 9;
if (encoding = UTF8)
   printf("done"); // 出力されてしまう

本来このコードは「encodingUTF8の値が同じ場合のみdoneを出力する」という趣旨で書かれたコードである。

しかしながら、上記のコードでは常に“done”が出力されてしまう。上記if文内のコードが比較演算==ではなく代入処理=としてミスタイプされているためだ。

if文実行時、encoding変数には定数UTF8の値4が代入され、if文はその代入後のencoding変数を評価する。結果として条件式は真とみなされてしてしまうのだ。

C言語やJavaScriptの条件文は、0以外の結果や値を真と見なす。

本来意図した動作をさせるためにはencoding == UTF8と書かなければならない。

そこでヨーダ記法の出番

これらの問題の対応策として、左辺派は以下の様な記法を推奨している。

if (UTF8 == encoding) printf("done");

これなら==を間違って=と書いてしまってもコンパイラがエラーとして警告を発してくれる(定数への代入はできないため)

// !!!: Read-only variable is not assignable
// !!!: Cannot assign to variable 'UTF8' with const-qualified type 'const int'
if (UTF8 = encoding) printf("done");

ちなみにこのように定数を左辺に記述するスタイルにはヨーダ記法というなんとも有り難い俗称がある。

あのWordPressも採用

通販番組の謳い文句ではないが、世界的に有名なPHP製のブログシステム「WordPress」には、なんとこのヨーダ記法が取り入れられている。「あのWordPressも認めたコーディングテクニック」といったところだろうか。

Javaの世界でも無意識の内に使われている

なお若干趣旨は異なるが、Javaの世界では安全性のためのイディオム("".equals(object))にある種のヨーダ記法が用いられている。

String encoding = null;
// nullに対するメソッド呼び出しはクラッシュする(いわゆるヌルポ)
if (encoding.equals("UTF8"));
// クラッシュしない
if ("UTF8".equals(encoding));
スポンサーリンク

現代では時代遅れの記法

先人たちの知恵とも言えるヨーダ記法であるが、テクノロジーが発展した現代では、大したメリットの無い記法となってしまった。

コンパイラが警告してくれる

そもそも現代のコンパイラーの多くは条件式内での代入処理を事前に警告してくれる。

int currentEncoding = SJIS;

// ???: Using the result of an assignment as a condition without parentheses
if (currentEncoding = UTF8) printf("changed");

逆に、意図的に条件式内での代入処理を行いたい場合には、以下のように括弧を二重に記述する必要がある。

if ((currentEncoding = UTF8)) printf("changed");
if ((next = node.next()));
while ((line = file.read()));

最近の言語は条件式の評価が厳格

JavaやSwiftのような現代的な言語の多くは、そもそもif (encoding = UTF8)のような書き方が出来なくなっている。これは条件式の評価基準がBoolean型の結果(true/false)に統一されているためである。ただ、この場合if (that = false)の問題は残ってしまうが、コンパイラ側のチェックを厳しく設定したり、if (!that)記法で統一したりすれば良いだけの話である。

ちなみにSwift言語では代入演算子の戻り値がVoid型に統一されているため、上記のif (that = false)記法自体が無効となっている。

安全性ありきのスタイル

ヨーダ記法支持者の多くは、先程説明した===の誤用問題を回避する目的でヨーダ記法を推している。故に<=<演算子ではこのヨーダ記法を用いる必要はない/用いるべきではないと主張するものも多い。実際にWordPressでは==, !=, ===, !==演算子に対するヨーダ記法の利用は推奨しているものの、<, >, <=, >=演算子に関しては、可読性の観点から利用を避けるよう指示している。Symfonyも同様にヨーダ記法を「==, !=, ===, !==」演算子への適用に留めている。

ヨーダ記法には安全性以外のメリットが見出されていないという現状がある。

必要性よりもデメリットが大きい

安全性を担保するためのテクニックとしては非常に有益なヨーダ記法であるが、現在ではメリットよりもデメリットのほうが上回ってしまっている。

そもそも直感的ではない

人間が読むことを意識した場合、「定数 == 変数」は「B は A」という文法となり若干の違和感がある。日常生活では圧倒的に「A は B」の文法が使われているわけだから、「変数 == 定数」のほうが直感的だという主張は頷ける。

// 一般的な記法
if (he == "tom"); // 彼はトムです
// ヨーダ記法
if ("tom" == he); // トムは彼です ←(あまり聞き慣れない)
「トムは彼です」という言い方は「トムはどの人ですか?」という質問に対する受け答えとしてなら問題なく使えるが、一般的な他者紹介のケースでは滅多に使われることはないはずである。

コードの文脈が読み取りづらくなる

ヨーダ記法は主語が右辺に記述されてしまうため、複数の文の繋がりや流れが読み取りにくくなるという欠点がある。

一般的に、コードというのは各行の前後の文脈やその流れを一つ一つ理解しながら読むものだが、ヨーダ記法を用いて書かれたコードはこの前後の文脈の紐付けや関係性の理解がしづらくなる傾向にある。これは前後の文脈を結びつけるための重要な要素(この場合、変数や主語のこと)が、右辺側に書かれてしまうことによって目につきにくくなってしまうためだ。

int value = INT_MAX;
value = -1 - value;
if (INT_MIN == value) print("same");

例えば、上記のコードの主体は変数valueである。1行〜3行の処理はいずれもvalueに関連した処理であることがわかる。

しかし3行目の処理だけは妙な違和感や、何か引っかかるものを感じるかもしれない。処理内で最初に目につく値がvalueではなくINT_MINになっているためだ。INT_MINを見た段階で、3行の処理は1行〜2行の処理とは関係のない処理であると解釈されてしまう恐れもある。

実際に読み手はINT_MINを見た段階で、前の行で意識していたvalue変数のことを一旦忘れて、頭を完全に切り替えてしまうかもしれない。短期記憶からvalueの情報を取り除き、まっさらな思考でその後の処理を読み解こうとしてしまうかもしれない。そしてその後の右辺でvalue変数を見かけて拍子抜けしてしまうのだ。「valueって何だったっけ?」となってしまうのだ。

ヨーダ記法は前の文脈から引き継がれる前提や流れというものを崩してしまう、いわばジョーカーのような存在だとも言える。

つまるところ、ヨーダ記法には文法が不自然になるという従来のデメリットに加えて、文脈が不自然になる文脈が読み取りにくくなるというデメリットも持ち合わせているのである。

なお、上記サンプルコードの不自然さは、以下の日本語文章の不自然さに近いものがある。

① 彼はヨーダという名前です。フォースが使えるのは彼です。
② 彼はヨーダという名前です。彼はフォースが使えます。
// ↑ ②のほうが自然な流れで理解もしやすい

前の文との繋がりを意識させるためには、②の例のように主語を最初に持ってくる必要がある。そのほうが文章の流れも自然になり理解もしやすくなる。

人間が読みやすく理解のしやすいコードを意識する場合、やはり主語である変数は左辺に書くべきなのだ。

左辺値/右辺値の慣習に反する

コンピュータ・サイエンスの世界には「左辺値」「右辺値」という考え方がある。そして世の中のプログラムの大半は左辺値は左側、右辺値は右側に記述するという慣習に沿って書かれている。

左辺値 = 右辺値;
左辺値 == 右辺値;

ヨーダ記法の利用はこの慣習や一貫性を壊すことに繋がる。

左辺値 = 右辺値;
右辺値 == 左辺値;

演算子オーバーロードの手間

一般的な「演算子オーバーロード」の書き方ではヨーダ記法に対応出来ない。

struct INT {
  int V;
  // INT == int 式にしか対応出来ない。
  bool operator==(int v) { return V == v; }
};

INT{1} == 2; // OK => INT{1}.operator==(2)
2 == INT{1}; // エラー: Invalid operands to binary expression ('int' and 'INT')

以下のように、ヨーダ記法向けに余計な定義を行う必要がある。

struct INT { int V; };
bool operator==(const INT& n, int v) { return n.V == v; }
bool operator==(int v, const INT& n) { return v == n.V; }

INT{1} == 2;
2 == INT{1}; // ヨーダ記法OK

スクリプト言語でのメリット

ならば、もう定数を左辺に記述する必要はないのではないかと思われるかも知れない。

ただし、スクリプト言語や軽量言語を使うとなれば話は別だ。特にインタプリタ言語の中には条件式内の代入処理を警告しない物も多くある。またそもそもPHPやRubyなどの軽量言語では、if文内での変数代入をむしろテクニックとして活用することがある。

if文内での変数代入・テクニック

if ($addr = getPost("address")) // ①
   echo "Address: " . $addr;

多くのスクリプト言語では上記のようなコードが書ける。 変数宣言値チェックが①のif文内一行で完結している点に注目してほしい。上記のコードは以下のコードと等価である。

$addr = getPost("address");
if ($addr)
   echo "Address: " . $addr;

本来二行に分けて書かなければならない処理を一行にまとめる事ができるため、チェック項目の多い業務アプリ開発やモニターの小さな開発環境(ノートPC、零細企業)では非常に重宝するテクニックである。

しかし欠点も

しかし、以下の記法との区別が付きにくくなるという欠点がある。

if ($addr == getPost("address")) // ②
   echo "Address: " . $addr;

ここでいう$addrはすでに宣言されている変数のことである。その変数の値をgetPost関数由来の右辺値と比較しているわけだが、前回の ① のようなコードと混在すると紛らわしくなる。開発チームによってはこの紛らわしさを回避するために先ほどの ① の記法自体を認めない所もあるくらいだ。

両記法の区別が明確になる

しかしどうだろう、以下のように(関数呼び出し == 変数)や(定数 == 変数)の記法で統一すれば両者の紛らわしさは完全に払拭され、① のテクニックも躊躇なく使えるようになる。

if (getPost("address") == $addr)
   echo "Address: " . $addr;
   
if ($_POST["age"] == $age)
   echo "Age: " . $age;

なんとヨーダ記法には両記法の区別を明確にする意外な効果があったのだ。

$_POST["age"] = $ageによる代入処理の危険性は残るが、この点は静的コード解析でどうにでもなる。
ここで重要なのは、似たような記法をヨーダ記法によって区別することが出来たという点である。

ヨーダ記法には多様性を実現するための隠れた可能性が備わっていたのである。

スポンサーリンク

ヨーダ記法は流行るのか

ヨーダ記法が受け入れられない主な要因は# 必要性よりもデメリットが大きいで既に述べた。ここではヨーダ記法をより実践的な世界に当てはめて考えてみる。

ヨーダ記法は、先程の軽量言語の世界では非常に有効な発想になりえるものの、静的な型付け言語とIDEが主流の現代では当面の間は流行らないと思われる。というのも、定数を左辺に記述するスタイルはIDEの入力支援が受けづらい。

if (align == TextAlignLeft) {}

上記のコードを書く際、高性能なIDEはalign == と入力した時点で、左辺の変数に対応する定数TextAlignLeftTextAlignRightをサジェスト/自動補完してくれる。逆にTextAlignLeft == と入力した際に変数alignをサジェストしてくれるIDEは稀である。

そもそもヨーダ記法の場合はTextAlignLeftを手入力する手間がある。IDEによってはインテリセンス機能によってtalからTextAlignLeftをサジェストしてくれるものもあるが、Swift言語のように定数に対して名前空間とその推論をサポートしている言語では.Left.Right等の省略記法を用いることが多く、またこの記法は左辺側でのサジェストがまだ弱い。

IDEの入力支援を意識した場合、ヨーダ記法はいろいろと面倒なのである。 IDEがより賢くなり、ヨーダ記法への対応も意識されるようになれば、状況は変わるかもしれない。

ヨーダ記法の意外なメリット

一つ目の意外なメリットは「# 両記法の区別が明確になる」で既に取り上げている。ヨーダ記法は多様性を実現するためのメカニズムにもなり得るというという話であった。

ここで紹介するもう一つのメリットは、ヨーダ記法を用いるとコードに規則性が生まれやすくなるという物だ。

// ①
if (application_name == null) return -1;
if (exp == null) return -1;
if (version > 99) { } else { }
if (val > -1) { } else { }
bool foundA() { return this.find(string) != -1; }
bool foundB() { return this.find(string, position) != -1; }

// ②
if (null == application_name) return -1;
if (null == exp) return -1;
if (99 < version) { } else { }
if (-1 < val) { } else { }
bool foundA() { return -1 != this.find(string); }
bool foundB() { return -1 != this.find(string, position); }

上記のコードは ② のほうが明らかに規則的である。nullチェック処理が2つと数値比較処理が2つ検索方法のバリエーションが2つという規則を視覚的・直感的に瞬時に理解できる。判読性と視認性は相当高いことがうかがえる。

逆に ① のコードに規則性を持たせ判読性と視認性を向上させるためには、概ね以下のようなコード揃えが必要になる。

// 改善案1
if (application_name == null) return -1;
if (             exp == null) return -1;
if (version > 99) { } else { }
if (val     > -1) { } else { }
bool foundA() { return this.find(string)           != -1; }
bool foundB() { return this.find(string, position) != -1; }

// 改善案2
if (app == null) return -1;
if (exp == null) return -1;
if (ver > 99) { } else { }
if (val > -1) { } else { }

こうすることで、コードの規則性や法則性をより効率的に読み手に示すことが出来るようになったわけだが、そもそもヨーダ記法を用いた ② の方法であれば、このような面倒な整形は不要になる。

ヨーダ記法には、非常に少ない労力で手軽にしかも自然な形で、コードに規則性を持たせることができるというメリットがあるのだ。

ヨーダ記法は人と用途を選ぶ

ただし、これらをメリットと感じるプログラマはごく少数かもしれない。これは結局の所、好みと用途の問題なのである。「結果に注目するか、要因に注目するか」によって意見が分かれるはずだ。

チェック処理の多い業務ソフトウェア開発や、構文解析・データ解析プログラムの開発ではこのヨーダー記法によるメリットを受けやすいと感じる。

ヨーダ記法は結果に注目する際に真の威力を発揮する

ヨーダ記法は比較処理の結果に注目する際に大変有効な記法となる。 次回はこの「結果に注目する」という部分についてより深く考察する。

ヨーダ記法のススメ ─ 比較対象を左辺に記述するメリット

広告

関連するオススメの記事

ヨーダ記法とは 〜定数を左辺に記述するメリットと流行らない理由〜」への1件のフィードバック

  1. ピンバック: ヨーダ記法のススメ 〜比較対象を左辺に記述するメリット〜 | MaryCore

コメントは停止中です。