FindBugsとPMDを使ったコード品質規則の紹介
1概要
この記事では、FindBugs、PMD、およびCheckStyleなどのコード分析ツールで取り上げられている重要な規則のいくつかを取り上げます。
2周期的複雑度
2.1. 循環的複雑度とは何ですか?
コードの複雑さは重要ですが、測定が困難です。 PMDはそのhttp://pmd.sourceforge.net/pmd-4.3.0/rules/codesize.html[Code Size Rules]セクションの下にしっかりした一連の規則を提供しています。これらの規則は、メソッドのサイズと構造の複雑さに関する違反を検出するように設計されています。
CheckStyleは、コーディング標準、およびフォーマット規則に照らしてコードを分析する機能で知られています。ただし、複雑さを計算することによってクラス/メソッド設計の問題を検出することもできますhttp://checkstyle.sourceforge.net/config__metrics.html[metrics]。
どちらのツールにも含まれる最も関連性の高い複雑さ測定の1つがCC(Cyclomatic Complexity)です。
CC値はプログラムの独立した実行パスの数を測定することによって計算できます。
たとえば、次の方法では、巡回複雑度は3になります。
public void callInsurance(Vehicle vehicle) {
if (vehicle.isValid()) {
if (vehicle instanceof Car) {
callCarInsurance();
} else {
delegateInsurance();
}
}
}
CCは、条件付きステートメントとマルチパートブール式のネストを考慮に入れます。
一般的に言って、CCに関して11より高い値を持つコードは、非常に複雑で、テストと保守が困難であると見なされます。
静的解析ツールで使用されるいくつかの一般的な値を以下に示します。
-
1-4:複雑さが少ない – テストが簡単
-
5-7:中程度の複雑さ – 許容できる
-
8-10:高い複雑性 – リファクタリングは緩和するために考慮されるべきです
テスト中
** 11 +非常に高い複雑性 – テストが非常に難しい
複雑さのレベルは、コードのテスト容易性にも影響します。
CCが高いほど、適切なテストを実装するのが難しくなります
。
実際、循環的複雑度の値は、100%のブランチカバレッジスコアを達成するために必要なテストケースの数を正確に示しています。
callInsurance()
メソッドに関連付けられたフローグラフは、次のとおりです。
リンク:/uploads/flowgraph__CC-1.png%20324w[]
可能な実行パスは次のとおりです。
-
0 ⇒ 3
-
0 ⇒ 1 ⇒ 3
-
0 ⇒ 2 ⇒ 3
数学的に言えば、CCは次の簡単な式を使って計算できます。
CC = E - N + 2P
-
E:総エッジ数
-
N:総ノード数
-
P:出口点の総数
** 2.2. 循環的な複雑さを減らす方法?
大幅に複雑でないコードを書くために、開発者は状況に応じて異なるアプローチを使う傾向があります。
-
デザインパターンを使用して長い
switch
ステートメントを記述しないでください。
例えばビルダーと戦略のパターンは対処するのに良い候補かもしれません
コードサイズと複雑さの問題
** コードをモジュール化して再利用可能で拡張可能なメソッドを書く
構造と実装
https://en.wikipedia.org/wiki/Single
responsibility
principle
他のPMDをフォローする
コードサイズ
ルールはCC ** に直接影響を与える可能性があります。過剰なメソッド長の規則、単一クラス内のフィールドが多すぎる、単一メソッド内の過剰なパラメータリストなど
また、コードサイズと複雑さに関する次の原則とパターンを検討することもできます。
https://en.wikipedia.org/wiki/KISS
principle[
KISS(単純で愚かな)原則
]、および
https://en.wikipedia.org/wiki/Don’t
repeat__yourself[DRY(Don) t繰り返します)]。
3例外処理ルール
例外に関連した欠陥は普通のことかもしれませんが、それらのいくつかは非常に過小評価されており、本番コードの重大な機能不全を避けるために修正されるべきです。
PMDとFindBugsは、例外に関する少数の規則を提供しています。
ここでは、例外を処理するときにJavaプログラムで重要と見なされる可能性があるものについて説明します。
3.1. 最後に例外をスローしないでください
ご存知のように、Javaの
finally \ {}
ブロックは一般にファイルを閉じたりリソースを解放するために使用されます。他の目的に使用することはhttps://en.wikipedia.org/wiki/Code__smell[
コードのにおい
]。
典型的なエラーが発生しやすいルーチンは、
finally \ {}
ブロック内に例外をスローします。
String content = null;
try {
String lowerCaseString = content.toLowerCase();
} finally {
throw new IOException();
}
このメソッドは
NullPointerException
をスローすることになっていますが、驚くべきことに
IOException
をスローします。
3.2. __最終ブロックに戻る
finally \ {}
ブロック内でreturn文を使用することは、混乱を招くだけです。この規則が非常に重要なのは、コードが例外をスローするたびに
return
ステートメントによって破棄されるためです。
たとえば、次のコードはエラーなしで実行されます。
String content = null;
try {
String lowerCaseString = content.toLowerCase();
} finally {
return;
}
NullPointerException
はキャッチされませんでしたが、それでも
finally
ブロック内のreturnステートメントによって破棄されました。
3.3. 例外でストリームを閉じることができない
ストリームを閉じることが
finally
ブロックを使用する主な理由の1つですが、それはそれほど簡単ではないタスクです。
次のコードは、
finally
ブロック内の2つのストリームを閉じようとします。
OutputStream outStream = null;
OutputStream outStream2 = null;
try {
outStream = new FileOutputStream("test1.txt");
outStream2 = new FileOutputStream("test2.txt");
outStream.write(bytes);
outStream2.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outStream.close();
outStream2.close();
} catch (IOException e) {
//Handling IOException
}
}
outStream.close()
命令が
IOException
をスローした場合、
outStream2.close()
はスキップされます。
簡単な解決策は、2番目のストリームを閉じるために別のtry/catchブロックを使用することです。
finally {
try {
outStream.close();
} catch (IOException e) {
//Handling IOException
}
try {
outStream2.close();
} catch (IOException e) {
//Handling IOException
}
}
連続した
try/catch
ブロックを回避するための優れた方法が必要な場合は、https://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/IOUtils.html[Apache commonsのIOUtils.closeQuiety]メソッドを使用すると、
IOException
をスローせずにストリームのクローズを簡単に処理できます。
5悪い習慣
5.1. クラスは
compareTo()
を定義し、
Object.equals()
を使用します
compareTo()
メソッドを実装するときはいつでも、
equals()
メソッドを使って同じことを忘れないでください。
Car car = new Car();
Car car2 = new Car();
if(car.equals(car2)) {
logger.info("They're equal");
} else {
logger.info("They're not equal");
}
if(car.compareTo(car2) == 0) {
logger.info("They're equal");
} else {
logger.info("They're not equal");
}
結果:
They're not equal
They're equal
混乱を避けるために、
Comparableを実装するときに
Object.equals()__が呼び出されないようにすることをお勧めします。代わりに、次のようにオーバーライドしてください。
boolean equals(Object o) {
return compareTo(o) == 0;
}
5.2. NULLポインタ参照の可能性
NullPointerException
(NPE)は、Javaプログラミングで最も遭遇した
Exception
と見なされています。FindBugsは、Null PointeDの逆参照について、それをスローしないようにしています。
これがNPEを投げる最も基本的な例です。
Car car = null;
car.doSomething();
NPEを回避する最も簡単な方法は、nullチェックを実行することです。
Car car = null;
if (car != null) {
car.doSomething();
}
NULLチェックはNPEを回避する可能性がありますが、広範囲に使用されると、コードの読みやすさに確実に影響します。
そのため、nullチェックなしでNPEを回避するために使用されていたテクニックがいくつかあります。
-
_のコーディング中はキーワード
null
を避けてください。
_
この規則は簡単です。
変数を初期化するときにキーワード
null
を使用すること、または戻る
値
つかいます
-
https://en.wikipedia.org/wiki/Null
Object
pattern[** Nullを実装する
オブジェクトパターン** ]
6. 結論
この記事では、検出された問題に適切に対処するための基本的なガイドラインと共に、静的分析ツールによって検出された重大な欠陥のいくつかを全体的に調べました。
次のリンクにアクセスして、それぞれのルールのフルセットを閲覧できます。
FindBugs
、http://pmd.sourceforge.net/pmd-4.3.0/rules/index.html[PMD]。