Railsをバージョンアップし続けるために必要なこと

本当は、RubyWorld Conf辺りでこういう内容も交えてなんか話せればいいなあと思ってたんだけど、CFPに落ちたのでQiitaにポエムを書いてみました。

Railsはそれなりに学習コストはかかりますが、慣れてくるとデフォルトで便利なものが揃ってるしサードパーティライブラリも豊富で、未だに最も便利なWebアプリケーションフレームワークの一つだと思います。
なので、最近のスタートアップ界隈ではRailsで開発をスタートする、という話をよく耳にします。(個人の感想です)
しかし、Railsは本体に新しい要素をガンガン取り入れてくるので、バージョンアップのサイクルはかなり早く、それに追従していくのはそれなりに大変です。
Railsで開発をする場合には、一旦レールに乗ったらプロダクトが死ぬまで走り続ける覚悟が必要です。(時速60km以下になったら爆発する)
それを最初に理解しておかないと、あっという間にレガシー化してメンテの辛みがじわじわと増していきます。
まあ、Rails3.1以降から開発をスタートした場合は、比較的マシな痛みで済みますが……。
そもそも何でバージョンアップしないといけないかというと、もちろん脆弱性の問題もありますが、新しく入った便利機能やガンガン更新されていくgemを横目に開発し続けていくのが心理的に辛いってのが結構大きい理由でもあります。

というわけで、Railsで開発している以上、せっせと新しいバージョンに上げていきたいわけですが、そのためにはやっておかなければならないことがいくつかあります。

## テストを書く
まず第一に、何はなくてもテストコードが必要です。もしテストコードが全く無いならば、Railsのバージョンを上げるのはほぼ不可能です。
現在テストコードが無いプロジェクトなら、もう頑張って書くしか方法は無いのですが、それは非常に辛い……。

そうならないためにも、いいからサボらずにテストコードを書くんだ。TDDをやるんだ。
よりよいテストケースの切り方とか、テスト設計とか色々あるだろうが、とりあえずまず書いてからだ。
最悪、ちゃんと網羅されてるテストじゃなくてもいい。無いよりはマシだ。
Railsってのはそういうフレームワークだからテストを書くしかない。素早くリリースして改善のサイクルを回したいのは分かるが、テストを書くのは最低限だから絶対に書け。

バージョンアップという視点から見れば、最も必要なのはfeature specです。
とは言え、feature specで細かい例外を全部カバーするのはかなりコストがかかります。
少なくとも、コア機能の正常系とよくあるエラーや業務例外、この辺りは必須です。
重要な機能、ややこしい機能の動作をしっかり把握できるようにしておきます。
どうしてもコストが合わなかったり、自動化が厳しい所はあると思うので、手動でテストするテストケースもちゃんと用意しておきます。

細かい挙動の変化で発生するバグを修正しやすくするためには、ユニットテストも重要です。
TDDがやりやすい箇所なので、最初からしっかりテストを書いて高いカバレッジを維持しておきます。

テストが揃っていて、概ねちゃんと動くことはテストを実行すれば分かるところまで来たら次の段階です。

## バージョンアップ用のブランチを用意する
Railsのバージョンアップの流れは大体以下の通りです。

1. Gemfileを編集してバージョンを書き換える
2. bundle updateで依存関係の解決に失敗したgemもバージョンアップする
3. 引っ掛かったらgemのリポジトリを見にいって対応ブランチが無いか探す
4. 対応ブランチ等が無かったら (後半部分を参照)
5. bundle updateが終わったらrake rails:updateを実行する
6. configファイル等のdiffを確認して影響無さそうな所を上書きしていく (分からん場所は保留)
7. configファイル等の変更点をリリースノートやRails guidesで確認する
8. configファイルの更新が終わったらテストを実行する
9. テストケースが流れるようになるまでエラーを潰し続ける
10. 引っかかるgemがあったら、再びリポジトリを見にいって調査する
11. 使ってるgemのバージョンがx.y.zのy以上の部分で更新されていたら、gemのchangelogも見る
12. 実行して赤くなるテストケースをひたすら直すとともに、deprecationを修正しまくる
13. 全てのテストケースが通ったら、手動で残りの部分をテストする

と、まあ大体こんな感じのことをやります。
こんなの一度にやるのは不可能なので、とりあえずブランチを用意して、masterの開発は続けながらこまめにリベースして追っ掛けます。

この作業は、新しいRailsのリリースタイミングじゃなくても、手が空いたら小まめにやっておくと負荷が分散されて少しマシになります。
特にedgeのRailsで動かすブランチを用意しておくと、バグを踏みやすくなるのでPull Reqのチャンスが増えます。

