SideCI Blog

自動コードレビューサービスSideCIを提供している株式会社アクトキャットのコーポレートブログです。



Rubyのコードスメルチェックツールreekで「こんなコードは嫌!」っていうのを防ごう

reekというRubyのコードスメルチェックツールを皆さんご存じですか? この記事では、Code Smellチェックツールであるreekの利用方法を説明します。

Table Of Contents

  • こんなコードは嫌!なコードを検出する
  • reekのインストール・基本的な使い方
  • 多種多様なCode Smellたち
  • さすがに厳しすぎ…?なチェック項目
  • 適度にチェック項目をゆるくした.reekファイルサンプル

こんなコードは嫌!なコードを検出する

reekは、rubyアプリのコードに潜んでいる「臭う」部分を指摘してくれるチェックツールです。

以下の様なコードを目にしたら、どのように思うでしょうか。

def duck_names
  File.open(@output_path, "w") do |file|
    %i!tick trick track!.each do |surname|
      %i!duck!.each do |last_name|
        file.puts "full name is #{surname} #{last_name}"
      end
    end
  end
end

ネストが深く、読みづらいと考える人も多いように思えます。

また、以下の様なコードを目にしたら、どのように思うでしょうか。

def password
  if request.post?
    unless @member.authenticated?(params[:password])
      @member.errors.add :password, "is invalid"
      return
    end
    @member.crypted_password = ""
    @member.password = params[:new_password]
    @member.password_confirmation = params[:new_password_confirmation]
    @member.save
  end
end

無駄は無いかもしれませんが、少し行数が多いですね。

reekは、こういった、「なんとなく嫌な感じがする」・「臭う(smell)」ようなコードを検出して警告を出してくれます。

Smellは、reekのドキュメントによると、「コードが読みにくい、または保守しづらい場所を示唆するもの(indicators of where your code might be hard to read, maintain or evolve)」とのことです。

現在、28個ほどのSmell(チェック項目)が定義されています。

基本的な使い方

reekは、gemで提供されます。

