【Ruby on Rails】使われていないコードを見つけるためにCoverbandを入れた話

はじめまして、広告事業本部でアプリケーションエンジニアをしている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に載っていなかった、計測対象のディレクトリを追加する方法についても説明します。

計測対象となるディレクトリはデフォルトではapplibconfigですが、これに例えば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のキャッシュノードタイプの話は自分で調べていてもあまり情報がなかったので、今回の記事がどなたかの参考になれば幸いです。