## gemが新しいRailsに対応していない場合や、バージョンが競合する場合
Railsで開発していてサードパーティ製のgemを使っていないなんてことは、ほとんど無いと思います。
なので、Railsのバージョンを上げる時にはgemのバージョンも上げていく必要があります。
普段から小まめにbundle updateしておくと影響が少なくて済みます。

Railsに合わせてgemを更新する場合、いくつか取れる手段があるので紹介しておきます。

### 対応ブランチを探す
まず、第一の手段は先程書いたように、開発元リポジトリに対応ブランチが無いか探すことです。
最近のgemは大体GitHubにリポジトリがあるので、そこを見にいってブランチを探しましょう。
あったらGemfileを書き換えてそちらを使うようにします。
しばらくしたらリリース版に統合されるかもしれないので、ブランチ版を利用している事を忘れないようにします。

### issueやpull reqの状況を確認する
対応ブランチが無い場合、issueとpull reqを確認します。(あっても既知のバグが存在する可能性も高いので確認しておいた方がいいですが)
対応するためのissueかpull reqが上がっていて、ちゃんとやり取りされているようなら、しばらく待つのもアリです。
もちろん自分で直して貢献できそうなら、突っ込んでいってもいいんですが、途中から入っていくのは中々難しいでしょう。
急ぎバージョンアップして動作確認をしたい場合は、そのpull reqをwatchしておき状況をモニタリングできるようにして、pull reqされているパッチ適用版を暫定的に利用することも検討します。
本体にマージされたら、本体のリポジトリに差し替えます。

規模の大きいメジャーなgemの場合は、この手段が取れる事が多いです。

### forkする or 捨てる
マイナーなgemや規模の小さいgemだとバージョンアップ対応issueが無い場合もよくあります。
ざっくりとコードを読んでみて、自分で直せそうならforkする事を検討します。
まずは、バージョン制約を書き換えてとりあえずインストールします。
小さいgemならこれだけで動くことも多いです。
その場合は、依存バージョンを書き換えてpull reqを出しておきます。
しばらくは自分のfork版を使い、マージされたら本家に戻します。

この時、gem側にテストコードが無いと多少面倒なことになる可能性があります。
微妙な違いによるバグが発生した時に原因が分かり辛くなります。
そのgemに引き続きお世話になるつもりなら、テストコードを整備してあげる必要があるかもしれません。

バージョンを上げただけでは動かなかった場合は、コードを読み解いて自力でなんとかするしかありません。
修正したら一応pull reqを上げておきます。
マージされればめでたしめでたし。

しかし、開発が活発じゃないgemだったりすると全然マージされない可能性もあります。
その場合、しばらくfork版を使い続ける覚悟が必要です。
最悪、自分が使い続けている間は、自力で何とかし続けなければいけなくなります。
そのコストを負うのは割に合わない場合、そのgemを捨てる決断をすることになります。

捨ててどうするかというと、同種の機能を持った代替gemを探すか自作するか、です。
どちらになるかは用途の汎用性次第です。よくある機能なら代替gemが見つかる可能性も高いので、何とかなるかもしれません。
そうでない場合は、自分で新しく作るしかありません。
もしくは、本家が対応するか代替が出てくるまで待つか、です。

実際の所、使ってるgemの対応状況がバージョンアップの障壁になる場合はそれなりにあります。
ここで引っかかって先送りし続けていると、バージョンアップの機会を逃してレガシーの闇に落ちていくことにもなりかねません。
どこかで踏ん切りをつけることが重要です。
そのためにも、使っているgemのコードが読めて、何をやっているか理解できることが重要です。


## まとめ
Railsをバージョンアップし健康な状態を保つためには、結構色々やらなきゃいけません。
テストコードを維持しメンテしていく、新しいRailsが出たらしっかりリリースノートを確認し変更点を把握する、変な挙動に気付いたり思った通りに動かない時にRailsのソースコードを追える、使っているgemのソースコードはざっくり把握していて最悪の場合自分でメンテナンスできること。
こういったことがちゃんと出来るように、常に準備しておく必要があります。
要は、自分が使ってる道具はちゃんと把握しておけ、って事です。

もちろん完全に全てを把握しておくのは現実的ではありません。
ポイントを抑えること、いざって時に読めば分かるだろうという状況にしておく事が大事です。
日頃から、気になったコードを軽く読んでおいたり、新しいgemを使う前にざっくりと実装を確認しておくと、いざという時に捗ります。

そして、Railsを拡張しなければいけない時は、無茶な拡張はできるだけ避けること。レールから外れる時も、無茶な脱線の仕方をしないようにしましょう。

Railsに限らず、アプリケーションを開発し運用し続けていくには、バージョンアップに対応するための「覚悟」が必要です。
「覚悟」が暗闇の荒野に進むべき道を切り開く。
備えよう。