CoffeeScriptを使うべきか、使わざるべきか?
最近CoffeeScript界隈のブロゴスフィア(死語)を賑わせていた「CoffeeScriptを使うべきか、使わざるべきか?」という話題についてまとめてみた。
以下の記事紹介は超訳かつ要約なので詳しく知りたい人は元記事を参照のこと。
ことの発端はこの記事。
SnackJSの作者がCoffeeScriptをディスる。
A Case Against Using CoffeeScript by Ryan Florence
デバッグの問題
CoffeeScriptが生成するJavaScriptはきちんとしているけど、結局は自分が書いたコードじゃないため読みにくい。自分で直接書いたほうが見やすい。
それにCoffeeScriptをデバッグするワークフローは大変だ。
- まず問題がJavaScript内のどこで発生したのかを突き止める(CoffeeScriptのコードと行単位で対応してないから大変だ)
- そして、そのJavaScriptを理解する(自分が書いたコードじゃないからね)
- 理解した後、問題の原因を探す
- 問題の原因を把握した後に、直すべきCoffeeScript内のコードを探す
- CoffeeScriptを修正する
- コンパイルする(大抵の場合は自動化されてるけど、それでもステップの1つだ)
- 問題が解決した場合はそれでよし、しかしそうでない場合は・・・
- コンパイルが上手く行ったのか疑う
- 自分の予想通りになっているかJavaScriptを確認する(スタートに戻る)
普通にJavaScriptを書いてデバッグする場合のワークフローはこうなる。
- JavaScript内の問題を見つける(自分が書いたコードの!)
- 修正する
- 問題が解決した場合はそれでよし、しかしそうでない場合はスタートに戻る
どう考えても自分で書いたJavaScriptをデバッグしたほうがいい。
シンタックスがみにくい
if (five && six && seven) doStuff();
doSomething() if five and six and seven人間は言葉よりもシンボルを認識しやすいものだ。
そのため、&&のほうが何を意味しているのか理解しやすい。
ワンライナーは魅力的だけど、恐ろしい
CoffeeScriptはロジカル文を書くよりも一文でコードを書くことを推奨している。
eat food for food in foods一見よさそうに見えるが、こんな風に書くとどうしようもなくなる。
wash plate, brush, sink for key, plate of dishes when plate.dirty if meal.status is 'done'意味不明だ。
CoffeeScriptは"bad parts"を持っている
一見美しい言語に見えるけども、すべてがそうではない。
これは奇妙に思える。
getUser = (id) -> url = "users/#{id}" dfd = $.ajax url: url format: 'json' method: 'get' url: url promise: dfd.promise()$.ajaxを代入しているのではなく実行しているのだと把握するのは難しい。それにもしCoffeeScriptを知らない人は最後の2行に困惑するだろう。
もっと自分の意図通りに書くとこうなる。
getUser = (id) -> url = "users/#{id}" dfd = $.ajax url: url format: 'json' method: 'post' return url: url promise: dfd.promise()しかしこれはCoffeeScriptがカバーしようとしている"bad parts"であるautomatic semi-colon insertion(ASI)でパースエラーになる。
return { foo: 'bar', baz: 'qux' } // not returned due to ASIファットアロウ(Fat arrow)はアンチパターンだ
ファットアロウはクールに見える。jQueryを使ったコード:
var widget = { attach: function () { this.el.bind('click', $.proxy(function (event) { doStuffWithThis(); }, this)); } };がこうなる。
widget = attach: -> el.bind 'click', (event) => doStuffWithThis()無名コールバック関数を連鎖させていくのはアンチパターンの1つで、恐ろしいホワイトスペース問題を引き起こす。ファットアロウは忘却へいたる道を指し示している。
しかし、CoffeeScriptでこう書くこともできる。widget = attach: -> el.bind 'click', $.proxy this, 'handler' handler: (event) -> doStuffWithThis()重要なホワイトスペース + スパゲッティ === 死
重要なホワイトスペースはとってもクールだ。
しかし、30行のぐちゃぐちゃにネストしている無名コールバック関数や終わらないjQueryのメソッドチェイン、いい加減な条件文(if else if if elseif else if unless else)みたいな酷いJavaScriptで書く人がCoffeeScriptを書いたらもっとひどい事に成るだろう。
CoffeeScriptは美しい、けれども使うな
CoffeeScriptを書いているときは楽しいけれど、いずれ悪夢に変わるだろう。理解すること、デバッグすることが難しくなるからだ。
結局これは我々の現状と同じなんだけれども。
上記記事に対する反論記事。
CoffeeScript is not a language worth learning(CoffeeScriptは学ぶに値する言語ではない) by Reg Braithwaite
A Case Against Using CoffeeScriptの中で、Ryan Florenceは下手くそなCoffeeScriptを書く人のプログラムはJavaScriptで書いた場合よりもひどくなると語っている。率直に言って彼は正しい。この世の中には恐ろしい出来の生成されたJavaScriptコードがある。スタージョンが言ったように「あらゆるものの90%はクズである」。しかし、90%のクズがクズである理由はCoffeeScriptにあるのだろうか?私はそうは思わない。
CoffeeScriptはJavaScriptだ
CoffeeScriptは学ぶに値する言語ではない。なぜならCoffeeScriptは言語ではないからだ。CoffeeScriptはJavaScriptだ。"CoffeeScriptで考える"のではなく、"JavaScriptで考える"のだ。
明らかに、CoffeeScriptは異なるシンタックスを持っている。しかし、それは極めて表面的なものだ。JavaScriptが英語だとしたら、CoffeeScriptはフランス語のような他の言語でも、ジャマイカン・パトワのような方言でもない。テクニカルなジャーゴンみたいなものだ。
CoffeeScriptはなプログラムを書く上での劇的な新しい方法(モナドのような)をもたらしてくれるものではない。すべての変換はローカルなものだ。CoffeeScriptのスニペットを見てもらえば、それが他の部分にはなんの影響もないJavaScriptに変換されることがわかってもらえるだろう。
this.render()の代わりに@render()を書くとしよう。これはただの短縮記法で、言語じゃない。こう書くと
if foo and @get('bar') doThis() doThat()こうなる。
if (foo && this.get('bar')) { doThis(); doThat(); }これのどこに躍起になるんだ?CoffeeScriptは配列内包表記や破壊的代入、罵られた"ファットアロウ"など沢山の器用な変換を行う。これらはすべてJavaScriptではできないけどCoffeeScriptだからできる"言語機能"じゃない。
私はこれらの機能を別の視点で見る。これらはJavaScriptのデザインパターンだ。CoffeeScriptを謎めいたJavaScriptにコンパイルするための言語ではなく、スタンダードなデザインパターンにそったコードを生成してくれるJavaScriptだと考えている。これがループを書く方法だ。これがデフォルト引数をとる関数を書く方法だ。これがthisを指す定値を持つ関数を書く方法だ。これがクラス志向OOを書く方法だ。これがsuper()を関数内で呼ぶ方法だ。
CoffeeScriptが生成するJavaScriptは一貫性を持ち、JavaScriptが持つ一般的な問題を標準的な方法で解決する方法を持っている。一番素晴らしいのは、CoffeeScriptは同じ問題を同じ方法で正確に解決してくれることだ。
想像してもらいたい。Javaを使う人々がJavaScriptを書いたらどうなるか。彼らはデザインパターンを利用するだろう。彼らは自動的にデザインパターンスケルトンを生成してくれるIDEを使うだろう。
class OneTimeWrapper constructor: (@what) -> K: (fn, args...) -> functionalize(fn)(@what, args...) @what T: (fn, args...) -> functionalize(fn)(@what, args...) chain: -> new MonadicWrapper(@what) value: -> @whatIDEは自動的にこれを拡張する。
OneTimeWrapper = (function() { function OneTimeWrapper(what) { this.what = what; } OneTimeWrapper.prototype.K = function() { var args, fn; fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; functionalize(fn).apply(null, [this.what].concat(__slice.call(args))); return this.what; }; OneTimeWrapper.prototype.T = function() { var args, fn; fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; return functionalize(fn).apply(null, [this.what].concat(__slice.call(args))); }; OneTimeWrapper.prototype.chain = function() { return new MonadicWrapper(this.what); }; OneTimeWrapper.prototype.value = function() { return this.what; }; return OneTimeWrapper; })();謎めいたところはない。これは私が書くべきJavaScriptだ。
I treat CoffeeScript as a big TextMate snippet that's always editable.―@topfunky
私にとっては、CoffeeScriptを書くことはJavaScriptを書くことだ。省略記法でスタンダードなデザインパターンを意味することを補助してくれるツールがあるだけだ。
CoffeeScriptは言語ではない。JavaScriptのためのコーディングスタンダードだ。
私見をまとめよう。"CoffeeScript"は新しいプログラミング言語ではない。CoffeeScriptはJavaScriptをスタンダードなデザインパターンで書くための省略形の集合だ。生成されるJavaScriptは高度に最適化されたスパゲッティではなく、JavaScriptのGood Partsだ。
CoffeeScriptは他人が書いたコードを読みやすくしてくれる。CoffeeScriptユーザーは同じ方法でループを書く。同じ方法でクラスを書く。私たちは同じパターンを使う。なぜならCoffeeScriptがそれらを生成するからだ。
これはPythonの重要なホワイトスペースと同じ事だ。それぞれが少しずつ違ったやり方でコードを書くことは損失だ。スタンダードなインデンテーションで、スタンダードなOOPで、スタンダードなループで書くことでJavaScriptは読みやすく、理解しやすく、保守しやすいものになる。
感想
僕は後者の記事を支持する。
Ryan Florenceのデバッグをする過程が面倒だという指摘はもっともだが、Reg Braithwaiteが言うようにCoffeeScriptでJavaScriptを書くことによってコードが平準化されてデバック自体はしやすく成るのではないかと思う。
それにCoffeeScriptはJavaScriptのGood Partsの集合体だという指摘もごもっともで、僕はCoffeeScriptを使うことでよりJavaScriptの理解が深まったと思う。
というわけで、CoffeeScriptを使おう!