エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Rails アプリケーションへの型導入検討

この記事はエムスリー Advent Calendar 2025 5日目の記事です。

エムスリーエンジニアリングG コンシューマーチームの松原です。

Rails アプリケーション開発で型が欲しいと思ったことありますか? 正直なところ、筆者はそれほど必要ないと考えていました。日々開発・運用している Rails アプリケーションの中には10年選手のものもあり、コードベースの勘所もほぼ把握できている状態だったためです。 しかし、ここ最近は TypeScript で開発されているプロジェクトに触れて型のメリットを実感し、型があった方が嬉しいことの方が多いかもしれないと感じています。 ここ半年ほどを振り返ってみても、型があれば防げたであろう問題に大小合わせると数回以上は遭遇しており、型導入による恩恵がコストを上回る可能性が高いのではないかと考えるようになりました。 また生成 AI が出力するコードにも良い影響を与えてくれそうな期待もあります。 そこで今回は、現在メインで開発している 10 年ものの Rails アプリケーションへ試しに型を導入し、検証したことを記事にまとめてみました。

型チェッカーの候補

2025 年現在で調べた範囲では主に Steep と Sorbet が型チェッカーとして使われているようです。

Steep

Ruby 3.0 から標準ライブラリとして導入された RBS を用いて、型の静的解析をします。 RBS は次のサンプルコードのように実際の ruby ファイルとは別ファイルに書くのが特徴です。

# example.rb

def repeat(str, n)
  str * n
end
# example.rbs
# 別ファイルに型だけを書く
def repeat: (String str, Integer n) -> String

さらに Inline RBS というツールを組み合わせると、次のように ruby ファイルに書いた RBS の型定義コメントから、RBS ファイルを生成してくれます。

# example.rbs

#: (String, Integer) -> String
def repeat(str, n)
  str * n
end

ruby ファイルと RBS ファイルを行ったり来たりするよりも、ruby ファイル中で完結するこのアプローチは、型のある他言語と同様の記述スタイルに近くなるのでより入りやすそうです。

また Steep は Language Server Protocol も実装されているため Visual Studio Code など IDE 等との連携も可能です。

Sorbet

RBS ではなく独自の型定義言語である RBI を使用し、静的解析だけでなく実行時の型チェックもしてくれます。 型定義は RBS と異なり次のサンプルのように ruby ファイルに直接書きます。

# example.rb
extend T::Sig

# sig でメソッドの型定義を書く
sig { params(str: String, n: Integer).returns(String) }
def repeat(str, n)
  str * n
end

ruby ファイルに直接 sig を書けるのですが、それと同時に普通の ruby ファイルとは異なる見た目となるため違和感を覚える方もいるかと思います。

試験的にではありますが、Sorbet も RBS 型定義コメントをサポートしていて、 Inline RBS を使用した時と同様の記述スタイルにすることもできます。 ただし動的な型チェックはできなくなったり、それ以外にも RBS コメントでできなくなることはあるため注意が必要です。

Sorbet も Steep 同様 Language Server Protocol が実装されているため IDE やエディタとの連携が可能です。

候補選定

特徴を挙げただけでもトレードオフが見て取れます。書きやすさを重視するのか、それとも機能を重視するか悩む点かと思います。 今回はなるべく見た目がより ruby っぽい方がチームへの導入もスムーズであろうと考え、 Steep と Sorbet ともに RBS のコメントによる型定義を試してみます。

導入

今回はチームで見ているモデル数が 300 ほどある中規模なプロダクトに導入してみます。 実際に日々デプロイしているプロダクトなので、型を導入した際の恩恵も受けやすいと考えられます。

Steep、Sorbet それぞれ以下のような流れで導入しました。詳細な手順はドキュメントを見ていただくのが良いと思うのでざっくりとした流れのみ記載します。

Steep

標準の rbs や RBS RailsSteepInline RBS と複数のツールを組み合わせていく必要があるため、 型の情報を作るために必要な全体を把握するのが少し大変でした。大まかには以下の流れで対応していきました。

# Rails のモデルと URL ヘルパーの RBS を生成
bin/rails g rbs_rails:install
bin/rails rbs_rails:all

# gem の RBS ファイルをインストール
bundle exec rbs collection init
bundle exec rbs collection install

# モデルに絞って RBS ファイルを生成
# 最初は --output に app を指定していたのですが、steep check でエラーが出力され、プロセスが止まってしまう事象が発生したためモデルに絞りました
bundle exec rbs-inline --output app/models --opt-out

# Steep の初期設定
bundle exec steep init

# Steep での型チェック実行
bundle exec steep check

