[Ruby] RuboCopをインストールしてvimで実用化するまで

概要

Rubyの静的解析ツールであるRuboCopをインストールし、基本的な使い方と、vimからの利用方法を確認し、Rubyコーディング時のコード品質を高めるためにやったことの記録。

前提

以下環境で動作確認済み

debian 8.6
ruby 2.2.2
rubocop 0.52.1
vim 7.4

RuboCopとは

Rubyのソースコードを静的解析してくれるツール。インストール直後から特に設定などを行うこと無く、Rubyスタイルガイドを概ね踏襲したコーディング規約を強制できるようになる。

例えば以下のRubyコードに対してRuboCopを実行すると

def badName
  if something
    test
    end
end
$ rubocop
Inspecting 1 file
W

Offenses:

01.rb:1:5: C: Naming/MethodName: Use snake_case for method names.
def badName
    ^^^^^^^
01.rb:2:3: C: Style/GuardClause: Use a guard clause instead of wrapping the code inside a conditional expression.
  if something
  ^^
01.rb:2:3: C: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if something
  ^^
01.rb:4:5: W: Lint/EndAlignment: end at 4, 4 is not aligned with if at 2, 2.
    end
    ^^^

1 file inspected, 4 offenses detected
  • メソッド名はスネークケースにしようね
  • if文が一行の場合は後置ifを使おうね
  • endがインデントずれてるよ

と教えてくれる。

さらに、

$ rubocop --auto-correct

のように、auto-correctオプションをつけると、その一部を自動で修正してくれる。自動修正後のコードは以下の通り。

def badName
  test if something
end

見ての通り、メソッド名をスネークケースにするべきところは治ってないので、自動修正は一部にしか適用されない。また、この機能はまだ実験段階なので注意して使うようにと公式ドキュメントにも書かれている。

とにかく、Rubyのコード品質を高めたり、チーム内でのコーディングルールを強制したりするために活用することができる。

ちなみにJavaScriptにも同じコンセプトのESLintというツールがあるが、それについては以下記事で触れている。
ESLintによるJavaScriptコーディングルールの強制 | qs Developers

RuboCopのインストール

Rubyのライブラリなので当然gemでインストール可能。もちろんbundler経由でインストールもできるが、今回はグローバルインストールする。

$ gem install rubocop

インストール後、rubocopコマンドですぐに使用可能。

$ rubocop -v
0.52.1

Cop/Departmentについて

RuboCopは、Copというコーディングルールの集合で構成されている。例えば前述の例で言う「メソッド名はスネークケース」「1行のif文は後置ifを使う」「ブロックのインデントを揃える」といったのがCopである。(正確にはCopはもう少し細かいルールだがここではわかりやすく簡略化している)

個々のCopは、コーディングルールのカテゴリであるDepartmentに属している。

Departmentの一覧は以下の通り

Department Bundler

Bundlerに関する規約。「同じgemが被ってるよ」や「gemはアルファベット順に書こうね」など

Department Gemspec

Gemspecに関する規約。「アルファベット順に書こうね」など

Department Style

Rubyスタイルガイドに基づいた一貫性のあるスタイルに関する規約。「for文とかやめてeach使お」「空のelseとか辞めよう」「引数のないメソッドは括弧を省略しよう」など

Department Layout

インデントや空白、整形などの規約。「ブロックの開始と終了のインデントを揃えようね」「範囲式でスペース入れるのやめようね」「メソッドチェインを複数行で書く時は縦を揃えようね」など

Department Lint

バグの原因になりえるコードに関する規約。「ハッシュリテラルでキーが重複してるよ」「シングルコーテーションで変数展開しようとしてるよ」「無意味なアクセス修飾子ついてるよ」など

Department Metrics

ブロックの行数、1行の文字数などに関する規約。「ブロックの行数はN行まで」「一行の長さはN文字まで」「ブロックの入れ子はN段まで」など

Department Naming

命名に関する規約。「変数名はスネークケースにしてね」「メソッド名はスネークケースにしてね」「クラス名やモジュール名はキャメルケースにしてね」など