$ gem install reek` でインストールできます。

これより先の説明は、以下のバージョンで動作確認しています。

  • MacOSX 10.10.3
  • ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
  • reek 2.2.0

実行コマンドは以下のとおりです。 以下、Bundlerを利用している場合は適宜bundle execを先頭につけてください。

$ reek [options] [path]

htmlに結果を出力する

先ほどの、「なんとなく臭う感じのコード」をdemo.rbというファイル名で保存しておきます。

同じディレクトリで、以下のコマンドを実行します。

$ reek -f html demo.rb
Html file saved

今回はレポートフォーマットをhtmlとして結果出力してみます。

-f(--format)オプションで出力形式を指定できます。

上記コマンドを実行するとカレントディレクトリにreek.htmlというファイルが出力されます。

ブラウザで開いてみると以下の様な画面で確認することが出来ます。

$ open reek.html

alt="reek output"

duck_names contains iterators nested 3 deep (NestedIterators)
Line [4]
demo.rb

password has approx 6 statements (TooManyStatements)
Line [11]
demo.rb

上記の通り、2つの警告が確認出ました。

以下のとおりに解釈できます。

1つ目は、demo.rbの4行目付近のduck_namesというメソッドに、NextedIterators というCode Smellによる警告が出ています。

これは「イテレータによるネストが多い」ということを検出したものです。

2つ目は、demo.rbの11行目付近のparseというメソッドに、TooManyStatementsというCode Smellによる警告が出ています。

これは、「1つのメソッドに命令文が多い」という警告です。

reekがチェックするさまざまなチェック項目(Code Smell)は、reekのdocsで1項目1ページ毎に解説されており、そこで確認することが出来ます。

試しに、この2つ目の警告「TooManyStatements」を直してみます

before:

def password
  if request.post?
    unless @member.authenticated?(params[:password])
      @member.errors.add :password, "is invalid"
      return
    end
    @member.crypted_password = ""
    @member.password = params[:new_password]
    @member.password_confirmation = params[:new_password_confirmation]
    @member.save
  end
end

after:

def password
  if request.post?
    unless @member.authenticated?(params[:password])
      @member.errors.add :password, "is invalid"
      return
    end

    @member.update_password(params[:new_password], params[:new_password_confirmation])
  end
end

# モデル側

class Member
  def update_password(new_password, new_password_confirmation)
    self.crypted_password = ""
    self.password = new_password
    self.password_confirmation = new_password_confirmation
    save
  end
end

このあと$ reek demo.rb を実行すると警告がなくなっていることが確認できます。

YAMLファイル(.reekファイル)によるカスタマイズ方法

reekでは、他のメトリクスツール(RuboCoprails_best_practices)と同様に、YAMLファイルによって検出内容を設定することが可能です。

このYAMLファイルは、reekコマンドを実行するディレクトリにファイル名が.reekで終わるファイルとして保存しておけばOKです。

YAMLの設定例として、先ほどのNestedIteratorsというCode Smellについてカスタマイズしたい場合についてを示します。

単純にNestedIteratorsの警告を全て抑えるには、以下のようにenabled: falseとします。

NestedIterators:
  enabled: false

また、NestedIteratorsにおいて、ある特定のメソッド名だけはチェック対象としない(exclude)、という設定もできます。

以下は#duck_namesという名前のメソッド、または、AboutControllerにおける#indexメソッドにおいてはチェック対象としないという設定です。

NestedIterators:
  exclude:
    - duck_names
    - AboutController#index

また、Code Smellの種類ごとに、追加で渡せるオプションが定義されています。 詳しくは個々のsmellについてのドキュメント(NestedIteratorsの例はこちら)を参照してみてください。

多種多様なCode Smellたち

reekでは現在28個ほどのCode Smellが定義されています。

その中からreekならではのユニークなものを4つ、ピックアップして説明します。

Data Clump

引数の名前をチェックして、同じような引数名の組が複数箇所で使われていると警告を出します。

警告が出るケース:

class Dummy
  def x(y1,y2); end
  def y(y1,y2); end
  def z(y1,y2); end
end

何度も出てくる同じような引数名の組(y1, y2)は、オブジェクト化したほうが良いという示唆を与えます。

Utility Function

インスタンスの状態に依存していないインスタンスメソッドに対し、警告を出します。

警告が出るケース:

class Dummy
  def showcase(argument)
    argument.to_s + argument.to_i
  end
end

このメソッドはargumentの状態にしか依らないので、このメソッド自体がargument側にあるべきではないかということを示唆しています。

Feature Envy

ある別のオブジェクトのメソッドばかりを呼び出していると警告を出します。

警告が出るケース:

class Cart
  def price
    @item.price + @item.tax
  end
end

このメソッドは、@itemの機能(feature)ばかりに興味があり、まるで妬んでいる(envy)かのようです。

この税込み計算のメソッドは、@item側に置くことを検討すべきではないか、ということを示唆しています。

Prima Donna Method

bang(!)付きのメソッド名で、かつ、同じ名前でbang無しのものが定義されていなかった場合、警告します。

(!)は同名の(!)なしのメソッドがあり、危険な方であるということを示す際にのみ使うべき、という思想のようです。

警告が出るケース:

class C
  def foo; end
  def foo!; end
  def bar!; end
end

foo!は警告されませんが、bar!barが無いため、警告されます。

このあたりはコーディングスタイルともいえそうな部分なので、プロジェクトごとに採用不採用を検討してもよいと考えます。

さすがに厳しすぎ…? なチェック項目

デフォルトでそのまま使うと、警告が出すぎて煩わしさを感じてしまうと思われるものを紹介します。

IrresponsibleModule

クラスの説明のコメントが無いと警告が発生するチェック項目です。

コメントを書いていないプロジェクトではオフにしたほうが良いと思われます。

TooManyStatements

メソッドの中に命令文が多すぎると警告を出します。

デフォルトが許容量が6と、厳しい設定になっているので、10ぐらいにするのが良いと思われます。

NestedIterators

イテレータによるネストが多いと警告を出します。

デフォルトの許容量が1と、厳しい設定になっているので、3ぐらいにするのが良いと思われます。

DuplicateMethodCall

同じ引数で何度も同じメソッドを呼ぶと警告になります。

例えば

class SomeController < ApplicationController
  def index
    ...
    if @feed
      respond_to do |format|
        format.html { render file: ... } # 1
        format.json { render json: ... } # 2
      end
    else
      respond_to do |format|
        format.html { render file: ... } # 1と同じ内容
        format.json { render json: ... } # 2と同じ内容
      end
    end
  end
end

のというふうにしたときに、警告が出てしまいます。

デフォルトの許容量が1と、厳しい設定になっているので、2ぐらいにするのが良いと思われます。

.reekファイルのおすすめの設定

以上を踏まえ、適度にチェック項目をゆるくした.reekファイルサンプルを下記に示します。

IrresponsibleModule:
  enabled: false

TooManyStatements:
  enabled: true
  exclude:
  - initialize
  max_statements: 10

NestedIterators:
  max_allowed_nesting: 3

DuplicateMethodCall:
  max_calls: 2

この他にも、プロジェクトのコーディングスタイルにマッチしなかったりするものは、enabled: falseにすることなどを検討してもよいでしょう。

まとめ

いかがでしたでしょうか。
本エントリーでは、Rubyのコードスメルチェックツール、reekというgemの使い方、設定例などをご紹介させて頂きました。

継続的に使っていきたい方やチームに対して導入していきたい方はぜひSideCI上で動くreekをお試し下さい!

GitHub Pull Request x reek による自動コードレビューが出来る唯一のウェブサービスです。(本記事投稿時点)