rubocop のしつけ方

TL;DR

これが onkcop か…(ゴクリ / “僕の使っている .rubocop.yml” https://t.co/KVryle9SJq

— そのっつ (SEO Naotoshi) (@sonots) October 26, 2015

rubocop とは

コーディング規約に準拠しているかをチェックする gem です。

https://github.com/bbatsov/rubocop

こんなコードに対してかけると

def badName
  if something
    test
    end
end

以下のような警告をしてくれます。

Offenses:

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

1 file inspected, 4 offenses detected

rubocop によるチェックを始める

rubocop は --auto-gen-config というオプションを渡すことでスモールスタートすることができます。

$ rubocop --auto-gen-config

...(中略)...

506 files inspected, 23359 offenses detected
Created .rubocop_todo.yml.
Run `rubocop --config .rubocop_todo.yml`, or
add inherit_from: .rubocop_todo.yml in a .rubocop.yml file.

生成された .rubocop_todo.yml を使って rubocop を回すとなんと警告がなくなります。

$ rubocop -c .rubocop_todo.yml

...(中略)...

506 files inspected, no offenses detected

検出条件を緩めた設定ファイルができるので、「デフォルトから何が緩くなっているのか」を見ながら コーディング規約に合わせていけるようになっています。

参考までに、150 model ぐらいあるプロジェクト (Pull Request ベースのコードレビューは行っていた) で 素の rubocop を実行すると TOP 10 の指摘は以下のようになりました。

# Offense count: 12421
Style/StringLiterals
# Offense count: 4083
Style/SingleSpaceBeforeFirstArg
# Offense count: 2548
Metrics/LineLength
# Offense count: 1294
Style/ExtraSpacing
# Offense count: 386
Style/AsciiComments
# Offense count: 348
Style/Documentation
# Offense count: 221
Style/SpaceInsideHashLiteralBraces
# Offense count: 199
Style/RedundantSelf
# Offense count: 157
Style/SignalException
# Offense count: 126
Lint/UnderscorePrefixedVariableName

.rubocop.yml を育てる

一つずつ検出項目を確認していくんですが、このような流れになります。

先ほど検出数の多かった Style/StringLiteralsStyle/SingleSpaceBeforeFirstArgMetrics/LineLength を例にすると以下のような感じ。

Style/StringLiterals

.rubocop_todo.yml

# Offense count: 12421
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/StringLiterals:
  Enabled: false

https://github.com/bbatsov/rubocop/blob/v0.34.2/config/default.yml#L627-L631

Style/StringLiterals:
  EnforcedStyle: single_quotes
  SupportedStyles:
    - single_quotes
    - double_quotes

僕は double_quotes の方が好きです。

理由は

の 3 点。

Style/SingleSpaceBeforeFirstArg

.rubocop_todo.yml

# Offense count: 4083
# Cop supports --auto-correct.
Style/SingleSpaceBeforeFirstArg:
  Enabled: false

設定項目ナシ。

引っかかるのは主に

です。

例を上げると

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string   :nickname,    limit: 10
      t.integer  :level,       default: 1
      t.datetime :accessed_at
      t.timestamps
    end
  end
end

class User < ActiveRecord::Base
  has_one  :user_account
  has_one  :user_weapon_property
  has_many :user_active_skills
  has_many :user_cards
end

で、除外しきれないのでチェックしないのが妥当だと思う。

Metrics/LineLength:

.rubocop_todo.yml

# Offense count: 2548
# Configuration parameters: AllowURI, URISchemes.
Metrics/LineLength:
  Max: 185

https://github.com/bbatsov/rubocop/blob/v0.34.2/config/default.yml#L796-L803

Metrics/LineLength:
  Max: 80
  AllowURI: true
  URISchemes:
    - http
    - https

80 文字は割と超えるので厳しい。

という幅にしたい。

参考までに、このプロジェクトだと

です。

rubocop v0.34.2 現在で 240 のチェック項目がありますが、まぁ 100 個ぐらいしか引っかからないんじゃないかな。 これを地道に毎日数件ずつ調べていけば 1 ヶ月後には理想の .rubocop.yml が完成です。

……というのを行った結果が こちら です。 僕の趣味が多分に入っていますが、どうぞご利用ください。

著名なコーディング規約についてはココに集まってそうな感じです。

Ginza.rb 第20回 Rubyを使っているプロジェクトのコーディング規約を見てみよう - Ginza.rb | Doorkeeper

.rubocop.yml を公開するきっかけだったので少し思い出深い。

ドタキャンですみません; お詫びに .rubocop.yml 置いときます https://t.co/CcVwI2IZLU #ginzarb

— Takafumi ONAKA (@onk) February 17, 2015

知っていると便利な小技

どの cop に引っかかったのかを表示する

デフォルトの出力だと

$ rubocop hoge.rb
Inspecting 1 file
C

Offenses:

hoge.rb:1:9: C: Prefer single-quoted strings when you don't need string interpolation or special symbols.
require "yaml"
        ^^^^^^

1 file inspected, 1 offense detected

と、警告から Style/StringLiterals というルール名は分かりません。

これを表示するには -D オプションを指定するか、

$ rubocop -D hoge.rb
Inspecting 1 file
C

Offenses:

hoge.rb:1:9: C: Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
require "yaml"
        ^^^^^^

1 file inspected, 1 offense detected

もしくは .rubocop.yml に

AllCops:
  DisplayCopNames: true

を追加します。

特定の cop だけ auto-correct しない

v0.30.0 からの神機能です。

Style/BlockDelimiters:
  AutoCorrect: false

警告であること自体は許せるんだけど auto-correct には従いたくない奴に使います。

具体的には Style/PerlBackrefs とか Style/BlockDelimiters とか。

Style/PerlBackrefs

特殊変数 $1Regexp.last_match(1) に直すんじゃなくて、 そもそも名前付きキャプチャを使って MatchData から参照すべきと考える。

# NG
r = /\$(\d+)\.(\d+)/
"$3.67" =~ r
p [$1, $2]
# => ["3", "67"]

# OK
r = /\$(?<dollars>\d+)\.(?<cents>\d+)/
m = r.match("$3.67")
p [m[:dollars], m[:cents]]
# => ["3", "67"]

Style/BlockDelimiters

複数行の場合は { ... } ではなく do ... end を使うべきというチェック項目。

指摘に納得はしてるんだけど、メソッドチェーンしてる場合 (稀にある) に単純修正だと問題になる。

user.user_cards.select { |uc|
  uc.card.buyable?
}.sort_by { |uc|
  [-uc.attack, uc.id]
}

これが auto-correct によって

user.user_cards.select do |uc|
  uc.card.buyable?
end.sort_by do |uc|
  [-uc.attack, uc.id]
end

となる。

end.sort_by なんていうメソッドチェーンは認めたくないですね。

user.user_cards
  .select  { |uc| uc.card.buyable? }
  .sort_by { |uc| [-uc.attack, uc.id] }

なら許せる。

.rubocop.yml を継承する

--auto-gen-config で実行した時に

add inherit_from: .rubocop_todo.yml in a .rubocop.yml file.

とメッセージが出ていたのを覚えていますでしょうか。

.rubocop.yml は継承できます。

社内の共通の .rubocop_standard.yml を作って、プロジェクトごとに 上書きをする使い方が望ましいんじゃないでしょうか。

inherit_from: .rubocop_standard.yml

# このプロジェクトでは
#   class: 150 行
#   method: 40 行
# までは許可する
Metrics/ClassLength:
  Max: 150

Metrics/MethodLength:
  Max: 40

コミットする前にチェックする

コーディング規約を作っても、守らない人がいるとあまり意味がありません。

guardsyntastic でチェックするように促しておくと良いでしょう。

ただ、どちらも強制力という面では少し弱いので、もうちょっと強い強制力を持つものとして pre-commit というものがあります。

git の pre-commit hook を管理できる便利な子です。

$ gem install pre-commit
$ pre-commit enable yaml checks rubocop

で生成された config/pre_commit.yml をリポジトリにコミットし、チームメンバーには

$ pre-commit install

を叩くよう共有してください。

以降は commit 前に勝手に変更差分に対して rubocop が走るようになります。

pre-commit なら rubocop だけではなく「binding.pry が含まれてないこと」とか「ハードタブが含まれていないこと」とか、 割と便利なチェックが同時に走るので比較的受け入れられやすいと思います。

警告を無視したければ git commit -n で hook を回避することができるので、緊急時にも安心。

リリースする前にチェックする

各自が pre-commit や guard 等で自発的にチェックするのが理想ですが、 絶対に漏らす人が出てくるので、Pull Request に反応して bot で拾う必要があります。

houndciprontosaddler といったものが該当します。

ドリコムでは pronto を飼っています。

カーチャン

なお、英語の無味乾燥なきっつい指摘が連続して書き込まれると心が折れるので

@onk J( 'ー`)し < かーちゃんに話させる

— Hiroyuki Morita (@chiastolite) March 24, 2014

のように温かみを加えておくと、指摘を少し受け入れやすくなります。

治安の悪いアプリに rubocop を導入する

これだけやれば、ギリギリなんとか許せる感じになるんじゃないかなぁ。

メソッド定義やメソッド呼び出しの () をいい感じにする

$ rubocop -a --only Style/DefWithParentheses,\
Style/MethodCallParentheses,\
Style/MethodDefParentheses

インデント崩れを修正

$ rubocop -a --only Style/IndentationConsistency,\
Style/IndentationWidth,\
Style/MultilineOperationIndentation

空行をいい感じにする

$ rubocop -a --only Style/EmptyLineBetweenDefs,\
Style/EmptyLines,\
Style/EmptyLinesAroundAccessModifier,\
Style/EmptyLinesAroundBlockBody,\
Style/EmptyLinesAroundClassBody,\
Style/EmptyLinesAroundMethodBody,\
Style/EmptyLinesAroundModuleBody,\
Style/TrailingBlankLines

コロンやカンマの前後のスペースをいい感じにする

$ rubocop -a --only Style/SpaceAfterColon,\
Style/SpaceAfterComma,\
Style/SpaceAfterNot,\
Style/SpaceAfterSemicolon,\
Style/SpaceAroundEqualsInParameterDefault,\
Style/SpaceBeforeSemicolon

似ている cop に Style/SpaceBeforeComma, Style/ExtraSpacing があるんですが、 桁揃えが崩れる可能性が高いので省いてあります。

また、(){} の前後のスペースもチームで揉まないと異論がありそうなので省略。

オペレータ前後のスペースをいい感じにする

$ rubocop -a --only Style/SpaceAroundOperators

Style/SpaceAroundOperators は auto-correct だと気持ち悪くなる部分もあるので、diff 見て上手に変更してください。例えばこんなの。

# before
range = now .. now+1.day
# after
range = now..now + 1.day
# たぶんこっちの方が better
range = now..(now + 1.day)

行末のスペース削除

$ rubocop -a --only Style/TrailingWhitespace

以上で「まぁ手を触れたくもない悪臭は取れたかな」と思えるようになります。

毎日やる

量が多いけどやること自体は単純で、全部終わると幸せになる系の作業ってちょいちょい発生すると思うんですが、 やり始めたら思ってたよりすぐ終わるので頑張って下さい。

当時の様子を見直したところ、本当に一ヶ月ぐらいで完成させたようです。

編集履歴

それでは楽しい rubocop ライフをお送りください。