🤖

RuboCopによるコード修正のススメ:効率的な開発フロー

2024/06/06に公開

はじめに

Rubyための静的コード解析ツール、RuboCopの導入と使い方の紹介です

動作環境

RSpecの導入と使い方で構築した環境を使います。
Ruby 3.2.2
Rails 7.0.8
MySQL 8.0

RuboCopとは

Rubocopは、Rubyプログラミング言語のための静的コード解析ツールで、コードスタイルのガイドラインに従ってコードが書かれているかどうかをチェックするために使用されます。Rubocopは、特にRubyコミュニティで広く受け入れられているスタイルガイドに基づいており、コードの一貫性と可読性を向上させることを目的としています。

RailsにRuboCopを導入する

インストール

api/Gemfile
group :development, :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
  gem "bullet"
+ gem 'rubocop', require: false
+ gem 'rubocop-performance', require: false
+ gem 'rubocop-rails', require: false
+ gem 'rubocop-rspec', require: false
end
ターミナル
docker compose run --rm api bundle install
docker compose build api # イメージの再構築

初期設定

ターミナル
docker compose run --rm api bundle exec rubocop --auto-gen-config

bundle exec rubocop --auto-gen-config のコマンドでapi/配下に.rubocop.yml.rubocop_todo.ymlのファイルが作成されます。

.rubocop.yml

このファイルはRuboCopのメイン設定ファイルで、プロジェクト全体のルールや設定を定義します。

.rubocop_todo.yml

このファイルは、現在のコードベースでRuboCopが検出した問題点を一時的に無視するために使用されます。--auto-gen-config コマンドを実行すると、RuboCopはコードベース全体をチェックし、検出された違反をこのファイルに記録します。
違反しない形に修正して、このファイルから該当行を削除していきます。

おすすめの設定

デフォルトの設定は厳しめなので、状況あわせて調整するのがいいと思います。

api/.rubocop.yml
inherit_from: .rubocop_todo.yml
require:
  - rubocop-rails
  - rubocop-performance
  - rubocop-rspec
AllCops:
  TargetRubyVersion: 3.1
  SuggestExtensions: false
  # 最新のルールを適用する
  NewCops: enable
  # 何のルールに引っかかったか表示する
  DisplayCopNames: true
  # rubocop対象外(リポジトリ毎で調節)
  Exclude:
    - "Gemfile"
    - "bin/**/*"
    - "db/**/*"
    - "log/**/*"
    - 'public/**/*'
    - "tmp/**/*"
    - "vendor/**/*"
    - "config/environments/*"
    - 'config/initializers/**/*'
    - "config/puma.rb"
### ルールのカスタマイズ
# RSpecは1つのブロックあたりの行数が多くなるため、チェックの除外から外す
# ブロック内の行数をチェックする
Metrics/BlockLength:
  Exclude:
    - "spec/**/*"
# Assignment: 変数への代入
# Branch: メソッド呼び出し
# Condition: 条件文
# 上記項目をRubocopが計算して基準値を超えると警告を出す(上記頭文字をとって'Abc')
Metrics/AbcSize:
  Max: 30
# メソッドの中身が複雑になっていないか、Rubocopが計算して基準値を超えると警告を出す
Metrics/PerceivedComplexity:
  Max: 8
# 循環的複雑度が高すぎないかをチェック(ifやforなどを1メソッド内で使いすぎている)
Metrics/CyclomaticComplexity:
  Max: 10
# メソッドの行数が多すぎないかをチェック
Metrics/MethodLength:
  Max: 30
# ネストが深すぎないかをチェック(if文のネストもチェック)
Metrics/BlockNesting:
  Max: 5
# クラスの行数をチェック(無効)
Metrics/ClassLength:
  Enabled: false
# 一行あたりの文字数
Layout/LineLength:
  Max: 130
  # 下記ファイルはチェックの対象から外す
  Exclude:
    - "Rakefile"
    - "spec/rails_helper.rb"
    - "spec/spec_helper.rb"
# メソッドの改行ルール
Layout/MultilineMethodCallIndentation:
  EnforcedStyle: indented
# 日本語にコメントを許可
Style/AsciiComments:
  Enabled: false
# クラスにコメントを残さなくても良い
Style/Documentation:
  Enabled: false
# コントローラ等のモジュールをネストしての宣言
Style/ClassAndModuleChildren:
  Enabled: false
# 文字列のfreeze(Ruby3からは自動でfreezeされるので要らない)
Style/FrozenStringLiteralComment:
  Enabled: false
# ガード節の提案
# メソッドの冒頭部分で条件判定して処理を終了し、メインの処理へ影響を与えないようにします。
Style/GuardClause:
  MinBodyLength: 5
# 文字列のダブルクォートチェック
Style/StringLiterals:
  Enabled: false
# シンボルで構成される配列リテラルをチェック
Style/SymbolArray:
  Enabled: false
# 文字列による配列の%記法のチェック
Style/WordArray:
  Enabled: false
# 変数名に数字を許可
Naming/VariableNumber:
  Enabled: false
# メソッド名等の命名の指摘
Naming/PredicateName:
  Enabled: false

実行

ターミナル
docker compose run --rm api bundle exec rubocop
# 実行結果
Inspecting 24 files
........C...........C.C.
Offenses:
app/models/author.rb:2:3: C: Rails/HasManyOrHasOneDependent: Specify a :dependent option.
  has_many :books
  ^^^^^^^^
spec/models/user_spec.rb:3:22: C: [Correctable] RSpecRails/InferredSpecType: Remove redundant spec type.
RSpec.describe User, type: :model do
                     ^^^^^^^^^^^^
spec/models/user_spec.rb:4:3: C: [Correctable] RSpec/EmptyLineAfterFinalLet: Add an empty line after the last let!.
  let!(:user) { create(:user, first_name: "Suzuki", last_name: "Jiro") }
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
spec/requests/api/v1/authors_spec.rb:3:36: C: [Correctable] RSpecRails/InferredSpecType: Remove redundant spec type.
RSpec.describe "Api::V1::Authors", type: :request do
                                   ^^^^^^^^^^^^^^
spec/requests/api/v1/authors_spec.rb:10:5: C: RSpec/ExampleLength: Example has too many lines. [13/5]
    it "returns a list of authors with their books" do ...
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
spec/requests/api/v1/authors_spec.rb:10:5: C: RSpec/MultipleExpectations: Example has too many expectations [7/1].
    it "returns a list of authors with their books" do
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
spec/requests/api/v1/authors_spec.rb:14:14: C: [Correctable] Rails/ResponseParsedBody: Prefer response.parsed_body to JSON.parse(response.body).
      json = JSON.parse(response.body)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
spec/requests/api/v1/authors_spec.rb:22:9: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
        author["books"].each do |book|
        ^^^^^^^^^^^^^^^^^^^^
24 files inspected, 8 offenses detected, 4 offenses autocorrectable

指摘事項を修正して再度実行します

ターミナル
docker compose run --rm api bundle exec rubocop
# 実行結果
Inspecting 24 files
........................
24 files inspected, no offenses detected

Github ActionsでRuboCopを実行する

push時にGithub Actionsで実行するようにWorkFlowを追加します。

.github/workflows/api_rubocop.yml
name: Run RuboCop
on:
  push:
    paths:
      - 'api/**'
jobs:
  rubocop:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        working-directory: api
        ruby-version: '3.2.2'
        bundler-cache: true
    - name: Run RuboCop
      working-directory: api
      run: bundle exec rubocop

Discussion