jshint のポリシーには共感出来ない

あんまり人が集まらないでくれることを祈る。

こういう見出しってどうも経験的に危険な気がする。ともあれリラックスして読んでくれ。

ちょっとずつ本格的に使おうと、ドキュメントもちゃんと読み出したんだけれど。というかね、「Functions declared within loops referencing an outer scoped variable may lead to confusing semantics.」の意味がさっぱり取れず、何言ってんだろうか、とドキュメントを読もうとしたんだけれども。

その本題よりも前に、なんだかやたらに「Warning This option has been deprecated and will be removed in the next major release of JSHint. JSHint is limiting its scope to issues of code correctness.」が大量に目に飛び込んでくるわけだよ。

あー…、なんか lint の本来の役目を忘れてるな? このチーム。

そもそも lint はその名の示す通り「落ち穂拾い」。元となった C 言語のための lint は、「コンパイラがやってくれない「落とし穴回避のための示唆」」「書き手のみならず読み手にとってもためになるベストプラクティス」をこそ示唆するために生まれたものである。つまり、「正しく動くつもりになったつもりになってればそれでいい」という考え方をプログラマにさせないように、つまり、たとえば Python で言うところの「readability count」も一緒にアドバイスしてくれるもの、だったはずだ。

つまり、「lint 系ツール」というのは、「動けばそれでいっしょ」「オレがわかればいっしょ」という個人主義を排除するために使うもんなんではねーの?

実際 java の checkstyle 経験者も良くわかると思うけれど、とかくこの手のチェックツールは「うるさい」。大きなお世話だ、と思うものが多い。そして結構なものが「動けばいいコードにとっては全く価値のない」ものが多い。

たとえば「一行 80 カラムルール」なんてのがその最たるもんだ。けど、どのルールも「ちゃんと理由があって」それが推奨されている、ということについては、これは「ちょっとだけいつもと違う開発環境で作ってみる」ということをするだけで大抵すぐにわかる。(80文字ルールの件なら、たとえば emacs なら ctrl-x 2 とか ctrl-x 3 でバッファを分割するとか。) すなわち、そういうものというのは、「とても多くの人たちが経験してきた困ったこと」に備えるように考えられてる。(これみたく「なんも考えてへんやろ」てのもたまにはあるが。) そう、得てしてこの手の標準は「不毛な宗教論争から開発者を守る」という思いを持って作られていて、そうした「小さな精神疲労が積み重なることによる品質低下」を防ごうとしてきたわけである。

だから「うるさい」と思っても守る、というのがこの手の lint 系ツールだし、期待するのは「プログラムの正しさとは関係のないところに煩わされたくない」ということでもあるのだ。

しかるに「is limiting its scope to issues of code correctness.」は。わかってねぇなぁ、てことなんじゃねーの? (どうやら元にした jslint から機能を削除していこう、てことらしい。)

まぁ…、好意的に言えばさ、「lint と言う名前を付けていないのだから」というエクスキューズは認めてあげてもいいさ。そうね、lint じゃないんでしょ。ならいいのかもね。


と、ここまで書いてから、「そういうことがしたいなら JSCS を使えや」と誘導されるので行ってみると、「JSCS has merged with ESLint!」。

ほらみろ。そういうことだよ。「スタイルは別問題」という考え方は、本当に品質を考えたい人にとっては、滅茶苦茶迷惑なんだってば。

実際どうなんだろうなぁ、「全てが ESLint でまかなえそう」なら、もうワタシは ESLint 一択にしたいところなんだけれど、まだ個々の個性が見極められてないからなぁ。将来性まで見込めば JSHint がないのは明白でも、「今使えない」てことではないんだもん。うーん、保留。


12:50追記:
「square bracket より dot notation の方がいいんだぜっ」に共感出来ない、の件と、今回の件で、ほぼ「JSHint を日常的に使うのはやめた」とほぼ心に決めたのだが、こんなコード:

