Sider Blog

コードレビュー自動化サービス「Sider」を運営するSider株式会社のコーポレートブログです。

RuboCop vs Rails Best Pratices それぞれの特徴。初心者はどう使う?

目次

開発をする際にテストを利用しする事は、今日最早あたりまえになったと思います。 また、GitHubやBitBucketといったWebサービスの普及により、merge前にcode reviewやテストを行なう事もあたりまえになったと思います。

それでは、テストで担保出来ない点はどうでしょう。例えば

  • コードの複雑性
  • 変数名の妥当性
  • フレームワークに沿ったコーディング
  • 紛らわしい評価のされるコード
  • etc.

などはテストでは担保されませんし、reviewerの負担になります。

では、どのようにしてその負担を減らす事が出来るでしょうか。

この記事では、Ruby on Rails のプロダクトをターゲットに、これらの unit testを走らせる前にreviewの支援を行なうツール について論じてみようと思います。

どのようなツールがあるか

現在、Rubyにおいては各種そのような支援プロダクトがあると思いますが、今回は利用者も多いと考えられる、

を試していきます。

RuboCop

特徴

Copと飛ばれる各種検査モジュールがあり、それらが表記揺れであったり、文法であったり、methodの複雑性の検査を行ないます。 また、 .rubocop.yml と呼ばれるファイルでそれらの on / off, 閾値の設定などが可能となっています。 さらに、 auto-correct という機能があり、RuboCopが判断出来る範囲で規約通りに修正してくれます。

rails_best_practices

特徴

rails-bestpractices.com というrails applicationを実装する際のベストプラクティス集サイトがあります。このベストプラクティスに沿っているかを検査する機能を持ちます。

Ruby on RailsのMVCの静的解析などが可能となっています。 また Ruby on Rails標準のORMである ActiveRecord 以外にも mongoidmongomapper にも対応しています。 template engineも erb はもちろん、 slim, haml, rabl 等豊富です。

試してみる。

対象

  • ruby 2.4.2
  • ターゲットはrailsアプリの代表格で、gem / rails_best_practices を採用していない Redmineの3.4.3を対象とします。
  • redmineを動作させる事は目的としませんので、起動までは確認しません。

RuboCop

導入

Gemfileの書き換え

group :test

gem 'rubocop', require: false

を追加します。

database.ymlの作成

bundle install 時にGemfileでdatabase.ymlを見ているので追加します。

production: &production
  adapter: sqlite3
  database: db/redmine.sqlite3

development:
  <<: *production

test:
  <<: *production
Gemの導入
$ bundle install --path=vendor/bundle --binstubs

環境によっては事前にImageMagick6を入れておく必要があります。

作動させてみる

$ bin/rubocop -P

Pオプションで並列実行されます。rubocopは比較的重い処理の為、CPUが多数ある環境では付けておくと良いでしょう。

さて結果は、

以上略

db/migrate/20110226120112_change_repositories_password_limit.rb:1:1: C: Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:54: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => nil, :default => ''
                                                     ^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:69: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => nil, :default => ''
                                                                    ^^^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:81: C: Metrics/LineLength: Line is too long. [82/80]
    change_column :repositories, :password, :string, :limit => nil, :default => ''
                                                                                ^^
db/migrate/20110226120112_change_repositories_password_limit.rb:7:54: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => 60, :default => ''
                                                     ^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:7:68: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => 60, :default => ''
                                                                   ^^^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:7:81: C: Metrics/LineLength: Line is too long. [81/80]
    change_column :repositories, :password, :string, :limit => 60, :default => ''
                                                                                ^

979 files inspected, 64504 offenses detected

読者のみなさんを驚かせてしまったかもしれませんが、細かい設定をしないでrubocopを作動させると、非常に多数の違反を検知し、今回は64504件の違反(ちなみに原文のoffenseは犯罪の意味もあり、cop(警官)ならではのジョークを感じます)という結果となりました。

failに対応してみる

上記のOffenceの大まかな理由は、

  1. Stringをfrozen指定するCommentが入っていない(Style系Cop)
  2. hashのsyntaxがruby 1.9系の書式(Style系Cop)
  3. 1行が長すぎる(Metrics系Cop)