Department Performance

性能劣化になりえるコードに関する規約。「この場合sortメソッド使うよりsort_byメソッド使ったほうが早いよ」「その処理、このメソッド使ったほうが早いよ」など

Department Rails

Ruby on railsのコードに関する規約。「nil? || empty? ← blank?メソッドで済むよ」「whereつかうよりfind_byで済むよ」など

Department Security

セキュリティリスクのあるコードに関する規約。「evalとか使わないほうがいいよ」「JSON.loadよりJSON.parse使おう」など

.rubocop.ymlにRuboCopの設定を記述する

前述の通り、RuboCopには様々なCopがあり、デフォルトでもrubocopコマンドだけで多くの恩恵が受けられる。

しかし、Copの中には明示的に設定しないと有効にならないCopがあったり、デフォルトで有効だけど使いたくないCopがあったり、有効にはしたいけど一部制限したいCopなどもある。

そういった場合にRuboCopの設定ファイルになる、.rubocop.ymlを記述することで、事細かにRuboCopを制御することができる。通常は、rubocopコマンドを実行するディレクトリに配置されている.rubocop.ymlが参照される。

例えば以下のコードでそのままRuboCopを実行すると

# 標準出力するメソッド
def say
  puts '01234567890123456789012345678901234567890'
end
$ rubocop
Inspecting 1 file
C

Offenses:

01.rb:1:3: C: Style/AsciiComments: Use only ascii symbols in comments.
# 標準出力するメソッド
  ^^^^^^^^^^

1 file inspected, 1 offense detected

となり、コメントにマルチバイト文字を使っていることが指摘される。

コメントに日本語は普通に使いたいので、同ディレクトリに以下の.rubocop.ymlを作成する。

# コメントに日本語を書きたいので無効にする
Style/AsciiComments:
  Enabled: false

# 1行は40文字までにする
Metrics/LineLength:
  Max: 40

日本語コメントが使えない原因のCopを無効にして、ついでにデフォルトでは無効の1行の最大文字数Copを40文字設定で有効にする。

これでもう一度rubocopを実行すると、今度は日本語コメントに関する通知が無くなり、1行の文字数に関する通知が出るようになった。

$ rubocop
Inspecting 1 file
C

Offenses:

01.rb:3:41: C: Metrics/LineLength: Line is too long. [50/40]
  puts '01234567890123456789012345678901234567890'
                                        ^^^^^^^^^^

1 file inspected, 1 offense detected
vagrant$
vagrant$ rubocop
Inspecting 1 file
C

Offenses:

01.rb:3:41: C: Metrics/LineLength: Line is too long. [50/40]
  puts '01234567890123456789012345678901234567890'
                                        ^^^^^^^^^^

1 file inspected, 1 offense detected

vimから自動でRuboCopを実行できるようにする

ここまで、rubocopコマンドを用いて静的解析を実行していたが、どうにも一々コマンドを実行するのが面倒くさい。vimでコード編集中にリアルタイムで教えてほしい。

ということで、vimからrubocopを使うためのvimプラグインである、ngmy/vim-rubocopを導入した。

インストールはNeoBundleで行うので、vimrcに以下を追加。

NeoBundle 'ngmy/vim-rubocop'

:RuboCop コマンドで、RuboCopの実行結果をVim上に表示することができる。

一々コマンドを打つのも面倒なので、適当なキーバインドを設定するとよい。

これだけでも充分使えるが、私が普段から愛用していた汎用構文チェック用のvimプラグインであるvim-syntastic/syntastic と組み合わせることでさらに協力になる。

vimrcを以下のようにすると

NeoBundle 'ngmy/vim-rubocop'
NeoBundle 'scrooloose/syntastic.git'
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*
let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0
let g:syntastic_ruby_checkers=['rubocop', 'mri']

ファイルの保存をフックに、どの行でどの問題が起こっているかを可視化してくれる

2018/03/18 更新 ファイルの保存毎に解析が走ると結構重いので改善した。
[vim] syntastic + rubocop で保存時には実行しないようにする | qs Developers

参考

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です