Rubyの静的コードアナライザーモジュール「RoboCop」

koic氏:こんばんは。今回は「Breaking Change」というタイトルで発表します。Twitterは「@koic」というハンドルネームでやっています。永和システムマネジメントという会社から来ました。

(Rubyの静的コードアナライザーモジュールの)RuboCopは、今日も含めて何度か話に出ていると思うのですが、私はそこのコミッターをしていたり、RailsのActive RecordのOracleを使っている部分のコミッターをしていたり、そして先ほど紹介した永和システムマネジメントで働いていたり、そのようなことをやっています。

オープンソースでは、だいたい毎日なにかやっています。jQuery開発者のJohn Resigは「Write Code Every Day」と言っていましたが、僕は最近、毎日コードを書いているわけではありません。というのは、レビューがとても多くてPull Requestで時間がかかったり、マージのコミットもけっこう入ったりしているからです。それでもなるべく毎日なんらかのかたちでコードに関わろうと心懸けています。

僕が一応リリース権を持っているgemは1億6,800万ぐらいです。日本人人口が1億2,000なんぼだから、1人1個使っていたら一応日本人全員使っているはず……なわけないですけど、そんなようなことをやっています。

(会場笑)

会社はOSSを大きく支援

先ほど話したRuboCopモジュールですが、ドリコムさんのブースによれば、今年(2019年)のRubyKaigiで一応4位。BundlerがRuby標準に組み込まれたので、繰り上がると3番目に入ります。このモジュールを僕が今のところ一番gemのメンテナンスをやっているわけですね。

会社もこうしたOSS活動を支援しています。コミュニティーには顧問の松田明さんとかもいて、この日本にいるRailsコミッター全員(だと思う)がわりと参加していて、(多いほうが)いろいろと捗ると思うので、よければご参加ください。

といったところで、RailsとかOSSとかそういったところを通じて成長したいプログラマー・エンジニアのみなさま、あとは(スライドを見れば)わかると思うので、このスライド1枚で終わりです。

(会場笑)

2016年に一緒に飲んでいた人たちと、今日この場で

平成Ruby会議なんですけど、今回オープニングのキーノートで一緒にしゃべっていた金子さんとは、ちょうどRubyKaigi 2016の京都の前日に一緒に飲んでいました。そのころから金子さんは、けっこうRailsにパッチとかを送っていて、すごいと思っていました。そんな人と、3年経ったこのイベントでキーノートというかたちで一緒に参加できて、非常に光栄です。ありがとうございます。

では「Breaking Change」の内容に入ります。今回の話で得られることは、テストやbundle updateの重要性の再認識という基本的なところです。僕はRuboCopのちょっと斜め上の使い方をしているので、そのあたりも話します。

あと、先ほどの武者さんの話でもあったのですが、OSSの世界、僕が見ているOSSの世界観とか、そのあたりも、もしかしたら伝えられるかもしれません。そして破壊的変更に対してあなたができるかもしれないようなことについてもお話しします。

アジェンダとしては、スライドのような流れで話そうと思っています。

破壊的変更で起きること

まずは「破壊的変更とはなにか?」といったところからはじめますね。Ruby開発のbugsのところでディスカッションされている「キーワード引数を通常の引数から分離」を例にしますが、ruby-lang.orgでも、このあたりのmameさんの記事でも書かれています。Ruby 2.7では図のように非推奨の動きになって、警告で終わるのですが、バージョン3ではそのあたりを正式にしたい気持ちがある、計画があるというお話です。

このあたりについて、とくにオープンソースを中心に僕はやっているわけですが、いろいろと「このケースもか」というところもありまして。

このスライドを見てください。これはあんまりやったことある人がいるかわからないんですが、Rubyって、すでに今日何度か話があがっていると思うんですけど、難しいんですよ。これオープンソースで見るまで、こんな書き方あるって、僕は知りませんでした。

これは何かというと、この「ary =」の配列のところはブレースが省略されたハッシュなんですね。要素1個のハッシュ。このeachのdoブロックにある「foo:, bar:」の部分です。これでキーを引っ張った値がこのformatに入るんですけど、こんな書き方知らなくて……。でもこういうのがあるんですよ。

でもこう書くと警告が出るんですね。ハッシュのところで。このformatはRuby標準で、キーワード引数を期待していて、噛み合わないかららしく。このへんについてはアプリケーション側が書き直せばいいんですけれども……。こうしたところが、今後ちょっと書き直していかない箇所だと思っています。

