首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Error Prone 通过检测常见错误帮助改善Java代码

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 创建测试工程的示例:

代码语言:javascript
复制
<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 方法来对比两个数组,更准确地说,此处所比较的是对象本身而不是数组的内容。

代码语言:javascript
复制
public boolean compare(String firstList[], String secondList[]) {
    return firstList.equals(secondList);
}

执行 mvn clean verify 触发 Error Prone 分析,下面是运行结果中的错误信息中:

代码语言:javascript
复制
[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 的建议是修改实现方式,以比较数组的内容而不是比较对象。

代码语言:javascript
复制
return Arrays.equals(firstList, secondList);

报错不仅可以帮助改善代码,也可以让 Error Prone 自动应用解决方案。 -XepPatchChecks 参数的应用应该包含由逗号分隔开的 bug 模式列表,在上面的情况中,只有 ArrayEquals 解决方案用于这段代码。 -XepPatchLocation 参数用于具体定位解决方案文件位置,在当前情境中是修改了源文件:

代码语言:javascript
复制
<compilerArgs>
    <arg>-XDcompilePolicy=simple</arg>
    <arg>-Xplugin:ErrorProne -XepPatchChecks:ArrayEquals    
        -XepPatchLocation:IN_PLACE</arg>
</compilerArgs>

现在,在执行 mvn clean verify 之后,类文件被自动修改为:

代码语言:javascript
复制
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 注解。

代码语言:javascript
复制
@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 依赖的。

代码语言:javascript
复制
<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 应该被配置为一个注解处理器。

代码语言:javascript
复制
<annotationProcessorPaths>
    <path>
        <groupId>com.google.auto.service</groupId>
        <artifactId>auto-service</artifactId>
        <version>1.0.1</version>
    </path>
</annotationProcessorPaths>

现在,自定义的 Error Prone 插件可以通过 mvn install 命令,安装在本地的 Maven 仓库。执行命令后,示例工程应该会被配置为使用新的自定义插件作为注解处理器。

代码语言:javascript
复制
<annotationProcessorPaths>
    <path>
        <groupId>org.example.custom.plugin</groupId>
        <artifactId>ErrorProneBeforeCheck</artifactId>
        <version>1.0-SNAPSHOT</version>
    </path>
</annotationProcessorPaths>

新的 BeforeCheck 应该被加入到了 Error Prone 分析中。

代码语言:javascript
复制
<compilerArgs>
  <arg>-XDcompilePolicy=simple</arg>
  <arg>-Xplugin:ErrorProne -XepPatchChecks:BeforeCheck  
          -XepPatchLocation:IN_PLACE</arg>
</compilerArgs>

添加一个示例测试类,其中包含@Before@BeforeEach的两个注解。

代码语言:javascript
复制
public class ErrorProneTest {
  @Before
  void before() {
  }
  @BeforeEach
  void beforeEach() {
  }
}

运行 mvn verify 时,新的自定义 Error Prone 插件将用@BeforeEach注解替换@Before注解。

代码语言:javascript
复制
public class ErrorProneTest {
  @BeforeEach
  void before() {
  }
  @BeforeEach
  void beforeEach() {
  }
}

Error Prone 所使用的 Java internal 目前处于隐藏状态,可能会导致如下错误:

代码语言:javascript
复制
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 文件,其中配置如下:

代码语言:javascript
复制
--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 文件中:

代码语言:javascript
复制
<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 的信息可参见安装引导

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/lpOyO0ClfYLGmmakS3is
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券