になります。

まずは、これらに対応してみましょう。

diff --git a/db/migrate/20110226120112_change_repositories_password_limit.rb b/db/migrate/20110226120112_change_repositories_password_limit.rb
index 1ad937c7d..0d244f031 100644
--- a/db/migrate/20110226120112_change_repositories_password_limit.rb
+++ b/db/migrate/20110226120112_change_repositories_password_limit.rb
@@ -1,9 +1,10 @@
+# frozen_string_literal: true
 class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
   def self.up
-    change_column :repositories, :password, :string, :limit => nil, :default => ''
+    change_column :repositories, :password, :string, limit: nil, default: ''
   end

   def self.down
-    change_column :repositories, :password, :string, :limit => 60, :default => ''
+    change_column :repositories, :password, :string, limit: 60, default: ''
   end
 end

そして再実行。

$ bin/rubocop db/migrate/20110226120112_change_repositories_password_limit.rb
Inspecting 1 file
C

Offenses:

db/migrate/20110226120112_change_repositories_password_limit.rb:2:1: C: Layout/EmptyLineAfterMagicComment: Add an empty line after magic comments.
class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
^
db/migrate/20110226120112_change_repositories_password_limit.rb:2:1: C: Style/Documentation: Missing top-level class documentation comment.
class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
^^^^^

1 file inspected, 2 offenses detected

修正した事により、他の問題が出たようです。

  • MagicComment後に空行が入ってない。(Layout系Cop)
  • TopLevelのclassに対してドキュメントが記述されていない。(Style系Cop)

上記ではさらに細かい部分が検知されていますが、これらも検知されないように、さらに対応してみましょう。

diff --git a/db/migrate/20110226120112_change_repositories_password_limit.rb b/db/migrate/20110226120112_change_repositories_password_limit.rb
index 1ad937c7d..29c91ad56 100644
--- a/db/migrate/20110226120112_change_repositories_password_limit.rb
+++ b/db/migrate/20110226120112_change_repositories_password_limit.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
+# @class Abracadabra
 class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
   def self.up
-    change_column :repositories, :password, :string, :limit => nil, :default => ''
+    change_column :repositories, :password, :string, limit: nil, default: ''
   end

   def self.down
-    change_column :repositories, :password, :string, :limit => 60, :default => ''
+    change_column :repositories, :password, :string, limit: 60, default: ''
   end
 end

そして再実行。

$ bin/rubocop db/migrate/20110226120112_change_repositories_password_limit.rb
Inspecting 1 file
.

1 file inspected, no offenses detected

ようやくrubocopを納得させました。

ちなみに --auto-correct オプションを使う事により、殆どの内容を自動修正する事が可能でした。

$ bin/rubocop -a db/migrate/20110226120112_change_repositories_password_limit.rb
Inspecting 1 file
C

Offenses:

db/migrate/20110226120112_change_repositories_password_limit.rb:1:1: C: [Corrected] Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
^
db/migrate/20110226120112_change_repositories_password_limit.rb:2:1: C: [Corrected] Layout/EmptyLineAfterMagicComment: Add an empty line after magic comments.
class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:54: C: [Corrected] Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => nil, :default => ''
                                                     ^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:69: C: [Corrected] Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => nil, :default => ''
                                                                    ^^^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:7:54: C: [Corrected] Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => 60, :default => ''
                                                     ^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:7:68: C: [Corrected] Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => 60, :default => ''
                                                                   ^^^^^^^^^^^

1 file inspected, 7 offenses detected, 6 offenses corrected

設定ファイルでコーディングスタンダードを定める

.rubocop.yml を設定をする事により、重要視しないCopを無効化したり、Metrics系等の閾値を調整したりする事が可能です。

プロジェクトは規約を遵守する為でなく、効率良く開発する為に規約が存在すると私は考えるので、この全てを押し付けず調整出来る機能はありがたいです。

先程のsourceを元に戻し、以下の内容で rubocop.yml を作ってみましょう。ただし、この内容は私が妥当と思う内容では決して無く、あくまでテスト用です。

Style/Documentation:
  Enabled: false
  
