(編集者より:この記事は、SiderのMediumに2018年6月28日に公開された英語記事を翻訳し、加筆したものです。)
Siderは、2018年8月よりRuboCopのスポンサーになりました。コードレビュー自動化サービスであるSiderを利用すると、さまざまなプログラミング言語や環境で、色々な種類の解析器をご利用いただけます。Siderではさまざまなオープンソースプロジェクトを活用しながらプロダクト開発を進めており、コミュニティへの貢献を大切にしています。このスポンサー活動を通して、RuboCopのさらなる発展に協力できれば幸いです。
今回はこれを記念して、RubyKaigi 2018にて実現した、RuboCopの生みの親である@bbatsovことBozhidar Batsov氏への独占インタビューの日本語版を公開いたします。SiderのCTOである松本宗太郎 (@soutaro) とRuboCopコミッターでありSirderの技術顧問のpocke (@pocke) の前で、Batsov氏はRuboCopやRubyスタイルガイドに関するご本人の考えを、とても気さくに語ってくれました。
RubyスタイルガイドからRuboCopができるまで
Soutaro:
最初に、RuboCopの開発に至るまでの動機について聞かせてください。RuboCopを通じて、どんな問題を解決しようと思われたんですか?
Bozhidar Batsov:
私は、Rubyで開発するようになる前はJavaで開発をしていました。Javaも色々な問題があるものの、Java用の良いLintツールが沢山あります。以前わたしの勤めていたのはコンサルティング会社で、様々なプロジェクトに取り組んでいました。チームメンバーはプロジェクト間を行ったり来たりする必要があり、全てのプロジェクトに対して統一されたコーディングスタイルを持つことが非常に重要でした。そうしないと、プロジェクトを異動した際に「あれ、このチームではこのやり方だけど、あっちのチームでは別のやり方だったぞ」というように、混乱してしまいます。
仕事でRubyを扱うようになって最初に取り組んだ問題は、統一されたコーディング規約を作ることでした。Javaではこういう問題はありませんでした。Sun公式のコーディング規約や、非公式だけど人気があるGoogleによる規約などがあり、皆がそのどちらかを利用していたからです。コードベースも一貫していたので、プロジェクトの移動もとても簡単でした。「タブとスペースどっちにする?どこに括弧つけるんだっけ?」みたいな話は滅多にしなくてよかったので、楽でしたね。
それから、ある企業でRubyの開発者として新しい仕事を始めたのですが、Rubyが分かるのは私一人だけでした。その会社は、ブルガリアで二番目にRubyを企業として利用した会社で、沢山のプロジェクトを作ろうとしていましたが、その頃はまだRubyは広く使われているプログラミング言語ではなく、開発者はそれぞれが違う言語のバックグラウンドを持っていました。10年以上前の話ですね。チームのメンバーは優秀でしたが、みんなPHPやPython、Javaの開発者でした。なので、コードレビューの半分は、Rubyの基本的なスタイルの問題の指摘でした。
そこで、まず私は社内用に、Matzの本やピッケル本などを参考にしてRubyのスタイルガイドを書きました。良いとされているものを全て盛り込みました。本によっては違うスタイルを推奨していたので、自分で判断をする必要もありました。いくつかの有名なプロジェクトのコードも参考にしました。Railsのコーディング規約は参考にしませんでした。かなり独特で、Rails開発者ですらRails以外ではそのスタイルを採用しないことがあるからです。
その後、Ruby Style Guideの最初のバージョンが出来上がり、公開を決めました。そうしたら人気が出て、フィードバックを色々もらうようになりました。あるチケットには「これ、すごく良いんだけど内容が多すぎて全部のルールを覚えられないから、PythonのPEP8みたいにスタイルガイド文書とそれを検査できる公式ツールがあったらいいのに。」というコメントがありました。そこで、やってみようと思ってRuboCopの開発を始めて、今に至ります。これが動機でした。
Soutaro:
スタイルガイドが長すぎて、自動でチェックできるツールが必要だったんですね。
Bozhidar Batsov:
そうです。RubyのコミュニティにはそうしたガイドやLinterがないことにびっくりしました。こういうことをやりたかった、というより、やらざるを得なかった。その頃にもスタイルガイドは不完全ながらいくつかありましたが、Linterは非常に原始的なものばかりでした。
Ruby 1.8から1.9への移行を覚えている人がどれくらいいるかわかりませんが、ほとんどのLintツールが構文解析にSeattle.rbのruby_parserを使っていました。そして、ruby_parserの1.9対応には時間がかかっていました。これが、初期のRuboCopがRipperを使っていた理由です。Ripperも大変でしたが、1.9で動くのは本当にそれしかなくて、ものすごく辛かったです。1.8から1.9への移行は、当時いくつかあったRubyのLintツールにとって逆風になったと私は考えています。
コミュニティがRuboCopを育てた
Soutaro:
今、RuboCopはとても人気ですよね。最も使われているRubyのLintツールの一つだと思うのですが、RuboCopが人気になったマイルストンはありましたか?
Bozhidar Batsov:
まあ、私がとてもチャーミングだから皆が信じてくれたのかな、と(笑)。冗談はさておき、(RuboCopができる以前はこういうツールが存在せず)皆が必要としていたので、最初にツールを作った人が人気になるような、言わば真空状態のような状況だったんだと思います。当時はここまではっきり思いませんでしたが、今はそう確信しています。沢山の人が全然機能がない初期のリリースを熱烈に歓迎してくれましたし、リリースの度に発生する破壊的な変更を受け入れてくれました。新しい機能を追加するたびに、誰かのビルドが壊れていました。おそらくご存知だと思うんですけど。とにかく、誰かが埋めなきゃいけない穴があったんです。そして、RuboCopがその穴を埋めた。もしそうでなくても、次に出てきた別のLintツールがその穴を埋めていたと思います。だから私たちが特別な何かを作ったとは思っていません。もし何か特別なことがあったとしたら、わたしがコミュニティからのフィードバックを積極的に受け入れたことだったのかなと思います。
私はRuboCopをそこまで柔軟なものにするつもりは全然ありませんでした。Rubyスタイルガイドが守られるような何かが欲しい、とは思っていました。皆さんが抱えているユースケースの多くは私が当初は想像しなかったものでした。プラガブルなアーキテクチャを設計したり、設定を継承できるようにすることなんて考えてもいませんでした。初期バージョンはスタイルガイドを守るためのものだったので、設定は存在しませんでした。
コミュニティと連携したことと、たくさんの人が助けてくれたことが、私たちの成功の秘伝のタレなんだと思います。一人で大きなことをやり遂げることはできません。RuboCopの成功に大きく貢献してくれた人は、少なくとも10名はいます。これは本当に重要なことです。チームが無くては、そしてコミュニティが無くては、どんなことでも絶対に成功できないからです。
bbatsov自身のRuboCopの設定とは?一貫性は個人の嗜好よりも大切
Soutaro:
ありがとうございます。次の質問に移ります。
ご自身のチームでもRuboCopを使ってらっしゃるんですよね?
Bozhidar Batsov:
もちろん。
Soutaro:
チームでのRuboCopの設定はどんな風にされてますか?デフォルト?
Bozhidar Batsov:
いや、デフォルトではないです。全然違います。
私はToptalのVP of engineeringなのでデフォルトを使うよう指示することもできますが、民主主義を重んじる上司として、チームの皆に投票をしてもらって決定を委ねています。ただ、色々な困難もありました。
うちの会社では、Ruby開発者が「スタイルガイド更新会議」なるものを6か月ごとに開催して、そこでオフィシャルのRubyとRailsのスタイルガイドからフォークする作業を行っています。チームが小さかった初めの頃は私も会議に参加していたんですが、議論がとても白熱していてたので、干渉しない方が良いなと思い直しました。この会議では、どのルールを見直したいかアジェンダを作って投票をするのですが、一部の開発者は熱が入るあまり偽のアカウントまで作ってチートしようとした人もいましたね。40名が参加した会議に、60票の合計票が集まってびっくりしたことがありました(笑)。
たくさんの方がRuby Style Guideが私個人の嗜好を表していると思っているようなのですが、これは事実とはかけ離れている、ということを胸を張ってお伝えします。私個人としては真逆の意見を持っていて、一方で大勢の方が良いアイデアだと考えていてRubyのコミュニティで広く使われているような、そういう項目もあります。私はそれで構いません。
このインタビューの前に、 SiderのMatz氏や松田明氏とのインタビュー記事を読んだのですが、Matz氏もRubyのスタイルガイドの中の多くの部分に同意してはいないと言っていたのが興味深いなと思いました。具体的にはどの部分なのか、ぜひ聞いてみたいですね。皆が喜ぶようなものは作るのは不可能だし、どこかで妥協をしなければいけない、と私は思います。最終的には、自分自身が何に同意するかよりも、何かに同意したら一貫してそれに従うことのほうがずっと重要なことでしょう。
一貫性というのはスタイルです。主観的には、良いスタイルとひどいスタイルがありますが、スタイルというものに意識を向けて、一貫した形の中で作業することの方がもっと大切なことだと思います。この考えに同意しない人がいることは知っています。「インタプリタがコードを受け付けるのであれば、もうそれで十分。コードの挙動だけ気にすれば良い」と考える人もいます。ですが、このことで誰かが行った仕事やオンボーディングプロセスなどが妨げられているのを日々見ていると、やっぱりそれは想像上の問題ではないことが明らかだと思います。これは実際に起こっている問題なのです。現状、Rubyの内部コードはぐちゃぐちゃなので、新しくコミッターになりたい人が貢献しにくくなっていると思います。「こっちのファイルみたいに書いたらいいのかな?それともこっちかな?どうしたらいいんだろう?」みたいに、結構な時間をかけて悩んでしまうと思うんですよね。 この定まらない部分を無くしていくことで、皆がやらなければいけないことに集中できて、魔法が起きるんじゃないでしょうか。
RuboCop 1.0 のロードマップ、そして新しいオーガニゼーションのゴールとは
ここで、同席していたSiderの技術顧問でありRuboCopコミッターであるPocke氏が、RuboCop 1.0についてBatsov氏について質問しました。
Pocke:
昨日(編集注:2018年5月31日)のRubyKaigiの発表で RuboCop 1.0について話してらっしゃいましたが、私は最後までお話を聞けなかったので、 RuboCop 1.0のロードマップについて詳しく聞かせてくれませんか?
Bozhidar Batsov:
ロードマップはまだ確定したわけではないのですが、RuboCop 1.0の一番大きなマイルストンは、Railsの機能を別のgemに抽出してrubocop-rails
と命名することになるでしょう。まだ考えているところですが、おそらくパフォーマンスの機能も、MRIのバージョンに強く依存するので、別のgemに抽出することになります。正確に問題を検出できれば価値のあることだと思うのですが、人々を誤解させるようであれば、価値あるものとは言えないでしょう。Performance copについては、適用されるMRIのバージョンを指定できるようにするつもりです。それぞれのCopがMRIのどのバージョンで有効で、または無効なのかを指定できるようにして、一目で理解できるようにする。つまり、RuboCopのスコープを減らし、よりモジュラーにする必要があります。
拡張機能を作りやすくするために、より良いAPIを作る必要があります。今はちゃんと定義されたAPIが存在しないのですが、これは大問題です。RuboCop拡張のgemを持っている人が皆「これは多分publicで、これはprivate」と推測しなければいけないような状況はまずいですね。なので、良いAPIを作って、コミットしメンテナンスしなくてはいけません。そうすれば、RuboCopがあんまり壊れなくなっていくのではないでしょうか。
もう一つの1.0に関する問題は、安定してアップデートができるようなメカニズムです。RubyKaigiでも話した私の簡単なアイディアは、それぞれのCopに対して有効・無効の他に、もう一つステータスを追加することです。ここでは仮に“new”と呼びましょう。新しいバージョンのRuboCopを使い始めたときに、ステータスが“new”のCopが存在する場合には警告を出します。つまり、「ステータスがnewのCopが10個あります」というメッセージが表示されます。。そして、有効にするか無効にするかを決めるとこれらの警告が消えるようにするのです。すごく単純な機能ですが、アップグレードの煩雑さをぐっと減らしてくれると思います。それから、breaking changeが何かという定義をする必要がありますね。例えば、あるCopのデフォルトを変更することはbreaking changeなのか、non-breaking changeなのか。おそらくこれはbreaking changeだとは思います。breaking changeはメジャーバージョンアップでしか起きないようにしないといけません。
もう一つ1.0に期待することには、node matcherエンジンをRuboCopから抽出しプラガブルにすることがあります。Stripeの人が、C++でマッチャーを実装するアイディアを教えてくれたんですが、むちゃくちゃ速くなりそうです。彼らはRuboCopをprofileして、node matcherをC++で実装したらRuboCopが10倍速くなると言っています。これが本当かどうかは検証する必要がありますけど、教えてくれた方達のバックグラウンドを考えても、多分本当だと思います。これは、そこまでではないけど、重要なことですね。
そして、今後の全体的な一貫性を保証するために、私たちは、既存のCopの名前や設定を慎重に見直さなければいけません。全体のコードベースを通して名前に一貫性をもたせて、後になって名前を変更して回るような必要が生じないようにしたいのです。
また、デフォルトの設定についても、実際に皆さんに役立つものであり、変更しなくて済むようにしていく必要があります。GitHubのBigQuery APIを使ってRubyプロジェクトを検索するとか、昨日(RubyKaigi 2018のトーク)でお見せしたGemを使うとか、そういうことをやろうと考えています。正直に言って、私はどういうデフォルト設定が良いのか判断ができないので、こんな方法でやるのがいいでしょう。今のデフォルト設定は私にとっては理にかなったものですが、明らかに私の判断は偏っています。
今お話したようなことが主要ポイントになるかと思います。巨大なロードマップを1.0には持たせたくはないですね。数ヶ月内に実際に達成できるような何かを提示したいです。現実的には、Railsとパフォーマンスの分離だけが厄介な点だと思います。それでも、rubocop-rspec
みたいな gemが既にあるわけで、似たようなことができるでしょうから、ものすごく面倒というわけではありません。
プロジェクトへの導入を補助するような機能があってもいいですね。初期設定時に、より賢い設定を生成するとか。これで、現行のものを置き換えられるかもしれません。
それから、マイナーなバージョン間で設定のmigrationが必要なくなるといいなと思っています。つまり、メジャーバージョンアップでのみ設定ファイルの修正が必要になるような形です。そのためには、より安定した開発サイクルにコミットすることになります。1.0の大事な部分はこんなところだと思います。
Pocke:
ありがとうございます。もう一つだけ質問があります。昨日、RuboCopのレポジトリーをbbatosv
からrubocop-hq
に移動しましたよね。このオーガニゼーション (rubocop-hq) は、rubocop-jp/issues
を移動するとか(編集中:2018年6月に移動済)、いくつかの目的があると思うのですが、オーガニゼーションの運用計画など、他にもあれば教えてください。
Bozhidar Batsov:
このオーガニゼーションのゴールは、コアのRuboCopに関連する活動全ての中心になることです。より大きなチームを私たちが作りあげていけたらと思っています。できれば、異なるプロジェクト間でより大きな同期ができるようにしたいですね。なぜかというと、現在RuboCopの拡張を書いてくれている人のことを、私はほとんど把握できていないからです。rubocop-rspec
の人のことは多少知っています。一番大きくて複雑な拡張だから。でもRuboCop用のRuby gemを検索したら189個もあって、本当にびっくりしました。PockeさんのMryやGryなどのgemも実際こうして見つけたんですけどね。RuboCopで一緒に仕事をしていても、これらのGemのことは知らなかった。古いgemの中にも、便利だけどメンテナンスされていないものがありますよね。
なので、情報の中心となるものがあれば素敵ですね。例えば、guard-rubocopのアップデートについて尋ねる人をよく見かけます。開発者であるYuji Nakayamaさんは忙しくて時間がないそうですが、でも彼がこのプロジェクトを移管してくれたら、rubocop-hqの中でメンテナンスしていけるかなと思います。私は皆さんにRuboCopはRubyコミュニティー全体のものだし、スタイルガイドも然り、ということを分かってもらいたいんです。これは既にわたしの個人的なプロジェクトではありませんし、もう長いこと、そういう状況だったと思います。これほど多くの方が使っていて、たくさんのことを行う必要があるならば、これはやはりコミュニティのものでしょう。
Siderの可能性
Soutaro:
弊社のサービスSiderをご存知だといいんですけど。Siderやコードレビューを行う他の同様のサービスが、RuboCopを使用する事でどうコードレビューを改善していけるか、Batsovさんの意見を聞かせてくれませんか?
Bozhidar Batsov:
この質問は「RuboCopがSiderなどのサービスに対して何ができるか」という意味でしょうか。Siderのようなサービスの価値は疑いようがないですが、エンドユーザーの役に立つにはその土台となるツールが必要です。
初めの頃は、重複した作業がたくさんありました。解析となると皆が自分のやり方でやっていて。でもここ数年は、RuboCopなどのツールやライブラリを内部で活用することに皆さんが賛成してくれるので、私は満足しています。つまり、サービスはエンドユーザーエクスペリエンスに注力するようになり、車輪を毎回再発明することをしなくなりました。
私の働く会社では、たくさんの同様のツールを使用しています。RuboCopに関しては、実はGitHubとの独自のインテグレーションが存在します。巨大なコードベースがあるので、我々の望むほどには簡単に移行ができないのです。実際、私たちが一緒に作業するようになるまで(※PockeがRuboCopコミッターになるまで)は、Siderについては知りませんでした。
ある意味では、RuboCopが必要な物を全て兼ね備えた良質なCIを提供できるようなところまで行くといいなと思っています。Siderにとっても、これをどうやって見せていくかというだけの問題になっていくでしょう。Siderのようなサービスに対して、RuboCopがプロファイルのサポートを提供していたら良いだろうなって考えていました。
デフォルトのスタイルが好きじゃなくてカスタムしたものを使いたければ、「GitHubのプロファイル」や「Stripeのプロファイル」を実行したい、というように設定するんです。これはそんなに手間がかかるものではない、数時間でできるんじゃないかな。エンドユーザーにとってすごくありがたいと思いますよ。設定の中にドロップダウンメニューがあって、魔法が起きる。たくさんの人に設定を色々いじる必要があって始めにくいとよく文句を言われますが、こちらがたくさんの設定を予め用意しておいてあげられたら、素晴らしいでしょうね。私は、これはSiderのようなサービスがやるべきことだと思っています。RuboCopではなくて。RuboCopはプロファイルの読み込みに対応する機能を提供し、各サービスがプロファイルを提供する。
それから、誰かが設定のヒエラルキーやオーバーライドを使用している場合、CIやUIがこれを表示してくれるようになると良いですね。いろんな人が違う.rubocop.yml
を別のフォルダに入れては忘れてしまうので、あのフォルダではこのオフェンスが検出されるのにこっちでは検出されない、なんてことが起きたりすることがよくあります。バグの報告を見て、「いくつ.rubocop.yml
があるんだろう?」なんてこともあります。
inclusion
やexclusions
も同じです。ユーザーが何かを間違えた際に、「このinclusion
で合っていますか?」なんて聞いてくれるUIを表示できたらすごく良いなと思います。exclusions
でも良いんですけど。最近、「RuboCopが.rb ファイルの処理を止めてしまった」というチケットがありました。これはまあ、file includesの中に*.rbがないからなんですね。RuboCopはこの警告をすべきなのかもしれませんが、ただの設定だし、ユーザーが設定を間違えただけなので、しないほうがいいでしょうね。
でも、Siderは、そうした部分を手助けできるのではないでしょうか。
わたしはそんな風に我々の関係を見ています。Siderのようなサービスには、ユーザーを喜ばせるために必要なものを教えてもらう。私たちがその基礎を一緒に作る。そして、Siderは、それを活用するための方法を共有し、Rubyの精神に乗っ取り、人々を喜ばせ、生産性を上げていく。
RuboCop 初心者へのアドバイス
Soutaro:
RuboCop ユーザーや、これからRuboCopを使おうとしている人たちに、コメントやアドバイスをお願いできますか?
Bozhidar Batsov:
マニュアルを読んでください (一同笑) 。私に寄せられる質問の多くや、バグレポートの多くはだいたいマニュアルの中に記載されているんですけど、誰も読んでないんです。
Copについての説明や、何が設定できるのかなど、もっと皆さんに読んでもらいたいですね。設定をいじれるCopがただ無効にされていて、使い方をわかってないんだろうな、という状況をしょっちゅう見かけます。様々なスタイルが設定できるCopなのに、単に無効にしておいて「これの一貫性には気を配らないのか」と言う。非常に問題だと思います。もし、個人や企業でRuboCopを使っていてCopを無効にする際には、.rubocop.yml
の中に説明を残しておいてもらえるとすごく助かります。私や他のRuboCop開発者は、GitHubを検索して何を改善すべきか洞察を得ているので。何も説明が残っていなければ、それが報告されていないバグなのか、何かよくわからないことがあって手助けが必要だったのか、私たちにはわかりません。ドキュメンテーションは完璧からは程遠いですが、そこまでひどくはないと思います。皆さんが少しだけ時間を投資してくださると、ものすごく助かります。
もう一つ、もっと多くの方に提案したいのは、「プロジェクトがそんなに大きくないのなら一気に直してしまう」ということです。警告を一度に全部修正してしまって、「この場合は警告だけどこっちの場合は出さない」というふうな複雑な設定をすることに時間をかけないほうが良いと思います。私の意見では、チーム自体にとっても、コードベースを一度にまとめて綺麗にするほうが良いと思います。コードが10万行以下のコードベースなら、みんなで集中すれば一日か二日で修正することができます。私たちToptalのようにたくさんのRubyプロジェクトとレガシーコードを残念ながら抱えている場合は、もう少し時間がかかると思います。でもそれですら、私たちも最後にはきれいに片付けることができました。違うコードスタイルをずっと切り込み続けることはできないと思います。いずれ、どこかで線を引いて「これはやめた」と言わなくてはならなくなるでしょう。私からのアドバイスは以上です。
Soutaro:
ありがとうございます。最後に、日本のユーザーに向けて、コメントをいただけないでしょうか。
Bozhidar Batsov:
そうですね。日本のユーザーのからの応援は、本当に嬉しいです。最も活発なコミッターの何人かは日本人ですし、もっとたくさんの日本のプロジェクトとか、オーガニゼーションなどに繁に参加してほしいです。日本はRubyコミュニティの中心ですし、RuboCopコミュニティの中心にだってなり得ると思います。
どんな形でもいいので手伝ってもらえることは本当に大歓迎です。日本ではシャイな人が多いようですが、私たち皆、気さくな人の集まりです。Genadi氏が言ったように、ブルガリア人はスラブの楽園に住んでいるので、ハッピーで、ウェルカムな人たちばかりですよ。
Soutaro:
あらためて、あたたかいコメントどうもありがとうございます。
Bozhidar Batsov:
嬉しいです。ありがとうございました。
インタビューのあとで、RuboCopコミッターでありRubyKaigiに登壇もしている伊藤 浩一氏も交え、夕食を一緒にとりました。おいしいお酒とともにRuboCopの話をできて、楽しい夜になりました。
Batsovさん、どうもありがとうございました。また日本でお会いできることをSiderメンバー一同楽しみにしております!