Java開発者の皆さん、NullPointerExceptionに悩まされていませんか?
Kotlinが登場して以来、そのNull安全性に魅了された方も多いでしょう。しかし、既存のJavaプロジェクトをKotlinに移行するのは現実的ではない場合もあります。そんな時に役立つのが「NullAway」です。
NullAwayは、JavaコードにKotlinのようなNull安全性をもたらすための静的解析ツールです。このツールを使えば、コンパイル時に潜在的なNPEを検出し、未然に防ぐことができます。この記事では、NullAwayの導入方法や基本的な使い方、そしてその効果について解説していきます!
導入方法
Gradleを前提に解説していきます!
build.gradle
に以下の設定を追加します。
plugins {
// errorproneプラグインを追加する
id "net.ltgt.errorprone" version '4.0.0'
}
dependencies {
// 依存ライブラリを2つ追加
errorprone 'com.google.errorprone:error_prone_core:2.+'
errorprone 'com.uber.nullaway:nullaway:0.+'
}
// NullAwayの設定(一度プラグインと依存だけ追加してからじゃないと反映失敗するかも)
import net.ltgt.gradle.errorprone.CheckSeverity
tasks {
compileTestJava {
options.errorprone {
enabled = false
}
}
compileJava {
options.errorprone {
disableAllChecks = true
check('NullAway', CheckSeverity.ERROR)
// NullAwayの検査対象のパッケージを指定
option("NullAway:AnnotatedPackages", "hirabay")
// エラーから除外したいだけならこちら
// option("NullAway:ExcludedClasses", "")
// @Nullableをつけるつもりがないクラスはこちら
// option("NullAway:UnannotatedClasses", "")
}
options.compilerArgs << '-Xmaxwarns' << '9999'
}
}
設定はこれで完了です!
基本的な使い方
NullAwayでは、基本的にすべての変数がNon nullな変数であると見なして実装をチェックしてくれます。
ここからは以下のサンプルコードと共に解説していきます!
package hirabay.bestpractice.nullaway;
public class NullAwaySample {
public void sample() {
var result = nonNullArgMethod(null);
System.out.println(result.toLowerCase());
}
public String nonNullArgMethod(String arg) {
System.out.println(arg);
return null;
}
}
@Nullable
のついていない引数にnulを設定してはならない
$ ./gradlew compileJava
src/main/java/hirabay/bestpractice/nullaway/NullAwaySample.java:5: エラー: [NullAway] passing @Nullable parameter 'null' where @NonNull is required
var result = nonNullArgMethod(null);
^
nonNullArgMethod
の引数を見てみると特にアノテーションが付与されていません。
先ほどお伝えした通り、NullAwayはデフォルトですべての変数をNon nullで扱うことを求めるためエラーとなっています。
なので
- メソッドの呼び元でnullを渡さないようにする
- @Nullableを付与してnullを許容する
のどちらかの対応が必要になります。
今回は @Nullableを付与することで解決します。
public String nonNullArgMethod(@Nullable String arg) {
...
}
@Nullableのついていないメソッドではnullを返してはならない
$ ./gradlew compileJava
src/main/java/hirabay/bestpractice/nullaway/NullAwaySample.java:12: エラー: [NullAway] returning @Nullable expression from method with @NonNull return type
return null;
^
nonNullArgMethod
のメソッド定義を見てみると特にアノテーションが付与されていません。
アノテーションが付与されていないので、このメソッドはNon nullの値のみを返すことを求められるためエラーとなっています。
なので
- メソッドでnullを返さないようにする
- @Nullableを付与してnullを許容する
のどちらかの対応が必要になります。
今回は @Nullableを付与することで解決します。
@Nullable
public String nonNullArgMethod(@Nullable String arg) {
...
}
Null疑いがある変数のメソッドを呼び出してはならない
$ ./gradlew compileJava
src/main/java/hirabay/bestpractice/nullaway/NullAwaySample.java:8: エラー: [NullAway] dereferenced expression result is @Nullable
System.out.println(result.toLowerCase());
^
先ほどnonNullArgMethodに @Nullable
を付与したので、nonNullArgMethodからはnullが返る可能性があるとNullAwayが判断し、エラーとなっています。
public void sample() {
var result = nonNullArgMethod(null); // nullが返ってくる可能性
System.out.println(result.toLowerCase()); // NullPointerException発生の可能性
}
なので、nullを考慮した実装の修正が必要です。
if (result != null) {
System.out.println(result.toLowerCase());
}
Tips
特定のパッケージをNullAwayのエラー検知対象から除外する
既存のプロジェクトに途中からNullAwayを導入しようとすると、大量のエラーが発生し初期導入コストで導入ができないと思うかもしれません。
そんなときは特定のパッケージをNullAwayのエラー検知対象から除外する設定を行うことで、これから実装するクラスにだけNullAwayを適用する。なんてことができます!
// NullAwayの設定
import net.ltgt.gradle.errorprone.CheckSeverity
tasks {
compileTestJava {
options.errorprone {
enabled = false
}
}
compileJava {
options.errorprone {
disableAllChecks = true
check('NullAway', CheckSeverity.ERROR)
// NullAwayの検査対象のパッケージを指定
option("NullAway:AnnotatedPackages", "hirabay")
// ★エラーから除外したいクラスやパッケージを指定する
option("NullAway:ExcludedClasses", "hirabay.bestpractice.nullaway,hirabay.bestpractice.HogeClass")
}
options.compilerArgs << '-Xmaxwarns' << '9999'
}
}