bitjourney team
5048
2
0

KibelaにRBS / Steepを導入しようと考えています

Published at January 14, 2021 12:37 a.m.

KibelaにRBS / Steep、つまり型を導入しようと考えています。ただ、後半に書きますが導入はしばらく後になると思います。2月に入ってからかなあ。

(社内向けドキュメントですが、別に隠すこともないので外部共有しています)

目次

  • 導入する目的
  • 導入する方針
  • 導入する方法
  • 導入すると何が変わるのか、どう対応する必要があるのか
  • 導入のスケジュール

導入する目的

Kibelaの開発体験をより向上させるためです。また、 @masataka-kuwabara がRBS, RBS Rails, Steepなどの都合の良い実験台としてkibelaを使うためでもあります。

導入する方針

RBSはまだ発展途上です。そのため、Kibelaのようなある程度育ったRailsアプリケーションで型エラーをゼロに保って開発するのは、現状ではとても難しいと予想しています。
これは現状のコードベースをすべて型エラーゼロの状態に持っていくことが難しいという理由もあります。ですがそれ以上に、仮にそれを達成したとしても、それを維持するコストは現状では高すぎるだろうという理由も大きいです。

そのため、ごく部分的に、かつ選択的に導入する予定です。
つまり、開発者はSteepを使うことを選択しない限り、特に今までの開発フローが変わることはなく、Steepの存在を意識する必要はありません。そのため必然的に全員が使わなくても効果がある機能だけを部分的に導入することになります。

何を導入するか

型チェックには期待せず、SteepのLSPを使うことを目的として導入します。

SteepのLSPによって、開発者はエディタでの補完や型情報の表示ができます。型チェックには期待しないので当然間違った型やuntyped(TypeScriptのany)は多く現れると思いますが、それでも意味のある補完や型情報の表示ができるぐらいには型がつくのではないか、と予想しています。

導入すると何が変わるのか、どう対応する必要があるのか

まず、Steepを使わない人は特に何も気にする必要はありません。

Steepを使う場合、steep langserverコマンドで提供されるLSP serverを適当にエディタで使うように設定します。この辺はエディタ毎に設定が異なると思うので、いい感じにやってください。

どう導入するか

  • 型のためにRubyのコードはいじらない
  • RBS生成のためのコードをコミットして、自動生成されたRBSはgit commitしない
    • Steepを使う開発者の手元でだけ動けばいいので、コミットする必要がまったくない
  • RBSの自動生成やgem_rbsのセットアップはguardからoptionalにできるようにする。
    • GENERATE_RBS環境変数を定義してたら生成する、みたいなイメージ
    • 必要ない人は無視できる
  • RBSの手書きはgit commitする
    • ただ、ガッツリは書かないと思う。

導入までのスケジュール

まずKibelaに導入する以前に、周辺ツールの整備をします。この整備が2月中には一段落すると良いなあ。

  • gem_rbs_cliの目処を付ける
    • https://github.com/pocke/gem_rbs_cli
    • ruby/gem_rbsからライブラリのRBSを落としてくるツール
    • これがないと多分gem_rbsをgit submoduleすることになると思うのだけど、なんとなくそれは避けたい。git submoduleあまり好きではない。
    • 今仕様をデザイン中。つまり、これに一番時間がかかりそう。
      • まあ現状でも中途半端に動く実装はあるので、時間がかかりそうならそれでとりあえず突っ込んでしまっても良いかもしれない。
  • vim-lspでいい感じにsteepを動かす設定を探る
    • 少なくとも私はSteepを手元で動かしたいので、動かせる状態にしておく必要がある
    • 現状でも動くのだけど、型エラー部分が全部赤くハイライトされてしまうので、今回の導入方針には圧倒的に合わない。エラーを無視する設定を探す。

この目処が経ったら実際にKibelaにSteepを導入していきます。
と言ってもすでに手元でSteepを導入したブランチはあるので、それを整えれば良いだけです。そのため、これはそんなに時間がかからないかなあと思っています。

ここまでを、2月の前半にできたら良いかなあ、と思っています。

将来

また、将来的にはより多くの部分にSteepを導入していくことも考えています。

次のステップでは型チェックを活用することになると思います。その場合、おそらくすべての開発者がSteepの存在を意識して開発する必要が出るでしょう。

冒頭にも書いたとおり型エラーをアプリ全体でゼロにするのは難しいため、次の2つの方法を考えています。

一部のコードにのみSteepを適用する

1つは、一部のコードにのみSteepを適用することです。

たとえばサービスクラス(app/services/)にのみSteepを適用するのは良いアイディアでしょう。サービスクラスはPOROであり、かつRBS Railsによって型がサポートされるモデルを扱うことが多いため、型をつけやすいし、扱うコードに型が付きやすいです。

実際しばらく前に手元で型をつけて試していたときも、サービスクラスにのみSteepを適用した場合には型エラー数が20個程度で、充分直せるなと感じたのを覚えています。

Output based testingを活用する

もう1つは、SteepのOutput based testingを活用することです。

https://github.com/soutaro/steep/pull/288/commits/862eb02ec9cb17cd921042269099a872c6a88fb2#diff-2a892c2086046beac8d244b1a8a4dc32b006b5c7b578538acff1ec49618f5b32

これはまだSteep内部でしか使われていない機能(たぶん)でユーザーにexposeされるかも未定(たぶん)なのですが、Steepの出力を保存しておいて、新規に出た型エラーだけをテストする機能です。RuboCopの.rubocop_todo.ymlやshift reduce conflictのexpectを想像すると近いかなと思います。

これを使うとコード全体に型チェックができます。一方、既存の型エラーはすべて無視することになります。

個人的にはこっちのほうが楽しそうだからこっちを採用してみたいと思っています。しかしexpectationをgit commitすると膨大になりそうで、うーむという気持ちもあり。

まあどうしていくかは、両方試してみないとなんとも言えないですね。


なお、この文書はまだ先が見えない中で書いているので、当然SteepをRailsアプリに導入するベストプラクティスなどではなくて、1つの案に過ぎません。また、記事執筆時点では何も実行に移していないので、今後よりよい別の方法が浮かべば、そちらを取るかもしれません。