Layout/EmptyLineAfterMagicComment:
  Enabled: false

Metrics/LineLength:
  Max: 100 

実行結果は以下の通りです。

$ bin/rubocop  db/migrate/20110226120112_change_repositories_password_limit.rb
Inspecting 1 file
C

Offenses:

db/migrate/20110226120112_change_repositories_password_limit.rb:1:1: C: Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:54: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => nil, :default => ''
                                                     ^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:3:69: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => nil, :default => ''
                                                                    ^^^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:7:54: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => 60, :default => ''
                                                     ^^^^^^^^^
db/migrate/20110226120112_change_repositories_password_limit.rb:7:68: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    change_column :repositories, :password, :string, :limit => 60, :default => ''
                                                                   ^^^^^^^^^^^

1 file inspected, 5 offenses detected

設定したCopが外れていますね。

実際どのような設定項目があるかは、一番簡単な方法はRuboCopの

  • config/disaled.yml
  • config/enabled.yml

を見る事でしょう。

対応しているチェック種類

rubydocを見るのが速いでしょう。

Layout等の表記揺れから、Lintなどのバグの温床チェック、Security周り、Railsまで、かなり多岐に渡ります。

少し覗いてみると、

  • Layout
    • インデントなどのレイアウトに関するCop
  • Lint
    • 文法的に間違いでは無いが、紛らわしい書式に関するCop
  • Metrics
    • classやmethodの複雑度に関するCop
  • Naming
    • methodや定数、ファイル名などの命名に関するCop
  • Performance
    • 性能に関するCop
  • Rails
    • Rails特有の記述に関するCop
  • Security
    • セキュリティホールになりやすい記述に関するCop
  • Style
    • 細かめの記述方法に関するCop
  • Bundler
    • bundlerのGemfile等の書式に関するCop
  • Gemspec
    • Gemspecの記述に関するCop

などがあります。

触れてみた感触

上記でも述べたように、チェック項目がかなり多岐に渡っています。また、開発活発度もかなりのものです。

ただ、基本方針として、かなり固い方向に初期設定を振っているので、開発内で意見調整をしながら.rubocop.ymlを随時チューニングする必要があると感じました。

auto-correctによる自動修正は非常に強力で、開発者からcoding時のわずらわしさから相当開放し、レビュー時に、もっと本質を見る事が可能になりそうです。

多用はあまり推奨しませんが、前述のように規約を守ることよりも効率よく開発することを優先したい場合に使える、部分的にチェックを無効にする方法も、簡単に記載します。

# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def foo
    ながーーーいmethod
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize

rails_best_practices

導入

Gemfileの書き換え

group: test

 gem  'rails_best_practices'

を追加します。

SublimeText2やTextmate2との連動

筆者は生粋のvimmerの為、詳しくは調査していませんが、下記のように各editorと連携出来るようです。

  • handlerを追加する事により、SublimeText2とシームレスに使えるようです。
  • tmbundleを追加する事により、Textmate2とシームレスに使えるようです。
databases.ymlの作成とGemの導入

rubocopと同様です。

production: &production
  adapter: sqlite3
  database: db/redmine.sqlite3

development:
  <<: *production

test:
  <<: *production
$ bundle install --path=vendor/bundle --binstubs

作動させてみる

以下いくつか抜粋。

$ bin/rails_best_practices .
Source Code: |===================================================================================================================================================================|
/Users/foobar/vcswork/redmine/app/models/enumeration.rb:21 - default_scope is evil
/Users/foobar/vcswork/redmine/db/migrate/007_create_journals.rb:34 - isolate seed data
/Users/foobar/vcswork/redmine/app/controllers/account_controller.rb:61 - move model logic into model (@token use_count > 4)
/Users/foobar/vcswork/redmine/app/views/imports/settings.html.erb:9 - move code into helper (array_count >= 3)
/Users/foobar/vcswork/redmine/app/views/wiki/rename.html.erb:14 - remove trailing whitespace

Please go to https://rails-bestpractices.com to see more useful Rails Best Practices.

Found 1316 warnings.

なにも設定しないと全部で1316件のwarningを検知したようです。これはrubocopよりは少ないと言えます。 が、move code into modeldefault_scope is evil など、railsに特化したメッセージが見受けられます。

