追記 2017/01/20
1/18 に、0.47.0で発生したバグ修正を含む 0.47.1 がリリースされました。
0.47.0 を使わず、こちらのバージョンを使用したほうが良いでしょう。
追記ここまで
本日RuboCopのバージョン(0.47.0)がリリースされました。
CHANGELOGから新機能を見ていこうと思います。
新規Cop追加
Copとは、RuboCopにおいてひとつのルールを指す言葉です。例えば、「インデントが正しいかチェックする」「非推奨メソッドを使っていないかチェックする」などが1つのCopの単位になります。
この章では、0.47.0で新たに追加されたCopをひとつずつ紹介します。
Lint/MultipleCompare
- PR https://github.com/bbatsov/rubocop/pull/3795
- Document http://rubocop.readthedocs.io/en/latest/cops_lint/#lintmultiplecompare
10 < x < 20
上のような式はPythonや数学では正しい式ですが、Rubyではそれらとは異なる動きをします。
上記の式は(10 < x) < 20
の順に解釈され、10 < x
の結果であるtrue
やfalse
と20
という数値の大小が比較されることになってしまいます。
これは「xが10と20の間にある」ことを検査したいであろうプログラマの意図とは異なります。
このCopではこの問題を検出し、自動で10 < x && x < 20
の様なコードに修正することが出来ます。
熟練したRubyプログラマであればこのような間違いを犯すことは少ないと思われますが、普段Rubyを書かない人やプログラミング初心者にとっては陥りやすい罠でしょう。
実際にTwitterでアンケートを取ったところ、半数近くの人からこのような間違いをしたことがあるという解答を得ることが出来ました。
アンケート
— Pocke (@p_ck_) December 18, 2016
10 < x < 20
のような書き方をサポートしていないプログラミング言語(例: Ruby)で、上記のようなコードを書いてしまったことが
Lint/SafeNavigationChain
- PR https://github.com/bbatsov/rubocop/pull/3804
- Document http://rubocop.readthedocs.io/en/latest/cops_lint/#lintsafenavigationchain
このCopは、Ruby 2.3 から導入されたSafe Navigation Operator(通称 ぼっち演算子)の誤った使い方を指摘します。
foo&.bar
例えば、nil
を返す可能性があるfoo
メソッドにbar
メソッドをチェインをしたい場合、Safe Navigation Operatorを使用するとシンプルに記述することが可能です。
では、このコードに更にbaz
メソッドをチェインする場合を考えます。
foo&.bar.baz
この場合に上記の様に書いてしまうと、foo
メソッドがnil
を返した際にnil
に対してbaz
を呼び出してしまうため、NoMethodError
が発生してしまいます。
このコードは以下のように書き換えるのが正しいでしょう。
foo&.bar&.baz
このCopは、この様なSafe Navigation Operatorをメソッドチェインで使用している際のバグを検出、自動修正します。
なお、このCopはRuby 2.3以降を使用している場合にのみ有効になります。
もしRuby 2.3を使用している場合は、.rubocop.yml
に以下を追記する必要があります。
AllCops:
TargetRubyVersion: 2.3
Security/MarshalLoad
- PR https://github.com/bbatsov/rubocop/pull/3816
- Document http://rubocop.readthedocs.io/en/latest/cops_security/#securitymarshalload
Marshal.load
及び Marshal.restore
メソッドには、設計上任意コードを実行できる危険性が存在します。
Module: Marshal (Ruby 2.3.3)
そのため、Webアプリケーションなどにおいてユーザーが入力可能な値をMarshal.load
することは避け、代わりにJSONなどを採用すべきでしょう。
具体的には、以下のようなコードに対してこのCopは警告を出します。
Marshal.load(dump)
なお、Marshal.load
を安全に使用できるアプリケーションや実装方法も存在します。
例として、通信を行わないコマンドラインツールではMarshal.load
を安全に使用することが出来ると考えられます。
そのような場合は、.rubocop.yml
に以下のように記述することでこのCopを無効にすることが可能です。
Security/MarshalLoad:
Enabled: false
Security/YAMLLoad
- PR https://github.com/bbatsov/rubocop/pull/3821
- Document http://rubocop.readthedocs.io/en/latest/cops_security/#securityyamlload
このCopはSecurity/MarshalLoad
のYAML版です。
以下のようなコードに対して警告を出します。
YAML.load(dump)
Performance/RegexpMatch
- PR https://github.com/bbatsov/rubocop/pull/3824
- Document http://rubocop.readthedocs.io/en/latest/cops_performance/#performanceregexpmatch
このCopは、Ruby 2.4 で追加された String#match?
メソッドなどに対応しています。
String#match?
メソッドは、String#match
や String#=~
などと同様正規表現とのパターンマッチを行いますが、MatchData
オブジェクトを生成しないためそれらより高速です。
String#match?
メソッドの詳細については以下の記事がよくまとまっているので是非ご覧ください。
サンプルコードでわかる!Ruby 2.4の新機能と変更点 - Qiita
このCopは、match?
メソッドで充分である(MatchData
を使用していない)match
, =~
メソッドの使用を検出、自動修正します。
# `MatchData` を使用していないため、`match?`に置き換え可能
def foo
if x.match /re/
puts 'matched'
end
end
# 上記と同様 `MatchData` を使用していないため、`match?`に置き換え可能
def foo
if x =~ /re/
puts "matched #{$~}"
end
end
# `MatchData` を使用しているため、`match?`に置き換えは不可能(Copは警告を出さない)
def foo
if x.match /re/
puts "matched #{$~}"
end
end
このCopはRuby 2.4以降を使用している場合にのみ動作します。そのため、Ruby 2.4を使用している場合は.rubocop.yml
に以下のように追記してください。
AllCops:
TargetRubyVersion: 2.4
尚、このCopの機能や実装の詳細などに関しては別途記事が存在するので、よろしければそちらもご覧ください。
Rails/FilePath
- PR https://github.com/bbatsov/rubocop/pull/3822
- Issue https://github.com/bbatsov/rubocop/issues/3779
- Document http://rubocop.readthedocs.io/en/latest/cops_rails/#railsfilepath
ファイルパスの連結はバグを生みやすい問題です。
セパレータを重複して書いてしまったり、あるいはセパレータを抜かして書いてしまうと、意図しないパスを表してしまうことがあります。
このCopは、Railsアプリケーションにおけるパスの連結の際に、Rails.root.join
メソッドを使用する様に推奨します。
# bad
Rails.root.join('app/models/goober')
File.join(Rails.root, 'app/models/goober')
"#{Rails.root}/app/models/goober"
# good
Rails.root.join('app', 'models', 'goober')
Rails/SkipsModelValidations
- PR https://github.com/bbatsov/rubocop/pull/3825
- Issue https://github.com/bbatsov/rubocop/issues/3743
- Document http://rubocop.readthedocs.io/en/latest/cops_rails/#railsskipsmodelvalidations
ActiveRecord の update_attribute
などのメソッドは、モデルに対するUpdateを行いますが、その際にバリデーションをスキップします。
このCopでは、そのようなメソッドの使用を検出します。
バリデーションについて、詳しくはこの記事をご覧ください。 Active Record Validations — Ruby on Rails Guides
# bad
Article.first.decrement!(:view_count)
DiscussionBoard.decrement_counter(:post_count, 5)
Article.first.increment!(:view_count)
DiscussionBoard.increment_counter(:post_count, 5)
person.toggle :active
product.touch
Billing.update_all("category = 'authorized', author = 'David'")
user.update_attribute(website: 'example.com')
user.update_columns(last_request_at: Time.current)
Post.update_counters 5, comment_count: -1, action_count: 1
# good
user.update_attributes(website: 'example.com')
Rails/ReversibleMigration
- PR https://github.com/bbatsov/rubocop/pull/3854
- Document http://rubocop.readthedocs.io/en/latest/cops_rails/#railsreversiblemigration
- Document https://github.com/bbatsov/rails-style-guide#reversible-migration
Rails のマイグレーションには、ロールバック(rake db:rollback
)が可能なものと、不可能なものがあります。
# bad
def change
# qualification カラムの型がわからないため、rollback出来ない。
remove_column(:suppliers, :qualification)
end
# good
def change
# remove_columnの際も明示的に型を指定することで、rollbackが可能になる。
remove_column(:suppliers, :qualification, :string)
end
このCopは、上記の例の様にrollbackすることが出来ないマイグレーションを検出します。
このCopを作成された方が記事を書かれているので、詳しくはそちらをご覧ください。 rubocopでreversibleなmigrationかどうかチェックしたかったので作った - くりにっき
Style/MethodCallWithArgsParentheses
- PR https://github.com/bbatsov/rubocop/pull/3797
- Document http://rubocop.readthedocs.io/en/latest/cops_style/#stylemethodcallwithargsparentheses
引数を受け取るメソッド呼び出しに、()
が使われていない場合を検出、自動修正します。
# メソッド呼び出しに()がないため、警告を出す
do_something x
# () があるためok
do_something(x)
なお、このCopはrequire
やinclude
などの構文的メソッドの呼び出しにも()
をつけるよう警告を出します。
この様なメソッドに対しては警告を出さないようにするには、.rubocop.yml
に以下を追記します。
Style/MethodCallWithArgsParentheses:
IgnoredMethods:
- require
- include
このCopはデフォルトでは無効になっており、使用するには以下のように明示的に有効にする必要があります。
Style/MethodCallWithArgsParentheses:
Enabled: true
まとめ
この記事は以上になりますが、RuboCop 0.47.0ではこの他にも多くの機能追加、バグ修正が行われています。 より詳しい変更を知りたい方は、リリースノートをご覧ください。
Release RuboCop 0.47 · bbatsov/rubocop
ちなみに、前回(v0.46.0)リリースの時の私のツイートです。
0.46.0で追加されたCop、どれも面白くないな
— Pocke (@p_ck_) November 30, 2016
次のリリースでは本当のCopってものをお見せしますよ
— Pocke (@p_ck_) November 30, 2016
本当のCopをお楽しみいただけたでしょうか?