1     if (!isNaN(dt.getFullYear())) {
2         return dt.getFullYear()
3             + "-" + _pad(dt.getMonth() + 1)
4             + "-" + _pad(dt.getDate())
5             + "Z" + _pad(-dt.getTimezoneOffset() / 60, true);
6     }

これを JSHint だけが:

1 Misleading line break before '+'; 
2     readers may interpret this as an expression boundary.

という指摘をするのね。こうしろと言ってるてこと:

1     if (!isNaN(dt.getFullYear())) {
2         return dt.getFullYear() +
3             "-" + _pad(dt.getMonth() + 1) +
4             "-" + _pad(dt.getDate()) +
5             "Z" + _pad(-dt.getTimezoneOffset() / 60, true);
6     }

えっと Python の PEP8 も同じトピックで推奨があったはずだがどっちだったっけか? なんか JSHint と同じだったような気がする。だとしたらなんだ、ワタシは無頓着に PEP8 違反コード書いてたか?

と思ったが、いや、Python の場合そもそも性質が違うね。+ が暗黙の継続行をトリガーしない、ので行継続のバックスラッシュ必要、そしてプラスで終わらないつもりなら迷うことなくどっちみちバックスラッシュ不可欠、という、プラスで終わる終わらないでそもそも違う。(ツールの方の pep8 かけてみたらどっちでも警告されなかった。そうでしたっけか?)

まぁ今は Python の話は置いといて、この JSHint の指摘は有用だったりもするかしら、とも思う。ので…、「TDD サイクルに完全に乗っけて使うのは避けたいが、たまに JSHint する」という使い方がいいのかなぁ、と今のところ思っている。


2022-02-24追記:
このネタの書き出しが「あんまり人が集まらないでくれることを祈る」であったにも関わらず、案の定このネタにはワタシのサイトの中では人を集めてしまっている。気分は良くない、こういうちょっと攻撃的なものばかりが読まれてしまうのは。もっと読んで欲しいもん、いっぱい書いてるつもりなんだけどね。

自分がそうだったからわかるんだけど、攻撃的な論述ってね、わかりやすくて熱中しやすいわけよ、それが共感なのか反感なのかによらず。ワタシがあなたの思考を深めるキッカケとなるのであればまぁ喜ばしいことなのかもしれんけれど、そうとも限らないわけよ、ワタシの経験則から言えば。特に、読者側の知識レベルが話者側よりも少し低い場合の「鵜呑み」が非常にオソロシイ。実際ワタシ自身、何度それをやったかわからない。「ワタシより上級者がそう言ってた」は、浅い知識のまま受け取ると危険で、これはただの強迫観念になる。そしてこういう宗教的とか思想的なネタばかりに喰い付かれていることこそが、その徴候なのではないのかと、そうワタシはどうしても感じてしまうのだ。だからさ…、こんなんばっか読んでないで、ワタシのサイトにはもちっとアホでオモロイことも書いてるつもりだからさ、そっちも読んでってよ。

みないな話は前置きね。

4年前に書いたネタに追記したくなったキッカケは、ここ何ヶ月かの Go 言語遊びからの派生。ちょっとこの言語には思うところが多くてな。

Python と Perl/Ruby が大きく思想を違えているのは、「コードは書いている時間よりも読んでいる時間のほうが長い」ということを重視しているかどうか、だ。Ruby はまぁ一応「汚く短く書くほどクレバーで美しい」というほどには尖ってはいなけれど、それでもやっぱり「極限までコードを記述する時間を短縮する」ことに命をかけがちであることは Perl と思想を同じくしていて、保守の時間は Python ほどには重視されない。いや…、ちょっと説明が難しいな…、言語そのものとライブラリ管理の指針として組み込みかどうかの話よ、関わる人々がどう考えるかではなくて。Ruby はまつもと氏自身が何度も強調するように「書いていて気分が良い」ことこそが至福の命題である。対して Python は「Zen Of Python」が思想であるし、設計の根幹には常にこれがある。

