pockestrap

Programmer's memo

RuboCop 0.49.0 のCHANGELOGを読む

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

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

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

破壊的変更

Layout Department

今回のリリースでは、新たに Layout Department が新設されました(ちなみに、Department とは RuboCop 用語で、Cop のグループのことです)。 Layout Department はインデントや行の整列、空白などコードのレイアウトに関する Cop が所属しています。

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

例:

# 旧
Style/SpaceInsideHashLiteralBraces:
  EnforcedStyle: no_space

# 新
Layout/SpaceInsideHashLiteralBraces:
  EnforcedStyle: no_space

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

新規Cop追加

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

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

Rails/ApplicationJob / Rails/ApplicationRecord

Rails 5 からActiveRecord::BaseActiveJob::Baseを直接継承するスタイルから、ApplicationRecordなどの共通の親クラスを定義してそれを個々のクラスで継承するスタイルへと変わりました。

この Cop は、ActiveRecord::Baseを直接継承しているようなコードに警告を出します。

# bad
class User < ActiveRecord::Base
end

# good
class User < ApplicationRecord
end

なお、Rails 5よりも古いバージョンを使用している場合、TargetRailsVersionを指定することでこれらの Cop を無効にすることが可能です。

# .rubocop.yml
# Rails 4.0 を使っている場合
AllCops:
  TargetRailsVersion: 4.0

Performance/Caller

Kernel.callerメソッドを使用することで、スタックトレースを配列で入手することが出来ます。

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

def foo
  bar
end

def bar
  p caller
end

foo # => ["test.rb:2:in `foo'", "test.rb:9:in `<main>'"]

この際callercaller(1, 1)のように範囲を渡すことで、指定した範囲のみのトレースを取得することが可能です。
限られた範囲のみのトレースが必要だと分かっている場合(例えば、caller.firstで最初だけがほしい場合など)は、範囲を渡して取得するトレースの範囲を狭めることでパフォーマンスの改善が見込めます。

この Cop は上記のような不必要に広い範囲のトレースを取得しているコードに対して警告を出します。

# bad
caller.first

# good
caller(1, 1)

Style/FormatStringToken

Ruby では文字列をフォーマットする際、いくつかの方法があります。

# printf などでよく見る方式
format('%s', 'Hello')

# 名前付きバージョン。
format('%<greeting>s', greeting: 'Hello')

# テンプレート。`s`が無いため、greeting には String 以外の値も入れられる。
format('%{greeting}', greeting: 'Hello')

この Cop は、この内の2つ目の方法を使うことを推奨します。 2つ目の方法は、1つ目の方法と比べて%s に greeting という名前がついているので、より説明的です。 また、3つ目と比べて、タイプチェックを行うため防御的と言えます。

format('%<length>d', length: 'foo') # => ArgumentError: invalid value for Integer(): "foo"

ただし、2つ目と3つ目は等価ではないことに注意して下さい。 2つ目は与えられた引数をフォーマットします。 そのため、%fなどを使用する場合に差異が出ます。

format('%<length>f', length: 42) # => "42.000000"
format('%{length}', length: 42)  # => "42"

Lint/ScriptPermission

Shebang と呼ばれる特殊なコメントをファイルの先頭に置くことにより、そのファイルを読み込むインタプリタを指定することが出来ます。

#!/usr/bin/env ruby
puts 'hello'

これを利用する際は、対象のファイルが実行可能である必要があります。

この Cop は、Shebang が存在するファイルが実行可能となっていることを検証します。 例えば、先程の例に挙げた Ruby コードが、パーミッション644のファイルとして保存されていた場合、この Cop は警告を出します。

Style/YodaCondition

一般的に、Yoda Condition と呼ばれるテクニックが存在します。 これは条件式での誤った代入を避けるためのテクニックです。

# 一般的な if
if x == 42
end

# Yoda Condition の if
if 42 == x
end

このように Yoda Condition では条件式の変数と値の配置が逆になります。 この書式のメリットとして、イコールを抜かしてしまった場合(x = 42 / 42 = x)は Syntax Error になるため、誤ってイコールを抜くのを防ぐことが出来ます。

ですが、この記法はあまり可読性が高いとは言えず、また MRIif x = 42のような条件式内で定数を代入している文に対して警告を出します。

そのため、この Cop は Yoda Condition を避けるように警告を出します。

# bad
if 42 == x
end

# good
if x == 42
end

なお、この Cop は以下のような数値比較のコードも Yoda Condition だとみなします。

if 0 <= x && x <= 42
end

このようなコードを許可したい場合、この Cop を無効にするべきでしょう。

# In .rubocop.yml
Style/YodaCondition:
  Enabled: false

また、この Cop にはいくつかバグがあるようなので、使用したい場合でも次のバージョンがリリースされるまでは無効にしておいても良いかも知れません。

Style/MultipleComparison

Ruby では、「変数が複数の値のどれかに当てはまること」をArray#include?を使用してスマートに書くことが出来ます。

# bad - a が3回も登場して長い
if a == 'a' || a == 'b' || a == 'c'
end

# good - a が一度しか登場せず、スッキリしている
if ['a', 'b', 'c'].include? a
end

この Cop は、上記のような条件式をスマートに書くように推奨します。

なお、good としている書き方は、スマートですが bad としている書き方よりも若干遅いです。

require 'benchmark'

a = 'c'

Benchmark.bm(20) do |x|
  x.report('== ||')         {10000000.times{a == 'a' || a == 'b' || a == 'c'}}
  x.report('Array#include?'){10000000.times{['a', 'b', 'c'].include? a}}
end
                           user     system      total        real
== ||                  1.190000   0.000000   1.190000 (  1.190595)
Array#include?         1.510000   0.000000   1.510000 (  1.514413)

そのため、パフォーマンスが要求されるケースでは、bad とされているスタイルをあえて使う必要があることもあるでしょう。

Lint/RescueType

rescueにクラス/モジュール以外の値を指定した場合、Ruby は「例外が起きた際」に TypeError を発生させます。

begin
  puts 'hello' # => hello
rescue 42      # => 特に何も起きない
end

begin
  raise
rescue 42      # => class or module required for rescue clause (TypeError)
end

そのため、万が一クラス以外の値をrescueに指定してしまっていた場合、発見が遅れることが考えられます。

この Cop は上記のようなコードを検出します。

その他新機能など

--parallelオプション

並列実行をサポートする--parallelオプションが追加されました。
このオプションを付与することで、RuboCop は解析を並列に行います。
そのため、複数コアを積んでいるマシンで RuboCop を使用する際には、--parallelを付与することでより高速に RuboCop を実行することが可能でしょう。

$ time rubocop
total  50.82s
user   50.55s
system 0.25s
CPU    99%
cmd    rubocop

$ time rubocop --parallel
total  13.86s
user   90.43s
system 0.63s
CPU    657%
cmd    rubocop --parallel

まとめ

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

Release RuboCop 0.49 · bbatsov/rubocop

過去のリリース