タイミーでバックエンドのテックリードをしている新谷(@euglena1215)です。
タイミーのバックエンドはモノリスの Rails を中心に構成されています。そのモノリスな Rails に書かれていた YARD を rbs-inline に一通り移行した事例を紹介します。
前編では、rbs-inline の紹介と rbs-inline への移行理由について触れ、後編では実際の移行の流れや詰まったポイント、今後の展望について触れる予定です。
rbs-inline とは
まずは rbs-inline について簡単に紹介します。
rbs-inline とは Ruby コードにコメントの形式で RBS を記述することで、対応する RBS ファイルを自動生成してくれるツールです。
README にあるサンプルコードを引用するだけになってしまいますが、以下の Ruby コードに対して rbs-inline コマンドを実行すると
# rbs_inline: enabled class Person attr_reader :name #: String attr_reader :addresses #: Array[String] # @rbs name: String # @rbs addresses: Array[String] # @rbs return: void def initialize(name:, addresses:) @name = name @addresses = addresses end def to_s #: String "Person(name = #{name}, addresses = #{addresses.join(", ")})" end # @rbs &block: (String) -> void def each_address(&block) #:: void addresses.each(&block) end end
以下の RBS ファイルが生成されるようになっています。
class Person attr_reader name: String attr_reader addresses: Array[String] def initialize: (name: String, addresses: Array[String]) -> void def to_s: () -> String def each_address: () { (String) -> void } -> void end
サポートしている構文はこちらにまとまっています。
Syntax guide · soutaro/rbs-inline Wiki · GitHub
rbs-inline が作られた動機に関しては RubyKaigi 2024 での発表スライドを見てもらうのが一番かなと思います。
RBS 活用推進の背景
まずは rbs-inline の前段階である RBS 活用推進の背景について紹介します。
メルカリがメルカリハロをリリースし、リクルートもスポットワーク業界への参入を表明するなどタイミーを取り巻く環境は激化の一途をたどっています。まさに戦国時代です。競合サービスと切磋琢磨し勝ち抜いていくために我々は1段階ギアを上げた開発をしていく必要があります。
そして、開発速度を高める方法はいくつかありますが、その中でも実装のフィードバックサイクルの高速化は良いアイデアの1つだと考えています。
タイミーを含む一般的な Rails アプリケーションの開発では、実装の検証はテストコードを用いた自動テストもしくは開発・検証環境での手動テストがほとんどなのではないかと思います。手動テストに一定の時間がかかるのは当然として、自動テストもそこそこのサイズの Rails アプリケーションでは数秒かかることは少なくありません。
それが仮にエディタ上でリアルタイムに静的な型検査という形でフィードバックが返ってくるとなるとどうでしょうか。もちろん手動・自動テストほど詳細なロジックミスは検知できませんが、多くのミスはちょっとした NoMethodError
など型検査で気付けるものが大半です。
これまで数秒かけて気付いていたことにリアルタイムで気付けるようになれば、開発速度の改善には間違いなく寄与するはずです。
また、前提としてタイミーは元々 YARD コメントを書く文化があり、YARD コメントから RBS を生成する sord gem を使って RBS を補完用途で導入していました。詳しくは以下の資料をご覧ください。
移行理由
RBS を活用し、型検査をしていくぞ!というのは前述の通りです。
一方、sord gem を使うことで YARD から RBS の生成はできていました。それでも rbs-inline に移行した理由は以下の通りです。
1. YARD(sord) よりも rbs-inline の方が表現力が高い
YARD(sord) では interface や type などの表現ができません。rbs-inline では@rbs!
を使えば記述できます。
# RBS class X type name = String | Symbol def foo: () -> name end # Ruby with YARD class X # (String | Symbol) は表現できるが type を使った alias は表現できない # @return [String, Symbol] def foo = ['foo', :foo].sample end # Ruby with rbs-inline class X # @rbs! # type name = String | Symbol # @rbs () -> name def foo = ['foo', :foo].sample end
2. YARD は書いていたが yardoc は使っていなかった
これは社内の事情ですが、YARD をコードリーディングを手助けするドキュメンテーションツールとしてしか使っておらず yardoc を用いてドキュメントページの生成はしていませんでした。(正確には一時期生成していましたが、誰も見ていなかったので生成をストップしました。)
同様の YARD を使っているプロジェクトであっても、yardoc を活用しているプロジェクトは yardoc 相当の挙動を rbs-inline で実現するツールを自作するか、yardoc によるドキュメント生成を諦めるかの判断を迫られることになります。
3. rbs-inline が今後言語標準の機能になっていく
rbs-inline gem は今後廃止されて rbs gem に統合される予定です。
rbs gem は Ruby 標準の機能なので rbs-inline も Ruby 標準の機能になるはずです。なので、今のうちに乗り換えておいて損はないだろう、という魂胆です。
また、開発者が社内にいる soutaro さんというのも大きなポイントでした。
rbs-inline はまだ experimental なので仕様が不安定という側面はあるものの、逆に考えるとフィードバックをすれば受け入れてもらう可能性が高いということでもあります。標準機能になるのなら、社内にいる soutaro さんに今のうちにフィードバックしておくことで我々のユースケースで困りにくい形で仕様が確定するといいなと思っています。*1
前編では rbs-inline の紹介、移行の目的などを紹介しました。このまま肝心の「実際どうだったのか」もお伝えしたいところですが、長くなったので一旦ここで一区切り。後編では実際の移行の流れや詰まったポイント、今後の展望をまとめます。お楽しみに!
今回は RBS 活用によって開発速度を向上させる作戦を取りましたが、開発速度向上には色んな方法があると思っています。各社がどんなアプローチで取り組まれているのかはとても興味があります。カジュアル面談でお待ちしています!
追記:後編を公開しました。
*1:事実として我々のフィードバックによってバグ修正や新たな構文サポートが行われました。詳しくは後編で紹介します