Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

リンティング(Lint)とは、ソースコードに含まれるプログラム上の誤りやスタイル違反を自動的に検出する仕組みです。このチェックは「リンター」と呼ばれる静的コード解析ツールによって行われます。一方、コードフォーマッターは、あらかじめ設定されたルールセットにソースコードを厳密に準拠させることに特化したツールです。一般的にリンターは違反を報告するだけで、実際の修正はプログラマーの手に委ねられます。それに対してコードフォーマッターは、ルールを直接ソースコードに適用し、フォーマットのミスを自動的に修正してくれます。

プロジェクトで一貫性のあるコードスタイルを実現するには、通常リンティング用とフォーマット用の別々のツールを導入する必要がありますが、両方の役割を1つのツールでカバーできる場合もあります。その好例がRuboCopです。本記事ではRuboCopを取り上げ、Rubyプロジェクトへのセットアップ方法から、設定オプションの調整による期待通りの出力の実現までを詳しく解説します。さらに、ローカル開発環境への統合にとどまらず、継続的インテグレーション(CI)ワークフローの一部として組み込む方法についても学びます。

RuboCopのインストール

RuboCopはRubyGems経由で簡単にインストールできます。

$ gem install rubocop

インストールされたバージョンを確認しましょう。

$ rubocop --version
1.18.3

Bundlerを使いたい場合は、以下の記述をGemfileに追加してbundle installを実行してください。require: falseの指定により、Bundler.requireがこのgemを読み込まなくなります。RuboCopはコマンドラインからのみ使用するため、この設定が推奨されます。

gem 'rubocop', require: false

Bundler経由の場合も、バージョンの確認方法は同様です。

$ bundle exec rubocop --version
1.18.3

RuboCopの実行

デフォルト設定のままプロジェクトに対してRuboCopを実行するには、rubocopコマンド(Bundler利用時はbundle exec rubocop)を入力するだけです。引数を何も渡さない場合、カレントディレクトリおよびすべてのサブディレクトリ内のRubyファイルがチェック対象になります。また、解析対象となるファイルやディレクトリを明示的に指定することも可能です。

$ bundle exec rubocop
$ bundle exec rubocop src/lib

特別な設定を行わなくても、RuboCopはコミュニティ主導のRuby Style Guideで定められたガイドラインの多くを強制します。コマンド実行後には複数のエラー(違反)が報告されることがあります。各違反には、違反内容の説明や発生箇所のファイル名・行番号など、解決に必要な情報がすべて付与されています。

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

レポートの最下部には、検査されたファイル数、違反の総数、そして自動修正可能な違反の数が表示されます。-aまたは--auto-correctオプションを付けると、RuboCopはソースファイル内の問題([Correctable]と表示されているもの)を自動的に修正しようとします。

$ bundle exec rubocop -a

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

修正された違反には[Corrected]というプレフィックスが付き、レポート下部には修正件数のサマリーも表示されます。上記の例では、-aフラグを付けても自動修正されなかった違反が1つ残っています。これは、一部の自動修正がコードの意味(セマンティクス)をわずかに変える可能性があるため、RuboCopが「安全でない」と判断しているからです。こうした違反も含めて自動修正したい場合は、-Aまたは--auto-correct-allフラグを使用します。

$ bundle exec rubocop -A

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

経験則として、自動修正機能を使った後は必ずテストスイートを実行し、コードの挙動が意図せず変わっていないことを確認するとよいでしょう。

RuboCopの設定

RuboCopは、プロジェクトのルートに配置した.rubocop.ymlファイルで設定できます。すべてのプロジェクトで共通のチェックを使いたい場合は、ホームディレクトリ(~/.rubocop.yml)またはXDG設定ディレクトリ(~/.config/rubocop/config.yml)にグローバル設定ファイルを置くことも可能です。カレントディレクトリや親ディレクトリ群にプロジェクト固有の設定ファイルが見つからない場合、このグローバル設定が使われます。

