Timee Product Team Blog

タイミー開発者ブログ

【イベントレポート】「Railsアプリと型検査」

イベント概要

2023年11月15日に「GENBA #1 〜RubyRails開発の現場〜」と題してRuby/Railsでの開発に関するトピックでタイミーとエンペイ社合同で勉強会を開催しました。 その中でタイミーバックエンドエンジニアの正徳さん a.k.a 神速さん(@sinsoku_listy)の発表「Railsアプリと型検査」をイベントレポート形式でお届けします。

登壇者情報

Railsアプリと型検査

RBSの基本

RBSとは

RBSRuby Signature)は、Ruby 3.0から導入された言語機能で、Rubyのコードに型情報を追加し、型検査と入力補完を可能にするための言語です。RBSファイルの拡張子は .rbsで、通常はプロジェクト内の sig/ ディレクトリに配置されます。

RBSのメリット

RBSの主なメリットは「型検査」と「入力補完」の2つがあります。

型検査とは

型検査には、Steepと呼ばれるツールがあります。Steepを使用すると、RBSの定義に違反するコードを検出する役割を果たします。例えば、関数に指定された引数の数が実際の呼び出しと一致しない場合、エラーが検出されます。この型検査は、Rubyコードを実行する前に問題を発見できます。

下記は型検査(Steep)の実行例です。

入力補完とは

VS Codesoutaro.steep-vscode 拡張をインストールすると、RBSファイルを読み込んで、Rubyコードの入力補完が賢くなります。

IRB v1.9.0で、RBSを使った入力補完がサポートされました。入力補完を有効にするには、gem install prismコマンドでprismをインストールし、IRBを起動するときに-r prismオプションを指定します。

タイミーの導入事例:実際の手順

ここまで一応RBSの基本の話だったんですけど、弊社が実際に導入事例を紹介したいと思います。

RBS導入のきっかけ

RubyKaigi 2023やメドピアのブログを読んだことから、RBSの導入への意欲が高まりました。 実際に試してみると、型チェックを無効にして、入力補完に重点を置くことで、比較的簡単にRBSを導入できることが分かりました。すぐに社内での調整を行い、方針を策定しました。 社内の開発者に質問してみたところ、RBSを完全に書くことには乗り気ではないものの、重要な部分だけを一部記述したいというニーズがありました。 しかし、それを別のファイルに書くのは少しつらいという声も聞かれました。

そこで元々弊社で使用していた Yard(ヤード)のドキュメントを活用し、重要な部分に型情報を明示し、コメントからRBSを生成して活用するアプローチを考えました。

実際にプロジェクトで検証した結果、うまく機能しそうだったため、「入れてみてダメだったら消そう」というノリで、試してみることになりました。

1. Gemfile に rbs_rails, steepを追加します

2. Steepfileを追加し、既存の型検査エラーは無視する

3. rbs_collection.yaml を作成する

Ruby公式で管理されている rbs-gem-collection というリポジトリに、有志の方が提供している情報を利用し、rbs_collection.yaml ファイルを作成します。

4. RBSを生成するRakeタスクを追加する

様々なコマンドを個別に実行するのは手間がかかるため、一度にタスクを生成できる「RBSセットアップ」という独自のコードを作成しました。 このコードは、rbs-gem-collection からRBSをダウンロードし、自社のプロダクトのコードを解析し、適切な雛形を生成し、全てのクラスとメソッド名が含まれたRBSを生成できるものです。

5. .gitignoreディレクトリを無視する

RBSは生成されるものとして扱っているため、.gitignore ファイルでこれを無視するように設定します。

6. RBSの定義エラーを回避するために最小限の型を書く

これまでの作業で、多くの型情報を生成できましたが、やはり一部の型情報は自動生成できない場合があります。そのような場合、手動で最小限の型情報を記述することで、エラーを回避します。例えば、Active Admin など一部のジェムは公式の型情報が提供されていないため、自社で型情報を記述する必要があります。ここまで実施し、RBSの文法エラーがない状態にプロジェクトを持っていきます。

まとめ:最小限のRBSで入力補完に型を使用できる

RBS + Sord の導入方法

Sordというジェムを使用しヤードドキュメントからRBSコマンドの型を生成します。

まずSordを導入し、Rakeタスクでsordを実行します。このとき sord のバグは、GSub で書き換えるとうまくいくことがあります。

この手順で、bin/rails rbs:setup を実行するとRBSが生成され、入力補完が賢くなる世界を作ることができました。 ちなみに、RubyMine や VS Code を使用していると、入力補完が賢くなるケースがあるようです。

型検査の課題

タイミーにおける型検査の課題は、Steepの型定義が少ないため、誤検知が発生する可能性があること、 また、特定のファイルのみを無視することができないため、段階的な導入が難しいことが挙げられます。

実際にどれくらいエラーが検出されるか検証をしてみると、656件ありました。ほとんどが誤検知ですが、そのなかで3件ほど面白いエラーがありましたのでご紹介します。

型検査で見つけたコード例1

型検査では、nilチェックが甘いため、ハーストした後にnilが来る可能性があることを考慮せず、IDなどの値に代入してしまうケースがありました。

型検査で見つけたコード例2

タイミーでは、ハッシュから銀行コードを取得する際に、全銀コードがない場合、nilが返されます。このときエラーを検知してしまいます。これは、Steepが全銀コードが正しい値しか渡されないことを前提としているためです(全銀コードが存在しないケースを想定できていない)

型検査で見つけたコード例3

足し算と掛け算をするときに、右の値をぼっち演算子を使うのですが、このときNilエラーが発生します

まとめ

今後、開発がさらに進化し、堅牢なコードを手軽に書ける世界がやってくるかもしれません。特に、入力補完の文脈で型検査を導入することは導入ハードルが低く、Railsアプリケーションの開発に大きな価値をもたらす可能性があります。明日からでも是非、Railsアプリ開発に型検査を取り入れてみてください。

記事の発表やその他の発表が気になる方はこちら!

www.youtube.com

少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう!

devenable.timee.co.jp