概要
Rubyの静的解析ツールであるRuboCopをインストールし、基本的な使い方と、vimからの利用方法を確認し、Rubyコーディング時のコード品質を高めるためにやったことの記録。
前提
以下環境で動作確認済み
debian | 8.6 |
ruby | 2.2.2 |
rubocop | 0.52.1 |
vim | 7.4 |
RuboCopとは
Rubyのソースコードを静的解析してくれるツール。インストール直後から特に設定などを行うこと無く、Rubyスタイルガイドを概ね踏襲したコーディング規約を強制できるようになる。
例えば以下のRubyコードに対してRuboCopを実行すると
defbadNameifsomethingtestendend
$ 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オプションをつけると、その一部を自動で修正してくれる。自動修正後のコードは以下の通り。
defbadNametestifsomethingend
見ての通り、メソッド名をスネークケースにするべきところは治ってないので、自動修正は一部にしか適用されない。また、この機能はまだ実験段階なので注意して使うようにと公式ドキュメントにも書かれている。
とにかく、Rubyのコード品質を高めたり、チーム内でのコーディングルールを強制したりするために活用することができる。
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を実行すると
# 標準出力するメソッドdefsayputs'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+=%*
letg:syntastic_always_populate_loc_list=1letg:syntastic_auto_loc_list=1letg:syntastic_check_on_open=1letg:syntastic_check_on_wq=0letg:syntastic_ruby_checkers=['rubocop','mri']
ファイルの保存をフックに、どの行でどの問題が起こっているかを可視化してくれる
ファイル保存のたびにRubocopが実行されると結構重いので、syntasticをファイル保存ごとでなく、別途キーバインドで実行できるようにすることもできる。vimrcに以下のように記述すれば、ファイル保存時にはsyntasticが走らなくなり、<C-C>
で手動で走らせられるようになる。
NeoBundle 'scrooloose/syntastic.git'set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}set statusline+=%*
letg:syntastic_always_populate_loc_list=1letg:syntastic_auto_loc_list=1letg:syntastic_check_on_open=0letg:syntastic_check_on_wq=0letg:syntastic_mode_map={'mode':'passive','passive_filetypes':['ruby']}letg:syntastic_ruby_checkers=['rubocop']
nnoremap <C-C>:w<CR>:SyntasticCheck<CR>