最初は指摘がかなり多くて驚きましたが、どうやら一部の gem に対する RBS が存在しないためでした。 これらは一旦空の RBS 定義を置いて回避しましたが、rbs collection でサポートされていない gem の追加時など運用上の課題となるかもしれません。

Sorbet

公式の手順に従ってインストールし、各手順を実行しました。

# 動作チェック
bundle exec srb typecheck -e 'puts "Hello, world!"'

# tapioca の初期化
bundle exec tapioca init

# 公式手順に載っていないが、rails アプリではメタプログラミングを多用しており、
# それら動的に追加されたメソッドの型定義を生成
bundle exec tapioca dsl

# 初回チェック
bundle exec srb tc

# 以下にあるようにいくつかの Sorbet の制限となる部分について対応
# https://sorbet.org/docs/adopting#step-4-fix-constant-resolution-errors
# 2度目のチェックでエラーがなくなることを確認
bundle exec srb tc

# これも公式に手順にはなかったが tapioca とともにインストールされている Spoom で
# 一旦全ての .rb ファイルの先頭に `# typed: false` を書いた後
# 次のコマンドで、現時点で `typed: true` にできるファイルを自動で変換してくれる
bundle exec spoom srb bump

ここまでできると後は型を追加していきたいファイルの typed: falsetrue に変更し、型定義を Inline RBS コメント形式で書いていくだけになりました。

使ってみての評価

どちらも読み書きについては Inline RBS にしたので大きな差はなくなりましたので、それ以外の点で評価をしてみます。

Steep

導入が若干手間ではありましたが、Sorbet と比較しても許容範囲内であり、十分使えそうなポテンシャルがありそうです。 今回はいずれの型チェッカーについても Visual Studio Code を用いて使い勝手を確認したのですが、連携もスムーズですし型エラーがすぐに確認できるのも良い点でした。

一方で CLI での都度実行だと型チェックの速度は遅めです。まだチェック対象をモデルのみにしていますが、それでも10秒ほどかかってしまうためテンポの良い開発体験を損なう懸念が若干あります。 生成されたものを使っていく上では、サクサクと候補が表示されるのでそこに若干ギャップがありました。

運用観点では、新しい gem を追加する際に若干障壁となるかもしれないとは思いましたが、最初は untyped として扱っておいて段階的にも進められるので全く運用に耐えられないということもなさそうです。 また、型チェックする対象のファイルを選ぶことはできるのですが、Steepfile に対象ファイルを列挙していく必要があるのでここは手間を要しそうです。

Sorbet

Steep と比べると、tapioca init で参照している gem の型定義は一旦全て生成されるのが便利で導入しやすそうです。 Visual Studio Code との連携も Watchman への依存があるくらいで、Steep 同様スムーズに入りましたし、型エラーのチェック、候補表示も使いやすいです。 また型チェック自体の速度は本当に早くて1秒ほどで返ってくるため Steep と比べた時に大きなメリットの一つでした。おかげでサクサクと型チェックでき、フィードバックループを回しやすい点が魅力的でした。

運用観点では、型チェック対象のファイルをそれぞれのファイルにある typed: xxx コメントで制御できるので、徐々に型チェック対象を広げていけるのが導入を順次進めていくことに繋げられそうです。 新しい gem の追加等についても、自動生成してあげればすぐに使えるところも良い点でした。

一方で今後を考えたときに Ruby 標準でサポートされている RBS ではないという点は、引っかかるポイントです。 公式でも Inline RBS コメントのサポートはあくまで実験的なものという位置付けであるので、どこまでサポートが続くだろうかという懸念はあります。 ただ内部的には sig と同等の AST に一度変換しているようなので、 もしサポート対象外となったとしても自動生成でなんとかなるであろうと考えても良さそうです。

現時点の評価

ツールとしてのこなれ感と、運用時に段階的に型チェックを進めていきやすそうなところで Sorbet を導入していくのが今のチームには適していそうです。 Inline RBS はコメント部分になるので見た目上それほど違和感がなく、これはアリだなと確信を得ることができました。 運用部分については導入してしばらくしないとわからない点もありそうなので、RBS ライクな記述スタイルを維持しつつ、速度も優秀な Sorbet を導入して随時評価していくというのもありではないかと考えています。

まとめ

実プロジェクトに Ruby の型チェッカーを2つ導入して試してみました。 最初に考えていた以上に導入しやすく感じたので、実際に取り組むことは大事ですね。 また型情報や型チェックの仕組み自体が生成 AI との相性も良さそうなので、実際に運用へ取り込んでその辺りも比較できたらなと考えています。

エムスリーでは技術的チャレンジを通して開発・運用をよくしていきたい技術好きなエンジニアを募集しております。ご興味のある方は、ぜひお気軽にお問い合わせください。

jobs.m3.com