ヨーダ記法とは
ヨーダ記法(Yoda notation)は、条件式の左辺に値や定数を記述するスタイルのことである。
// ヨーダ記法
if (9 == value);
// 通常の記法
if (value == 9);
従来「変数 is 定数」という形式で記述する所を、ヨーダ記法では「定数 is 変数」という逆の形式で記述する。この記法は「ヨーダ条件式(Yoda conditions)」や「Left-hand comparisons(左側比較、左辺比較)」と呼ばれることもある。
ヨーダ記法の活用によって、比較処理と代入処理の誤用による問題を回避することができる。
// いずれも異なる動作となる
if (value == 9); // 比較処理
if (value = 9); // 代入処理(おかしな処理だが動いてしまう)
// ヨーダ記法
if (9 = value); // コンパイルエラーが発生する
ヨーダ記法を用いれば、比較演算子==
を間違って代入演算子=
として記述してしまっても、コンパイルエラーや実行時エラーが発生するため、コードのバグや問題の早期発見にも繋がる。
ただしヨーダ記法は一般的な文法とは異なるため、読み手にとっては読みづらく、また違和感の強い記法に感じられる恐れがある。
また現代では、条件式内での意図しない代入処理を検知するためのツールや仕組み、言語仕様が整備されており、ヨーダ記法の必要性は徐々に失われつつあるのが現状である。
ヨーダ記法に関するより具体的かつ詳細な説明については、次節以降で行うこととする。
本記事ではヨーダ記法の概要とそのメリット・デメリットについて説明する。
左辺定数スタイルを考える上で、ヨーダ記法派の主張はもはやテクノロジーが発展した現代においては無意味な主張となってしまったように思うのだが、スター・ウォーズ的にタイムリーな話題でもあるため、これを機会に紹介したい。後半ではヨーダ記法の意外なメリットについても触れていく。
目次
- ヨーダ記法の由来
- ヨーダ記法の例
- ヨーダ記法のメリット
- 現代では時代遅れの記法
- 必要性よりもデメリットが大きい
- スクリプト言語でのメリット
- 現代プログラミングにおけるメリット
- ヨーダ記法は流行るのか
- ヨーダ記法の意外なメリット
なお本記事で言う「定数」とは、名前付き定数のことだけではなく、nullや、true、文字リテラル、数値リテラル等を含めたもののことを指す。
ヨーダ記法の由来
処理の目的を左辺に記述するこのスタイルは、俗に「ヨーダ記法」と呼ばれているが、これは映画「スターウォーズ」に登場する仙人「ヨーダ」が倒置法に似た特殊な文法で話すことに由来している。
一般的な表現法 | 我々で、シスを倒さねばならない。 |
倒置法 | シスを倒さねばならない、我々で。 |
ヨーダ記法の例
if (0 > less);
if (-1 != state);
if (UTF8 == enco);
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 Strong == vader->force;
return E == m * pow(c, 2);
assert(Blue == sky);
ヨーダ記法のそれは要するに「B は A である」という文であり、日常生活でよく使われる「A は B である」とは大きく異なる。そのためヨーダ記法は読みづらく不自然で直感的でないと批判されることも多い。
しかしヨーダ記法にはそれなりのメリットがある。
ヨーダ記法のメリット
ヨーダ記法には=
演算子と==
演算子の誤用による問題を回避できるという利点がある。
IF文内での誤った代入を回避できる
左辺派の主張としてよく聞くのが、以下の様な問題である。
int const UTF8 = 4;
int encoding = 9;
if (encoding = UTF8)
printf("done"); // 出力されてしまう
本来このコードは「encoding
とUTF8
の値が同じ場合のみ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));
もっともJava 7以降ではboolean java.lang.Object.equals(Object a, Object b)
クラス・メソッドの利用が可能となっている。これによって前記のイディオムと同等の安全性が確保される。
// NullPointerExceptionが発生しない
if (Object.equals(null, "UTF8"));
現代では時代遅れの記法
先人たちの知恵とも言えるヨーダ記法であるが、テクノロジーが発展した現代では、大したメリットの無い記法となってしまった。
コンパイラが警告してくれる
そもそも現代のコンパイラーの多くは条件式内での代入処理を事前に警告してくれる。
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()));
構文チェックツールによるチェックが可能
コンパイラ言語でなくとも、外部ツールを利用することで、条件式内での不正な代入処理を検知することが可能となっている。C言語の世界には古くから「Lint」と呼ばれるツールが存在している。現在ではプログラミング言語別に、様々な静的コード解析ツールが存在している。
最近の言語は条件式の評価が厳格
JavaやSwiftのような現代的な言語の多くは、そもそもif (encoding = UTF8)
のような書き方ができなくなっている。これは条件式の評価基準がBoolean型の結果(true/false)に統一されているためである。ただ、この場合if (that = false)
の問題は残ってしまうが、コンパイラ側のチェックを厳しく設定したり、if (!that)
記法で統一したりすれば良いだけの話である。
ちなみにSwift言語では代入演算子の戻り値がVoid型に統一されているため、上記のif (that = false)
記法自体が無効となっている。
インタプリタ言語であるPythonについても、式中での代入が制限されている。
安全性ありきのスタイル
ヨーダ記法支持者の多くは、先程説明した=
と==
の誤用問題を回避する目的でヨーダ記法を推している。故に<
や=<
演算子ではこのヨーダ記法を用いる必要はない/用いるべきではないと主張するものも多い。実際にWordPressでは==, !=, ===, !==
演算子に対するヨーダ記法の利用は推奨しているものの、<, >, <=, >=
演算子に関しては、可読性の観点から利用を避けるよう指示している。Symfonyも同様にヨーダ記法を「==, !=, ===, !==」演算子への適用に留めている。
ヨーダ記法には安全性以外のメリットが見出されていないという現状がある。
再代入を制限することができる
多くのプログラミング言語では、一時変数や仮引数を定数として宣言することで、再代入が行えない変数や引数を定義することができる。
実際にC/C++などの言語では、型名に対して「const 修飾子」を指定することで、再代入や値の変更が行えない定数を宣言することが可能となっている。
void f(const int a, int b) {
a = 1; // 再代入不可 Cannot assign to variable 'a' with const-qualified type 'const int'
b = 2; // 再代入可 constではないため
const int c = b; // 一時変数を定数としてコピーすることも可能
c = 3; // 再代入不可 Cannot assign to variable 'c' with const-qualified type 'const int'
}
現代的なプログラミング言語では、定数宣言用にlet
キーワード(Swift, Rust)やval
キーワード(Kotlin, Scala)が用意されていることもある。またSwift言語では仮引数がデフォルトで定数として宣言される。
let a = "a"
a = "A" // Error: cannot assign to value: 'a' is a 'let' constant
func f(a: String) {
a = "A" // Error: cannot assign to value: 'a' is a 'let' constant
}
JavaScriptでは「const 宣言」を用いることで、ローカル内で有効な定数を宣言することができる。
function f() {
const a = "a";
a = "A"; // ERROR: 再代入できない
if (a = "A"); // Uncaught TypeError: Assignment to constant variable.
var b = "b";
b = "B"; // OK: 再代入できてしまう
if (b = "2") alert("ハロー"); // "ハロー"
}
function g(a) {
const b = a;
if (b = "B"); // 再代入できない
if (a = "A"); // 再代入できてしまう
}
必要性よりもデメリットが大きい
安全性を担保するためのテクニックとしては非常に有益なヨーダ記法であるが、現在ではメリットよりもデメリットのほうが上回ってしまっている。
そもそも直感的ではない
人間が読むことを意識した場合、定数 == 変数
は目的語 + 主語
という文法となり若干の違和感がある。日常生活では圧倒的に主語 + 目的語
の文法が使われているわけだから、変数 == 定数
のほうが直感的だという主張は頷ける。
// 一般的な記法
if (he == "tom"); // 彼はトムです
// ヨーダ記法
if ("tom" == he); // トムは彼です ←(あまり聞き慣れない)
「トムは彼です」という言い方は「トムはどの人ですか?」という質問に対する受け答えとしてなら問題なく使えるが、一般的な他者紹介のケースでは滅多に使われることはないはずである。
コードを英文や文章のように記述するスタイルのコーディングでは、ヨーダ記法は違和感の強い記法に感じられやすいというデメリットもある。
if ("tom" is he) print "Hello, Tom"
if (Empty is not it) print it
コードの文脈が読み取りづらくなる
ヨーダ記法は、主語が右辺に記述されているために、複数の文の繋がりや流れが読み取りにくくなるという問題を引き起こす。
一般的に、コードというのは、各行の前後の文脈やその流れを一つ一つ理解しながら読むものだが、ヨーダ記法を用いて書かれたコードは、この前後の文脈の紐付けや関係性の理解がしづらくなる傾向にある。これは前後の文脈を結びつけるための重要な要素(この場合、変数や主語のこと)が、右辺側に書かれてしまうことによって目につきにくくなってしまうためだ。
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
による代入処理の危険性は残るが、この点は静的コード解析でどうにでもなる。
ここで重要なのは、似たような記法をヨーダ記法によって区別することができたという点である。
ヨーダ記法には多様性を実現するための隠れた可能性が備わっていたのである。
現代プログラミングにおけるメリット
現代のプログラミングでは、メソッドチェーンの多用や略称を用いない厳格な命名規則によって、コードの横幅が広くなる傾向にある。
if (self.getPost("main-form").getInput("title").toString() != "");
上記のようなコードは意外と読みづらい。なかなか結論にたどり着かない長ったらしいコードであり、何をしたいコードなのかが、ぱっと見では理解できない。左辺の != ""
まで読んでやっと全体像が掴めるような構成になっている。実に効率の悪い一文である。
if ("" != self.getPost("main-form").getInput("title").toString());
しかし上記コードのように、ヨーダ記法を用いて結論を先に示すことで「どんな処理をしたいコードなのか」を早期に明確に示す事ができる。右辺のコードもこの結論("" !=
)を前提に読み解くことができるため、読解性の向上や可読性の向上が期待できる。
ヨーダ記法にはコードの目的を明確化するための意外な活用方法があったのである。ノイズたる大きな主語への理解を後回しにすることで、コードの本質をより優先的に示すことができるというわけである。ヨーダ記法によって文章をより抽象的なものとして捉えることができる。
関連: ヨーダ記法のススメ - #結論を先に書くことのメリット
もっとも、一時変数をこまめに活用することで複雑な比較処理を避けることもできる。
String title = self.getPost("main-form").getInput("title").toString();
if (title != "");
また今回のようなヨーダ記法の活用方法には、目的語と述語のセットが主語から極端に離れてしまうという別の問題もある。
// メインフォームの性別《主語》が女性なら《述語》
if (getPost("main-form").getInteger("sex") == FEMALE);
// 女性なら《述語》(メインフォームの性別《主語》が)
if (FEMALE == getPost("main-form").getInteger("sex"));
この場合も一時変数による名前付けが有効な対処となる。
// 対処方法1
Integer sex = getPost("main-form").getInteger("sex");
if (FEMALE == sex);
// 対処方法2(C/C++)
const Integer sex = getPost("main-form").getInteger("sex");
if (sex == FEMALE);
if (sex = FEMALE); // 一時変数を定数として宣言すれば適切なエラーに繋がる
ヨーダ記法への期待
現代プログラミングではメソッドチェーンや、ラムダ式、関数型プログラミングの乱用によってコードのセマンティクスが複雑化しやすい傾向にあり、今後はこの問題への対応が大きな課題となっていくと考えられる。
一時変数の利用やサブルーチン化はその問題への対処として有効ではあるが、今後はこれらに加えて、ヨーダ記法の特性にも注目したいところである。
ヨーダ記法は流行るのか
ヨーダ記法が受け入れられない主な要因は# 必要性よりもデメリットが大きいで既に述べた。ここではヨーダ記法をより実践的な世界に当てはめて考えてみる。
ヨーダ記法は、先程の軽量言語の世界では非常に有効な発想になりえるものの、静的な型付け言語とIDEが主流の現代では当面の間は流行らないと思われる。というのも、定数を左辺に記述するスタイルはIDEの入力支援が受けづらい。
if (align == TextAlignLeft) {}
上記のコードを書く際、高性能なIDEはalign ==
と入力した時点で、左辺の変数に対応する定数TextAlignLeft
やTextAlignRight
をサジェスト/自動補完してくれる。逆にTextAlignLeft ==
と入力した際に変数align
をサジェストしてくれるIDEは稀である。
そもそもヨーダ記法の場合はTextAlignLeft
を手入力する手間がある。IDEによってはインテリセンス機能によってtal
からTextAlignLeft
をサジェストしてくれるものもあるが、Swift言語のように定数に対して名前空間とその推論をサポートしている言語では.Left
や.Right
等の省略記法を用いることが多く、またこの記法は左辺側でのサジェストがまだ弱い。
IDEの入力支援を意識した場合、ヨーダ記法はいろいろと面倒なのである。 IDEがより賢くなり、ヨーダ記法への対応も意識されるようになれば、状況は変わるかもしれない。
ヨーダ記法の意外なメリット
一つ目の意外なメリットは「# 両記法の区別が明確になる」で既に取り上げている。ヨーダ記法は多様性を実現するためのメカニズムにもなり得るというという話であった。
「# 現代プログラミングにおけるメリット」についても一読の価値がある。こちらはヨーダ記法の活用によって、処理の目的を明確化することができるようになる、というメリットが示されている。
その他、ヨーダ記法に関するメリットや可能性については、以下の記事の内容が参考になるかもしれない。
関連: ヨーダ記法の可能性【ヨーダセマンティクスとプログラミング哲学】
ヨーダ記法は人と用途を選ぶ
これまで挙げてきたメリットをメリットと感じるプログラマはごく少数かもしれない。これは結局の所、好みと用途の問題なのである。「結果に注目するか、要因に注目するか」によって意見が分かれるはずだ。チェック処理の多い業務ソフトウェア開発や、構文解析・データ解析プログラムの開発ではこのヨーダー記法によるメリットを受けやすいと感じる。
またヨーダ記法が用いられたソースコードは、初めて読む際には違和感を感じやすいが、二回目以降にコードを保守ないしデバッグする際には、目的の処理を見つけ出しやすいと感じることがある。ヨーダ記法は、読み慣れたコードの中から目的の処理を見つけ出す作業に適しているように思える。
ヨーダ記法は結果に注目する際に真の威力を発揮する
ヨーダ記法は比較処理の結果の部分に注目する際に大変有効な記法となる。この「結果に注目する」という考え方については以下の記事でより深く考察されているため参考にされたい。
関連: ヨーダ記法のススメ ─ 比較対象を左辺に記述するメリット
ピンバック: ヨーダ記法のススメ 〜比較対象を左辺に記述するメリット〜 | MaryCore