Timee Product Team Blog

タイミー開発者ブログ

RBS に出会って変わった Ruby への向き合い方

目次

はじめに

こちらは Timee Product Advent Calendar 2024 の24日目の記事です。

前日は @beryu の iOSの職能チームが存在しない組織で、WWDCハッカソンを企画・開催しました でした。

こんにちは。バックエンドエンジニアの @dak2 です。

タイミーではバックエンドの Ruby on Rails アプリケーションに型定義情報(RBS)を記述して運用しています。

もう少し具体的に話すと、rbs-inline を利用して RBS ファイルを生成し、Steep によって型検査しています。後半で詳しく説明します。

自分はタイミーに Join するまで RBS に触れたことがなかったので、Ruby で型を記述するという体験が新鮮でした。

型を記述して運用する中で Ruby への向き合い方が変わってきたなあと感じているので、今回はそれを文字に起こしてみたいと思います。

RBS について

RBS とは Ruby のプログラム構造、いわゆる型を記述する言語のことを指します。

Ruby ファイルとは別に .rbs という拡張子のファイルにクラスやモジュールの型を記述します。

class Foo
  def bar(str)
      "#{str}"
  end
end
class Foo
  def bar: (String) -> String
end

簡単な例ですが、上記のように foo.rb のクラスに対して、foo.rbs ファイルに RBS を書いて型定義ができます。

*詳細は公式リポジトリを参照してください。

rbs-inline と Steep について

rbs-inline & Steep は、弊社のフルタイム Ruby コミッタである soutaro が開発している gem です。

rbs-inline はコメントとして型を記述できます。このコメントをもとに RBS ファイルが生成されます。

下記左のようにメソッド上部にコメントを追記して、rbs-inline コマンドを実行すると右のような RBS ファイルが生成されます。

class Foo
    # @rbs (String) -> String
  def bar(str)
      "#{str}"
  end
end
class Foo
  def bar: (String) -> String
end

*詳細は公式リポジトリを参照してください。

Steep は Ruby の型検査器であり、実装と型宣言に矛盾がないかをチェックします。

上記のような RBS を記述した上で steep check コマンドを実行すると型チェックをします。

Steep は VSCode での拡張機能もあり、関数ホバー時に型定義を教えてくれたり、メソッドの補完や型エラーになっているコードを赤の下線で示したりしてくれます。

メソッド補完

未定義メソッドのエラー

*詳細は公式リポジトリを参照してください。

ちなみに、弊社バックエンドのテックリードである shintani が Steep のエラーリファレンスをまとめた記事があるので、興味がある方はご覧ください。(自分もちょくちょく見ています)

RBS に出会ってからの Ruby への向き合い方

自分の所属している Working Relations Squad というチームでは Done の定義の一環として、インクリメンタルな差分に対しては必ず rbs-inline を記述するようにしています。

*Done の定義の話について詳しく知りたい方は、こちらの記事を参照してください。

rbs-inline を記述して型を意識することで、Ruby への向き合い方が変わったなあと思う点を下記に挙げてみました。

  • 単一の型を返す意識がついた
  • メソッドの戻り値の型だけを見て実装する機会が増えた
  • Ruby で型を書くのも良いなと思った

単一の型を返す意識がついた

自分は戻り値の型が単一の方が扱いやすいと考えています。

その方が呼び出し側で戻り値の型ごとにハンドリングしなくて良いし、メソッド自体の再利用性も高まると思っています。

rbs-inilne を書くまではぼんやりとそういった意識はあったものの、あまり意識できていなかったように思います。

rbs-inline を書くことで、自分が追加したメソッドの型を定義するようになり、自然と戻り値の型がどうあるべきかに対して明確に思考が向くようになりました。

メソッドの戻り値の型だけを見て実装する機会が増えた

Ruby を記述していると、このレシーバってこのメソッド使えるんだっけ?と思ってメソッドの中身を再度読みに戻った経験はありませんか?もしくはテストを見に行ったり、場合によってはコンソールで Object#class を実行して確かめたりなど。自分は何度もあります。

既存メソッドの中身を確認することはあるのですが、詳細まで深く確認するというケースは少し減ったかなと思っています。

メソッドなどの型情報を主なインターフェースとして捉え、呼び出し側で利用するみたいなケースが増えたなと。

Ruby で型を書くのも良いなと思った

良いか悪いかは別として、「メソッドの戻り値の型だけを見て実装する」ことで、余計な情報を知らずに済むようになってきて、本当に実現したい処理に集中しやすくなりつつあるなと思っています。

この状態は理想的で、やりたいことにフォーカスできると Ruby の高い表現力を活かして素早く価値提供できるんじゃないかと思います。

過去に TypeScript などで型を書いてはいましたが、型の意義が自分の中で腹落ちしていませんでした。デフォルトで型付けしないといけない世界線だとそれが当たり前だったので、Ruby の世界との差分が大きくてうまく解釈できていなかった感覚があります。

ですので、それまでは型を書くというのがただ退屈な作業に感じていましたが、“型のある” Ruby を書くことと、”型のない” Ruby を書く体験差分を通じて、上述したように型の意義が自分の中で腹落ちしてきたなと思っています。

「Ruby に型なんかいらないんじゃないかな?」と思ってた自分が、型の恩恵を受けた世界線はより Ruby の表現力にフォーカスできるんじゃないかと思い直していて、Ruby への向き合い方がまた一つ変わったような気がします。面白いなあ。

これからも型やっていき!の精神で書いていこうと思います。

明日は @naoya の 「【iOS】Live Textを活用した画像解析 です。」お楽しみに!