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
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
では、他のメトリクスツール(RuboCop
やrails_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 による自動コードレビューが出来る唯一のウェブサービスです。(本記事投稿時点)
reekのrails向け設定のテンプレを見たことありますか?
railsでreekを使うと、自動生成されたコードでかなりうるさく言われてしまうので、テンプレがあると嬉しいなぁと思ってます。