これでも ESLint の話なのだが。
まだ ESLint 「めぐり」してる。ただ今回の「Require or disallow Yoda Conditions (yoda)」を自分のに適用したい、と思う話とは全然違う。「共感」の話。
1 if ("red" === color) {
2 // ...
3 }
This is called a Yoda condition because it reads as, “if red equals the color”, similar to the way the Star Wars character Yoda speaks. Compare to the other way of arranging the operands:
1 if (color === "red") {
2 // ...
3 }
This typically reads, “if the color equals red”, which is arguably a more natural way to describe the comparison.
Proponents of Yoda conditions highlight that it is impossible to mistakenly use =
instead of ==
because you cannot assign to a literal value. Doing so will cause a syntax error and you will be informed of the mistake early on. This practice was therefore very common in early programming where tools were not yet available.
Opponents of Yoda conditions point out that tooling has made us better programmers because tools will catch the mistaken use of =
instead of ==
(ESLint will catch this for you). Therefore, they argue, the utility of the pattern doesn’t outweigh the readability hit the code takes while using Yoda conditions.
ESLint のルールへの共感については exceptRange がちゃんと提供されてる、てこともある。がその前に。
訳さなくてもわかる? どうしても、という部分だけ訳してみる? 「This is called a Yoda condition because it reads as, “if red equals the color”, similar to the way the Star Wars character Yoda speaks.」はこれは、「これはヨーダ条件と呼ばれているのだが、これは「もしも赤が色ならば」、つまり丁度スターウォーズのヨーダの語り口そのものだからだ」。いいね?
普通に「もしも、赤が色に等しいならば」という判定を「したい」のだとするなら「いったいチミは何を作ろうとしているんだい?」と、脳みそを解剖して調べられたくなるだろう。調べられたいんだからしょうがない、てことならいいが、「それが自然だ!」と強硬に主張したいなら、「毒され過ぎている」。
このバッドノウハウが誕生したのは ESLint ドキュメントも言う通り「C/C++、java において、「ある開発者が開発者人生50年のうち一回あるかないかの特定のミス」」を「とんでもない誰もが陥る凶悪な言語仕様における罠だ!」という根拠薄弱な主張に基いて「ある時期に急速に広まったテクニック」である。つまりこれである:
1 while (a = 1) { // 100人開発者がいれば99人が3秒に一回間違う!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2 // = と == を、間違うに決まっている、間違うのだ、そうだ、間違うのだだだだだだだだ
3 }
という「アホな」理屈でもって、
1 while (1 = a) { // 逆に書けばコンパイラが気付いてくれる!!!!!!!!!!!!!!!!!!!!!!!!!
2 }
ということを「そうしないのは万死に値する」という強烈な主張でもって「歓迎」されたものだ。
現実にこんなミスを犯すのは実際何十年もエンジニアやるつもりなら、それこそ何十年に一回だし、これが根拠が非常に薄いのは、これが「非常にわかりやすい振る舞いの違いをもたらす」ということを忘れ去られている、ということである。つまり、「= と == を打ち間違えた場合」には、すぐさま unit test で期待と異なる振る舞いをもたらす。普通は。検出が困難なバグになることはまず十中八九ありえない。だからこのスタイルを支持する派閥が言うような大げさなことは普通は起こらないし、起こるわけがない。起こるようなら、そんなプロジェクト、「そもそもまともにテストしようとしない狂ったプロジェクト」である。
つまり、このような「不自然な書き方を推奨する」という小技に頼らず、「ちゃんとしろ」というのが ESLint のこのルールの主張だし、ワタシの意見とも一致する。(ESLint の主張は「ほかのチェックに頼ればこんな書き方、必要ないんだからするんじゃないわよ」、ということの方を言ってるんだけれど、根っこにあるのは同じ。というか ESLint は中立、なんだろうね。どっちかに肩入れはしてない。見かけ上は。けど名前からバカにしてるのは明らか、と解読するのが素直と思う。)
ただし、「java の特定のケース」について、一つだけ「これとはちょっとだけ観点が異なる「不自然な記述」」があって、残念だがこれだけはやや多めの「価値」がある:
1 if ("abc".equals(s)) {
2 }
これは「=、==」の件とは関係ない。まさに「ヨーダ条件」そのものなのだが、これは「間違いを防ぐ」目的ではなく、一種の「ショートカット」である。つまりこれは以下:
1 if (s != null && s.equals("abc")) {
2 }
を「一言」で記述しようとするテクニックである。ワタシは「だったらこれでいいじゃん」とさえも思うけれど、「実用的な理由で」 "abc".equals(s)
を非難するつもりはない。(読みにくいので嫌いは嫌いだけど。)
なんにしてもこの件でずっと不思議なのが、これを身に付けた C/C++, java 開発経験者が、この「ステキなテクニック」を、絶対に頑なに守り続けていることである。どうも「こんなん意味がない」という主張が耳に入らないか、あるいは入ってもその「効果」を信じて疑わないらしい。つまり javascript でも python でもなんだろうがこの「素晴らしいテクニック」を「撒き散らす」。Python でそれしたって意味のあるケースなんかないのに(だって最初から構文エラーになるっつーの)。
ほかの場所でも結構書いているが、なぜか「わかりやすい主張」には皆熱中しがちで、本当に繊細でわかりにくい問題を解決してくれるような「小さくて気が利くヤツ」には目もくれないのが「主流」らしい。こんなのにこだわってる暇があったら、ほんとに「未使用変数チェックをありがたがる」方が本来有益なはずなのに。(マジックナンバーは悪だ、の過度な反応もそれ。見かけがわかりやすいものほどこういうことが起こりやすいらしい。)
ワタシははなからこの Yoda condition が大嫌いだし、人のコードをコピペした際もわざわざ書き換えるほどなので、そもそもこのルールを適用したところで何も検知出来ないんだけれど、exceptRange の件だけは一応触れておきたい。つまり、「数学的な記述としては自然」な以下である:
1 if (10 <= x && x < 20) {
2 }
言うまでもなくこれは「数学的な記述」としては自然な \(10 \le x \lt 20\) を「Yoda だ Yoda だやーいやーい」と「言わない」という制御。これはワタシなんぞは非常によく書くので、これを非難されたらたまらん、と思う。まぁ「そもそも Yoda じゃない」しね、ワタシは。
追記:
さらっと「検出困難なバグ」という言い方をしたが、こういうの、ひょっとしたらちゃんと説明しないと納得出来ない人もいると思うので、補足しておきたい。
「検出が容易なバグ」というのは、要するに「仕様」(デザイン)で決定付けられた「期待される振る舞い」を直接テスト出来、検出出来るタイプのバグである。
つまり、一言で言えば「assert.equal などで直接テストを記述出来る」、あるいは unit test フレームワークになんらか事情があって頼れない場合であっても、「動作確認で正解不正解が即座に判別できる」ものである。例えば後者のケースであっても、「このボタンを押せばダイアログが起動するのが正しい」(が起動しない)、など。だから「全てのパスをテストする」という行為をする限りは、「見つからない」ということはほとんどありえない。
繰り返すけどこれが見つからないようなら、それは「テストのやり方・考え方」そのものが否定されるべきであって、「ほらみたことか、ヨーダしないからだ」なんて結論を出すバカなんか、この世に一人として存在しない。
対して、特にその「ヨーダが発明された」C/C++ において、本当に「厄介で、まず普通には簡単には検出出来ない」、滅茶苦茶簡単な例がある。ちょっと前にポインタの話をしたが、まさにそれでワタシは「気付いたけど放置した」バグの芽があった。これだ:
1 #include <string>
2 struct MyClass {
3 int x;
4 int y;
5 std::string name;
6 MyClass(const std::string& name) {
7 this->name = name;
8 }
9 };
10 int main()
11 {
12 MyClass* const mc1 = new MyClass("佐藤"); // あなたを一生愛し続けます…、サトゥーさん
13 const MyClass* mc2 = new MyClass("後藤"); // あなたは一生ゴトゥーさんのままです…
14
15 //mc1 = new MyClass("近藤"); // ゴメン、好きな人が出来ちゃった (許さん! by コンパイラ)
16 //mc2 = new MyClass("近藤"); // ゴメン、好きな人が出来ちゃった (ええよ、別に。by コンパイラ)
17 //mc2->x = 100; // ゴトゥーさん、変わっちゃったのね (許さん! by コンパイラ)
18 return 0;
19 }
これ、「コメントアウトしているからたまたま問題ではない、が、これはダメである:
1 #include <string>
2 struct MyClass {
3 int x;
4 int y;
5 std::string name;
6 MyClass(const std::string& name) {
7 this->name = name;
8 }
9 };
10 int main()
11 {
12 MyClass* const mc1 = new MyClass("佐藤"); // あなたを一生愛し続けます…、サトゥーさん
13 const MyClass* mc2 = new MyClass("後藤"); // あなたは一生ゴトゥーさんのままです…
14
15 //mc1 = new MyClass("近藤"); // ゴメン、好きな人が出来ちゃった (許さん! by コンパイラ)
16 mc2 = new MyClass("近藤"); // ゴメン、好きな人が出来ちゃった (ええよ、別に。by コンパイラ)
17 //mc2->x = 100; // ゴトゥーさん、変わっちゃったのね (許さん! by コンパイラ)
18 return 0;
19 }
コンパイラは何も言わず許してしまうから「OK」ではないのである。実際この場合、「後藤」オブジェクトを「愛する人」を失ってしまうため、つまり「後藤を管理出来るものが誰もいなくなってしまう」ため、「誰も後藤を消せなくなる」。C/C++ では非常に有名な「リークバグ」である。
これを機械的に検出するのは結構高等なツールが必要だ。ワタシが現役で本職 C++ プログラマだった頃は、これを検出出来る安価もしくはフリーのツールなんか存在していなかった。せいぜい「性能テスト」のフェーズでメモリ監視ツールをかけて「何かどこかでリークしているらしい」というのが辛うじて「わかれば幸せ」で、かなりの割合で、このミスを犯したまま出荷されてしまっていたアプリケーションは数知れず実在している(はずである)。実際経験豊富なエンジニアはそもそも最初からこれをしないように気を配りながらコーディングするため、まず滅多にこのミスをしないのだが、「それでも手が滑ってやってしまう」、からこそ、つまり「自分ではやっていないつもり」になって書いているからこそ、コーディング完了時にこれに心当たりがあることはほぼ絶望的に皆無であり、本当にやってれば普通「絶対に気付かない」。
これは「有名」だけれど「とても優秀だがこ難しい本」が指摘するばかりで、「アホな雑誌のアホなライター」が春になれば「ヨーダであなたのコードを守る!!!!!」とばかり叫んで、こういう本質を伝えないことを毎年繰り返すので、そこそこの経験年数でもこれを知らないエンジニアは多かった。
つまりはそういうことである。「あなたが学ぶべきことはそこじゃない」という話。もっと「ちゃんと、正しく理解せよ」、ヘンな魔法ばかりに頼ろうとするんではなく。地道にやるしかないんだよ、こんなん。