【NullAway】JavaだけどKotlinみたいにNull安全な開発がしたい…!

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'
	}
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です