Ruby RBS/Steep を既存Railsアプリに導入しようとした話

Ruby における型を明示する方法としてRBSというのがRuby3から登場して、徐々に良くなっていっている感があったので、現在仕事で関わっているRailsに導入してみようとした話。

導入自体は難しくなく

zenn.dev

こちらにまとめた。

結局、さわってみるだけ触ってみて、導入は一旦見送ったのだけれど、その理由を並べていく。

  • DSLとして利用していたクラスなどで日本語 エイリアス、日本語引数が使われており、それらの相性がわるかった。
  • 自動生成ツール(rbs prototype rb)がdelegateに対応しておらず、delegateされたメソッドが存在しないものとして扱われており、手動での定義がつらかった。
  • VSCode Steepでの補完を主な目的としていたが、初期実行がデフォルトの4並列で4-5分かかり、遅い。
  • 一部ファイルがSteep実行で刺さり、CPU100%食ったまま数時間経っても戻ってこない(これはSteepfileで除外していた)
  • 型推論がこなれておらず、faflse positiveなものが多い
  • controller + model の普通のRailsなら良いが、 service 層などを追加すると、 rbs_rails によるそこそこ精度のいい自動生成の恩恵をうけられず、頑張って自分で定義する必要があるが、なかなか大変。

型推論はたとえば

tの型が Integer | nil だった時

s = t + 1 if t.present?

のようなとき、t + 1 の箇所で tが nilである可能性がある。と指摘してくる。 t.present? により、tがnilでないことは確定している。

また modelを

User.merge( Group.where(xxx))

などとやると、 Group::Relation と User::Relation は型が違う。と指摘してくる。(これは既存コードに問題がある可能性もある)

そのほか自動生成したものをそのまま使うと

CONST_ARR = ["a","b","c"]

t = "some string"

CONST_ARR.include?(t)

で、 CONST_ARR::Array["a"|"b"|"c"] なので include? に ::Stringを渡すのはダメと指摘してくる。 t が "a"|"b"|"c" か? と聞きたいのにそれは。(これは CONST_ARR の型定義を ::Array[::String] に直してあげればいいだけ)

と、ちょっと試しに導入してみる、にしては手間がかかりすぎる。というのが冬休みの遊びの延長でとりあえず触ってみた感触だった。

  configure_code_diagnostics do |hash|
    hash[D::Ruby::NoMethod] = nil
    hash[D::Ruby::FallbackAny] = nil
    hash[D::Ruby::UnknownInstanceVariable] = nil
    hash[D::Ruby::MethodDefinitionMissing] = nil
    hash[D::Ruby::UnknownConstant] = nil 
    hash[D::Ruby::UnexpectedError] = nil 
    hash[D::Ruby::UnexpectedSuper] = nil 
    hash[D::Ruby::IncompatibleAssignment] = nil
    hash[D::Ruby::UnexpectedBlockGiven] = nil 
    hash[D::Ruby::UnexpectedKeywordArgument] = nil 
    hash[D::Ruby::UnexpectedPositionalArgument] = nil 
    hash[D::Ruby::InsufficientKeywordArguments] = nil 
    hash[D::Ruby::UnresolvedOverloading] = nil 
    hash[D::Ruby::UnexpectedJump] = nil 
  end

このくらい警告を無効化して、補完と定義確認のみに使おうとして、それでもちょっとまだ無理となっていた。