メッセージの詳細や対処方法は、 rails-bestpractices.comを参考にとの事。

少し面白い機能として、 $ bin/rails_best_practices -f html . するとrails_best_practices_output.htmlが出力され、warning種別毎にリストを表示したりする事が可能になります。

warningに対応してみる

上記のwarningの大まかな理由は、

  1. default_scope is evil : default scopeを使っている。
  2. isolate seed data : migrationにseedを書いている。
  3. move model logic into model : いわゆるfat controller
  4. move code into helper : viewの複雑なcodeをhelperへ逃がせてない。
  5. remove trailing whitespace : 不要な行末スペースがある。

になります。各warningに関するページは warning名 rails-bestpractices で検索しましょう。

それではこれらに対応してみましょう。

default scope is evil

ActiveRecordのdefault scopeを使っています。これは要件によって対応方法が変わるので、画一的に対応は不可能です。 ただし、以下の理由により、利用する事は好ましくないと言えましょう。(詳細は上記のweb page参照の事)

  • unscopeを使って解除しない限り、default scopeをoverrideする事が出来ない。
  • modelのinitializationに影響を与えてしまう。
isolate seed data

migration内でrecordをイジるのは、recordの宣言箇所の一貫性の意味で好ましくありません。schema変更とseed dataの投入は分けましょう。

具体的にはrecordの挿入は db/seeeds.rb に任せましょう。

$ git diff --cached
diff --git a/db/migrate/007_create_journals.rb b/db/migrate/007_create_journals.rb
index 63bdd2374..6fa28c61c 100644
--- a/db/migrate/007_create_journals.rb
+++ b/db/migrate/007_create_journals.rb
@@ -27,13 +27,6 @@ class CreateJournals < ActiveRecord::Migration

     Permission.create :controller => "issues", :action => "history", :description => "label_history", :sort => 1006, :is_public => true, :mail_option => 0, :mail_enabled => 0

-    # data migration
-    IssueHistory.all.each {|h|
-      j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on)
-      j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id)
-      j.save
-    }
-
     drop_table :issue_histories
   end

diff --git a/db/seeds.rb b/db/seeds.rb
new file mode 100644
index 000000000..f8f66c2f5
--- /dev/null
+++ b/db/seeds.rb
@@ -0,0 +1,8 @@
+# data migration
+IssueHistory.all.each {|h|
+  j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on)
+  j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id)
+  j.save
+}
move model logic into model

本来modelに寄せるべきロジックがcontrollerに寄っています。いわゆるFat Controllerです。業務ロジックは業務ModelとしてModelに寄せましょう。

warningになっているapp/controllers/account_controller.rb:61lost_passwordは下記に示しますが、実際長く、メッセージ通り、 @token が4回以上使われています。

ここのrefactoringには @token もですが、そこから取り出されている @user も込みで改修を考える必要があるでしょう。

def lost_password
  (redirect_to(home_url); return) unless Setting.lost_password?
  if prt = (params[:token] || session[:password_recovery_token])
    @token = Token.find_token("recovery", prt.to_s)
    if @token.nil? || @token.expired?
      redirect_to home_url
      return
    end

    # redirect to remove the token query parameter from the URL and add it to the session
    if request.query_parameters[:token].present?
      session[:password_recovery_token] = @token.value
      redirect_to lost_password_url
      return
    end

    @user = @token.user
    unless @user && @user.active?
      redirect_to home_url
      return
    end
    if request.post?
      if @user.must_change_passwd? && @user.check_password?(params[:new_password])
        flash.now[:error] = l(:notice_new_password_must_be_different)
      else
        @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
        @user.must_change_passwd = false
        if @user.save
          @token.destroy
          Mailer.password_updated(@user)
          flash[:notice] = l(:notice_account_password_updated)
          redirect_to signin_path
          return
        end
      end
    end
    render :template => "account/password_recovery"
    return
  else
    if request.post?
      email = params[:mail].to_s
      user = User.find_by_mail(email)
      # user not found
      unless user
        flash.now[:error] = l(:notice_account_unknown_email)
        return
      end
      unless user.active?
        handle_inactive_user(user, lost_password_path)
        return
      end
      # user cannot change its password
      unless user.change_password_allowed?
        flash.now[:error] = l(:notice_can_t_change_password)
        return
      end
      # create a new token for password recovery
      token = Token.new(:user => user, :action => "recovery")
      if token.save
        # Don't use the param to send the email
        recipent = user.mails.detect {|e| email.casecmp(e) == 0} || user.mail
        Mailer.lost_password(token, recipent).deliver
        flash[:notice] = l(:notice_account_lost_email_sent)
        redirect_to signin_path
        return
      end
    end
  end
