LoginSignup
3
3

More than 3 years have passed since last update.

静的解析ツール ErrorProne でJavaのコンパイルエラーを強化

Last updated at Posted at 2019-09-07

静的解析ツール ErrorProne でJavaのコンパイルエラーを強化

ErrorProneとは?

ErrorProne はGoogle製のJavaの静的解析ツールです。
他の静的解析ツール(FindBugsなど)との大きな違いは、チェックNGが コンパイルエラーとなること です。

以下、公式サイトから引用

It’s common for even the best programmers to make simple mistakes. And sometimes a refactoring which seems safe can leave behind code which will never do what’s intended.

We’re used to getting help from the compiler, but it doesn’t do much beyond static type checking. Using Error Prone to augment the compiler’s type analysis, you can catch more mistakes before they cost you time, or end up as bugs in production. We use Error Prone in Google’s Java build system to eliminate classes of serious bugs from entering our code, and we’ve open-sourced it, so you can too!

Googleでもこれを使って静的解析しているとのこと。
チェック対象のバグパターンはこちらに記載されています。

環境構築

今回は以下の検証用の環境を構築しました。

  • IDE: IntelliJ IDEA 2019.2.2
  • Gradle: 5.6.2
  • Java: jdk11.0.3_7 (Amazon Corretto)

IntelliJのGradleプロジェクトを作成。

image.png

なお、公式サイトを見ると以下に対応しているようです。

  • ビルドツール: Bazel, Maven, Gradle, Ant
  • IDE: IntelliJ IDEA, Eclipse

注意点として、ErrorProne には JDK9 以上が必要です。

Gradleの設定

ErrorProneを動かす為に、以下のビルドファイルを用意しました。

build.gradle
plugins {
    id 'java'
    id "net.ltgt.errorprone" version "0.8.1"
}

group 'errorprone-sample2'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    errorprone("com.google.errorprone:error_prone_core:2.+")
}

def defaultEncoding = 'UTF-8'
tasks.withType(AbstractCompile) each { it.options.encoding = defaultEncoding }

compileTestJava {
    options.encoding = defaultEncoding
}
//tasks.withType(JavaCompile) {
//    options.errorprone {
//        disableAllChecks = true // 全てのチェックを無効化する
//        enable("CollectionIncompatibleType", "ArrayToString") // 先に指定したチェックを有効にする
//    }
//}

検証

ErrorProneによるバグパターンのチェックについて検証します。

CollectionIncompatibleType

Incompatible type as argument to Object-accepting Java collections method

実行ファイル

public class CollectionIncompatibleTypeSample {
    public static void main(String... args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "One");
        map.put(2, "Two");

        map.get("1"); // ジェネリクスと型が異なる
        map.remove("2"); // ジェネリクスと型が異なる
    }
}

ErrorProneのチェック結果

src\main\java\CollectionIncompatibleTypeSample.java:10: エラー: [CollectionIncompatibleType] Argument '"1"' should not be passed to t
his method; its type String is not compatible with its collection's type argument Integer
        map.get("1"); // ジェネリクスと型が異なる
               ^
    (see https://errorprone.info/bugpattern/CollectionIncompatibleType)
src\main\java\CollectionIncompatibleTypeSample.java:11: エラー: [CollectionIncompatibleType] Argument '"2"' should not be passed to t
his method; its type String is not compatible with its collection's type argument Integer
        map.remove("2"); // ジェネリクスと型が異なる
                  ^
    (see https://errorprone.info/bugpattern/CollectionIncompatibleType)


java.util.Map は上記のメソッドの引数の型が Object となっているので上記は通常コンパイルエラーとなりません。
しかし、これは実装誤りである為、ErrorProneでコンパイルエラーとなる。
image.png
image.png

ArrayToString

Calling toString on an array does not provide useful information

実行ファイル

public class ArrayToStringSample {
    public static void main(String... args) {
        int[] ary = {1, 2, 3};
        System.out.println(ary); // => [I@3ac3fd8b
        System.out.println(Arrays.toString(ary)); // => [1, 2, 3]
    }
}

System.out.println() では暗黙的に toString() メソッドが呼び出される。

ErrorProneのチェック結果

src\main\java\ArrayToStringSample.java:6: エラー: [ArrayToString] Calling toString on an array does not provide useful information
        System.out.println(ary); // => [I@3ac3fd8b
                           ^
    (see https://errorprone.info/bugpattern/ArrayToString)
  Did you mean 'System.out.println(Arrays.toString(ary));'?

配列の toString() メソッドの使用は誤りであることが多いのでErrorProneでコンパイルエラーとなる。

FormatString

Invalid printf-style format string

実行ファイル

public class FormatStringSample {
    public static void main(String... args) {
        String text1 = String.format("arg1: %d. arg2: %s", 1, "a");
        System.out.println(text1); // => arg1: 1. arg2: a
        String text2 = String.format("arg1: %d. arg2: %s", "2", "b");
        System.out.println(text2); // => java.util.IllegalFormatConversionException: d != java.lang.String
    }
}

text2String.format() の書式文字列と引数の型が異なるので IllegalFormatConversionException がスローされる。

ErrorProneのチェック結果

src\main\java\FormatStringSample.java:5: エラー: [FormatString] illegal format conversion: 'java.lang.String' cannot be formatted usi
ng '%d'
        String text2 = String.format("arg1: %d. arg2: %s", "2", "b");
                                    ^
    (see https://errorprone.info/bugpattern/FormatString)

書式文字列と引数の型が異なる場合、通常コンパイルエラーは発生せずに実行時に例外がスローされるがErrorProneでコンパイルエラーとなる。

一部のチェックのみ有効にしたい場合

以下のような場合は一部のチェックのみを有効にしたくなると思います。

  • 既存のコードでチェックNGが多い
  • 意図的に書いたコードがチェックNGとなる

※他の静的解析ツールと異なりErrorProneではコンパイルエラーとなる為、単純に無視できない。

その場合は、以下のように設定することで一部のチェックのみを有効にすることができます。

tasks.withType(JavaCompile) {
    options.errorprone {
        disableAllChecks = true // 全てのチェックを無効化する
        enable("CollectionIncompatibleType", "ArrayToString") // 先に指定したチェックを有効にする
    }
}

感想

上記の箇所についてはInteliJで警告を出してくれるのでIDEの警告に対応していれば基本的には問題ないと思いますが、
世の中にはIDEの警告を無視する人もいるのでコンパイルエラーなら強制力が働くので良さそう。

また、コンパイル時点で誤りに気づくことが出来るので
テストの差し戻しのような無駄な時間が発生しづらくなると思いました。

備考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3