RuboCopのデフォルト設定は設定ホームディレクトリ内の~/.config/rubocop/default.ymlにあり、他のすべての設定ファイルはこれを継承します。つまり、プロジェクトの設定ではデフォルトと異なる部分だけを記述すればよいのです。具体的には、特定のチェックの有効化・無効化や、パラメータを受け付けるチェックの挙動変更などが該当します。

RuboCopでは個々のチェックを「cop(警察官)」と呼び、それぞれが特定の違反の検出を担当します。利用可能なcopは、次のような部門(department)に分類されています。

  • Style cops: 前述のRuby Style Guideに基づくものが多く、コードの一貫性をチェックします。
  • Layout cops: 空白文字の使い方など、フォーマット関連の問題を検出します。
  • Lint cops: ruby -wと同様のコード内の潜在的なエラーを、さらに多くの追加チェック付きで検出します。
  • Metric cops: クラスの長さやメソッドの長さなど、ソースコードの計測に関する問題を扱います。
  • Naming cops: 命名規則に関わるチェックを行います。
  • Security cops: 潜在的なセキュリティ問題の検出を支援します。
  • Bundler cops: GemfileなどのBundlerファイルにおける悪い慣行をチェックします。
  • Gemspec cops: .gemspecファイルにおける悪い慣行をチェックします。

また、RuboCopは追加のリンターやフォーマッターで拡張することもできます。独自の拡張を作成してもよいですし、プロジェクトに関連する既存の拡張を活用することも可能です。たとえばRails向けには、Railsのベストプラクティスやコーディング規約を強制するための拡張が提供されています。

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

初めて設定ファイルを作成すると、「新しく追加されたcopが未設定である」という警告メッセージが大量に表示されることがあります。これは、RuboCopがリリースごとに新しいcopを追加しており、ユーザー設定で明示的に有効化または無効化されるまで、それらを特別な「pending」ステータスにしているためです。メッセージに列挙されたcopを個別に有効/無効にすることもできますが、以下のようにすべての新規copを有効化するのがおすすめです。設定後は警告が表示されなくなります。

# .rubocop.yml
AllCops:
  NewCops: enable

設定ファイルや豊富なオプションをいじりたくない場合は、Standardプロジェクトを検討してみてください。Standardは、事前設定済みのRuboCopと言えるもので、ルールのカスタマイズを許さずにRubyプロジェクトへ一貫したスタイルを強制することを目指しています。初めて発表された際のライトニングトークでは、その背景や動機について詳しく語られています。

Standardは、Gemfileに以下の行を追加してbundle installを実行すればインストールできます。

# Gemfile
gem "standard", group: [:development, :test]

その後、コマンドラインから次のように実行します。

$ bundle exec standardrb

既存プロジェクトへのRuboCop導入

多くのRubyistにとって、ゼロから始められるグリーンフィールドプロジェクトに恵まれることは稀です。開発時間の大半はレガシーコードベースでの作業に費やされ、すぐには対応しきれない膨大な量のリント違反が発生していることも少なくありません。幸い、RuboCopには既存の違反の許可リスト(allowlist)を生成できる便利な機能があり、時間をかけて段階的に対応することが可能です。これにより、手に負えない大量のリントエラーに圧倒されることなく既存プロジェクトへリンティングを導入でき、今後新たに発生する違反だけを確実に検知できるようになります。

$ bundle exec rubocop

523 files inspected, 1018 offenses detected

許可リストの設定ファイルは、次のコマンドで生成できます。

$ bundle exec rubocop --auto-gen-config
Added inheritance from `.rubocop_todo.yml` in `.rubocop.yml`.
Created .rubocop_todo.yml.

--auto-gen-configオプションは、すべての違反とその件数を収集し、現在の違反をすべて無視する.rubocop_todo.ymlファイルをカレントディレクトリに生成します。さらに、.rubocop.yml.rubocop_todo.ymlを継承するようになるため、コードベースに対して再度RuboCopを実行しても違反は一切報告されません。