end
move code into helper

view層に対して複雑なロジックや大きめなコードがあります。viewの見通しが悪くなる為、helperに寄せましょう。

$ git diff
diff --git a/app/helpers/imports_helper.rb b/app/helpers/imports_helper.rb
index 39f3b95ab..934b1d262 100644
--- a/app/helpers/imports_helper.rb
+++ b/app/helpers/imports_helper.rb
@@ -44,4 +44,12 @@ module ImportsHelper
       [format, f]
     end
   end
+
+  def formatting_for_options_separator(var)
+    options_for_select([[l(:label_comma_char), ','], [l(:label_semi_colon_char), ';']], var.settings['separator'])
+  end
+
+  def formatting_for_options_wrapper(var)
+    options_for_select([[l(:label_quote_char), "'"], [l(:label_double_quote_char), '"']], var.settings['wrapper'])
+  end
 end
diff --git a/app/views/imports/settings.html.erb b/app/views/imports/settings.html.erb
index 374dba5b1..c00ffbcfb 100644
--- a/app/views/imports/settings.html.erb
+++ b/app/views/imports/settings.html.erb
@@ -5,13 +5,11 @@
     <legend><%= l(:label_options) %></legend>
     <p>
       <label><%= l(:label_fields_separator) %></label>
-      <%= select_tag 'import_settings[separator]',
-            options_for_select([[l(:label_comma_char), ','], [l(:label_semi_colon_char), ';']], @import.settings['separator']) %>
+      <%= select_tag 'import_settings[separator]', formatting_for_options_separator(@import) %>
     </p>
     <p>
       <label><%= l(:label_fields_wrapper) %></label>
-      <%= select_tag 'import_settings[wrapper]',
-          options_for_select([[l(:label_quote_char), "'"], [l(:label_double_quote_char), '"']], @import.settings['wrapper']) %>
+      <%= select_tag 'import_settings[wrapper]', formatting_for_options_wrapper(@import) %>
     </p>
     <p>
       <label><%= l(:label_encoding) %></label>
remove trailing whitespace

不要な末尾のスペースが存在しています。

$ git diff
diff --git a/app/views/wiki/rename.html.erb b/app/views/wiki/rename.html.erb
index ac88fd4bf..602850d13 100644
--- a/app/views/wiki/rename.html.erb
+++ b/app/views/wiki/rename.html.erb
@@ -11,7 +11,7 @@
 <p><%= f.text_field :title, :required => true, :size => 100  %></p>
 <p><%= f.check_box :redirect_existing_links %></p>
 <p><%= f.select :parent_id,
-                content_tag('option', '', :value => '') + 
+                content_tag('option', '', :value => '') +
                   wiki_page_options_for_select(
                     @wiki.pages.includes(:parent).to_a - @page.self_and_descendants,
                     @page.parent),

その他

列挙したのは極く一部のものです。railsを構成する、Model / View / Controller から細かいhelper、routingなど多岐に亘ります。

一度 https://rails-bestpractices.com/ を見てみるのも良いかもしれません。

設定ファイルを調整してみる

設定ファイルを作成する事により、解析内容をカスタマイズする事が可能です。

まず雛形を作りましょう。 $ bin/rails_best_practices -g を実行すると config/rails_best_practices.yml が生成されます。

