はじめに
こんにちは。株式会社タイミーでバックエンドエンジニアをしている廣江です。以前、TECH Streetさん主催の「Ruby勉強会~各社の取り組みや課題から学ぶ会~」というイベントでRBSを手書きで利用しているという話をさせていただきましたが、今回はその後についてのお話を書かせていただこうと思います。
ここでの「手書きRBS」とは言葉の通り、手書きされたRBSを指しています。反対に手書きではないRBSとは、TypeProfやSordなどのRBS生成ツールを利用し、ソースコードやコメントから自動生成されたRBSを指しています。
RBSについて
RBSとは、Ruby3.0から追加されたRubyの型情報を記述するための言語です。RBSファイルと型検査機「Steep」を用いることにより、Rubyのソースコードの静的な型検査を行うことができるようになります。
タイミーでのRBSの活用状況
タイミーにはRuby on Rails製のアプリケーションがいくつか存在していますが、メインシステムともう一つ、小さめのサブシステムの開発でRBSを活用しています。以前の発表で取り上げたものはこのうち後者のサブシステムでの事例でしたが、メインシステムでも本格的にRBSを活用しており、直近ではドキュメンテーションツールのYARDからrbs-inlineに完全移行しています。この経緯については弊社テックブログにて掲載されています。
今回は、引き続きサブシステムにおける手書きRBSのその後について書かせていただこうと思います。前回のイベントでの発表内容については後ほどレポートが上がるようですので、そちらをご参照ください。
rbs-inlineを使うかどうか
rbs-inlineは実装のすぐ近くにコメントで型情報が書け、そこからRBSファイルを生成してくれる便利なツールです。将来的にRBS本体に取り込まれる想定で開発が進んでおり、私たちもrbs-inlineを採用すること自体には肯定的でしたが、現状以下の理由から採用していません。
1.手動でRBSファイルを生成し忘れた場合、コミット時にrbs-inlineからRBSファイルを自動生成するような仕組みが現状用意できていない
2.自動生成の整備にかかるコストが現状の規模と効果に対して見合わないと判断した
3.RBSのGit管理をやめ、型検査時に都度生成するスタイルであれば上記の仕組みは不要だが、この方法だと現在感じているメリット(後述)をいくつか失ってしまう
4.開発者はエディタにVS Codeを利用しているため、rbs-inlineを導入せずとも拡張機能によって実装のすぐ近くに型情報が表示される
5.RBSをわざわざ別ファイルに書くことに対する面倒臭さが慣れによって薄くなっている
ちなみに、私たちは「今後もrbs-inlineを使わない」という決定をしたわけではなく、使いたくなった場合は遠慮なく使っても良いということにしています。rbs-inlineのRBS生成はコメントが一切書かれていない実装であっても、型をuntyped
としてRBSの雛形を生成してくれるため、RBSファイルの生成サポートツールとしては非常に優秀なんじゃないかなと考えています。
ちなみにRBSファイルをGit管理するメリットには、Pullする度にRBSファイルを生成し直す手間が必要ないことや、後述するRBSファイルをレビューすることに価値があることが挙げられています。
RBSの恩恵
ケアレスミスの減少
RBSを導入したことで、エラーの発生率は格段に抑えられていると感じています。少なくともサブシステムの初回リリース以降NoMethodError
のようなケアレスミスによるバグは発生していません。
例えば、以下のようコードを書いた時、+
の部分で型エラーが起きます。
array = [1, 2, 3] array.first! + 1
Type `(::Integer | nil)` does not have method `+`(Ruby::NoMethod)
これはArray#first
がnil
を返す可能性を含んでいるからです。今回の例では直前にarray = [1, 2, 3]
であることが分かっているため、array.first
が1
であることは明らかですが、型チェックをパスするためには以下のように記述を変更する必要があります。
array = [1, 2, 3] array.fetch(0) + 1
些細な変化ですが、array.fetch(0)
がnil
である可能性がなくなり、array
に要素が必ず1つ以上求められることがソースコードから明確になります。こうした小さな変化が積み重なることにより、バグの起きにくいコードになっているのだと感じています。
さらにVS Codeを利用していると、このような型エラーをリアルタイムでフィードバックしてもらえます。これによりテストを実行せずとも記述の誤りに気づくことができるため、開発速度にも貢献できています。
また引数の間違いやtypoなども同様にリアルタイムのフィードバックで気づけるため、普段の開発においてかなりの恩恵を受けていると言えます。
インターフェースへの意識
RBSではメソッドの型を書くとき中にロジックを書かないため、引数や返り値の型に集中できます。これを利用し、Pull Requestではインターフェースに集中したレビューが可能になっています。Rubyにはインターフェースを表現する機能はありませんが、それに近いことをするテクニックは存在します。
このインターフェースに集中したレビューが行えることにより、共通のインターフェースを見つけ、適切なクラス設計を促せる場合があります。また、引数に明らかに場違いなオブジェクトを受け取っていたり、返り値に全く関連のないオブジェクトが返っている場合にもレビューで気づきやすく、そういった場合に設計を見直すきっかけとなります。
苦労している点
恩恵については前回の発表時から感じていたものが引き続き感じられているという状態なのですが、苦労している点は新たに少し増えています。
外部ライブラリのRBS不足
まずは、前回の発表時にもあった外部ライブラリのRBS不足です。こちらはgem_rbs_collectionにPRを出すことでなんとか解消しつつ進めていますが、バージョン更新頻度の高いRailsの新機能などの型情報が必要になった場合などは頭を抱えることがあります。
これについての対応としては、前回の発表時には外部ライブラリの型定義が不足していた場合はgem_rbs_collectionにPRを出すことを推奨していたのですが、そこそこ頻度が高いことと、それよりも優先すべきタスクがあるというケースが多くなったため、必ずしも実施していません。代わりに現在ではsig/gems/というディレクトリを掘り、ひとまずはそこに型検査を突破できる程度の必要最小限の型定義を書くという方法をとっています。手が空いた際にそれらを整理してgem_rbs_collectionにPRを出しています。
RBSのキャッチアップ
次に、新しいメンバーのRBSのキャッチアップについてです。当然RBSをガッツリ書いてきました!という人は稀だと思うので、RBSのキャッチアップはほぼ必ず発生すると考えています。このキャッチアップを考えた時に、rbs-inlineを使っていればYARDライクな記法もあるため誤魔化しが利くんですが、RBSをちゃんと書くとなるとキャッチアップもちゃんと行なってもらう必要があります。また書き方がわからずうまくいかないといった場合にきちんとフォローできる体制を取る必要があり、それがないといまくいかないからとsteep:ignore
等で型検査を無効にされてしまうとRBSの恩恵を受けられなくなってしまいます。
こちらについては現状、RBSのSyntaxが確認できるドキュメントへのリンクをREADMEに用意するにとどまっており、それ以上の対応策が十分に打てていない状態です。用意したドキュメントはあまり参照されておらず、これを改善するために周知を行うであったり、勉強会やワークショップの開催などを検討しています。キャッチアップも最初から全て完璧に行うのは難しいと考えているため、既存のRBSを参考にしつつできるところまでやってもらい、どうしても難しいようであれば気軽に聞いてもらえるような環境をちゃんと整備していくことが重要かなと考えています。
動的なメソッド生成やメソッド呼び出し
最後に動的なメソッド生成やメソッド呼び出しでは恩恵を受けづらいという点があります。特にmethod_missingを利用した実装には全くの無力です。これについてはRBSの恩恵を受けるために動的なメソッド生成や呼び出しのような実装は避けるという、RBSフレンドリーな実装を心がけることでなんとか回避していますが、Rubyの武器の1つを失ったような気持ちになっています。もちろんこういった実装は多用すべきではないので、避けるという方針自体は間違っていないとは思いつつ、完全に封印されてしまうとそれはそれで不便だなと感じています。
これについての対応としては、上で書いた通り、基本的にはRBSフレンドリーな実装を心がけるという方針によって回避していきます。その上でどうしても動的な実装が必要になった場合の策として、steepのアノテーションコメントを書くという方法でなんとかしていこうと思っています。これはソースコード上にコメントで変数やメソッドなどの型を記述できるというもので、型検査を無効にするよりは遥かに良い状態を維持できるだろうと睨んでいます。
最後に
苦労している点はいくつかあるものの、現状圧倒的にメリットが多いと考えているRBSですが、すでに成熟仕切ったRailsアプリケーションに導入するのは大変かと思います。それを実施しているのが弊社テックブログで掲載されている「YARD から rbs-inline に移行しました」という記事になっており、こちらの内容も参考にしていただけると良いかと思います。この記事を通じて少しでもRubyでの静的型検査に興味を持っていただけたり、何かの助けとなれば幸いです。
記事執筆者
廣江 亮佑
株式会社タイミー
2023年6月に株式会社タイミーに入社。岡山からフルリモートで勤務しています。SpotworkSystemTribeに所属し、現在はサービス内でのお金の動きに関わるドメインを主に担当しています。最近IDEをRubyMineからVS Codeに乗り換えました。SNSではだいたいrhiroeという名前で活動しています。