虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

社内のRuboCopを標準化した話

こんにちは、とらのあなラボのはっとりです。

今回は、開発環境で使用しているRuboCopの設定を標準化したプロセスについてお話しします。

RuboCopの重要性

RuboCopは、Ruby言語で書かれたコードを静的解析することで、一貫性のあるコーディングスタイルを促進し、潜在的なバグを指摘するツールです。開発チームが成長するにつれて、コードベースも複雑化し、多くの開発者が関与するようになります。このとき、一貫したコーディング標準がなければ、コードの可読性や保守性が大きく低下するリスクがあります。RuboCopは、このような問題を事前に防ぎ、効率的でバグの少ない開発プロセスを支援します。
https://github.com/rubocop/rubocop

プロダクト間でのRuboCop設定のばらつきとその問題点

とらのあなラボでは、多くのRailsプロダクトを手掛けていますが、プロダクトごとにRuboCopの設定が異なっていました。これが開発者にとって以下のような問題を引き起こしていました

  • バージョンの不一致
    • 開発が異なる時期に始まるため、RuboCopのバージョンが異なり、一貫したコーディングスタイルの適用が困難でした。
  • 過去の設定の継承
    • 新しいプロダクトで既存のプロダクトの設定をそのまま利用することがあり、最新のベストプラクティスが反映されていないことが多くありました。
  • コードスタイルのばらつき
    • 各プロダクトでRuboCopの設定が統一されていないため、コードの書き方にバリエーションが生じていました。
  • 新しいルールの採用遅れ
    • 特に古いプロダクトには新しいRuboCopのルールが反映されておらず、コード品質の向上が阻害されていました。

これらの問題を解消するために、全プロダクトで一貫したRuboCopの設定を導入することが必要とされました。

具体的な設定の紹介

RuboCopの設定を標準化するにあたり、以下の点を考慮しました

  • Gemのバージョン
    • 全プロダクトで同じバージョンのRuboCopを使用するようにしました
  • プロダクトごとのカスタマイズ
    • 標準を決めつつ、プロダクトごとにカスタマイズできるようにしました
    • カスタマイズできる範囲についても定めました
  • 既存を考慮した設定
    • 既存の修正が大量に発生しやすいものは今回は見送りました(将来的に少しずつ厳しくしていく予定)
    • 自動修正が可能なルールであれば既存の修正が多くても有効にしました
  • 非Rails用設定
    • 例えばLambdaなど Ruby を使っているがRailsを使っていないプロダクトもあるので非Rails用設定も用意しました
    • 今回のブログでは紹介しないのですが、Rails用の設定からRailsのルールを除いただけのものになります

具体的な設定手順を以下に示します

設定手順

Gemfileに対して以下を追加する ※gemバージョンについては定期的に更新予定

group :development, :test do
  gem 'rubocop', '~> 1.63.0', require: false
  gem 'rubocop-performance', '~> 1.21.0', require: false
  # Railsでない場合は以下1行削除
  gem 'rubocop-rails', '~> 2.24.1', require: false
  gem 'rubocop-rake', '~> 0.6.0', require: false
end

追加したら bundle install する

次に、以下の3ファイルを用意する

  • .rubocop.yml
    • プロダクト独自ルールを入れる場合はここに入れる。
  • .rubocop_base.yml
    • とらのあなラボ共通設定。定期的に最新を取得すること。
  • .rubocop_todo.yml
    • 標準設定導入にあたって、一時的に無効化するルール。

.rubocop_todo.yml は初期は空ファイルとして用意する

.rubocop_base.ymlの内容一部抜粋(全体はリンクから参照してください。)

require:
  - rubocop-performance
  - rubocop-rails
  - rubocop-rake
AllCops:
  NewCops: enable
  SuggestExtensions: true
Rails:
  Enabled: true

inherit_mode:
  merge:
    - Exclude

# クラスあたりの長さを制限
Metrics/ClassLength:
  Max: 120
  CountAsOne:
    - array
    - hash
    - heredoc
    - method_call

# #### 中略 ####

# 以降はRubyの専用記法を強制するルールだが、あえて書き直すほどではないため無効化

Style/NumericPredicate:
  Enabled: false
Style/SymbolArray:
  Enabled: false
Style/WordArray:
  Enabled: false

.rubocop.ymlの内容一部抜粋(全体はリンクから参照してください。)