$ bundle exec rubocop
523 files inspected, no offenses detected

許可リストファイルの生成時に、RuboCopは違反数が一定の閾値(デフォルトで15件)を超えたcopを丸ごと無効化します。これは通常望ましい挙動ではありません。既存の違反が多いために、新しく書いたコードまでそのcopでチェックされなくなってしまうからです。幸い、閾値を引き上げれば、違反数が多くてもcopが無効化されないようにできます。

$ bundle exec rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000

--auto-gen-only-excludeオプションを付けると、許可リスト内の各copがMax(copごとの除外ファイル最大数)ではなく、違反が発生したファイルを列挙するExcludeブロックを持つようになります。また、--exclude-limitを指定すると、各copのExcludeブロックに追加できるファイル数の上限も変更されます。検査対象の総ファイル数より大きい任意の数値を指定すれば、どのcopも丸ごと無効化されることはなく、既存・新規ファイルに追加された新しいコードも確実にチェックされるようになります。

既存の違反を修正する

.rubocop_todo.ymlを生成した後は、既存の違反を忘れずに、ひとつずつ着実に潰していくことが重要です。手順としては、あるcopのExcludeブロックからファイルを1つ削除し、報告された違反を修正し、バグを持ち込んでいないことを確認するためにテストスイートを実行してからコミットします。あるcopからすべてのファイルを除外し終えたら、そのcopの記述を手動で削除するか、許可リストファイルを再生成すればOKです。可能な場面では--auto-correctオプションを活用すれば、作業を大幅に高速化できます。

スタイルガイドの採用

RuboCopは非常に柔軟に設定できるため、どんな種類のプロジェクトにも対応できます。しかし、特にデフォルトのルールに同意できない点が多い場合、自分の要件に合わせてルールを調整するには時間がかかることがあります。そうしたケースでは、既存のスタイルガイドを採用するのが有効です。ShopifyやAirbnbなど、いくつかの企業がすでに自社のRubyスタイルガイドを公開しています。お好みのスタイルガイドをRuboCopで利用するには、対応するgemをGemfileに追加します。

# Gemfile
gem "rubocop-shopify", require: false

続いて、プロジェクトの設定ファイルでそれをrequireします。

# .rubocop.yml
inherit_gem:
  rubocop-shopify: rubocop.yml

リントエラーの抑制

RuboCopは優れたツールですが、ときに誤検出(false positive)を報告したり、プログラマーの意図に反する修正を提案したりすることがあります。そのような場面では、ソースコード内のコメントで違反を無視できます。無効化したい個々のcopや部門を指定するには、以下のように書きます。

# rubocop:disable Layout/LineLength, Style
[..]
# rubocop:enable Layout/LineLength, Style

あるいは、コードの一部区間についてすべてのcopをまとめて無効化することもできます。

# rubocop:disable all
[..]
# rubocop:enable all

行末コメントを使った場合は、指定したcopがその行のみで無効化されます。

for x in (0..10) # rubocop:disable Style/For

エディタとの統合

コマンドラインで毎回チェックを実行するのではなく、エディタでコードを入力しながらリアルタイムにRuboCopの警告やエラーを確認できれば便利です。幸い、RuboCopとの統合は主要なコードエディタやIDEのほとんどで、主にサードパーティ製プラグインを通じて利用可能です。Visual Studio Codeであれば、Ruby拡張機能をインストールし、ユーザーのsettings.jsonに以下を追記するだけです。

{
  "ruby.lint": {
    "rubocop": true
  }
}

VimやNeovimを使っている場合は、coc.nvim経由でRuboCopの診断結果を表示できます。まずSolargraph言語サーバー(gem install solargraph)をインストールし、続けてcoc-solargraph拡張(:CocInstall coc-solargraph)を入れます。その後、coc-settings.jsonを以下のように設定します。

