Feedforce Developer Blog

フィードフォース開発者ブログ

まだ .rubocop_todo.yml で消耗してるの?

どうも、バックエンドエンジニアのサトウリョウスケです ✌︎('ω')✌︎

若干釣り臭いタイトルですが、先日 RubocopChallenger という gem の v1.0.0 をリリースしたので紹介させて頂きます 🙏

github.com

経緯

僕が所属している ソーシャルPLUS は 2012 年頃から開発が始まりました。Rails のプロダクトとしては古株の方だと思います。

ソーシャルPLUS に RuboCop が導入されたのは 2017/02 頃 1 で、それまで特に RuboCop を意識したコードで開発を進めてこなかったので、巨大な .rubocop_todo.yml が出力され、それが手付かずのままになってしまっていました。ちなみに当初は 1669 行 195 種類 の違反ルールがありました。

このままでは RuboCop の恩恵が受けられないので、 RuboCop Challenge と称して (以前 Ice Bucket Challenge が流行っていたので) 週イチで .rubocop_todo.yml から違反ルールを一つ消して、 auto-correct で修正する、という事をやっていたのですが、数ヶ月(数週間だったかも)ですっかりやるのを忘れてしまいました 😇

最近また RuboCop Challenge を再開しよう、という流れになったのですが、手動でコツコツやるのも精神的にしんどくなって来たので、なんとか自動化したいな、という気持ちになり、手動でやっていた Rubocop Challenge を Ruby スクリプトで動かせるようにしました。 最初は単純な Ruby スクリプトだったのですが、せっかくなので gem 化しよう、という事になり、作成したのが RubocopChallenger です。

ところで、弊社エンジニアの id:masutaka26 が circleci-bundle-update-pr という CI を利用した Bundle Update の自動更新 gem を作成しています。

github.com

これに感銘を受けて (?) 自分の RubocopChallenger も CI から$ rubocop --auto-correct を実行した結果が PR として届くような仕組みになっています。

使い方

2021-09-23 修正

1. .circleci/config.yml の編集

以下に設定例を紹介します。

# .circleci/config.yml
version: 2

jobs:
  rubocop_challenge:
    docker:
      - image: circleci/ruby:2.5-node-browsers
    working_directory: ~/repo
    steps:
      - checkout
      - run:
          name: Rubocop Challenge
          command: |
            gem install rubocop_challenger
            bundle exec rubocop_challenger go \
              --email={RubocopChallenger が commit する際の user email} \
              --name="{RubocopChallenger が commit する際の user name}"

workflows:
  version: 2

  nightly:
    triggers:
      - schedule:
          cron: "30 23 * * 1,2,3" # この設定の場合、火水木 の毎朝 8:30 に RubocopChallnger の PR が届きます
          filters:
            branches:
              only:
                - master
    jobs:
      - rubocop_challenge

インストールの際、 Gemfile に gem 'rubocop_challenger' を追記しないようにご注意下さい。 他の gem との互換性問題により、 rubocop_challenger 実行中にエラーが発生するケースがあることを確認しております。

2. GitHub personal access token の作成

RubocopChallenger が PR を作成するために GitHub personal access token が必要になります。 Settings から Generate new token をクリックして access token を作成します。 Select Scopes では repo にチェック ✅ を入れて下さい。

Generate new token

3. CircleCI で環境変数の設定

今回は CircleCI での利用例を紹介します 🙏

ダッシュボード画面 に移動し、 RubocopChallenger を適用したいアプリケーションの Project Settings -> Environment Variables へと移動します。 Add Variable をクリックして GITHUB_ACCESS_TOKEN という Key で先程作成した GitHub personal access token を設定します。

Environment Variables

5. 作成された PR の確認

ここまでの手順を終えると、 CircleCI に指定したスケジュールで PR がされるようになると思います。 後は auto-correct の内容を確認して、 merge するだけです。

中には適用したくないルールも出てくると思いますが、その場合は .rubocop.yml にルールを再定義して auto-correct されないようにします。

どんな PR が作成されるのか?

ちょっと RubocopChallenger のバージョンが古い頃の物ですが、 以下のような PR が自動的に作成されます。

RubocopChallnge

デフォルトの設定では、 .rubocop_todo.yml の中から Cop supports --auto-correct かつ Offense count が最大 のルールを消して、 auto-correct した結果が PR として作成されます。

また、 auto-correct の後で $ rubocop --auto-gen-config を実行して .rubocop_todo.yml を再作成しています。 RuboCop のバージョンが変わった時とかに .rubocop_todo.yml に出力される内容も若干変わっていたりするのですが、毎回最新の状態に作り直してくれるのがちょっと便利だったりします。

