はじめまして、広告事業本部でアプリケーションエンジニアをしている24新卒の龍溪です。 入社してから早くも9か月が経ち、時の流れの速さを痛感しています。
本記事では、Ruby on Railsで使われていないコードを発見するためにCoverbandというGemを入れた話について紹介しようと思います。
背景
私たちのチームでは、Ruby on Railsを使って開発しています。 開発しているプロダクトは非常に息が長く、これまでに多くの機能が追加されてきました。 その一方で、不要になった機能も出てきています。 そのため、いつの間にかどの機能からも呼び出されなくなったメソッドやコードが存在します。
このようなデッドコードを削除するとき以下のことができると便利です。
- プロジェクトの各ファイルをスキャンし、デッドコードを可視化できる
- コード(メソッド)の呼び出し元を確認できる
このうち2つ目の「コード(メソッド)の呼び出し元を確認できる」については、すでに使っていたOkuribito RailsというGemでカバーできていました。 このGemを使うと、監視したいメソッドがいつどこから何回呼び出されたのか、などを計測できます。
一方、Okuribitoは監視したいメソッドを開発者側で指定する必要があり、監視しない限り使用状況を確認できません。 そのため1つ目の「使われていないコードを可視化できる」についてはカバーしきれていませんでした。
そこでデッドコードの可視化ツールとして新たにCoverbandを導入してみることにしました。
Coverband とは
Coverbandはコードの各行が何回実行されたかを計測してくれるGemです。 計測結果は下の画像のようなWeb UIで確認できます。 (出典)
各カラムには以下の情報が表示されます。
カラム名 | 説明 |
---|---|
% covered | ロードと実行がされた行数の割合 |
% runtime | 実行された行数の割合 |
Lines | ファイルの行数 |
Relevant Lines | 空白やコメントを除いた行数 |
Lines covered | ロードと実行がされた行数 |
Lines runtime | 実行された行数 |
Lines missed | ロードと実行がなされなかった行数 |
Avg. Hits / Line | 1 行あたり何回実行されたかの平均 |
ファイル名のところをクリックすると、下の画像のように詳細な情報を見ることができます。(出典)
四角で囲ってあるところではロードされた回数と実行された回数を見ることができます。 最新のCoverbandでは、下の画像のように各行が最後に実行された日付も確認できるようになっています。
Coverband の設定 Tips
Coverbandの導入手順については公式の手順に従うのが一番なのでここでは割愛させていただきます。 以下のデモアプリも参考になります。
以下ではCoverbandの設定項目の一部を解説していきます。
Web UI に認証をかける
Web UIではコードが見えてしまうので、必要に応じて認証をかける必要があります。 特にコードを公開していないプロジェクトの場合は認証をかけましょう。
# https://github.com/danmayer/coverband/blob/v6.1.4/README.md#mounting-as-a-rack-app Rails.application.routes.draw do authenticate :user, lambda { |u| u.admin? } do mount Coverband::Reporters::Web.new, at: '/coverage' end end
計測結果の収集頻度
background_reporting_sleep_seconds
という設定項目があります。
# https://github.com/danmayer/coverband/blob/v6.1.4/lib/coverband/configuration.rb#L155-L168 # The adjustments here either protect the redis or service from being overloaded # the tradeoff being the delay in when reporting data is available # if running your own redis increasing this number reduces load on the redis CPU def background_reporting_sleep_seconds @background_reporting_sleep_seconds ||= if service? # default to 10m for service (Coverband.configuration.coverband_env == "production") ? 600 : 60 elsif store.is_a?(Coverband::Adapters::HashRedisStore) # Default to 5 minutes if using the hash redis store 300 else 60 end end
(コメントの Chat GPT 訳)
ここでの調整は、Redis またはサービスが過負荷になるのを防ぎます。 トレードオフとして、データが利用可能になるまでの遅延があります。 自分で Redis を運用している場合、この数値を増やすことで Redis の CPU への負荷を軽減できます
Coverbandは計測結果をRedisで保持します。
このbackground_reporting_sleep_seconds
の値が大きいほど計測結果が反映されるまでの時間が遅れる代わりにRedisの負荷を軽減できます。
計測結果自体は頻繁に確認するものでもないので、デフォルトの設定のままにしました。
計測対象のディレクトリの追加
なぜかREADMEに載っていなかった、計測対象のディレクトリを追加する方法についても説明します。
計測対象となるディレクトリはデフォルトではapp
、lib
、config
ですが、これに例えばhoge
ディレクトリも加えたい場合は、config/coverband.rbに、
Coverband.configure do |config| config.search_paths = %w[hoge] end
と書けば実現できます。
一見すると+=
じゃなくていいの?と変な感じがするかもしれませんが、この書き方でOKなのは、実装が以下のようになっているからです。
# https://github.com/danmayer/coverband/blob/0d44226727d8bec200138f7af9e9a383a6efabc4/lib/coverband/configuration.rb#L214-L219 ### # Don't allow the to override defaults ### def search_paths=(path_array) @search_paths = (@search_paths + path_array).uniq end
以下のように明示的に書いてもいいと思います。
config.search_paths = %w[app lib config hoge]
Coverband と Amazon ElastiCache
先ほど少し述べた通り、Coverbandは計測データを保持するのにRedisを使用しています。必要なRedisのスペックについてはREADMEに以下のように書かれていました。
A few folks have asked about what size of Redis is needed to run Coverband. I have some of our largest services with hundreds of servers on cache.m3.medium with plenty of room to spare. I run most apps on the smallest AWS Redis instances available and bump up only if needed or if I am forced to be on a shared Redis instance, which I try to avoid.
(Chat GPT 訳)
Redis を使用してCoverbandを実行するために必要なサイズについて、何人かの方から質問がありました。 私たちの最大のサービスのいくつかは、cache.m3.medium で運用しており、余裕がたっぷりあります。 ほとんどのアプリは、AWS の最小の Redis インスタンスで運用しており、必要がある場合や共有 Redis インスタンスに移らざるを得ない場合にのみサイズを上げていますが、私はそれを避けるようにしています。
今回はAmazon ElastiCacheを利用してRedisを管理し、キャッシュノードタイプはcache.t4g.microを採用しました。 私たちのチームでは計測対象が約800ファイル、合計約18,000行で、メモリ使用率は3%台なので、結構余裕があります。
実際に運用してみて
Okuribitoの場合、監視したいメソッドを登録するところから始まりますが、Coverbandの場合は、使用していないメソッドや行が一目ですぐにわかるので非常に便利です。
一方で、Okuribitoにはメソッドがどこから呼び出されたのかを記録する機能があります。 この機能により、メソッドが動的に呼び出されている場合でも、どこから呼ばれているかを把握できるため、非常に便利です。 Coverbandにはこのような機能がなく、一見使われていなさそうなメソッドが実行されていることがわかっても、どこから呼ばれているのかはわかりません。
そのため私たちのチームでは、コードの使用状況の監視はCoverbandで行い、メソッドの呼び出し元の確認にはOkuribitoを使う、というように用途によってこれら2つを使い分けることにしました。
おわりに
本記事では未使用のコードを発見するためのGem、Coverbandの概要や、実際に使ってみてどうだったかを紹介しました。 計測対象のディレクトリを追加する方法やElasitCacheのキャッシュノードタイプの話は自分で調べていてもあまり情報がなかったので、今回の記事がどなたかの参考になれば幸いです。