あとは最近だと、正規表現のmatchにnilを渡すとfalseになるところ。これって今の安定版の2.6だと問題ないんですけど。このあたりはShugoさんの話にもあったんですが、2.7.0のpreview2だとエラーになってしまうんです。nilって書いてるのですが、これが変数で渡されたときにいきなり落ちるんです。preview3だとエラーではなく警告になったんですが、「いや、でも将来的に変わっても、ちょっとな」というところで、先ほどのパッチ会でもRailsコミッターを含めていろいろ話題になっていました。

そこでちょっと「nil避けを書かないといけないとかマジか」とツイートしたところ、@kamipoというRailsコミッターの人が引用してくれて。

「2.4で、高速化のために登場したものに書き換えることを推奨したのに、それがまた2.7で覆ることになった」って返信してくれて、「えー」みたいな話だったんですけど、今度はそれをRubyコミッターの成瀬さんと松田さんが拾って、プッシュしたりトス上げたりとかしてくれました。

そういう経緯があり、これが開発者のまつもとさんに届いて「戻しましょう」となって。声を挙げるとそうしたこともたまにはあるんです。

破壊的変更とは、以前と異なる結果を引き起こしうる変更のこと

というわけで今紹介した破壊的変更に関してですが、結局、破壊的変更とは何かというと、例えばbundle updateしたあとや、Rubyのバージョンを上げたあとといった、何かによってミドルウェアを上げたあとに、以前と異なる結果を引き起こしうるような変更のことです。

破壊的変更には、例えば公開APIの名前が変わるとか、公開APIの引数が変わるとか、公開APIの戻り値が変わるとか、公開APIの振る舞いが変わるとかがあります。「振る舞いが変わる」というのはまた難しいんですが、バグfixは本来期待してた動きに直すものなので、そこは破壊的変更とは扱いが変わるんでちょっと違います。

公開APIと非公開API

こうした公開APIのなにかしらが変化することが破壊的変更になるわけです。ここで「公開API」と「非公開API」についてもう1回考えてみましょう。

公開APIとは何かというと、Martin Fowlerという人が、公布済みインターフェースについて、パブリック/プライベートよりもパブリッシュとかパブリックかのほうがより重要という話をしていて。2003年だから、すごいですね、平成の何年? 16年前? マジか、という感じなんですけど。

いわゆる言語として、パブリックで外から呼び出しができるより、公開されてほかのユーザーから使えるパブリッシュなAPIであるほうが、より重要だよといったところがあって。オブジェクト指向のプログラミング設計でも、パブリックなAPIは安定しているわけですよ。その安定したAPIを使っていきましょうというところです。

ただ、安定なものがよくて不安定が悪いというわけではありません。ソフトウェアは変化し続けるので不安定な部分はどうしても残ります。そうしたものに依存するのではなく、安定したものに依存していきましょうということです。

非公開APIの利用に注意

RubyやRailsで言うと、内部的なAPIが明示されていないAPIは使わないことです。Railsだとメソッドコメントがあるdocが公開APIなので、書かれていないやつは使わない。そういうことです。

非公開APIって、図のような世界なんですよ。大いなる力には、大いなる責任が伴うんです。

非公開APIに依存することでなにが起きるかというと、例えばYARDというドキュメントの生成モジュールがあるんですが、ここで最近ちょっと事件が起きました。RubyのirbにあるslexというAPIがゴロっと削除されて、これに対してrequireしているため、それがなくてエラーになるというもの。

これについては、irbをメンテしてくれているAsakusa.rbの糸柳さんに相談したんですが、そこにコメントを残していて、内部APIはやめましょうという話になりました。もともと内部APIですと明示されているので、それは依存しているほうが責任をもって修正しなければいけないという話ですとか。

Railsですと、このへんの内部APIって、メソッドコメントが記されてない非公開APIなんですよ。kamipoさんから、この非公開APIをちょっと削除してという要望があって、そのへんのところをウォッチして手元のローカルリポジトリで、軽くgrepしたんですよ。

すると、いくつかヒットしました。自分の会社のオープンソースのやつがまず引っかかって、そこにパッチを出しました。あと、会場にいる松田さんのdatabase_rewinderもヒットしていました。

内部APIは、ライブラリ提供者が実装に応じて変更しているので、いきなりAPIがなくなるとNoMethodErrorが起きて死んだりします。例えばRailsをアップデートしたときに、非対応の何かに依存しているgemだと、エラーが起きたりします。

