Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

クラウド経費・債務支払を Ruby 2.7 -> 3.1 にバージョンアップしました!

こんにちは クラウド経費開発チームクラウド債務支払開発チーム の 宮村(みやむー) @miyamura.koyo です。

クラウド経費・債務支払は先日 Ruby 2.7 -> 3.1 にバージョンアップしました! (※ ちゃんと EOL までにアップデートしています!)

ただ、8年近く開発されている Ruby on Rails アプリケーションなので、バージョンアップは一筋縄ではいきませんでした。 今回はその汗と涙の軌跡をご紹介しようと思います。

方針

まず最初に進め方を考えました。 クラウド経費・債務支払 は活発に機能開発やバグ修正が行われているため、Ruby 3 更新に伴ってこれらの開発に影響を与えることは避ける必要があります。 そのため、できるだけ本番に細かくリリースすることを意識しました。

最終的なリリース差分をできる限り少なくし、開発中のブランチがリリースブランチを取り込む際にできるだけスムーズに取り込めるようにしました。 あとは簡単に Ruby 3 後の差分をドキュメントに起こし、開発部全体で Ruby 3 の世界に踏み出すことを意識して進めました。

やったこと

単体テストを通す

とりあえずブランチを切って Ruby のバージョンを 3 系にアップデートしてテスト回してみました。 その結果...1万件以上はテストケースが落ちました...。俺たちの戦いはここから始まるッ!

ということで直します! 調べてみると、多くはRuby3の変更の一つ、キーワード引数にハッシュを渡すことが非推奨となったことが原因のエラーでした。

www.ruby-lang.org

これらは Ruby 3 になる前に適用しても問題ないため、これらを洗い出して少しずつリリースを行いました。 ひたすら刺身タンポポ(単純作業)...!

細かいエラーを修正する

上記のキーワード引数のエラーを修正してもまだいくつかエラーが残ったのでそれらを修正していきます。

例えば Ruby が標準ライブラリとして含んでいる BigDecimal のバージョンが上がり、細かい挙動が少し変わってしまうことで単体テストが落ちていたり、いくつかの gem が Ruby 3 系に対応していないことで単体テストがエラーになったりしました。

※ BigDecimal の細かい挙動が変わってしまう例。

$ docker run -it --rm ruby:2.7

irb(main):001:0> require 'bigdecimal/util'
=> true
irb(main):002:0> 99_999_999_999_999.99.then { [RUBY_VERSION, BigDecimal::VERSION, _1.to_d] }
=> ["2.7.8", "2.0.0", 0.1e15]
$ docker run -it --rm ruby:3.1

irb(main):001:0> require 'bigdecimal/util'
=> true
irb(main):002:0> 99_999_999_999_999.99.then { [RUBY_VERSION, BigDecimal::VERSION, _1.to_d] }
=> ["3.1.4", "3.1.1", 0.9999999999999998e14]

これらに対しては issue を立てて手分けして一つ一つ対応していきました。 ライブラリのバージョンアップも先行して本番に取り込んで問題ないので、ビッグバンリリースを避けるためにもなるべく本番に適用するよう進めました。

gem に PR を出す

この対応の中で Ruby 3 対応のために gem をバージョンアップする必要がありました。 active_hash という(割と有名な) gem もこの流れでアップデートしたのですが、そこにニッチなバグがあってクラウド経費・債務支払で困るケースがありました。 一旦はモンキーパッチで凌ぎましたが、これはかなりニッチなバグなので、さすがに PR 出さないと永遠にリリースされないと思ったので、OSS 貢献も兼ねて PR を出してみました。

github.com

こちらも結構色々取り組んだので、詳細は別のテックブログにして公開したいと思います。

QA する

これらに対して対応していくことでついに単体テストが全て通過...! ということで QA チームと協力して意気揚々と QA をしてみたところ、基本的には問題なかったのですが、想定外のところでいくつかキーワード引数のエラーが出てしまいました。 単体テストは全部通過したので何かしら漏れているところがあるのか...?と思ったのでそこの調査を追加で行いました。

本番環境でキーワード引数エラーになりうるコードの検知を行って直す

調査してみたところ、単体テスト中にモックしているところ(主に外部の API とのやりとり)が主に漏れていることがわかりました。 ということはモックしていたりして、単体テストが通過できていない部分は本番でエラーになる可能性があります。

しかし、このままリリースしてしまっては本番障害になりかねません。 何とか「本番で」「網羅的に」エラー検知できないか...と思って色々試してみました。

結果としては以下のテックブログに記載した方法で、本番で修正対象のコードを網羅的に洗い出して修正することができました。 (ちなみに大体1ヶ月くらいは本番で監視を行ってエラーになるコードが含まれていないかを調査しました)

詳細が気になる方は是非読んでみてください!

moneyforward-dev.jp

最終確認する

再度 QA して結果も良好!ということでリリースです!

リリース

Ruby 自体のバージョンアップのリリースなので、リリース作業も慎重に行いました。 開発部のインフラ担当のエンジニアとも連携しつつ、サーバー1台にリリースしてみてエラーが出ないことを確認して全台に適用する手順を踏みました。 何かあった時にいつでもロールバックできるように気をつけつつ、無事リリースが完了しました!

まとめ

丁寧に進めたおかげで、バージョンアップ起因のエラーもなくリリースすることができました。 事前にリリース可能な差分を先行して本番に取り込んでいたおかげで、最終的なリリース差分も数十行に収めることができたので、開発中のブランチにも大きな影響を与えることなくリリースしました。 また、Ruby3 対応で気をつけるべきことのドキュメントも書いて開発部にも展開しました。

上記資料にはこんな感じでエラーになる例も書いておきました。

def hoge(fuga:)
  puts("引数 fuga の値は #{fuga} です。")
end

arguments = { fuga: '🐱' }

# Ruby 2.7 の場合
hoge(arguments)
# 引数 fuga の値は 🐱 です。
hoge(**arguments)
# 引数 fuga の値は 🐱 です。

# Ruby 3.1 の場合
hoge(arguments)
# wrong number of arguments (given 1, expected 0; required keyword: fuga) (ArgumentError)
hoge(**arguments)
# 引数 fuga の値は 🐱 です。

期間についてですが、他の作業もしつつ、主に先輩と2名がかりでちょこちょこ進めていましたが、概ね3~4ヶ月くらいで上記の対応を完了することができました! コードベースが大きく、開発が活発なプロダクトのアップデートということで色々と大変でしたが、Ruby 自体の知見も深まったし、OSS に PR を出すきっかけにもなったので、チャレンジしてみてよかったです!

なお、作業中に Ruby 3.2 出たので次はそちらを頑張りたいと思います笑


マネーフォワード福岡拠点では、エンジニアを募集しています! 多くの人が使っているプロダクトの Ruby バージョンアップをしたいエンジニアはぜひ応募してみてください!

hrmos.co

福岡開発拠点のサイトもあるのでぜひみてください!

fukuoka.moneyforward.com

直近でオフラインイベントもあるよ!

nulab.connpass.com