rubocopでrubyコードのベストプラクティスを学ぶ(Rails + Visual Studio Code)

以下の文章はこんな方を想定しています

vscodeでrailsを書いている。 自分のコードをもっと「いいコード」にしたい。が、指導してくれる人間は周囲にいない。

rubocop

github.com

rubyコードの静的解析を行うGem. ベストプラクティスに従っていない箇所を指摘してくれる。 ベストプラクティスについてはデフォルトで設定されている。変更もできる。

インストール

公式のインストール方法は以下のとおり。

github.com

自分はbundlerを使いインストールした。

group :development, :test do
+  # コードの静的解析ツール
+  gem 'rubocop', require: false
end

インストール。自分の場合、プロジェクト下のvendor/bundleにインストールされる。

bundle install

以下で解析が走る。(システムにrubocopをインストールされた方は、bundle exec無しの rubocop を実行してください。)

bundle exec rubocop

いい感じの解析ルールを設定する

ネットを見る感じ、デフォルトのルールはそのまま使っているひとはあまりいなさそう。厳しすぎるらしい。

しかし、自分で設定する気は起きない… @onk さんの設定を真似させていただくことにした。

github.com

group :development, :test do
   # コードの静的解析ツール
   gem 'rubocop', require: false
+  # rubocopの解析ルール設定
+  gem 'onkcop', require: false
 end
bundle install
bundle exec onkcop init

自動生成されたファイルを編集する

 inherit_gem:
   onkcop:
     - "config/rubocop.yml"
     # uncomment if use rails cops
-    # - "config/rails.yml"
+    - "config/rails.yml"
     # uncomment if use rspec cops
-    # - "config/rspec.yml"
+    - "config/rspec.yml"

 AllCops:
   TargetRubyVersion: 2.5
   # uncomment if use rails cops
-  # TargetRailsVersion: 5.1
+  TargetRailsVersion: 5.1
rubocop

で走る。解析ルールがrubocopデフォルトから変更されていることを確認する。自分の場合、

- 59 files inspected, 284 offenses detected
+ 58 files inspected, 216 offenses detected

と変化した。(このあたり、間にほかの操作を入れたかもしれません。とりあえず、vim等のテキストエディタでシングルクオートで文字列リテラルを宣言したときに、『[Style/StringLiterals] Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.』と怒られるようになっていれば、onkcopのルールが適用されているはずです。)

プロジェクトで動かしてみる

とりあえず試す

bundle exec rubocop で解析結果が出力される。自分が手元のRailsプロジェクトで実行したときは、たしか数百件のアラートが出た。アラートが大量に出るので面食らうが、ほとんどは自動修正できるものか、メトリクス関連(メソッドが長すぎる、など)だった。手動ですぐに直すべき箇所は少ないので、ビビらないでいいと思う。

自動修正する

bundle exec rubocop --auto-correct で、自動修正できるものはしてくれる。自分の場合、アラートは半分以下に減った。修正のほとんどは空白文字関連。自分が面白いと思ったのは以下の自動修正。

&:symbolによる文字数省略
users.map{|user| user.id}
=>
users.map(&:id)
if文(というか返り値があるので式)の返り値を使って代入
if condition
  @user = foo
else
  @user = bar
end
=>
@user = if condition
          foo
        else
          bar
        end
ミュータブルな定数のフリーズ
SOME_CONSTS = %w[aaa bbb ccc]
=>
SOME_CONSTS = %w[aaa bbb ccc].freeze

(注: rubyの定数は再代入が可能)

(注: rubyの定数はオブジェクトの変更が可能)

【2018/02/20追記 Pocke様からご指摘をいただき修正】

freezeすることで、オブジェクトの変更ができなくなります。上の書き方だと、定数SOME__COMSTSへの再代入は引き続き可能です。変更と再代入を混同し、freezeすると再代入が不可能になるような書き方をしてしまっておりました。修正いたします。

以下、freezeについて調べたメモ。

# 配列をフリーズ
NAMES = %w[yamada sato].freeze
=> ["yamada", "sato"]

# 配列への変更はできない
NAMES.sort!
#=> FrozenError (can't modify frozen Array)

# 配列をfreezeしても、配列の要素の変更はできる
NAMES.map!(&:upcase)
#=> FrozenError (can't modify frozen Array)
NAMES.map(&:upcase!)
=> ["YAMADA", "SATO"]
NAMES
=> ["YAMADA", "SATO"]

# 中身もフリーズすれば、上の操作も防げる
OTHER_NAMES = %w[takahasi akiyama].map(&:freeze).freeze
=> ["takahasi", "akiyama"]
OTHER_NAMES.map!(&:upcase)
FrozenError (can't modify frozen Array)
OTHER_NAMES.map(&:upcase!)
FrozenError (can't modify frozen String)

# freezeしても再代入はできる
OTHER_NAMES = ["bukkowasu"]
(irb):12: warning: already initialized constant OTHER_NAMES
(irb):8: warning: previous definition of OTHER_NAMES was here
=> ["bukkowasu"]
OTHER_NAMES
=> ["bukkowasu"]

【2018/02/20追記 Pocke様からご指摘をいただき修正 おわり】

残りを適宜手動で直す

bundle exec --auto-gen で、解析結果のまとめが.rubocop_todo.ymlに出力される。内容を見て、すぐ直せるものは修正していく。 メトリクス系(メソッドが長すぎる、等)はおいおい修正したい。

vscodeの設定

vscodeでもrubocop && onkcopが効くように設定する。

プラグインをインストール

以下の公式の記述に従い、Rubyプラグインをインストールする。

marketplace.visualstudio.com

設定ファイルを作成

vscodeの設定にはスコープがふたつある。ユーザーとワークスペースだ。 たとえばプロジェクトごとに設定を分けたい場合は、ワークスペース設定を行う。 わけなくていい場合はユーザー設定を行う。

以下に従い、ワークスペース設定を開く。

code.visualstudio.com

ruby language settingsを見つける。「設定の検索」欄に『ruby』と入れるとかんたんに絞りこめる。

ワークスペースを適宜設定する。設定は以下に記載がある。

marketplace.visualstudio.com

自分は以下のようにした。

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

code.visualstudio.com

vscodeを再起動してrubyファイルを開くと、規約に従っていない箇所に緑の波線が入る。たしかにrubocopのチェックが走ることを確認した。指摘のでないかたは、完璧なコードを書いている懸念があるので、おかしなコードをかいてみてください:)

自分はシステムには入れず、プロジェクトのvendor/bundle以下にrubocopを入れているが、特にそれを明示せずともうまく動いた。