Error Prone is 何?
- Google がオープンソース化(2012年)した Java コンパイラプラグイン
- SonarQube、FindBugs/SpotBugs などと同様の静的解析ツール
- コードの抽象構文木(AST)を解析し、バグやアンチパターンを特定する
- javac に直接フックし、バグやアンチパターンをコンパイルエラー/警告として報告
- 可能な場合は修正提案を含むパッチファイルを生成し、オプションで変更をコードベースに適用できる
- 独自のバグチェックを作成して拡張できる
リポジトリは以下になります
他の静的解析ツールとの大きな違いは、javac のプラグインとなっているため、javac を拡張したように動作する点です。
public class ErrorProneJavacPlugin implements Plugin { @Override public String getName() { return "ErrorProne"; } @Override public void init(JavacTask javacTask, String... args) { BaseErrorProneJavaCompiler.addTaskListener( javacTask, BuiltInCheckerSuppliers.defaultChecks(), ErrorProneOptions.processArgs(args)); } }
バグパターンにマッチする場合、コンパイル・エラーとなります。
その他、独自のチェックを追加したり、サードパーティ製のルールを追加したりすることもできます。例えば、NullAway で Java をヌルセーフにしたり。
Error Prone の導入
Gradle で利用する場合は、サードパーティからプラグインgradle-errorprone-pluginが公開されているので、これを利用できます。
build.gradle.kts を以下のようにして導入します。
plugins {
java
id("net.ltgt.errorprone") version "4.3.0"
}
repositories {
mavenCentral()
}
dependencies {
errorprone("com.google.errorprone:error_prone_core:2.45.0")
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
導入は以上で完了です。
Error Prone の動作
例えば、以下のような(例外のインスタンス化のみ行い、throw を忘れている)コードがあった場合、
if (args.length > 0 && args[0].isEmpty()) { new IllegalArgumentException(); }
ビルドでコンパイルエラーとなります。
$ gradlew build
...
Foo.java:13: error: [DeadException] Exception created but not thrown
new IllegalArgumentException();
^
(see https://errorprone.info/bugpattern/DeadException)
Did you mean 'throw new IllegalArgumentException();'?
例えば以下のようなコードも、
Set<Short> s = new HashSet<>(); for (short i = 0; i < 100; i++) { s.add(i); s.remove(i - 1); }
以下のようにコンパイルエラーとなります。
$ gradlew build
...
Foo.java:21: error: [CollectionIncompatibleType] Argument 'i - 1' should not be passed to this method; its type int is not compatible with its collection's type argument Short
s.remove(i - 1);
^
(see https://errorprone.info/bugpattern/CollectionIncompatibleType)
Bug patterns は大量にあり https://errorprone.info/bugpatterns にまとまっています。
まぁ、IDE 使っていれば大抵は警告表示してくれるので、ありがたみは少ないですが、コンパイルエラーとして修正が強制される点がメリットでしょうか。
チェックの抑止
該当箇所以下のようにアノテーションを付けることで、チェックを抑止することができます。
@SuppressWarnings("CollectionIncompatibleType") public void foo() { Set<Short> s = new HashSet<>(); for (short i = 0; i < 100; i++) { s.add(i); s.remove(i - 1); } ... }
特定の箇所だけでなく、コードベース全体でチェックの有効/無効を切り替えたり、警告とするかエラーとするかなどを切り替えることもできます。
コマンドラインオプション(-Xep:<checkName>[:severity])を利用します(severity には OFF WARN ERROR を指定します)。
例えば ReferenceEquality を無効にするには以下のようにコマンドラインオプションを指定します。
-Xep:ReferenceEquality:OFF
ReferenceEquality を警告表示に変更するには以下のようにします。
-Xep:ReferenceEquality:WARN
特定のチェックではなく、全体を一括で指定するコマンドラインオプションも用意されています。
-XepDisableAllChecks: 全てのチェックを完全に無効化する-XepDisableAllWarnings: 全ての警告を無効化する(エラーレベルの報告はそのまま)-XepDisableWarningsInGeneratedCode:@Generatedなどが付いた自動生成されたコードの警告を無効化する-XepAllErrorsAsWarnings: ERROR を WARNING として扱う(コンパイルは成功する)-XepAllSuggestionsAsWarnings: SUGGESTION(スタイル改善など)を WARNING として報告する-XepAllDisabledChecksAsWarnings: デフォルトで無効化されているチェックを有効化し WARNING として報告する
これらのオプションは Gradle プラグイン側で設定することもできます。
import net.ltgt.gradle.errorprone.errorprone //... tasks.withType<JavaCompile>().configureEach { options.errorprone.disableAllChecks = true options.errorprone.disableWarningsInGeneratedCode = true }