Timee Product Team Blog

タイミー開発者ブログ

前編:YARD から rbs-inline に移行しました

タイミーでバックエンドのテックリードをしている新谷(@euglena1215)です。

タイミーのバックエンドはモノリスの Rails を中心に構成されています。そのモノリスな Rails に書かれていた YARD を rbs-inline に一通り移行した事例を紹介します。
前編では、rbs-inline の紹介と rbs-inline への移行理由について触れ、後編では実際の移行の流れや詰まったポイント、今後の展望について触れる予定です。

rbs-inline とは

まずは rbs-inline について簡単に紹介します。

rbs-inline とは Ruby コードにコメントの形式で RBS を記述することで、対応する RBS ファイルを自動生成してくれるツールです。

github.com

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 での発表スライドを見てもらうのが一番かなと思います。

speakerdeck.com

RBS 活用推進の背景

まずは rbs-inline の前段階である RBS 活用推進の背景について紹介します。

メルカリがメルカリハロをリリースし、リクルートもスポットワーク業界への参入を表明するなどタイミーを取り巻く環境は激化の一途をたどっています。まさに戦国時代です。競合サービスと切磋琢磨し勝ち抜いていくために我々は1段階ギアを上げた開発をしていく必要があります。

そして、開発速度を高める方法はいくつかありますが、その中でも実装のフィードバックサイクルの高速化は良いアイデアの1つだと考えています。

タイミーを含む一般的な Rails アプリケーションの開発では、実装の検証はテストコードを用いた自動テストもしくは開発・検証環境での手動テストがほとんどなのではないかと思います。手動テストに一定の時間がかかるのは当然として、自動テストもそこそこのサイズの Rails アプリケーションでは数秒かかることは少なくありません。

それが仮にエディタ上でリアルタイムに静的な型検査という形でフィードバックが返ってくるとなるとどうでしょうか。もちろん手動・自動テストほど詳細なロジックミスは検知できませんが、多くのミスはちょっとした NoMethodError など型検査で気付けるものが大半です。

これまで数秒かけて気付いていたことにリアルタイムで気付けるようになれば、開発速度の改善には間違いなく寄与するはずです。

また、前提としてタイミーは元々 YARD コメントを書く文化があり、YARD コメントから RBS を生成する sord gem を使って RBS を補完用途で導入していました。詳しくは以下の資料をご覧ください。

tech.timee.co.jp

移行理由

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 さんというのも大きなポイントでした。

productpr.timee.co.jp

rbs-inline はまだ experimental なので仕様が不安定という側面はあるものの、逆に考えるとフィードバックをすれば受け入れてもらう可能性が高いということでもあります。標準機能になるのなら、社内にいる soutaro さんに今のうちにフィードバックしておくことで我々のユースケースで困りにくい形で仕様が確定するといいなと思っています。*1

 


 

前編では rbs-inline の紹介、移行の目的などを紹介しました。このまま肝心の「実際どうだったのか」もお伝えしたいところですが、長くなったので一旦ここで一区切り。後編では実際の移行の流れや詰まったポイント、今後の展望をまとめます。お楽しみに!

今回は RBS 活用によって開発速度を向上させる作戦を取りましたが、開発速度向上には色んな方法があると思っています。各社がどんなアプローチで取り組まれているのかはとても興味があります。カジュアル面談でお待ちしています!

product-recruit.timee.co.jp

追記:後編を公開しました。

tech.timee.co.jp

*1:事実として我々のフィードバックによってバグ修正や新たな構文サポートが行われました。詳しくは後編で紹介します