AddModelVirtualAttributeCheck: { }
AlwaysAddDbIndexCheck: { }
#CheckSaveReturnValueCheck: { }
............
............
MoveCodeIntoHelperCheck: { array_count: 3 }
MoveCodeIntoModelCheck: { use_count: 2 }
MoveFinderToNamedScopeCheck: { }
MoveModelLogicIntoModelCheck: { use_count: 4 }
NeedlessDeepNestingCheck: { nested_count: 2 }
............
............
RemoveUnusedMethodsInControllersCheck: { except_methods: [] }
RemoveUnusedMethodsInHelpersCheck: { except_methods: [] }
RemoveUnusedMethodsInModelsCheck: { except_methods: [] }
ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
............
UseScopeAccessCheck: { }
UseTurboSprocketsRails3Check: { }

不要なテストがある場合、#や行を消す事により、対象テストをキャンセルする事が可能となります。

また、defaultで入っている属性(オプション)の値を替えたり、配列に値を追加する事により、Checkのカスタマイズが可能となります。

たとえば

MoveModelLogicIntoModelCheck: { use_count: 4 }

use_count5 にすれば、controller内でmodelのあるModelの利用回数が5回以上の場合にwarningになるようになります。

また、全てのCheckには ignore_files オプションを設定する事が可能で、

DefaultScopeIsEvilCheck: { ignored_files: 'user\.rb' }
LongLineCheck: { max_line_length: 80, ignored_files: ['db/migrate', 'config/initializers'] }

のように、1エントリか、配列を受ける事が可能になっています。

対応しているチェック種別

  • Move code from Controller to Model
    • Move finder to named_scope (rails2 only)
    • Use model association
    • Use scope access
    • Add model virtual attribute
    • Replace complex creation with factory method
    • Move model logic into the Model
    • Check return value of "save!"
  • RESTful Conventions
    • Overuse route customizations
    • Needless deep nesting
    • Not use default route
    • Restrict auto-generated routes
  • Model
    • Keep finders on their own model (rails2 only)
    • The law of demeter
    • Use observer
    • Use query attribute
    • Remove unused methods in models
    • Protect mass assignment
    • Destroy return value (disabled by default)
  • Mailer
    • Use multipart/alternative as content_type of email
  • Migration
    • Isolating seed data
    • Always add database index
    • Use say with time in migrations
  • Controller
    • Use before_filter (disabled by default)
    • Simplify render in controllers
    • Remove unused methods in controllers
  • Helper
    • Remove empty helpers
    • Remove unused methods in helpers
  • View
    • Move code into controller
    • Move code into model
    • Move code into helper
    • Replace instance variable with local variable
    • Simplify render in views
    • Not use time_ago_in_words
  • Deployment
    • Dry bundler in Capistrano
    • Speed up assets precompilation with turbo-sprockets-rails3
  • Other
    • Remove trailing whitespace
    • Remove tab (disabled by default)
    • Hash syntax (disabled by default)
    • Use parentheses in method definition (disabled by default)
    • Long line (disabled by default)
    • Not rescue exception

触れてみた感触

Railsに関しては、MVCからmigration、helper、capistranoなど、ほとんどを含んでおり、かなりリッチな作りになっているように思われる。

ただし、 開発はあまり活発とは言えず、また、モトネタとなる Rails Bestpractices 自体も更新が止まっているようです。

とは言うものの、現状、提供されているCheckはRails5系でも有用に思われます。

結論

両方使え

筆者の個人的な感想を含んだ結論ですが、 RuboCop はrubyのcodeの静的解析を主に、rails_best_practices はその上に乗るrailsの静的解析を主に置いたツールと言えるでしょう。 ようするに 対象とするレイヤーが異なる のです。

双方のツールを導入し、それぞれのレイヤーのテストを重なる部分はあれど、補完しあえば良いかと考えます。

重要な点は、 解析ツールに合わせたコードを書くのでは無く、プロジェクトに必要とされるコードを素早く書く という事だと思います。ツールの結果に合わせてコードを曲げる(読みにくくしたり、リファクタリングの際にエンバグしたり)のは本末転倒と言えるでしょう。

これらの運用方法の検討に時間をかける余裕がない場合、 SideCI 等のcloud solutionを考えてみるのも良いかもしれません。SideCIではgithubやslackと連動させ、静的解析の結果をreviewer/revieweeへ通知し、reviewを行なう環境を簡単に得る事が出来ます。