{
  "coc.preferences.formatOnSaveFiletypes": ["ruby"],
  "solargraph.autoformat": true,
  "solargraph.diagnostics": true,
  "solargraph.formatting": true
}

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

pre-commitフックの設定

プロジェクト内のすべてのRubyコードが、バージョン管理にコミットされる前にきちんとリント・フォーマットされていることを保証する優れた方法が、Gitのpre-commitフックの設定です。ステージングされた各ファイルに対してRuboCopを実行するようにします。本記事では、Gitのpre-commitフックを管理・設定するためのツール「Overcommit」を使う方法を紹介しますが、すでにpre-commitワークフローをお持ちなら、他のツールとRuboCopを統合することも可能です。

まずOvercommitをRubyGems経由でインストールし、プロジェクトにセットアップします。

$ gem install overcommit
$ overcommit --install # プロジェクトのルートで実行

2番目のコマンドにより、カレントディレクトリにリポジトリ固有の設定ファイル(.overcommit.yml)が作成され、既存のフックはバックアップされます。このファイルはデフォルト設定を継承するため、デフォルトからの差分だけを記述すればよい構造になっています。たとえば、以下のスニペットでRuboCopのpre-commitフックを有効化できます。

# .overcommit.yml
PreCommit:
  RuboCop:
    enabled: true
    on_warn: fail
    problem_on_unmodified_line: ignore
    command: ['bundle', 'exec', 'rubocop']

on_warn: failを指定するとOvercommitは警告を失敗として扱い、problem_on_unmodified_line: ignoreを指定すると、ステージングされていない行に関する警告やエラーは無視されます。利用可能なフックオプションとその許容値については、プロジェクトのGitHubページで確認できます。設定ファイルを変更した後は、変更を反映させるためにovercommit --signの実行が必要な場合があります。

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

作業途中のファイルなど、すべてのチェックをパスしていないファイルをどうしてもコミットしたい場合は、ケースごとに個別のチェックをスキップできます。

$ SKIP=RuboCop git commit -m "WIP: Unfinished work"

CIワークフローへのRuboCop追加

プルリクエストごとにRuboCopのチェックを実行することも、整形不良のコードがプロジェクトにマージされるのを防ぐ有効な手段です。CIツールはどれでも使えますが、ここではGitHub Actions経由でRuboCopを実行する方法を説明します。

まず、プロジェクトのルートに.github/workflowsディレクトリを作成し、その中にrubocop.ymlファイルを作ります。エディタでファイルを開き、以下のように記述してください。

# .github/workflows/rubocop.yml
name: Lint code with RuboCop

on: [push, pull_request]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]

    steps:
    - uses: actions/checkout@v2

    - name: Setup Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: '3.0'
        bundler-cache: true

    - name: Run RuboCop
      run: bundle exec rubocop

上記のワークフローファイルは、コードがGitHubにプッシュされたとき、または任意のブランチに対してプルリクエストが出されたときに実行される単一のジョブを定義しています。ジョブとは、順番に実行される一連のステップのことです。このジョブは、runs-onstrategy.matrixで定義されているとおり、GitHub Actionsが提供する最新のUbuntu、macOS、Windows環境でそれぞれ1回ずつ実行されます。最初のステップでリポジトリのコードをチェックアウトし、次のステップでRubyツールチェーンと依存関係をセットアップし、最後のステップでRuboCopを実行します。

ファイルの編集が完了したら保存し、コミットしてGitHubにプッシュしましょう。以降のチェックインやプルリクエストでは、報告された問題がインラインで表示されるようになります。

RuboCopで実現するRubyコードのリントと自動フォーマット徹底ガイド

代替となる自動フォーマッター

RuboCopは包括的な自動フォーマット機能を備えていますが、ニーズを十分に満たせない場合に備えて、代替ツールの存在も把握しておくと安心です。

Prettier

