- RuboCop作者がRubyコードフォーマッタを比較してみた: 前編(翻訳)
- RuboCop作者がRubyコードフォーマッタを比較してみた: 中編(翻訳)-- 本記事
- RuboCop作者がRubyコードフォーマッタを比較してみた: 後編(翻訳)
RuboCop作者がRubyコードフォーマッタを比較してみた: 中編(翻訳)
エディタとの統合
人間は、最終的にフォーマッタと直接やりとりすることはほとんどありません。フォーマッタとのやりとりは、自分好みのエディタ1やIDEで行うのが普通でしょう。エディタと統合しやすいようにコードフォーマッタを設計することは重要です。
外部のコードフォーマッタを使う場合、エディタのインデントと外部ツールのインデントが食い違うと実に腹立たしい思いをします。もちろん、「Indent Line」のようなコマンドに一切意義を感じなくなるまで心頭滅却する手もなくはありませんが、それは勘弁願いたいものです。コードフォーマッタがエディタ向けのAPIを提供してくれればどんなによいことでしょう。それなら、フォーマッタからの情報を元にエディタが行やブロックやファイル全体をインデントできるようになります。現時点ではほとんどのフォーマッタは単純に「on the save」フックで統合していますが、もっといい方法はいろいろあります。もちろんそのためにはエディタ側も支援が必要でしょう。既存のRubyフォーマッタがエディタのインデントエンジンと特にうまく統合されているとは思えませんが、これは外部フォーマッタで一般によくある問題なのです。
もうひとつの便利な統合方法は、エディタで活用できる有用な情報をフォーマッタが提供することです。そうすることで、既存のコードでどんな問題が起きているかを知るのに役立つメッセージを、編集画面から切り替えることなく表示できるようになります。これを「エディタフィードバック」機能と呼ぶことにします。RuboCopでは、問題が起きたコードの位置や問題の説明といった情報には基本的にRuboCopのコンソール出力が用いられます。
RuboCopのoffenseレポートでは、まさにこうした情報が(さまざまな形式で)提供されます。
test.rb:4:5: W: Layout/EndAlignment: end at 4, 4 is not aligned with if at 2, 2.
end
^^^
上の情報によって、フォーマットツールがフラグを立てた位置や、修正の意図が明白になります。また、これはユーザーはフォーマッタからの忠告を却下して(設定ファイルなどで)振る舞いを変更する機会でもあります。もしフォーマッタがコードの再フォーマットのみをメインの機能に据え、エディタフィードバック機能をオプションとして脇役にとどめるのであれば、コードで有用なことを行うにはフォーマッタに全面的に依存せざるを得ません。これについては次のセクションで詳しく説明します。
RuboCopの設計は「エディタフィードバック」機能の提供を強く志向しています。この点については、まぎれもなくRuboCopの勝ちであると個人的に思っています。
なお公平性において抜かりないように申し添えておくと、保存時にファイルを再フォーマットする他のツールと比べてRuboCopは低速です(RuboCopが起動に時間がかかることが問題だと私は睨んでいます)。今後この問題を解決する糸口が見つかることを願っていますが、それまではrubocop-daemonがこの問題の解決に非常に役立ちます。
中休み:「1つのコードスタイルがすべてを統べる」
ここで私は、Rubyなどのプログラミング言語では「真のコードレイアウトスタイルは1種類だけ存在する」という一般的な前提に異を唱えたいと思います。Rubyは、その長い歴史において好みのスタイルでコードを書く自由を認めていましたし、構文も複雑で、あらゆるコードをかなりのレベルで美しくフォーマットする方法は多岐に渡ります。
かれこれ25年永らえているプログラミング言語で統一のフォーマッタとコードスタイルを採用することについては、(Matzが)トップダウンで推進しないかぎり成功はおぼつかないと私は考えます。フォーマッタツールの作者がどんな決定を下そうと、必ずや強力な反対意見を浴びせられます。誰しも時間をかけて自分好みのコードスタイルを確立しているのですから、スタイルを変更するには長い長い議論を経る必要があります。スタイルの変更はしんどい作業ですし、誰も引き受けようとは思わないでしょう。自分にとって必要でない変更であればなおさらです。
gofmt
はほぼ成功を収めました。その理由は、トップダウンで、かつ初期から導入され、Goユーザー全員が利用することを期待していたからです。この試みがRubyでうまくいかなかったのも無理はありません。他の言語でこの種のプロジェクトがどの程度成功しているかは私にもわかりません。いくつかの成功例を耳にしたことはありますが、頓挫した例もかなりあるようです。最近の私がClojure言語に入れ込んでいることをご存知の方もいらっしゃるかと思いますが、この言語はかなり若く、しかも構文が驚くほどシンプルであるにもかかわらず、未だに統一フォーマットが存在しません。
だからこそ、ツールが人気を得るには、一貫性の強要を「プロジェクト単位」とし、プロジェクトの現在のスタイルからかけ離れないようにコードを書けるようにすることが唯一の道であると私は考えます。
もちろん、コーディングスタイルを統一することのメリットや、フォーマットツールを設定オプションなしで使えるメリットが一般的にあることは私も認めます。しかしそれがRubyの現実に即しているかについては疑問です。
私が随分前にRuboCopを作り始めたときは、カスタム設定をできないようにしていた(コミュニティRubyスタイルガイドをひたすら強制適用していました)のですが、この仕様は非難轟々でした。当時のRubyコミュニティは約17年目を迎えていて、さまざまなフォーマットパターンが存在しており、全世界的にコードの一貫性を保つことを気にかけている人はほとんどいませんでした。皆が気にしているのは「自分たちの好みのスタイルに染め上げられた」「自分たちのプロジェクトでの一貫性」であることがやっと私にもわかってきました。RuboCopをカスタム設定可能にしたことで、Rubyコミュニティで広く受け入れられるようになったのです。スタイル部門で完全な合意を得られたためしは一度もありませんでしたが、合意を得られたものもあります。それがよかったのは明らかです。
フォーマットの統一でもうひとつ悩ましいのは、うまくはまるスタイルが複数あり、どれかひとつだけに絞り込む切実な理由がない場合です。以下は私が好んで引き合いに出す例です。
def send_mail(source)
Mailer.deliver(to: 'bob@example.com',
from: 'us@example.com',
subject: 'Important message',
body: source.text)
end
def send_mail(source)
Mailer.deliver(
to: 'bob@example.com',
from: 'us@example.com',
subject: 'Important message',
body: source.text
)
end
私は上の2つのスタイルを、そのときの気分や書いているコードのニュアンス(コードの長さなど)に応じて使い分けています。どちらか1つにはっきり決めようと思えばもちろん可能なのですが、そうするだけの価値をあまり見い出せません。RuboCopのスタイルチェックの多くで、強制を少々緩めに設定しているのはそうした理由によるものです。
これまでの私は全般的に、将来はRuboCop linterで複数のスタイルを同時に許容しつつ、オートコレクションではシンプルにどれかひとつを優先すべきだろうと考えています。
一部の人々は今もスタイルを完全に揃えることを目指していて、いつかは成功するかもしれませんが、今の私は些細なことで果てしない議論が続くことに疲れてしまったので、そうした活動に参加する気になれません。Rubyの魅力やマジックの多くは、あらゆることに多数の選択肢が与えられている点に由来しており、自分が幸せになれる選択肢をそこから自由に選べるからこそRubyの魅力やマジックがある、という事実を今の私は受け入れていると考えています。おそらくMatzの考える「幸せのための最適化」とはそのことを指しているのでしょう。
関連記事
- Emacs! ↩
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。記事が長いので3分割いたしました。参考までに、元記事の後にtestdouble社のstandard(standardrb)というgemも登場しています↓。