RuboCopを無理なく既存プロジェクトに導入する
TL;DR
- RuboCop良いよ
- 入れるだけだと誰も使わないよ
- 運用フローに入れると良いよ
- 千里の道も一歩からだよ
RuboCopとは
RuboCopはコーディング規約に従っているかをチェックし、コードの品質を保ってくれるgemです。
例えば以下のようなrubyとしてはあまり良くないコードだった場合に、RuboCopはルールに沿って問題点の指摘や自動修正を行ってくれます。
これ以外にもRuboCopは多様なルールをサポートしており、その中にはバグりやすいコードを回避するようなルールもあります。
RuboCopを使う事でコーディング規約に沿っていないコードや、バグを含んだコードを自動である程度抑制でき、レビュー等でそれらの指摘する手間を大きく削減できます。
導入方法
gem install rubocop するか、Gemfileに書いてインストールしてください。
rubocopコマンドが使えるようになるので、rubocop xxx.rbで解析を実行できます。また、rubocop -a xxx.rbで一部のルールでは自動修正を行います。
.rubocop.ymlファイルを作ることで様々な設定が可能ですが、細かい設定はこの記事では省略します。詳しくはドキュメントや他の記事を参考にしてください
http://rubocop.readthedocs.io/en/latest/configuration/
実際に入れた場合の問題点
残念ながら、既に動いているプロジェクトにRuboCopを入れても上手く動かないことが多いです。
例えば私の所属しているプロジェクトでは以下のように3年近く前にRuboCopがコミットされていますが、現在のmasterは13万件以上のエラーが出る状態です。
そのため、いかにルールを守らせるか?今の惨状をどうするか?という問題を解決する必要があります。
ルールを追加しても誰も守らない
robocopの解析はコマンドを実行しないと行われません。そのため、実行していない場合は気がつきにくく、PR等で指摘されずに通ってしまう場合があります。全てのメンバーがコミット前に実行してくれるとは限らないので、実行せずにコミットされた場合を検知するのは重要です。
RuboCopはルール違反があった場合にステータスコード1で終了するため、自動ビルドのフローには簡単に入れられます。ですが、ビルドログから違反内容を一々調べるのは手間がかかるため、RuboCopのルールを守るコストが高くなり、結局誰も守らなくなる可能性があります。
そこでこの問題に対しては、出されたPRの自動ビルド中に解析を行い、PR上で結果を指摘するようにしました。これにより、RuboCopを実行していないことを検知でき、かつその内容をPR上に表示させることができます。
RuboCopの結果をPRに書き込む
同じような事を考えている人は多いらしく、PRにRuboCopの結果を書き込む方法はいくつかあります。(有名どころだとpronto、SideCI)
今回はプロジェクトで自動ビルドにJenkinsを使用していることもあり、ビルドスクリプトにコマンドを1行増やすだけで導入できる、saddlerを使いました。(より詳しくは後述)
サンプルにあるTravis CI用のスクリプトから、手元のJenkins環境で動くように少し修正し、以下のコマンドラインを実行しています。
(環境変数にGithubのアクセストークンを設定する必要があります)
これにより、PRを送ると自動でRuboCopの解析が行われ、その結果をPRにコメントされます。
既存コードの対処
導入しようとしたプロジェクトではRuboCopが全く運用されていなかったため、13万件以上のルール違反が検出されていました。これを一度に修正するのはリスクがあるため、少しづつ修正していくしかありません。ただし、違反箇所がPRにコメントされるため、何も手を打たないと大量の違反コメントがPRにつけられ、通常のレビューを阻害してしまいます。
幸いにも同じ問題に直面している人は多いのか、RuboCopには実行した瞬間のルール違反を全て無効化するように設定ファイルを生成する機能があります。この機能を利用してある時点でのルール違反を0件し、少しづつ無効化されたルールを有効にしていくことで、一度に全部対応しなければいけない状態を避けられます。
より詳しい手順に関しては以下のブログ記事が詳しいです
http://blog.onk.ninja/2015/10/27/rubocop-getting-started
大量のルール違反を潰していく
我々のプロジェクトにかつてRuboCopが導入されていたため、基本的にはそのルールをちゃんと守るようにすれば良いのですが、RuboCopが運用フローに乗っていないため、各人がルールに縛られることなくのびのびとコードを書いており、大量のルール違反が起きていました。
闇雲に直していくのは大変なので、今回は無効にしたRuboCopルールをGithubのIssueで管理し、複数人で同時に作業をしています。また、あまりにも量が多すぎて現在の状況や、どれくらい進んでいるのかがわからないため、Issueのラベル機能を使い、無効にしているルールの件数や状態を利用し、ルールの適応状況の可視化を行う事にしました。
.rubocop_todo.ymlの中身をIssueにする
我々のプロジェクトでは、 — auto-gen-configによって200件近くのルールが無効化されました。これを全て手動でIssueにするのは大変なため、以下のスクリプトで.rubocop_todo.ymlの中身をIssue化しました。
また、前に設定済みのルールなのか、RuboCopのバージョンアップで増えたルールなのかを判別するために、前に作ったRuboCopの設定ファイルを読んで、設定済みならその設定内容も一緒にIssue化しています。
Issueを精査していく
rubocop_todoの中身が全てIssue化されたら、過去の設定や内容をチェックし、適応するかを決めていきます。過去の設定があったため基本的にはその設定を引き継ぐだけですが、設定が怪しいものや新しく追加されたものに関しては内容を見たり相談したりして設定を決めていきます。決定したものはRuboCopの設定ファイルに書いてマージし、後はコードを対応させるだけというラベル(rubocop-todo等)を作ってそれに貼り替えます。
議論をslack上で行ったため、最終的にどういった経緯でその設定したかをIssueのコメント欄に残しています。これにより、実際に作業する場合や、変だと思った際に経緯が確認できるようになり有益でした。
auto-correctしていく
どのような設定で適応させるかを決めれば、後は実際にコードを修正していくだけになります。これも量が多いので一つ一つ適応させるのはとても大変ですが、RuboCopは一部のルールに関しては自動修正機能があります。これを利用することで、修正自体はRuboCopの自動修正で行い、人の目とテストでOKかどうかをチェックするだけにできます。RuboCopには特定のルールのみに絞って実行する機能も備えていますので、Issueになったルール一つに対して一つのPull Requestを作り、修正を行っていきます。
もちろんこれも手でやるととてもつらいので、以下のようなスクリプトでIssueから対応するRuboCopのルールを自動修正し、Pull Requestを生成しています。
生成されたPull Requestのうち自動修正が行われた物に関しては、そのままレビューを行い、stagingでの動作検証の後に本番にマージしていきます。自動修正が行われない場合は.robocop_todo.ymlから設定が消された状態でブランチ作られるので、そのブランチを使って手動で直していきます。
手動で直していく
自動修正は全てのルールに対応しているわけではないので、最終的には手動で直す必要があります。 各ルールに対してIssueとPull Requestが作られていますので、それを活用して地道に、できれば複数人で平行して修正していきます。基本的にルール違反の件数は増加しないので、地道に一つずつ直していけば、いずれ全てのルール違反を消すことが出来ます。
その他
コードレビューあるなら要らないのでは?
RuboCopがサポートしているルールはある程度のレベルの人であれば見付けることができる物も多く、コードレビューで弾くことは可能です。ただ、RuboCopで事前に自動で指摘できるものを潰しておくことで、人間でしかわからない部分のレビューに集中できるため、コードレビューをより良く効率的にできるようになります。
なぜsaddlerを選んだか
RuboCopの解析をするには他にもprontoやSideCIがあります。saddlerは導入は楽ですがメンテやRuboCop以外へ広げることを考えるなら、他の選択肢の方が結果としては楽になります。ただ、他のプロジェクトも同様にRuboCopが全くメンテされていない現状でそれらを導入してもほぼ恩恵を受けることができないため、まず低コストで導入の楽なsaddlerを入れて実績を作り、広める段階で別のに乗り換えていく方法を選びました。
進捗管理を何故Issue数でやっているのか
今回は、精査してないものや確認中のものといった細かい状態管理をしたかったため、実際の違反件数ではなく無効にしているルール数で進捗管理をしています。ただ、現在ルールが全て対応待ちになったため、今後実際の違反件数を進捗管理として採用しようと考えています。
まとめ
RuboCopの紹介と、実際にプロジェクトに導入する際の問題点と、その解決方法を説明しました。既存プロジェクトに入れようとすると大量の件数に諦めてしまいがちですが、これらの手順を取ることで新しいコードはRuboCopの恩恵に預かりつつ、既存コードへの適応をコントロールできるため、比較的簡単に導入することができます。