FindBugs とは
Java のソースコードを静的に解析して、バグとなりそうなコードを見つけるツール。
ビルドプロセスの中に組み込んで自動化させることで、ソースコードレビューの手間を減らしたり、そもそも人目では見つけづらいバグコードを事前に検出したりできるようになる。
環境
OS
Windows 10
Java
1.8.0_102
FindBugs
3.0.1
Hello World
インストール
こちら から zip をダウンロードする。
任意の場所に解凍したら完了。
実装
public class HelloFindBugs {
public static void main(String[] args) {
String text = null;
System.out.println("text.length=" + text.length());
}
}
> javac -d classes .\src\HelloFindBugs.java
|-src/
| `-HelloFindBugs.java
`-classes/
`-HelloFindBugs.class
動かす
- 先ほど解凍した zip の中の、
bin
の下のfindbugs.bat
を実行する。 - [ファイル] → [新規プロジェクト] を選択。
- [Project] に任意の名前(とりあえず
hello findbugs
とした)を入力。 - [分析クラスパス] の [追加] ボタンをクリックして、先ほど作成した
HelloFindBugs.class
が存在するフォルダを選択する。 - [ソース・ディレクトリー] の [追加] ボタンをクリックして、
HelloFindBugs.java
が存在するフォルダを選択する。 - [分析] ボタンをクリックする。
- 分析が実行されて、バグの温床となるコードが指摘される。
CUI で動かす
ソースは HelloWorld のときと同じ物 を使用する。
zip を解凍してできたフォルダの下の bin
の下にパスを通した状態で、以下のようにコマンドを実行する。
> findbugs -textui -output result.txt .\classes
実行すると、カレントディレクトリに result.txt
が出力される。
H C NP: ? の null 値を利用しています。HelloFindBugs.main(String[]) 参照外しをした箇所 HelloFindBugs.java:[line 5]
xml 形式で出力したい場合は -xml
オプションを追加する。
html 形式で出力したい場合は -html
オプションを追加する。
> findbugs -textui -output result.xml -xml .\classes
その他使用できるオプションを確認したい場合は、 -textui
だけで findbugs
を起動すれば、一覧が確認できる。
もしくは こちらを参照。
検証の精度を指定する(effort)
> findbugs -textui -effort:max build\classes\main
- 検証の精度を指定する。
-
max
にすると、精度が最大になりより多くのバグを検出できるかもしれない。 - ただし、メモリ消費が多くなり、検証にかかる時間も長くなる。
-
min
にすると、精度は落ちるがメモリ消費は抑えられ、時間も短くなるらしい。 - 具体的に
min
,max
を切り替えることでどのように検証結果が変わるか確認しようと試みたが、残念ながら明確な差を確認することはできなかった。
main メソッドは特別?
package sample.findbugs;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public void method() {
String text = null;
System.out.println("text.length=" + text.length());
try {
InputStream in = new FileInputStream(new File("./test"));
} catch (IOException e) {
}
}
public static void main(String[] args) {
String text = null;
System.out.println("text.length=" + text.length());
try {
InputStream in = new FileInputStream(new File("./test"));
} catch (IOException e) {
}
}
}
main メソッドと、そうでないメソッドを定義している。
中身はそれぞれ同じ。
これを FindBugs に食わせると、以下のようにメッセージが出力される。
H C NP: text の null 値を利用しています。sample.findbugs.Main.method() 参照外しをした箇所 Main.java:[line 12]
H C NP: text の null 値を利用しています。sample.findbugs.Main.main(String[]) 参照外しをした箇所 Main.java:[line 22]
M B OS: sample.findbugs.Main.method() は、ストリームのクローズに失敗するかもしれません。 該当箇所 Main.java:[line 15]
M X OBL: sample.findbugs.Main.method() は、java.io.InputStream のクリーンアップに失敗するかもしれません。 Main.java:[line 15] で作成されたリソースのクリーンアップが行われていない
Warnings generated: 4
main メソッドのほうが、出力されているメッセージが少ない。
理由はよくわかっていない。
検知できるバグ
FindBugs が検知できるバグは、以下の一覧で確認できる。
FindBugs Bug Descriptions(日本語)
バグの分類
検知対象のバグは、以下の3つの粒度で分類されている。
後述するフィルター などでバグの検出を制御する際に、この分類を利用して対象のバグを指定することになる。
- バグカテゴリー(
bug category
) - バグ略称(
bug abbreviations
) - バグパターン(
bug pattern
)
※「バグ略称」は「バグコード」、「バグパターン」は「バグタイプ」と表記している記事もあったけど、ここでは公式ドキュメント(日本語訳)の言葉を使うことにする。
バグカテゴリー
前述のバグ一覧 の、一番右の列に記載されている Bad practice
や Correctness
が、バグカテゴリー。
一番粗い粒度でバグを分類する。
各カテゴリの意味などは以下のブログに詳細な説明がまとめられている。
FindBugsのバグパターン一覧 - 現場のためのソフトウェア開発プロセス - たかのり日記
バグ略称
前述のバグ一覧 の、各バグの説明の先頭に記載されている数文字のアルファベットが、バグ略称になる。
- BC: equals メソッドは引数の型を仮定するべきではない
- BIT: ビット演算の符号をチェックする
BC
と BIT
がバグ略称。
バグを中程度で分類している。
代表的なバグパターンの、各単語の頭文字を取ったものがバグ略称となっているので、なんとなく雰囲気で何のチェックかが分かる(NP
→ NullPointerException
関係のバグ、Eq
→ equals()
関係のバグ)
バグパターン
個々のバグを表す、最も粒度の細かい分類。
前述の一覧 では、先頭のテーブルの Description
には記載されていない。
個々のバグパターン に飛べば、丸括弧の中にバグパターンの名前が記載されている。
(BC_EQUALS_METHOD_SHOULD_WORK_FOR_ALL_OBJECTS
や BIT_SIGNED_CHECK
など)
信頼度(Confidence)
※公式ドキュメントで「信頼度」についての説明を見つけられなかったので、ここに記載しているのは Stackoverflow や実装を見て得た推測です。
検出された問題が本当にバグになりえる可能性を表した指標。
高信頼度(1
)、中信頼度(2
)、低信頼度(3
)の3つから成る。
ver 2.0 より前は「優先度」(Priority)と表現されていたが、それだと優先度の高いバグだけが重要という誤解を生みかねないので、 ver 2.0 から「信頼度」(Confidence)と名前が変更になった。
フィルターの設定ファイルで <Priority>
が指定できるが、これは後方互換のために残されている。
バグランク
検出された問題の危険性を表す指標。
1~20 まで存在し、 1~4 が最も恐ろしいバグ(scariest
)、5~9 は恐ろしいバグ(scary
)、10~14 は厄介なバグ(troubling
)、15~20 は不安なバグ(concern
)を表している。
信頼度とバグランクは実装内容によって変動する
ドキュメントを見ても、バグパターンごとの信頼度やバグランクは載っていなかった。
どのようにして信頼度が確定するのか FindBugs の実装を見てみたところ、どうやら検証対象の実装内容によって変動するっぽい。
...
private void sawMethodCallWithIgnoredReturnValue() {
...
int priority = annotation.getPriority();
if (catchSize <= 1) {
priority += 2;
} else if (catchSize <= 2) {
priority += 1;
}
...
BugInstance warning = new BugInstance(this, pattern, priority).addClassAndMethod(this).addMethod(callSeen)
.describe(MethodAnnotation.METHOD_CALLED);
...
}
...
priority
の値が、実装内容によって加算されたうえで BugInstance
を生成している。
実際に、以下のような実装で確認すると priority の変化が確認できる。
package sample.findbugs;
public class Main {
public void method() {
try {
new Exception();
} catch (Exception e) {
}
}
}
実行結果
<BugInstance type="RV_EXCEPTION_NOT_THROWN" priority="2" rank="3" abbrev="RV" category="CORRECTNESS">
priority
は 2
、 rank
は 3
になっている。
次に実装を以下のように変更して、再度 FindBugs で検査する。
package sample.findbugs;
public class Main {
public void method() {
new Exception();
}
}
実行結果
<BugInstance type="RV_EXCEPTION_NOT_THROWN" priority="1" rank="1" abbrev="RV" category="CORRECTNESS">
priority
は 1
に、 rank
は 1
になった。
フィルター
フィルターを使用することで、クラスや検知するバグを指定して検証の対象外にしたり、逆に対象を絞り込むことができるようになる。
フォルダ構成
|-src/main/java/
| `-sample/findbugs/
| `-Main.java
|
|-build/classes/main/
| `-sample/findbugs/
| `-Main.class
|
`-myfilter.xml
実装
package sample.findbugs;
public class Main {
private String value = "";
public void method() {
value = value;
if (value == value) {
new Exception();
}
}
}
基本
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml -output result.html -html build/classes/main
出力結果
説明
- フィルターの定義は XML で記述する。
- 一番トップのタグが
<FindBugsFilter>
。 - 続いて
<Match>
タグを使って「対象」を定義する。 - 定義した「対象」だけを処理するのか、それとも逆に除外するのかは、コマンドラインオプションの
-include
と-exclude
で指定する。- 上記例の場合、
-include myfilter.xml
としているので、myfilter.xml
で定義された「対象」だけが処理されるようになる。
- 上記例の場合、
※以下の例は、すべて -include
を指定して実行した結果です。
カテゴリーで絞り込む
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
<Bug category="CORRECTNESS" />
</Match>
</FindBugsFilter>
出力結果
-
<Bug>
タグを追加して、category
属性を指定する。 - カテゴリーの名前はすべて大文字で記述する。
-
一覧に書かれているカテゴリーはスペースなども含んでいるが(
Bad practice
)、フィルターで指定する場合はスペースをアンダーバー(_
)に置き換える(BAD_PRECTICE
)。
-
一覧に書かれているカテゴリーはスペースなども含んでいるが(
- カンマ区切りで複数指定することも可能。
略称で絞り込む
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
<Bug code="SA" />
</Match>
</FindBugsFilter>
出力結果
-
<Bug>
タグのcode
属性にバグ略称を指定することで絞り込みが可能。 - カンマ区切りで複数指定可能。
パターンで絞り込む
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
<Bug pattern="SA_FIELD_SELF_COMPARISON" />
</Match>
</FindBugsFilter>
出力結果
-
<Bug>
タグのpattern
属性にバグパターンを指定することで絞り込みが可能。 - カンマ区切りで複数指定可能。
カテゴリ・略称・パターンを組み合わせる
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
<Bug category="BAD_PRACTICE"
code="RV"
pattern="SA_FIELD_SELF_COMPARISON" />
</Match>
</FindBugsFilter>
実行結果
-
category
,code
,pattern
は複数を同時に指定することもできる。
信頼度で絞る
絞らない場合の実行結果
信頼度=2 で絞るように設定を変更
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
<Confidence value="2" />
</Match>
</FindBugsFilter>
実行結果
-
<Confidence>
タグで、検知する信頼度を指定できる。 - 信頼度の値は
value
属性で指定する。
バグランクで絞る
絞らない場合の実行結果
<BugInstance type="ES_COMPARING_STRINGS_WITH_EQ" priority="2" rank="11" abbrev="ES" category="BAD_PRACTICE">
<BugInstance type="RV_EXCEPTION_NOT_THROWN" priority="1" rank="1" abbrev="RV" category="CORRECTNESS">
<BugInstance type="SA_FIELD_SELF_ASSIGNMENT" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">
<BugInstance type="SA_FIELD_SELF_COMPARISON" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">
バグランク 3 と 11 で絞り込むよう設定を変更
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
<Or>
<Rank value="3" />
<Rank value="11" />
</Or>
</Match>
</FindBugsFilter>
<BugInstance type="ES_COMPARING_STRINGS_WITH_EQ" priority="2" rank="11" abbrev="ES" category="BAD_PRACTICE">
<BugInstance type="SA_FIELD_SELF_ASSIGNMENT" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">
<BugInstance type="SA_FIELD_SELF_COMPARISON" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">
-
Rank
タグで、検知するバグランクを指定できる。 - ランクの値は
value
属性で指定する。- 複数指定する場合は、
<Or>
タグなどを利用する。
- 複数指定する場合は、
パッケージで絞る
パッケージ構成
`-sample.findbugs.filter
|-Zzz.java
|-aaa
| |-Aaa.java
| `-ccc
| `-Ccc.java
`-bbb
`-Bbb.java
各クラスはバグが検出されるように実装している。
基本
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Package name="sample.findbugs.filter" />
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.filter.Zzz.method() 該当箇所 Zzz.java:[line 5]
-
<Package>
タグで、パッケージを指定して絞り込むことができる。 -
name
属性でパッケージ名を指定する。 - 指定したパッケージの直下だけが対象になり、サブパッケージは対象にはならない。
正規表現で指定する
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Package name="~sample\.findbugs\.filter(\..*)?" />
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.filter.bbb.Bbb.method() 該当箇所 Bbb.java:[line 5]
H C RV: new Exception() をスローしていません。sample.findbugs.filter.aaa.Aaa.method() 該当箇所 Aaa.java:[line 5]
H C RV: new Exception() をスローしていません。sample.findbugs.filter.aaa.ccc.Ccc.method() 該当箇所 Ccc.java:[line 5]
H C RV: new Exception() をスローしていません。sample.findbugs.filter.Zzz.method() 該当箇所 Zzz.java:[line 5]
-
name
属性値の先頭に~
をつけることで、正規表現によるマッチングが有効になる。
クラスで絞る
package sample.findbugs;
public class Main {
public void method() {
new Exception();
}
private static class InnerClass {
public void method() {
new Exception();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="sample.findbugs.Main" />
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main.method() 該当箇所 Main.java:[line 6]
-
<Class>
タグで、クラスを指定して絞り込むことができる。 -
name
属性でクラス名の完全修飾名を指定する。 -
~
を先頭に書けば、パッケージ同様正規表現での指定が可能。 - インナークラスは対象にならない。
ソースファイルで絞る
package sample.findbugs;
public class Main {
public void method() {
new Exception();
}
private static class InnerClass {
public void method() {
new Exception();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Source name="Main.java" />
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main.method() 該当箇所 Main.java:[line 6]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$InnerClass.method() 該当箇所 Main.java:[line 12]
-
<Source>
タグで、ソースファイルを指定して絞り込むことができる。 -
name
属性でソースファイルの名前を指定する。 -
~
を先頭に書けば、正規表現での指定が可能。 - ファイル単位の指定になるので、インナークラスも対象になる。
メソッドで絞る
package sample.findbugs;
public class Main {
public void foo() {
new Exception();
}
public void foo(String text) {
new Exception();
}
public void bar() {
new Exception();
}
public void bar(String text, int i) {
new Exception();
}
public void fizz() {
new Exception();
}
public int buzz() {
new Exception();
return 1;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Or>
<Method name="foo" />
<Method name="bar" params="java.lang.String, int" />
<Method returns="int" />
</Or>
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main.foo() 該当箇所 Main.java:[line 6]
H C RV: new Exception() をスローしていません。sample.findbugs.Main.foo(String) 該当箇所 Main.java:[line 10]
H C RV: new Exception() をスローしていません。sample.findbugs.Main.bar(String, int) 該当箇所 Main.java:[line 18]
H C RV: new Exception() をスローしていません。sample.findbugs.Main.buzz() 該当箇所 Main.java:[line 26]
-
<Method>
タグで、メソッドを指定して絞り込むことができる。 -
name
属性でメソッドの名前を指定する。-
~
を先頭に書けば、正規表現での指定が可能。
-
-
params
属性で、メソッドの引数を指定する。- 型名をカンマ区切りで列挙する。
- 型名は完全修飾クラス名でなければならない(×
String
, ○java.lang.String
)。
-
returns
属性で、メソッドの戻り値を指定する。
フィールドで絞る
package sample.findbugs;
import java.math.BigDecimal;
public class Main {
class Foo {
private int fizz;
private int buzz;
private BigDecimal hoge;
}
class Bar {
private String fizz;
private String buzz;
private BigDecimal fuga;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Or>
<Field name="fizz" />
<Field name="buzz" type="int" />
<Field type="java.math.BigDecimal" />
</Or>
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
M P UuF: 未使用のフィールド: sample.findbugs.Main$Foo.hoge 該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Bar.fizz 該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Foo.buzz 該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Bar.fuga 該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Foo.fizz 該当場所 Main.java
-
<Field>
タグで、フィールドを指定して絞り込むことができる。 -
name
属性でフィールドの名前を指定する。-
~
を先頭に書けば、正規表現での指定が可能。
-
-
type
属性で、フィールドの型を指定する。- 型名は完全修飾名で記述する。
ローカル変数を絞る
package sample.findbugs;
public class Main {
public void method() {
int fizz = 1;
fizz = fizz;
int buzz = 1;
buzz = buzz;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Local name="fizz" />
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
M D SA: fizz の自己代入です。sample.findbugs.Main.method() 該当箇所 Main.java:[line 6]
M D DLS: fizz への無効な代入です。sample.findbugs.Main.method() 該当箇所 Main.java:[line 6]
-
<Local>
タグで、ローカル変数を指定して絞り込むことができる。 -
name
属性でローカル変数の名前を指定する。-
~
を先頭に書けば、正規表現での指定が可能。
-
論理演算用のタグ
package sample.findbugs;
public class Main {
static class Foo {
void fizz() {
new Exception();
}
void buzz() {
new Exception();
}
void buzz(String text) {
new Exception();
}
void hoge() {
new Exception();
}
}
static class Bar {
void fizz() {
new Exception();
}
void fizz(String text) {
new Exception();
}
void buzz() {
new Exception();
}
void fuga() {
new Exception();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<And>
<Source name="Main.java" />
<Or>
<Method name="fizz" />
<Method name="buzz" />
</Or>
<Not>
<Method params="java.lang.String" />
</Not>
</And>
</Match>
</FindBugsFilter>
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Foo.fizz() 該当箇所 Main.java:[line 6]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Foo.buzz() 該当箇所 Main.java:[line 10]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Bar.fizz() 該当箇所 Main.java:[line 24]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Bar.buzz() 該当箇所 Main.java:[line 31]
-
<And>
で論理積、<Or>
で論理和、<Not>
で否定が演算できる。
Gradle で使用する
基本
|-build.gradle
`-src/main/java/
`-Main.java
apply plugin: 'java'
apply plugin: 'findbugs'
repositories {
mavenCentral()
}
Main.java
の実装は Hello World と同じ。
> gradle findbugsMain
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:findbugsMain FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':findbugsMain'.
> FindBugs rule violations were found. See the report at: file:///.../build/reports/findbugs/main.xml
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 5.456 secs
-
build/reports/findbugs
の下に xml 形式で結果が出力される。 - FindBugs のタスクを単体で実行する場合は、
findbugsMain
やfindbugsTest
を実行する。 -
check
タスクを実行するとこれらのタスクも自動で実行されるようになっている。
html で出力する
apply plugin: 'java'
apply plugin: 'findbugs'
repositories {
mavenCentral()
}
tasks.withType(FindBugs) {
reports {
xml.enabled = false
html.enabled = true
}
}
-
FindBugs オブジェクトの
reports
を変更することで、レポートの出力形式を切り替えることができる。 -
enabled
にできるのは1つだけなので、xml
とhtml
両方出力ということはできないっぽい。 - このままだと Jenkins などの CI サービスに連携する xml ファイルが出力できなくなってしまうので、その場合はおとなしく xml で出力して CI サービス上から結果を確認すればいいと思う。
フィルターの指定
|-myfilter.xml
|-build.gradle
`-build/classes/main/
`-sample/findbugs/
`-Main.class
apply plugin: 'java'
apply plugin: 'findbugs'
repositories {
mavenCentral()
}
findbugs {
includeFilter = file('./myfilter.xml')
}
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<And>
<Class name="sample.findbugs.Main" />
<Method params="int" />
</And>
</Match>
</FindBugsFilter>
package sample.findbugs;
public class Main {
public void hoge(int i) {
new Exception();
}
public void fuga(int i) {
new Exception();
}
public void piyo(long l) {
new Exception();
}
}
> gradle -q check
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':findbugsMain'.
> FindBugs rule violations were found. See the report at: file:///.../build/reports/findbugs/mai
n.xml
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
実行結果
※実際は xml で出力されているが、分かりやすさのために html による出力を記載している。
-
includeFilter
またはexcludeFilter
でフィルターのファイルを指定することができる。 - 他の詳細な設定については FindBugsExtension の説明 を参照。
アノテーション
アノテーションを使うことで、コードのチェックを宣言的に行うことができるようになる。
apply plugin: 'java'
apply plugin: 'findbugs'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.code.findbugs:annotations:3.0.1'
}
package sample.findbugs;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
@Immutable
public class Main {
private String name;
@Nonnull
public String method() {
return null;
}
}
実行結果
- ビルド時の依存関係に
com.google.code.findbugs:annotations
を追加してfindbugsMain
を実行する。 - すると、コード上で指定したアノテーションに従ってチェックが行われるようになる。
- どのようなアノテーションが使えるかは、 こちらを参照。
JSR-305 : Annotations for Software Defect Detection
FindBugs のアノテーションを依存関係に追加すると、合わせて JSR-305 の jar ファイルも追加される。
JSR-305 は、アノテーションを使ってコードの欠陥を検出する仕様の標準化を目指している(しかし、 2016 年 8 月現在は休止状態になっている)。
JSR-308 : Annotations on Java Types という、同じくアノテーションに関する JSR が存在する。
こちらはアノテーションを記述できる場所を増やそうという JSR で、アノテーションの標準化は行っていない。
この JSR は Java SE 8 で標準仕様として組み込まれている。
JSR-305 は結局頓挫しているものの、 FindBugs 自体はこの JSR-305 で定義されているアノテーションをサポートしており、 javax.annotation.Nonnull
などを使用することができる。
一応 FindBugs が独自に定義している edu.umd.cs.findbugs.annotations.NonNull
などのアノテーションも存在しているが、これらは軒並み非推奨(Deprecated)になっており、 JSR-305 のアノテーションを使うよう Javadoc に記載されている。
package edu.umd.cs.findbugs.annotations;
...
/**
* The annotated element must not be null.
*
* Annotated Fields must only not be null after construction has completed.
* Annotated methods must have non-null return values.
*
* @deprecated - use {@link javax.annotation.Nonnull} instead.
**/
...
@Deprecated
public @interface NonNull {
Eclipse で使用する
確認には Neon を使用した。
プラグインをインストールする
マーケットプレイスで findbugs
で検索すれば、 FindBugs Eclipse Plugin というのが出てくるので、それをインストールする。
インストールが完了すると、プロジェクトを右クリックしたときに表示されるメニューに [Find Bugs] が追加される。
検証を実行する
上記メニューの [Find Bugs] -> [Find Bugs] を選択すると、プロジェクトの検証が開始される。
問題のある箇所は、エディタ上で虫アイコンが表示されるようになる。
検証結果の一覧は、 [Bug Explorer] で確認することができる。
[Bug Explorer] が表示されていない場合は、 [Window] → [Show View] → [Other...] → [FindBugs] → [Bug Explorer] と選択して、表示させる。
フィルターを適用する
- [Window] → [Java] → [FindBugs] を選択。
- [フィルター・ファイル] タブを開き、 [包含フィルター・ファイル] または [除外フィルター・ファイル] にフィルターファイルのパスを設定する。
IntelliJ で使用する
Community Edition の 2016.2.2 を使用した。
インストール
「FindBugs-IDEA」というプラグインをインストールする。
インストールが完了して再起動すると、ツールバーに FindBugs-IDEA が追加される。
ソースを検証する
FindBugs-IDEA のツールバーを開けば、検証を実行するためのボタンが表示される。
(プロジェクトを右クリックして [FindBugs] を選択しても同じアクションを実行できる)
検証が完了すると、結果が FindBugs-IDEA のパネルに表示され、ソースコード上も問題箇所が赤く表示されるようになる。
フィルターを適用する
FindBugs-IDEA のパネルに [Plugin Preferences] というボタンがあるので、それをクリック。
設定画面が開くので、 [Filter] タブに移動して、 [Include filter files] または [Exclude filter files] にフィルターを定義した xml のパスを設定する。
この状態で再度検証処理を実行すれば、フィルターが適用された状態で検証が行われる。
ちなみに、フィルターの設定情報は .idea/findbugs-idea.xml
に保存される。
中を見た感じ、個々のローカル環境には依存しない形になっている模様。
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FindBugs-IDEA">
<includeFilterFiles>
<filter file="$PROJECT_DIR$/myfilter.xml" enabled="true" />
</includeFilterFiles>
</component>
</project>
Jenkins と連携する
Jenkins は 2.7.3 を使用した。
Jenkins のインストール方法や、プロジェクトの作り方などは割愛。
ビルド対象のプロジェクトは GitHub に作成した。
https://github.com/opengl-8080/findbugs-jenkins-test
プラグインのインストール
- [Jenkins の管理] → [プラグインの管理] を開き、 "FindBugs" で検索して "FindBugs Plugin-in" をインストールする。
プロジェクトのビルドで FindBugs の集計を設定する
FindBugs プラグインのインストールが完了すると、 [ビルド後の処理の追加] で [FindBugs警告の集計] が選択できるようになるので、これを追加する。
[集計するファイル] に、 Gradle タスクが実行されることで出力される FindBugs の結果ファイル(XML ファイル)の場所を入力する。
デフォルトだと、ビルドが失敗した場合は警告の集計を行わないようになっている。
FindBugs の検証で問題が発生した場合、ビルドは失敗扱いになるので、そのままだと集計は行われないことになる。
ビルドが失敗した場合も集計を行うようにするため、 [高度な設定] を選択する。
[常に実行] チェックボックスをオンにする。
プロジェクトをビルドする
ビルドを実行すると、ビルド結果に FindBugs の集計結果が出力されるようになる。
FindBugs の検証で出た問題の数に応じてビルド結果を変更する
FindBugs の検証を [常に実行] するというのは、検証以前に片付けるべき重大なビルドエラーが起こった時も無駄に検証処理が走ってしまい、無駄が多くなりそうな気がする。
理想は、ビルドは通った時だけ FindBugs の検証を実行して、問題があったら必要に応じてビルド結果を「失敗」や「不安定」にできるといい。
Gradle の設定を変更する
defaultTasks 'build'
apply plugin: 'java'
apply plugin: 'findbugs'
repositories {
mavenCentral()
}
findbugs {
ignoreFailures = true
}
-
findbugs.ignoreFailures
にtrue
を設定する。 - こうすることで、 FindBugs の検証で問題が発覚してもビルドをエラーにせず続行させられるようになる。
Jenkins のビルド設定を変更する
先ほどオンにした [常に実行] は再びオフに戻す。
しかし、これだけだと FindBugs の警告の集計は行われなくなるので、どんなに問題があってもビルド結果は「成功」になってしまう。
そのため、同じく [高度な設定] に存在する [ビルドステータスの閾値] を設定する。
上記設定の場合、重要度(信頼度のことだと思う)にかかわらず、 3 つより多い警告が検出されたら、ビルド結果は「不安定」になる。
詳しい説明は、下のコメントに記載されている通り。
警告が4つ出るようにしてビルドを実行した結果が以下。