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


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

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

ヨーダ記法とは

ヨーダ記法(Yoda conditions/Yoda notation)は、条件式の比較要素(定数/期待値)を左辺に記述するスタイルのことである。

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

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

要するに定数を式の左側に書くというだけの話である。

従来「変数 is 定数」と書く所を、ヨーダ記法では「定数 is 変数」という形式で記述する。この記法は「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 1 & bite;

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

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

ヨーダ記法のメリット

IF文内での誤った代入を回避することが出来る

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

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

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

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

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

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

本来意図した動作をさせるためにはencoding == UTF8と書かなければならない。 =の書き方一つで真逆の動作をするというのは、なんとも恐ろしい話である。

そこでヨーダ記法の出番

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

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

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

// !!!: Read-only variable is not assignable
if (UTF8 = encoding) printf("done");

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

あのWordPressも採用

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

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

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

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

現代では時代遅れの記法

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

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

そもそも現代のナウなコンパイラー達は条件式内での代入記法encoding = UTF8を事前に警告してくれる。

// ???: 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); // トムは彼です ←(あまり聞き慣れない)
「トムは彼です」という言い方は「トムはどの人ですか?」という質問に対する受け答えとしてなら問題なく使えるが、一般的な他者紹介のケースでは滅多に使われることはないはずである。

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

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

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

INT n = {1};
bool a = n == 2; // 3
bool b = 2 == n; // エラー: 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; }

bool a = INT{3} == 2;
bool b = 2 == INT{3}; // ヨーダ記法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

コメントは停止中です。