いきなりエラーではなく、まずは非推奨警告から

これがですね、Railsのこの図のような場合ーー今回はプライベートAPIなので少し特殊な例ですがーーラファエルというコミッターが「“バックリンクのここを変更したことで、ウチのがちょっと破壊された”というようなレポートがいくつか上がっているので、まず非推奨警告を出しませんか?」といった話です。

こうした流れで、このへんの非推奨警告を追加したパッチをkamipoさんが出してくれました。非推奨警告が入ると、エラーというよりは、警告のところに「こういう警告が出るようになるから直しましょう」みたいな説明が出て、か結局他にも影響するんです。内部APIは、使わないのがベストですが、使うのであれば、なるべく必要最小限にということを考えましょう。

公開APIでは警告をまず出すようにしていますから、アプリケーション開発者としては、そのへんのところをこまめにbundle updateして、テスト実行して、警告が出たら潰していくようにします。もう警告とか消されたあとで変更された世界にいきなりいくと、ハイジャンプとなり、けっこう面倒くさかったりするので、こまめにbundle updateしてテストを実行することをやっていくといいと思います。普通の話ではあるのですが……。

このあたりが破壊的変更の話です。だいたいどんなものかイメージができてるといいなと思います。

公開APIでも破壊的変更は起き得る

それなら公開APIを使えば問題は起きないかというと、そうではありません。ソフトウェア開発って、メチャクチャ難しいんですよ。人類にはまだ早いぐらい難しいです。

(会場笑)

まぁ、それでもがんばってやっていくしかありません。

最初からよい設計できればいいですが、もっと使いやすい設計が出てきたとき、あとからでもそう変えられたらいいなと、葛藤します。その葛藤の末に破壊されたりしたりすることがあります。実際そうした破壊事例として、Faker 2というgemがあります。

Faker 2は、こうしたダミーデータを作るgemです。ある日、Railsアプリケーションをbundle updateしてテストを実行したら、突然エラーが起きました。何が起きたかというと、Faker 1のころは、こういうふうに引数を渡したらなにかデータが返ってきていました。しかしFaker 2では、実行したらいきなりエラーになる。

インターフェースがキーワード引数を使うように変わっていたのが理由なのですが、それに辿りつくまでに、最新のものと現在のものの差分を見て確認しました。僕の場合は、bundle updateする際に、gemdiffというツールを使っています。GitHubソースコードの差分のURLが表示されるので、それを見て確認しました。その中のCHANGELOGというところを最初に見ると全体像がつかめるので、そこらへんを確認したわけです。

そのときのものがこれです。見たことがある人もいると思うんですが、ものすごく変更があった。

このFaker 2でImportant Noteが書かれてるAPI全部が変わったんですけど、これが全部じゃなくて、全部というのがこれなんですよ。

(会場笑)

こんななかで、新しいキーワードが何になるかとか、ただのエラーだと何にもわからなくて、いや、もう……125+引数分あるわけですよ。

ちょっとこれ、自分が踏んで非常に不便だと思いました。さっき武者さんも話されてましたけど、たぶんこの部分ってほかの人にとっても面倒かもしれなくて、実際そうなんですよ。

僕が面倒だと思うことは、きっとみんなも面倒なこと

というわけで、自分が踏んで面倒なところはほかの人にとっても面倒かもしれないですし、それを直せもするんです。だいたいgemは、Rubyで書かれているんですよ。パッチを送る場合は、オープンソースだと、ライセンスがやはり重要なんですが、パッチを送るのに支障のあるライセンスに、今のところ出会ったことがありません。まぁつまり、直せるんですよ。

解決策として考えたことは、アップグレードの実行でいきなりエラーにするのではなく、どういったインターフェースに変わるのかという警告を出すというもの。

これがそのPull Requestなんですが、これも……。Pull Requestって、とても説明が重要なんです。レビュアーとしても、やり取りを何度もせずに、即Mergeボタンを押したいわけですよ。Mergeボタンを押すには、実装もそうなんですが、なぜそれが必要かというところのMergeボタンをサッと押せるような説明を一応書いておくのが、非常に重要です。

そうしないと、興味をもたれなくなると、わりともうPull Requestをずっと放置されてMergeされなくなるので、そこは初手が重要だと思います。ここらへんのところはなんか……。僕は、たぶんこれからいい話をします。

(会場笑)