Error Prone本文转载自InfoQ中文站,作者:Johan Janssen,译者:BO,策划:丁晓昀,审校:马可薇
Error Prone是谷歌开源的一个 Java 编译插件,可以在编译时进行静态分析、bug 检测,或者对可能的优化提出建议。插件中包括了超过 500 个预定义的bug检查,并且允许第三方和自定义插件。检查到问题之后,Error Prone 能够将问题通过 warning 显示出来或者用预定义的解决方案自动修改代码。Error Prone 支持 Java 8、11,以及 17,可以被用来修复 bug 或者大规模重构。文档中提供了使用 Maven、Bazel、Ant 以及 Grandle 的安装和配置教程。需要将 Error Prone 在编译器中配置为 annotation processor(注解处理器),下面是通过 Maven 创建测试工程的示例:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<release>17</release>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.15.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
接下来可以创建一个示例类。下面的方法使用了 equals
方法来对比两个数组,更准确地说,此处所比较的是对象本身而不是数组的内容。
public boolean compare(String firstList[], String secondList[]) {
return firstList.equals(secondList);
}
执行 mvn clean verify
触发 Error Prone 分析,下面是运行结果中的错误信息中:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:
compile (default-compile) on project ErrorProne: Compilation failure
[ERROR] …/ErrorProne/src/main/java/org/example/Main.java:[5,28]
[ArrayEquals] Reference equality used to compare arrays
[ERROR] (see https://errorprone.info/bugpattern/ArrayEquals)
[ERROR] Did you mean 'return Arrays.equals(firstList, secondList);'?
报出了ArrayEquals错误,Error Prone 的建议是修改实现方式,以比较数组的内容而不是比较对象。
return Arrays.equals(firstList, secondList);
报错不仅可以帮助改善代码,也可以让 Error Prone 自动应用解决方案。 -XepPatchChecks
参数的应用应该包含由逗号分隔开的 bug 模式列表,在上面的情况中,只有 ArrayEquals 解决方案用于这段代码。 -XepPatchLocation
参数用于具体定位解决方案文件位置,在当前情境中是修改了源文件:
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne -XepPatchChecks:ArrayEquals
-XepPatchLocation:IN_PLACE</arg>
</compilerArgs>
现在,在执行 mvn clean verify
之后,类文件被自动修改为:
public boolean compare(String firstList[], String secondList[]) {
return Arrays.equals(firstList, secondList);
}
文档里提供了更多关于命令行标识的信息。除了内置的 bug 模式,也可以使用例如SLF4J等第三方发布的插件,或创建自定义插件。内置规则的源码提供了多种可用于定义插件的不同示例模板。例如,自定义一个能够用新的 JUnit 5 @BeforeEach 注解器代替旧版 @Before JUnit 注解器的 Error Prone 插件。
和前文例子不同,自定义的 Error Prone 插件应该被放置于 Maven 模块。Error Prone 通过服务加载器机制来加载 bug 检测。这类之际通常一定的配置,然而谷歌的AutoService项目借助 @AutoService 注解简化了配置工作。@BugPattern 注解用于定义 bug 的名称、简介以及严重性。在下面的例子中,如果没有找到 @Before 注解器会返回Description.NO_MATCH
,否则SuggestedFix会用 @BeforeEach 注解替代 @Before 注解。
@AutoService(BugChecker.class)
@BugPattern(
name = "BeforeCheck",
summary = "JUnit 4's @Before is replaced by JUnit 5's @BeforeEach",
severity = BugPattern.SeverityLevel.SUGGESTION
)
public class BeforeCheck extends BugChecker implements BugChecker.AnnotationTreeMatcher {
private static final Matcher<AnnotationTree> matcher =
isType("org.junit.Before");
@Override
public Description matchAnnotation(AnnotationTree annotationTree,
VisitorState visitorState) {
if (!matcher.matches(annotationTree, visitorState)) {
return Description.NO_MATCH;
}
return describeMatch(annotationTree,
SuggestedFix.replace(annotationTree, "@BeforeEach"));
}
}
构建自定义 Error Prone 插件的时候都是需要 Error Prone 和 AutoService 依赖的。
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_check_api</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<version>1.0.1</version>
</dependency>
AutoService 应该被配置为一个注解处理器。
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0.1</version>
</path>
</annotationProcessorPaths>
现在,自定义的 Error Prone 插件可以通过 mvn install
命令,安装在本地的 Maven 仓库。执行命令后,示例工程应该会被配置为使用新的自定义插件作为注解处理器。
<annotationProcessorPaths>
<path>
<groupId>org.example.custom.plugin</groupId>
<artifactId>ErrorProneBeforeCheck</artifactId>
<version>1.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
新的 BeforeCheck
应该被加入到了 Error Prone 分析中。
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne -XepPatchChecks:BeforeCheck
-XepPatchLocation:IN_PLACE</arg>
</compilerArgs>
添加一个示例测试类,其中包含@Before
和@BeforeEach
的两个注解。
public class ErrorProneTest {
@Before
void before() {
}
@BeforeEach
void beforeEach() {
}
}
运行 mvn verify
时,新的自定义 Error Prone 插件将用@BeforeEach
注解替换@Before
注解。
public class ErrorProneTest {
@BeforeEach
void before() {
}
@BeforeEach
void beforeEach() {
}
}
Error Prone 所使用的 Java internal 目前处于隐藏状态,可能会导致如下错误:
java.lang.IllegalAccessError: class com.google.errorprone.BaseErrorProneJavaCompiler
(in unnamed module @0x1a6cf771)
cannot access class com.sun.tools.javac.api.BasicJavacTask (in module jdk.compiler)
because module jdk.compiler does not export
com.sun.tools.javac.api to unnamed module @0x1a6cf771
Maven 的解决办法是通过在项目根目录下创建.mvn 目录来暴露 Java internal,在目录中创建一个 jvm.config 文件,其中配置如下:
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
或者可以将--add-exports
和--add-opens
参数配置添加到 Maven 编译器插件的 pom 文件中:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<compilerArgs>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
…
更多在 Bazel、Ant 和 Gradle 中使用 Error Prone 的信息可参见安装引导。