rubocop のしつけ方
TL;DR
-
rubocop --auto-gen-config
して - Offense count の多い順に毎日数個ずつ設定を確認したら
- 僕の使っている .rubocop.yml ができました
これが 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 を育てる
一つずつ検出項目を確認していくんですが、このような流れになります。
- .rubocop_todo.yml のコメントを見る
- rubocop の config/default.yml を見る
- 著名なコーディング規約でどうなっているかを見る
- 自プロダクトがどうあるべきか判断する
先ほど検出数の多かった Style/StringLiterals と Style/SingleSpaceBeforeFirstArg、Metrics/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 の方が好きです。
理由は
- 途中から式展開を使いたくなった際にミスが少ない
- 普通に文章を書くときは single quote の方が double quote よりも文章中に頻出する
- 実行時のオーバーヘッドは double quote でもほぼ無いらしい(ソース失念
の 3 点。
Style/SingleSpaceBeforeFirstArg
.rubocop_todo.yml
# Offense count: 4083 # Cop supports --auto-correct. Style/SingleSpaceBeforeFirstArg: Enabled: false
設定項目ナシ。
引っかかるのは主に
- migrate
- db/schame.rb
- jbuilder
- model の association
- controller の callback
です。
例を上げると
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 文字は割と超えるので厳しい。
- 警告 120文字
- 禁止 160文字
という幅にしたい。
参考までに、このプロジェクトだと
- 100文字 - 376 offenses
- 120文字 - 137 offenses
- 140文字 - 71 offenses
- 160文字 - 26 offenses
です。
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
特殊変数 $1
を Regexp.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
コミットする前にチェックする
コーディング規約を作っても、守らない人がいるとあまり意味がありません。
guard や syntastic でチェックするように促しておくと良いでしょう。
ただ、どちらも強制力という面では少し弱いので、もうちょっと強い強制力を持つものとして 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 で拾う必要があります。
houndci や pronto、saddler といったものが該当します。
ドリコムでは 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 ライフをお送りください。