RefactorFirstで倒すべき神クラスを探せ!
この記事は夏のリレー連載2023初日の記事です。
大きなクラスや複雑なクラスを見つけるとリファクタリングしたくなりますよね。ただ、そんなリファクタリングしたくなるようなイケてないクラスがいくつもあった場合、みなさんはどのように優先度付けしますか?そんな時に費用対効果の面からリファクタンリグすべきクラスを抽出し優先度付けを行ってくれるRefactorFirstを今回は紹介します。
RefactorFirstとは
#RefactorFirstはJavaの静的解析ツールの1つであるPMDを使い、技術的負債となっている「神クラス」を検出し、どの神クラスを優先的にリファクタリングするべきかを費用(労力)と効果の2軸でグラフィカルにレポートしてくれるMavenプラグインです。
このRefactorFirstは論文発表された理論[1]に基づき優先度付けを行っているため、ソフトウェアエンジニアリングの観点からその有効性が実証されています。
このため、優先度付けはプラグイン作者の主観によるオレオレ理論といったものではなく、その評価にはこれまで有効性が認められてきた様々なメトリクスが使われています。冒頭で登場した「神クラス」も複数のメトリクスによって識別されています。
今回はこのRefactorFirstについて、どのようなメトリクスをもとに評価しているかを堅苦しい理論ではなく、その意味を平易に説明してきたいと思います。
ということでまずは動かしてみましょう!
英語では"God Class"と呼ばれ、全知全能の神のように余りにも沢山のことを行い、その結果コードが肥大化し手に負えなくなったクラスに用いられる俗語。もちろん良くない意味で用いられる。
まずは動かしてみる
#説明には神クラスとなるイケてないクラスが複数個必要となりますが、イケてないクラスを探したり作ったりすのは結構難しかったりします。どうしようかなと思っていたところ、RefactorFirstのリポジトリには神クラスが複数個抽出されるイイ感じのサンプルが付いてました。今回はこのサンプルをもとに説明してきます。
まずはリポジトリをチェックアウトし、リポジトリに直下に移動します。
git clone https://github.com/jimbethancourt/RefactorFirst.git
cd RefactorFirst
次にRefactorFirstを実行します。実行するMavenコマンドは次のとおりです。
mvn org.hjug.refactorfirst.plugin:refactor-first-maven-plugin:0.4.0:report
実行が成功するとリポジトリ直下にtarget/site/refactor-first-report.html
ができています。これがRefactorFirstのレポートになります。
RefactorFirstのリポジトリはMavenのマルチモジュールによるモノレポ構成になっています。Mavenコマンドを実行するとトッププロジェクトのRefactorFirst
だけが実行され、他のサブプロジェクトはすべてSKIPされますが実行結果としては問題ありません。またサンプルのtarget/site/refactor-first-report.html
に登場するクラスはすべてリポジトリ直下のtest-resources
プロジェクトのクラスになっています。
なお、自分のプロジェクトにRefactorFirstを導入する際に必要となる手順はこちらのbuildとreportのプラグイン定義をpomに追加するだけです。
グラフの見方
#refactor-first-report.html
をダブルクリックし、ブラウザで開くと次のグラフが現れます。
グラフの中をドラックすることで中心を移動させることや、マウスホイールを動かすことでグラフを拡大/縮小することができます。
抽出対象
#大きさの違う円が5つプロットされていますが、これがリファクタリング対象として抽出された神クラスになります。この神クラスの詳細は後述しますが、PMDのGod Class Rule
に引っかかったクラスになります。
リファクタリングの必要がない、PMDのGod Class Rule
に掛からなかったクラスはその必要性がないためグラフには現れてきません。実際、test-resources
プロジェクトには6つのクラスがありますが、その中のAttributes
クラスはGod Class Rule
に掛からなかったため、グラフには出てきていません(コードを見ればリファクタリングの余地なしということはすぐ分かります)。
優先度
#1番優先してリファクタリングすべきはグラフの真ん中にある緑色のAttributeHandlerJavaEleven.java
になります。
RefactorFirstは優先度を色によって識別できるようにしています。優先度はグラフ上部のグラデーションで表されているように優先度1の緑から始まり、赤くなるほど優先度が低くなります。
よって、グラフから優先度が一番低いクラスは真ん中付近にある小さい赤丸のAttributeHandler2.java
となります(マウスカーソルをグラフ上の円に持っていくとポップアップで詳細が表示されます)。
この優先度の高低は、優先度高のものは「リファクタリングの手間が掛からず効果が高いもの」、対して優先度低は「リファクタリングの手間が掛かり効果も低いもの」という考えに基づいています。
では、ここでいっている手間や効果はどのように決めているのか?ですが、これは横軸の労力(Effort)と縦軸の変化傾向(Change Proneness)からとなります。ということで次からは縦軸と横軸の内容とその見方を見ていきます。
横軸の労力(Effort)とは
#細かいことは後回しに横軸の意味をザックリいうと横軸は「リファクタリングに掛かる労力」で、右に行けば行くほど労力が掛かる、つまり強敵クラスという意味になります。また横軸の数値はリファクタリング対象として抽出された神クラスをリファクタリングの労力が掛からない順にランク付けした値となります。
よって、神クラスの中で相対的にリファクタリングの労力が掛からないクラスはAttributeHandlerAndSorter.java
で、一番手間が掛かる最強クラスはConsole.java
となります(コードを見ると確かに、、という感じはします)。
PMDによる神クラスの判定
#PMDは神クラスを次の3つのメトリクスをもとに判定しています。
- 複雑度
- メソッドの数や制御構文が多ければ多いほどプログラムは複雑であるとした循環的複雑度(Cyclomatic complexity)に基づく
- 結合度
- 他のクラスのオブジェクトの属性を参照している数。他への依存が多ければ多いほど他からの変更影響を受けやすいという考えに基づく
- 凝集度
- クラス内の同じフィールドを参照しているメソッドの数。この数が多ければ多いほど、クラスに関係するもの(フィールド)とそれを扱うもの(メソッド)がまとまっているとみなせる
PMDはそれぞれのクラスごとに上述した3つのメトリクスを取得し、3つすべての閾値を超えたクラスを「神クラス」と判定しています。なので、神クラスとして判断されたものは「複雑で他からの影響を受けやすくまとまりもないクラス」と3拍子揃ったダメダメなクラスとなります。なお、PMDが取得しているメトリクスの詳細や閾値はこちらのサイトで分かりやすく説明されています。
労力(Effort)のランク付け
#RefactorFirstのドキュメントに記載はないですが、プラグインのコードを見るとRefactorFirstはPMDが神クラス判定に使ったメトリクスをもとに労力(Effort)のランク付けを行っています。複雑度、結合度、凝集度のそれぞれごとに数値が良いもの順にランク付けを行い、その3つを足し合わせた数値をもとに最終的に神クラスのリファクタリングに掛かる労力(Effort)をランク付けしています。
- 労力(Effort)のランク付けの例
複雑度 | 結合度 | 凝集度 | 合計 | 労力(Effort) | |
---|---|---|---|---|---|
Aクラス | 2位 | 3位 | 1位 | 6 | 2位 |
Bクラス | 3位 | 2位 | 2位 | 7 | 3位 |
Cクラス | 1位 | 1位 | 3位 | 5 | 1位 |
※数値的に良い方が順位が上
縦軸の変化傾向(Change Proneness)とは
#縦軸の変化傾向の意味は不具合が発生する確率になります。"Change Proneness"の直訳の「変化傾向」という単語から意味の飛躍がありますが、これは「リリース前に何度も変更されるコードは、同じ期間内であまり変更されないコードよりもリリース後の欠陥が多くなる可能性が高い」というWindows Server 2003の開発時に行われた研究結果[2]をもとにしています[3]。
こちらもドキュメントに記載はないですが、コードを見るとRefactorFirstはこの「リリース前に行われた変更」を数値化するため、Gitのcommitログから次の2つを取得しています。
- 該当ファイルが作成されてからの総コミット数(総コミット数)
- 該当ファイルの変更が含まれるコミット数(変更コミット数)
そしてこの2つの数値を変更コミット数 / 総コミット数
したものを変化傾向の値としています。つまり、RefactorFirstにおける変化傾向とは「コミットに該当クラスの変更が含まれる確率」を意味し、数値が高いクラスほど変更が頻繁に発生していると考えることができます。
この変化傾向が少ない順にランク付けした値が縦軸の変化傾向(Change Proneness)の数値となっています。
まとめ
#変化傾向(Change Proneness)と労力(Effort)はまとめると次の意味になります。
- 変更頻度が多いものは不具合の確立が高いため改善する価値がより高い(効果が大きい)
- 複雑度と結合度が低く、凝集度が高いクラスほど改善を行いやすい(コストが掛からない)
RefactorFirstは「変化傾向(Change Proneness)の数値(順位)」から「労力(Effort)の数値(順位)」を引いた値が大きければ大きいほど効果が高くコストも掛からないという考えから、最終的にこの数値をリファクタリングすべきクラスの優先度としています。
また、ここまでの内容をもとにグラフの見方を4象限に分けて一般化すると次ようになります(頭に「リファクタリングが」を付けて読みます)。
変化傾向と労力は神クラスとして抽出されたもの同士の相対評価となるため、今回のようにサンプル数が少ないとキレイに結果がプロットされませんが、READMEで紹介されている別の例を見てみると見方の特徴がハッキリ分かるかと思います。
引用元: RefactorFirs README - GitHub
RefactorFirstはコードベースをもとにソフトウェアエンジニアリングの観点から、リファクタリングに掛かるコストが少なく、効果が大きいクラスを見つけてくれるツールとなります。
ソフトウェアエンジニアリングは「あるプロジェクト」における経験を定型化、数値化した側面があるため、RefactorFirstは絶対で常にこれに従うべきというものではありませんが、一方でプロジェクトの規模が大きい場合などすべてを人間の目で確認することは非現実的な場合があります。このような時にRefactorFirstのようなツールで個別に確認しなければならない対象を手早く効果的に選別するにはとても有効です。これを機会にプロジェクトで試してみていただければと思います。
Nico Zazworka、Carolyn Seaman、Forrest Shull による論文「Prioritizing Design Debt Investment Opportunities」に基づいています。この論文に基づくプレゼンテーションは、こちらからから入手できます。 ↩︎
Use of relative code churn measures to predict system defect density ↩︎
RefactorFirstの元なっている論文から直接的な引用はありませんが、縦軸の意味を"Relative churn"としていることから該当の研究結果を意識していると推測できます ↩︎