pockestrap

Programmer's memo

RuboCop 0.50.0 のCHANGELOGを読む

RuboCop のバージョン 0.50.0 がリリースされました。

https://github.com/bbatsov/rubocop/releases/tag/v0.50.0

CHANGELOG から変更を見ていこうと思います。

破壊的変更

Naming Department

今回のリリースでは、新たに Naming Department が新設されました(ちなみに、Department とは RuboCop 用語で、Cop のグループのことです)。 Naming Department には、メソッドやクラスの名前付けに関する Cop が所属します。

今回の Naming Department の新設に伴い、多くの Cop が Style Department から移されました。 そのため、移動対象となる Cop の設定を.rubocop.ymlで行っている場合、設定の書き換えが必要となります。

例:

# 旧
Style/VariableName:
  EnforcedStyle: snake_case

# 新
Naming/VariableName:
  EnforcedStyle: snake_case

この書き換えを自動で行うツールを提供していますので、アップデートの際は是非お使い下さい。

新規Cop追加

Copとは、RuboCopにおいてひとつのルールを指す言葉です。例えば、「インデントが正しいかチェックする」「非推奨メソッドを使っていないかチェックする」などが1つのCopの単位になります。

この章では、0.50.0で新たに追加されたCopをひとつずつ紹介します。

Style/RedundantConditional

x == y ? true : false
x == y ? false : true
if x == y
  true
else
  false
end

上記のコードでは、三項演算子や if 式を使う必要がありません。何故ならば、==演算子は通常 true / false を返すため、三項演算子の部分がなくても全く同じ意味になります。

この Cop は、上記のような冗長な三項演算子や if 式を使っているコードに対して警告を出します。

Naming/HeredocDelimiterNaming

この Cop は、ヒアドキュメントのデリミタに説明的でない名前をつけているコードに対して警告します。

code = <<~END
  puts 'Hello'
END

code = <<~RUBY
  puts 'Hello'
RUBY

例えば、上記のコードはヒアドキュメントのデリミタが異なりますが、意味は変わりません。
ですが、後者の方がヒアドキュメントの中身がRubyのコードであることを説明している分、より説明的です。 また、使用しているテキストエディタによっては後者のヒアドキュメントの中身を適切にハイライトしてくれる場合もあるようです。

この Cop は、上記のように"END"などの説明的でない名前を使っているコードに対して警告を出します。

Lint/ReturnInVoidContext

Ruby では、initializeメソッドやfoo=メソッドなど、一部のメソッドの戻り値は無視されます。

例:

class A
  def initialize
    return 1
  end

  def foo=(var)
    @foo = var
    return 2
  end
end

# initialize メソッドの戻り値にかかわらず、A class のインスタンスが返る
a = A.new
p a # => #<A:0x0000560a195ebe28>

# foo= メソッドの戻り値にかかわらず、代入した値が返る
b = (a.foo = 100)
p b # => 100

そのような戻り値が無視されるメソッドで値付きのreturnを使っている場合、意図しない挙動となってしまう場合があります。 この Cop はそのようなコードに警告を出します。

Lint/BooleanSymbol

:trueのようなシンボルを書いた時、それはtrueリテラルの間違いではないか?と警告する Cop です。

Rails/HasManyOrHasOneDependent

ActiveRecord では、has_manyhas_oneの関係を指定する時、dependentオプションを指定することが出来ます。

class Post < ActiveRecord::Base
  has_many :comments, dependent: :destroy
end

これを指定することで、親レコードが削除された時、子レコードも同時に削除することが出来ます。 dependentオプションの指定が無い場合、RDBMS側で設定されたForeign KeyにON DELETEが指定されていれば子レコードも消すことが出来ますが、その指定が無い場合子レコードは放置されてしまいます。 そのような状況はデータベース上に壊れたデータが存在してしまうので、好ましくありません。

この Cop は、has_many関連を指定した時にdependentオプションをつけることを強制します。 これにより、dependentオプションを忘れてしまうことを防ぐことが出来ます。

Style/Dir

Ruby には、__dir__というメソッドが定義されています。 これはRubyプログラムが書かれたファイルのディレクトリを絶対パスで返します。

See. https://docs.ruby-lang.org/ja/latest/method/Kernel/m/dir.html

同様の出力を得ることは、以下のコードでも可能です。

File.expand_path(File.dirname(__FILE__))
File.dirname(File.realpath(__FILE__))

ですが、__dir__を使用したほうがよりスマートにディレクトリを得ることが出来ます。

この Cop は、上記のような冗長なディレクトリの取得を行っているコードに警告を出し、__dir__を使用するように促します。

Naming/HeredocDelimiterCase

ヒアドキュメントのデリミタには、大文字小文字どちらのアルファベットも用いることが出来ます。

code = <<~ruby
  puts 'hello'
ruby

code = <<~RUBY
  puts 'hello'
RUBY

ですが、多くの場合大文字のみを使用したほうが、ヒアドキュメントのデリミタであることがよりわかりやすくなるため良いでしょう。
この Cop は、ヒアドキュメントのデリミタが大文字だけであることを検査し、小文字が含まれていた場合には警告を出します。

また、場合によっては大文字ではなく小文字のみに制限したい場合もあるでしょう。そのような場合には、.rubocop.ymlにて設定を変更することが出来ます。

Naming/HeredocDelimiterCase:
  EnforcedStyle: lowercase

Lint/RescueWithoutErrorClass

begin
  foo
rescue
  bar
end

begin
  foo
rescue StandardError
  bar
end