Prettierは、JavaScript向けの独断的なコードフォーマッターとして登場しましたが、現在ではRubyを含む多くの言語をサポートしています。Rubyプラグインのインストールは簡単で、Gemfileprettier gemを追加してbundleを実行するだけです。

# Gemfile
gem 'prettier'

これで、次のコマンドでPrettierを使ってRubyコードをフォーマットできます。

$ bundle exec rbprettier --write '**/*.rb'

Prettierのルールの一部はRuboCopのものと競合するため、干渉を避けるには後者のフォーマット系チェックを無効化する必要があります。幸い、競合する、あるいはPrettierと併用時に不要なRuboCopのチェックは簡単にオフにできます。プロジェクトの.rubocop.ymlの先頭で、PrettierのRuboCop設定を継承するだけです。

# .rubocop.yml
inherit_gem:
  prettier: rubocop.yml

以降、bundle exec rubocopを実行してもレイアウト関連の違反は報告されなくなり、Prettierが自らのルールに従ってそれらを修正できるようになります。Prettierの出力は設定ファイルでも調整可能で、同じプロジェクト内のJavaScriptコードとRubyコード間で設定を共有できます。

RubyFmt

RubyFmtはRustで書かれた新しいコードフォーマッターで、現在も活発に開発が進められています。Prettierと同様、コード解析ツールではなくフォーマッターとして設計されています。まだ安定版リリースには至っていないため、今すぐ採用するのは控えたほうがよいでしょうが、今後注目すべきツールのひとつです。

まとめ

コードのリンティングと自動フォーマットは、コードベースに多大なメリットをもたらします。特に開発者チームで作業する場合にはその効果が顕著です。「自分のコードの書き方を指図されたくない」と感じる人でも、リンティングは自分のためだけでなく、一緒に開発する仲間のためのものであることを忘れてはいけません。全員が同じ規約を守ることで、同一プロジェクト内に複数のコーディングスタイルが混在することによる弊害を排除できます。

同時に、リンターの出力を絶対視してはいけません。本来の目的の妨げにならず、かつ最大限のメリットが得られるようにツールを設定することを心がけましょう。RuboCopには豊富な設定項目があるため、これは難しくありません。それでも設定に時間をかけすぎると感じたら、前述のような既存のスタイルガイドを利用するか、細部に悩むことなく誰もがすぐ使えるノンコンフィグの選択肢としてStandardを採用するのも良いでしょう。

最後までお読みいただきありがとうございました。Happy Coding!

  1. Rubyでの静的分析入門!parser gemでメソッド定義を抽出する方法

    ソースコードを解析して、すべてのメソッドがどこで定義され、どんな引数を受け取るのかを把握したいと思ったことはありませんか? どうすれば実現できるのでしょうか? 最初に思いつくのは、正規表現(regexp)を書くことかもしれません。 しかし、もっと良い方法があるとしたらどうでしょう? 答えは「あります」! 静的解析(Static Analysis)とは、ソースコードそのものから情報を抽出するためのテクニックです。 これは、ソースコードをトークンへと変換する(パースする)ことで実現されます。 それでは早速見ていきましょう! parser gemを使う Rubyには標準ライブラリとしてRipper

  2. iPadとiPhoneでQRコードをスキャンする方法|アプリ不要で簡単に読み取る手順

    2018年に登場したiOS 12には、iPadやiPhoneのユーザー体験を大きく向上させる便利な機能が多数追加されました。そのひとつが「QRコードのスキャン」機能です。この機能を使えば、チケットやクーポン、商品の成分表示などの情報を素早く取得でき、買い物の前に商品について数秒で調べることも可能になります。さらに、スマートフォンのカメラだけで直感的にQRコードを読み取れるため、面倒な操作も一切必要ありません。この記事では、iPadとiPhoneでQRコードをスキャンする具体的な方法を詳しく解説します。QRコードとは?QRコード(Quick Responseコード)とは、白と黒の四角形が格子状に