UUUMエンジニアブログ

UUUMのエンジニアによる技術ブログです

静的解析ツール「RubyCritic」の紹介

おはようございます こんにちは こんばんは。
UUUMのシステムユニットのmatsumotoです。
先日、RubyCriticという静的解析ツールを導入しましたので紹介します。

RubyCriticとは?

RubyCriticはrubyコードを静的解析するツールです。rubygemで提供されています。

ファイル毎の複雑度や重複した記述などを指摘してくれます。

解析結果はブラウザ上で確認できます。

導入に至った経緯

自分が担当しているシステムには既にメジャーな静的解析ツールであるRubocopが導入されており、コーディング規約に準拠してるかのチェックは行えていました。
しかし、より可読性、保守性が高いコードを書くためにもコーディング規約のチェックに加え、ファイル毎の複雑度や重複箇所などもチェックされるようになると良いと思いました。

また、RubyCriticを導入する事によってレビュー前にコーディングの指摘を受ける事ができ、結果としてレビュワーの負担を軽減する事ができると思いました。

あと解析結果を表示するブラウザがとても見やすい点もRubyCriticを導入した理由の一つです。

導入方法

導入方法はとてもシンプルです。

まずはrubycriticのgemをインストールします。

Gemfile

group :development do
  gem "rubycritic", :require => false
end

bundle installを行えば※基本的には導入完了です。


※ 補足

自分がrubycriticを導入しその後実行した時下記エラーが発生しましたので共有します。

RailsアプリにDockerを導入している場合に起こりうる可能性があるエラーです。(自分が担当しているシステムにもDockerが導入されています。)

エラー文

ArgumentError: invalid byte sequence in US-ASCII
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html/line.rb:22:in `delete'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html/line.rb:22:in `render'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html/code_file.rb:42:in `block in render'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html/code_file.rb:39:in `each'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html/code_file.rb:39:in `with_index'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html/code_file.rb:39:in `render'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html_report.rb:37:in `block (2 levels) in create_directories_and_files'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html_report.rb:36:in `open'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html_report.rb:36:in `block in create_directories_and_files'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html_report.rb:34:in `each'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html_report.rb:34:in `create_directories_and_files'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/generators/html_report.rb:21:in `generate_report'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/reporter.rb:9:in `block in generate_report'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/reporter.rb:8:in `each'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/reporter.rb:8:in `generate_report'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/commands/default.rb:29:in `report'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/commands/default.rb:19:in `execute'
  /usr/local/bundle/gems/rubycritic-4.6.1/lib/rubycritic/cli/application.rb:21:in `execute'
  /usr/local/bundle/gems/rubycritic-4.6.1/bin/rubycritic:10:in `<top (required)>'
  /usr/local/bundle/bin/rubycritic:23:in `load'
  /usr/local/bundle/bin/rubycritic:23:in `<top (required)>'
ERROR: 1

調査した事

Dockerfileを確認するとDockerhub上のRubyのimage(version 2.5.7)が使われていました。

そのイメージの詳細を確認するとデフォルトのエンコーディングがUS-ASCIIでした。

$ docker run -it ruby:2.5.7 bin/bash
root@49bf9357e1b3:/# ruby -e 'puts Encoding.default_external'
US-ASCII

なので上記エラーが出たようです。

export RUBYOPT=-EUTF-8すればエンコーディングを変更できます。

$ docker run -it ruby:2.5.0 bin/bash
root@6b9b22a6ce36:/# export RUBYOPT=-EUTF-8
root@6b9b22a6ce36:/# ruby -e 'puts Encoding.default_external'
UTF-8

対処方法

Dockerfileに下記コードを追加

ENV RUBYOPT -EUTF-8

これでエンコーディングを変更できエラーなくrubycriticを実行できます。

因みにMacにrbenv経由で入れたRubyの場合、デフォルトのエンコーディングはUTF-8になっています。

実行方法

$ rubycritic .

で実行できます。

実行後./tmp/rubycritic/直下に解析結果がhtmlで保存され、ブラウザが起動して閲覧することができます。

$ rubycritic app/controllers/api/v1

のように指定のディレクトリだけを対象にして実行することもできます。

その他オプションはREADMEのUsageに記載さされているのでご確認いただけたらと思います。

解析内容について

rubycriticを実行した結果は下記になります。

$ docker-compose run --rm web bundle exec rubycritic app/controllers                                                                                                                                                                                                                              11:47:35
Creating coda-develop_web_run ... done
running flay smells
...........................................................
running flog smells
....................................................................................................................................................................................
running reek smells
....................................................................................................................................................................................
running complexity
....................................................................................................................................................................................
running attributes
....................................................................................................................................................................................
running churn
....................................................................................................................................................................................
running simple_cov
....................................................................................................................................................................................
New critique at file:////coda/tmp/rubycritic/overview.html
Attempted to open file:////coda/tmp/rubycritic/overview.html and failed because Unable to find a browser command. If this is unexpected, Please rerun with environment variable LAUNCHY_DEBUG=true or the '-d' commandline option and file a bug at https://github.com/copiousfreetime/launchy/issues/new
Score: 53.79
open tmp/rubycritic/overview.html

RubyCriticは、以下のruby解析用Gemに依存しています。

  • flay

    • コード同士の類似度が高い部分を検出し、共通化に役立ちます。
  • flog

    • コードの複雑度が高い部分を検出します。
  • reek

    • 臭いコード(smell)を検出します。
    • smellは、reekのドキュメントによると、「コードが読みにくい、または保守しづらい場所を示唆するもの」とのことです。

解析結果の確認

Overview
Code: ソースコードごとの警告の数や複雑度や評価(Rating)が一覧表示されます。
Smells: 臭いコードが存在する箇所が一覧表示されます。
CodeやSmellsで対象コードのリンクを押した際に開かれる画面。
コードの中に警告内容がインラインで表示され、警告名をクリックすると警告解説記事にジャンプします。

reekが検出するsmellについて

rubycriticはreekというコードの"臭う"箇所を検出するgemに依存していますが、そのreekが検出するsmellはデフォルトの設定だととても個性的かつ厳しすぎるくらい指摘されます。

なので使い勝手が悪い、また肌に合わない場合はカスタマイズすると良いかと思います。

リポジトリのトップにyamlファイル(.reekファイルまたはconfig.reekファイル)を置くことで、reekが検出する特定のsmellをカスタマイズして抑制することができます。

詳細はこちらに記載されています。

RubyCriticを導入した結果(感想)

レビューをいただく前に実装で不安な箇所があった際は、今回導入した解析ツールを走らせる事によって改善点の早期発見とともにレビュアーの負担を削減できました。

また解析結果を表示するブラウザのUIもとても分かりやすく、今後リファクタをするタスクに着手する際はとても助けになるかと思います。