Rubyでは上記のようにrescue節で例外クラスの指定を省略した場合、StandardErrorを指定したことと同じ扱いになります。
これにより、想定より多くの例外をキャッチしてしまい、思いもよらないバグを生む可能性があります。
そのため、rescue節には想定する例外クラスのみを指定するべきでしょう。

この Cop はこのような例外クラスが指定されていないrescue節に対して警告を出します。

Performance/UnfreezeString

Ruby 2.3 からString#+@(単項+演算子)が追加されました。 このメソッドは freeze されていない String を返します。

str = 'foo'.freeze
p str.frozen?    # => true
p (+str).frozen? # => false

String#dupなどでもこれと同様にfreezeされていない String を得ることが出来ますが、String#+@の方がより高速です。

この Cop は、String#dupの代わりにString#+@を使うように推奨します。

See. https://qiita.com/k0kubun/items/1c3e605645ba5ff683a1

Style/OrAssignment

x = x ? x : 3
x = 3 unless x

上記のようなコードは、||=を使うことでより簡潔に書くことが可能です。

x ||= 3

この Cop は、上記のような冗長な代入を検出し、警告します。

Style/ReturnNil

Ruby ではreturnに何も値を渡さない場合、メソッドはnilを返します。

def foo
  return
end

p foo.nil? # => true

そのため、return nilといった記述は冗長です。 この Cop は、そのような冗長な記述に警告を出し、単にreturnと書くように推奨します。

なお、この Cop はデフォルトで無効になっているため、有効にするには明示的に.rubocop.ymlに指定をする必要があります。

Style/ReturnNil:
  Enabled: true

また、return nilと冗長に記述したい場合は.rubocop.ymlに以下のように設定することが可能です。

Style/ReturnNil:
  EnforcedStyle: return_nil

Lint/UriEscapeUnescape

URI.escape, URI.encode, URI.unescape, URI.decodeの各メソッドは obsolete です。
そのため、代わりにCGI.escape, URI.encode_www_form, URI.encode_www_form_componentなどのメソッドを使用すべきでしょう。

この Cop は、上記の obsolete なメソッドに対して警告を出します。

Performance/UriDefaultParser

URI に対する正規表現を作成する為に、URI::Parser.new.make_regexp(%w[https http])のようなコードを用いることが出来ます。
これにはより高速な書き方があり、URI::Parser.newURI::DEFAULT_PARSERに置き換えることでより高速に実行することが出来ます。

require 'benchmark'
require 'uri'

Benchmark.bm(20) do |x|
  puts 'empty arg'
  x.report('URI::Parser.new'){    2000.times{URI::Parser.new.make_regexp}}
  x.report('URI::DEFAULT_PARSER'){2000.times{URI::DEFAULT_PARSER.make_regexp}}

  puts 'with arg'
  x.report('URI::Parser.new'){    2000.times{URI::Parser.new.make_regexp('http://example.com')}}
  x.report('URI::DEFAULT_PARSER'){2000.times{URI::DEFAULT_PARSER.make_regexp('http://example.com')}}
end
                           user     system      total        real
empty arg
URI::Parser.new        2.010000   0.010000   2.020000 (  2.016804)
URI::DEFAULT_PARSER    0.000000   0.000000   0.000000 (  0.000144)
with arg
URI::Parser.new        2.460000   0.000000   2.460000 (  2.451649)
URI::DEFAULT_PARSER    0.330000   0.000000   0.330000 (  0.331841)

これはURI::DEFAULT_PARSERではURI::Parserインスタンスを使いまわすためです。

https://github.com/ruby/ruby/blob/4b9aeef1fa2f32a4555f88c6e83539374a1bd8d1/lib/uri/common.rb#L22

この Cop は、URI::Parser.newに警告を追加し、より速いURI::DEFAULT_PARSERを使うように促します。

Lint/UriRegexp

URI.regexpというURI正規表現を生成するメソッドがありますが、これは obsolete です。
そのため、代わりにURI::DEFAULT_PARSER.make_regexpを使用すべきです。

この Cop は obsolete なURI.regexpの使用を警告します。

Style/MinMax

Enumerable には、最小値と最大値を同時に入手するための#minmaxが定義されています。

a = [2, 4, 1, 3]
p [a.min, a.max] == a.minmax # => true

この Cop は、[a.min, a.max]のようなコードに対してminmaxメソッドを使用するように警告を出します。

Bundler/InsecureProtocolSource

Bundler は、source :rubygemsのような Symbol による source の指定に対して警告を出します。

この Cop はこのような Symbol による source の指定に警告を出します。
また、この Cop は auto-correct に対応しています。そのため、rubocop -aとして実行することで、:rubygems'https://rubygems.org'に自動的に書き換えることが可能です。

Lint/RedundantWithIndex

この Cop は、使用されていないwith_indexを検出します。

ary.each.with_index do |v|
  do_something v
end

上記のコードでは.with_indexが呼び出されていますが、これは使用されていません。 この Cop はこのような使用されていないwith_indexに対して警告を出します。

Lint/InterpolationCheck

Ruby では、ダブルクオート文字列内で式展開を行うことが出来ます。

"foo#{100}baz" # => "foo100baz"

ですが、シングルクオートで囲まれた文字列の中では式展開を行うことが出来ません。

'foo#{100}baz' # => "foo\#{100}baz"

この Cop は、このようなシングルクオート文字列の中で式展開をしようとしているコードに対して警告を出します。

まとめ

この記事は以上になりますが、RuboCop 0.50.0ではこの他にも多くの機能追加、バグ修正が行われています。 より詳しい変更を知りたい方は、リリースノートをご覧ください。

Release RuboCop 0.50 · bbatsov/rubocop

過去のリリース