さて。「スタイルチェッカー」が「何を節約するのか」について、もう一度考えてみよう。もしも「コードは書いている時間よりも読んでいる時間のほうが長い」に納得できるのであれば話は簡単である。保守の時間を節約する、それだけだ。そうでないなら、コードを書いている時間の「30秒の節約」が、将来の誰かの半年の浪費になりうるという想像を巡らして欲しい。ちょっとアホっぽいがありうる物語を考えてみた:

  1. libhoge に依存したプログラム fubar を書いていた。
  2. めでたく libhoge とは独立したプログラムにすることが出来た。
  3. libhoge 機能は一切呼び出していないが、時短のために、makefile の保守はさぼろう。
  4. …3年後、libhoge は github レポジトリからも消え、世界から消滅した。
  5. fubar 保守担当を引き継いだあなたは、「hoge がない」ことから決断を迫られる。
  6. 「fubar の代替品を作ろう」とか。
  7. 「fubar の代替品を作ろう」などの決断のために、技術調査や設計などに半年費やす。
  8. そして半年後「実は libhoge はまったく使っていない」ことに気付いて愕然とする。

馬鹿げている、こんなのありえない、と思うかどうかは、どの程度の規模のプロジェクトの経験があるか次第。関係者が数百人超のプロジェクトへの参加経験者であれば、これがどの程度ありうる話なのかは、わかるんじゃないかな? そしてスタイルチェッカーだとこれは「unused import」みたいなチェックで防げる、ということね。

さて「Go 言語」の話だ。これがまぁ、まつもとの言うところの「気分が良い」とは正反対で面白いんだわ。以下の Go コード、「ビルドに失敗する」:

1 package main
2 
3 import (
4     "fmt"
5 )
6 func main() {
7     //fmt.Println("debug...")
8 }

なぜビルド出来ないかと言えば、「fmt をインポートしているにも関わらず使っていないから」。警告じゃないんだよ、エラーなの、ビルド成功しないの。

これは「プログラムを書く」際はかなり苦痛だ。まつもと氏あたりは相当嫌う特徴なのではないかと思うが、彼でなくてもワタシですら、結構鬱陶しいと感じる。コメントアウトして一時的にコードの一部を取り除きたいときに、常に import も意識しなければならないので、コンパイル・ランのリズムはかなり悪くなる。

けれども、それでいいのだ、これでいいのだ。これは標準ライブラリでないものを使う場合に特に顕著。:

1 package main
2 
3 import (
4     "fmt"
5     "github.com/dsnet/compress/bzip2"
6 )
7 func main() {
8     //fmt.Println("debug...")
9 }

これをビルドするには「github.com/dsnet/compress/bzip2をお取り寄せる」手続きが別途必要なので、最初に書くあなた以外の、このコードを受け取った誰かにとって、その手続が必要かどうかは、その作業者の手間の差にもなる。まして、使ってもいないものに対してかけた時間だと知ったその作業者の怒りは、筆舌に尽くしがたい…、かもしれない。

「エキスパートCプログラミング」って本があってな。かなり古い本なんだけれど、そこで lint と C コンパイラの関係について論じていて、「lint がやっていることのほとんどは、本来は C コンパイラが担うべきである」と言っているのよ。それはまさしくその通りで、コード記述者を甘やかすことが、結果的に将来の誰かの膨大な時間の浪費に繋がりうる、ということを言ってるわけ。それをね、Go は言語処理系そのもの(今の場合はコンパイラ)で防ごうとしている、てことだよ。

繰り返すけれど、「JSHint is limiting its scope to issues of code correctness.」というポリシーは誰のためにもならない。最初に書いた時は「個人主義の排除」を中心に論じたけれど、「いつの誰の何に向けての何なのか」について思いを馳せよ、てこと。それを…、Go で遊んでて思った、てハナシでしたとさ。