# TODO: 利用しているライブラリがあればコメントアウトを外して追加する
# 必要なければコメントごと削除する
# require:
#   - rubocop-capybara
#   - rubocop-factory_bot
#   - rubocop-rspec
#   - rubocop-rspec_rails
AllCops:
  # TODO: 利用しているRubyバージョンを追加する
  TargetRubyVersion: 3.2
  # TODO: 利用しているRailsバージョンを追加する
  TargetRailsVersion: 6.1

# #### 中略 ####

# TODO: Metricsで必要な設定があればコメントアウトを外して値を変更する。
# ただし、許容値以上にはしないこと
# 必要がない設定はコメントごと削除する

# # クラスあたりの長さを制限
# Metrics/ClassLength:
#   Max: 120 # 200まで許容

# #### 中略 ####

.rubocop.yml は TODO項目を対応した上でプロダクトに配置する

設置したら以下のコマンドで .rubocop_todo.yml を生成する

bundle exec rubocop --auto-gen-config --exclude-limit 99999 --no-auto-gen-timestamp --auto-gen-only-exclude

これにより既存のRuboCopの警告は出なくなる。※後々、.rubocop_todo.yml の量を減らす対応は必要になる

具体的な改修ルールの紹介

.rubocop_todo.yml で無効化したものが大量にあるのでそれを減らすためのルールや手順も定めました。

  • 大量の修正になりそうであればプルリクエストを分ける
    • レビュワーの負担緩和のため
    • その際は機能改修は入れないようにする
  • .rubocop_base.yml は定期的に更新するので、定期的にプロダクトへ反映する必要がある
  • 自動修正は積極的に使う
    • rubocop -a で安全に修正できる自動修正である旨をプルリクエストに記載する。
    • rubocop -A で修正した場合は、動作やパフォーマンスが変わるものもあるので必ず手動での動作確認もする
  • 何らかの理由でルールの変更や無効化をする場合はなるべくアーキテクトチームに相談の上で行う

当初はRuboCopの修正と機能改修は必ず別にするルールにする予定でしたが、既存の問題修正を減らしやすくするため、大量でない限りは機能改修には混ぜてもよいルールにしました。

既存の警告へ対応するための工夫

.rubocop_todo.yml で既存の問題点を除外するようにしましたが、このままだと除外内容が放置されやすくなります。

そこで、コミット対象のファイルに抑制したRuboCop警告があれば表示する pre-commitのスクリプトを作りました。
※ChatGPTにほとんど作ってもらいました。

#!/bin/sh

# RuboCop実行関数
run_rubocop() {
  bundle exec rubocop
}

# ステージングされている Ruby ファイルのリストを取得
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.rb$')
if [ -z "$files" ]; then
  exit 0
fi

# `.rubocop_todo.yml` のバックアップを一時ディレクトリに作成
cp .rubocop_todo.yml /tmp/.rubocop_todo.yml.bak

# `.rubocop_todo.yml` を一時的に空にする
> .rubocop_todo.yml

# RuboCop を実行し、出力を変数に格納
output=$(run_rubocop "$files")
status=$?

# `.rubocop_todo.yml` を元に戻す
mv /tmp/.rubocop_todo.yml.bak .rubocop_todo.yml

# 終了ステータスが 0 以外の場合は、エラーがあるため出力を表示
if [ $status -ne 0 ]; then
  echo "======================================================================"
  echo "RuboCop warnings:"
  echo "$output"
  echo "\033[33m コミット対象ファイルの中にRuboCopの警告があります。可能であれば修正してください。 \033[0m"
  echo "\033[33m 修正後は以下のコマンドを実行してください。 \033[0m"
  echo "\033[33m bundle exec rubocop --auto-gen-config --exclude-limit 99999 --no-auto-gen-timestamp --auto-gen-only-exclude \033[0m"
  echo "======================================================================"
fi

# コミットを常に続行
exit 0

コミット対象ファイルにRuboCop警告があれば以下のような表示になります。

内容確認して、もし余裕があればついでに直してもらいます。
除外したRuboCop警告に気付いてもらうためのものであるためコミットの阻害まではしません。

フィードバックと今後の展望

新しいルールを適用した後、各チームからのフィードバックを受けます。このフィードバックを基に、ルールや設定の微調整を行っています。各開発チームがより効率的に、より快適で安全にコードを書ける環境を目指しています。

まとめ

この標準化プロジェクトを通じて、チーム全体のコードの一貫性が向上し、新しいメンバーがプロジェクトに参加しやすくなることを期待しています。また、RuboCopの新しいルールを随時評価し、適宜設定を更新していくことで、技術的負債の軽減とコード品質の維持向上を図っていきます。

採用情報

虎の穴ラボでは一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧ください。
toranoana-lab.co.jp