ちなみに PR の Description に表示されている内容は 本家 RuboCop の RubyDoc に記載されている内容と同じものです。これを表示するのに地味に苦労しました 😓

高度な設定

RubocopChallnger にはいくつかオプションが用意されているので、ご紹介します。

--mode

上述の通り、デフォルトでは auto-correct の対象は Cop supports --auto-correct かつ Offense count が最大 のルールが選択されますが、 mode に渡す値によって対象を変更することが出来ます。

  • most_occurrence (デフォルト)
    • Offense count が最大 のルールを選択する
  • least_occurrence
    • Offense count が最小 のルールを選択する
  • random
    • 全体からランダムに選択する

使用例

$ bundle exec rubocop_challenger go \
    --email=rubocop-challenger@example.com \
    --name="Rubocop Challenger" \
    --mode=random 

--labels

RubocopChallnger が作成する PR に付与される label を指定します。デフォルトでは rubocop challenge というラベルが付与されます。 ソーシャルPLUS では waffle.io を利用していたりするのですが、レビュー待ち状態の label を付与するようにすると見落としがなくて便利です。

スペース区切りで複数指定することが出来ます。

使用例

$ bundle exec rubocop_challenger go \
    --email=rubocop-challenger@example.com \
    --name="Rubocop Challenger" \
    --labels="rubocop challenge" "in progress"

--template

作成される PR をカスタマイズしたい場合などあるかと思います。 その場合は template に erb ファイルのパスを指定することが可能です。

デフォルトでは以下のテンプレートが使用されるので、必要に応じてカスタマイズしてご利用下さい 🙏

https://github.com/ryz310/rubocop_challenger/blob/master/lib/templates/default.md.erb

使用例

$ bundle exec rubocop_challenger go \
    --email=rubocop-challenger@example.com \
    --name="Rubocop Challenger" \
    --template=./path/to/template.md.erb

--no-regenerate-rubocop-todo

上述の通り、デフォルトでは auto-correct の後で $ rubocop --auto-gen-config を実行して .rubocop_todo.yml を再作成しています。これが不要な場合は no-regenerate-rubocop-todo オプションを指定します。

使用例

$ bundle exec rubocop_challenger go \
    --email=rubocop-challenger@example.com \
    --name="Rubocop Challenger" \
    --no-regenerate-rubocop-todo

既知の不具合 (v1.2.0 で解消済み)

RuboCop のルールの中には Cop supports --auto-correct と表記されているにも関わらず、部分的にしか auto-correct してくれないものがあります。 例えば Style/Semicolon が auto-correct できるのは行末に ; が存在する場合だけのようです。

puts 'hoge'; # => auto-correct される
puts 'fuga'; puts 'piyo' # => auto-correct されない

このようなルールが auto-correct 対象に選ばれると、RubocopChallenger を実行した後も違反が解決されず、後続の $ rubocop --auto-gen-config で再度 .rubocop_todo.yml に対象ルールが出てきてしまうので、 RubocopChallenger が機能しない状態になってしまいます。 現状では Style/Semicolon のようなルールに遭遇した場合は、手動で .rubocop.yml にルールを移動させる必要があります。 今のところ RubocopChallenger 側での対策を思い付いていないので、もし良いアイデアがありましたら教えて下さい 🙏

2019/03/26 追記

RubocopChallenger v1.2.0 で Ignore リスト機能が追加されました。 RubocopChallenger を実行した後も違反が解決されず、後続の $ rubocop --auto-gen-config で再度 .rubocop_todo.yml に対象ルールが出てきてしまった場合、 .rubocop_challenger.yml というファイルが作成され、対象ルールが Ignore リストに追加されます。 Ignore リストに追加されたルールは次回以降、 RubocopChallenger の対象ルールとして選択されなくなるので、特に何もしなくとも運用を続けることが可能となります。

最後に

当初は 1669 行 195 種類 の違反ルールが .rubocop_todo.yml に存在していましたが、 RubocopChallenger を導入してから 3 ヶ月で 1187 行 132 種類 まで減らすことが出来ました。 auto-correct 可能な違反ルールはあと 62 種類残っているので、あと半分くらいまでは減らすことができそうです。

本記事では肥大化してしまった .rubocop_todo.yml を自動的に修正していく RubocopChallnger を紹介し、導入方法について解説させて頂きました。 .rubocop_todo.yml が肥大化して困っているプロジェクトで役立てて頂ければ幸いです 🙏

また、利用してみてフィードバックなどあれば Issue にてご連絡下さい。 GitHub では頑張って拙い英語を書くようにしていますが、日本語でも大丈夫です 🙆 PR も大歓迎です。どうぞ宜しくお願い致します 🙇


  1. ちなみに導入したのは自分です