Catch Common Mistakes with Error Prone Library in Java – 用 Java 中容易出错的库来捕捉常见错误

最后修改: 2023年 12月 4日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

Ensuring code quality is crucial for the successful deployment of our applications. The presence of bugs and errors can significantly hamper the functionality and stability of software. Here comes one valuable tool that aids in identifying such errors: Error Prone.

确保代码质量对于成功部署应用程序至关重要。错误和错误的存在会严重影响软件的功能和稳定性。这里有一个很有价值的工具可以帮助识别这些错误:Error Prone

Error Prone is a library maintained and used internally by Google. It assists Java developers in detecting and fixing common programming mistakes during the compilation phase.

Error Prone 是由 Google 内部维护和使用的一个库。它可以帮助 Java 开发人员在编译阶段检测并修复常见的编程错误。

In this tutorial, we explore the functionalities of the Error Prone library, from installation to customization, and the benefits it offers in enhancing code quality and robustness.

在本教程中,我们将探讨 Error Prone 库的功能(从安装到定制),以及它在提高代码质量和健壮性方面的优势。

2. Installation

2.安装

The library is available in the Maven Central repository. We’ll add a new build configuration to configure our application compiler to run the Error Prone checks:

Maven Central 资源库中提供了该库。我们将添加一个新的构建配置,以配置应用程序编译器运行易出错检查:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.12.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.23.0</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Due to the strong encapsulations of the JDK internals added in version 16, we’ll need to add some flags to allow the plugin to run. One option would be creating a new file .mvn/jvm.config if it doesn’t already exist and adding the required flags for the plugin:

由于第 16 版中添加了 强封装的 JDK 内部,我们需要添加一些标志以允许插件运行。一种方法是创建一个新文件.mvn/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

If our maven-compiler-plugin uses an external executable or the maven-toolchains-plugin is enabled, we should add the exports and opens as compilerArgs:

如果我们的 maven-compiler-plugin 使用了外部可执行文件或启用了 maven-toolchains-plugin,我们应该将 exportsopens 添加为 compilerArgs

<compilerArgs>
    // ...
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
</compilerArgs>

3. Bug Patterns

3.错误模式

Identifying and understanding common bug patterns is essential for maintaining the stability and reliability of our software. By recognizing these patterns early in our development process, we can proactively implement strategies to prevent them and improve our code’s overall quality.

识别和了解常见的错误模式对于保持软件的稳定性和可靠性至关重要。通过在开发过程中尽早识别这些模式,我们可以积极主动地实施策略来防止它们的出现,并提高代码的整体质量。

3.1. Pre-defined Bug Patterns

3.1.预先定义的错误模式

The plugin contains more than 500 pre-defined bug patterns. One of those bugs is the DeadException, which we’ll exemplify:

该插件包含 500 多个预先定义的错误模式其中一个错误是DeadException,我们将举例说明:

public static void main(String[] args) {
    if (args.length == 0 || args[0] != null) {
        new IllegalArgumentException();
    }
    // other operations with args[0]
}

In the code above, we want to ensure that our program receives a non-null parameter. Otherwise, we want to throw an IllegalArgumentException. However, due to carelessness, we just created the exception and forgot to throw it. In many cases, without a bug-checking tool, this case could go unnoticed.

在上面的代码中,我们要确保程序接收到一个非空参数。否则,我们将抛出 IllegalArgumentException 异常。然而,由于粗心大意,我们只是创建了异常,却忘记了抛出它。在许多情况下,如果没有错误检查工具,这种情况可能会被忽略。

We can run the Error Prone checks on our code using the maven clean verify command. If we do so, we’ll get the following compilation error:

我们可以使用 maven clean verify 命令在代码上运行 Error Prone 检查。如果我们这样做,就会出现以下编译错误:

[ERROR] /C:/Dev/incercare_2/src/main/java/org/example/Main.java:[6,12] [DeadException] Exception created but not thrown
    (see https://errorprone.info/bugpattern/DeadException)
  Did you mean 'throw new IllegalArgumentException();'?

We can see that the plugin not only detected our error but also provided us with a solution for it.

我们可以看到,该插件不仅检测到了我们的错误,还为我们提供了解决方案。

3.2. Custom Bug Patterns

3.2.自定义错误模式

Another notable feature of Error Prone is its ability to support the creation of custom bug checkers. These custom bug checkers enable us to tailor the tool to our specific codebase and address domain-specific issues efficiently.

Error Prone 的另一个显著特点是它能够支持创建自定义错误检查器。这些自定义错误检查器使我们能够根据特定的代码库定制工具,并有效地解决特定领域的问题。

To create our custom checks, we need to initialize a new project. Let’s call it my-bugchecker-plugin. We’ll start by adding the configuration for the bug checker:

要创建自定义检查,我们需要初始化一个新项目。让我们称其为 my-bugchecker-plugin 。我们将首先添加错误检查程序的配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.12.1</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>com.google.auto.service</groupId>
                        <artifactId>auto-service</artifactId>
                        <version>1.0.1</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

<dependencies>
    <dependency>
        <groupId>com.google.errorprone</groupId>
        <artifactId>error_prone_annotation</artifactId>
        <version>2.23.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.errorprone</groupId>
        <artifactId>error_prone_check_api</artifactId>
        <version>2.23.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.auto.service</groupId>
        <artifactId>auto-service-annotations</artifactId>
        <version>1.0.1</version>
    </dependency>
</dependencies>

We added some more dependencies this time. As we can see, besides the Error Prone dependencies, we added Google AutoService. Google AutoService is an open-source code generator tool developed under the Google Auto project. This will discover and load our custom checks.

这次我们添加了更多依赖项。正如我们所看到的,除了 Error Prone 依赖项,我们还添加了 Google AutoService。Google AutoService 是在 Google Auto 项目下开发的开源代码生成工具。它将发现并加载我们的自定义检查。

Now we’ll create our custom check, which will verify if we have any empty methods in our code base:

现在,我们将创建自定义检查,它将验证我们的代码库中是否有任何空方法:

@AutoService(BugChecker.class)
@BugPattern(name = "EmptyMethodCheck", summary = "Empty methods should be deleted", severity = BugPattern.SeverityLevel.ERROR)
public class EmptyMethodChecker extends BugChecker implements BugChecker.MethodTreeMatcher {

    @Override
    public Description matchMethod(MethodTree methodTree, VisitorState visitorState) {
        if (methodTree.getBody()
          .getStatements()
          .isEmpty()) {
            return describeMatch(methodTree, SuggestedFix.delete(methodTree));
        }
        return Description.NO_MATCH;
    }
}

First, The annotation BugPattern contains the name, a short summary, and the severity of the bug. Next, the BugChecker itself is an implementation of MethodTreeMatcher because we want to match methods that have an empty body. Lastly, the logic in matchMethod() should return a match if the method tree body does not have any statements.

首先,注解 BugPattern 包含错误的名称、简短摘要和严重程度。其次,BugChecker 本身是 MethodTreeMatcher 的实现,因为我们希望匹配具有空主体的方法。最后,如果方法树主体中没有任何语句,matchMethod() 中的逻辑应返回匹配结果。

To use our custom bug checker in another project, we should compile it into a separate JAR. We’ll do it by running the maven clean install command. After that, we should include the generated JAR as a dependency in the build configuration of our main project by adding it to the annotationProcessorPaths:

要在另一个项目中使用自定义错误检查器,我们应将其编译为一个单独的 JAR。我们将通过运行 maven clean install 命令来实现这一点。之后,我们应将生成的 JAR 添加到 annotationProcessorPaths 中,作为主项目构建配置的依赖项:

<annotationProcessorPaths>
    <path>
        <groupId>com.google.errorprone</groupId>
        <artifactId>error_prone_core</artifactId>
        <version>2.23.0</version>
    </path>
    <path>
        <groupId>com.baeldung</groupId>
        <artifactId>my-bugchecker-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
    </path>
</annotationProcessorPaths>

This way, our bug checker also becomes reusable. Now, if we write a new class with an empty method:

这样,我们的错误检查程序就可以重复使用了。现在,如果我们编写一个带有空方法的新类:

public class ClassWithEmptyMethod {

    public void theEmptyMethod() {
    }
}

If we run the maven clean verify command again, we’ll get an error:

如果我们再次运行 maven clean verify 命令,就会出现错误:

[EmptyMethodCheck] Empty methods should be deleted

4. Customizing Checks

4.定制支票

Google Error Prone is a wonderful tool that can help us eliminate many bugs even before they are introduced into the code. However, it can be too harsh sometimes with our code. Let’s say we want to throw an exception for our empty function, but only for this one. We can add the SuppressWarnings annotation with the name of the check that we want to bypass:

Google Error Prone 是一款出色的工具,它可以帮助我们在代码中出现错误之前就将其消除。不过,它有时对我们的代码过于苛刻。比方说,我们想为空函数抛出异常,但只针对这个函数。我们可以在 SuppressWarnings 注解中添加想要绕过的检查的名称:

@SuppressWarnings("EmptyMethodCheck")
public void emptyMethod() {}

Suppressing warnings is not recommended but might be needed in some cases, like when working with external libraries that do not implement the same code standards as our project.

不建议压制警告,但在某些情况下可能需要,例如在使用与我们的项目不执行相同代码标准的外部库时。

In addition to this, we can control the severity of all checks using additional compiler arguments:

除此之外,我们还可以使用额外的编译器参数来控制所有检查的严重程度:

  • -Xep:EmptyMethodCheck: turns on EmptyMethodCheck with the severity level from its BugPattern annotation
  • -Xep:EmptyMethodCheck: OFF turns off EmptyMethodCheck check
  • -Xep:EmptyMethodCheck: WARN turns on EmptyMethodCheck check as a warning
  • -Xep:EmptyMethodCheck: ERROR turns on EmptyMethodCheck check as an error

We also have some blanket severity-changing flags that are global for all checks:

我们还有一些可一揽子改变严重性的标记,这些标记对所有检查都具有全局意义:

  • -XepAllErrorsAsWarnings
  • -XepAllSuggestionsAsWarnings
  • -XepAllDisabledChecksAsWarnings
  • -XepDisableAllChecks
  • -XepDisableAllWarnings
  • -XepDisableWarningsInGeneratedCode

We can also combine our custom compiler flags with the global ones:

我们还可以将自定义编译器标志与全局标志相结合:

<compilerArgs>
    <arg>-XDcompilePolicy=simple</arg>
    <arg>-Xplugin:ErrorProne -XepDisableAllChecks -Xep:EmptyMethodCheck:ERROR</arg>
</compilerArgs>

By configuring our compiler as above, we’ll disable all checks except the custom check we created.

通过如上配置编译器,除了我们创建的自定义检查外,我们将禁用所有检查。

5. Refactoring Code

5.重构代码

A feature that separates our plugin from the rest of the static code analysis programs is the possibility of patching the codebase. Aside from identifying errors during our standard compilation phase, Error Prone can also provide suggested replacements. As we saw in subpoint 3, when Error Prone found the DeadException, it also suggested a fix for it:

我们的插件与其他静态代码分析程序不同之处在于,它可以对代码库进行修补。除了在标准编译阶段识别错误外,Error Prone 还可以提供建议的替换。正如我们在第 3 小节中所看到的,当 Error Prone 发现 DeadException 时,它还会建议对其进行修复:

Did you mean 'throw new IllegalArgumentException();'?

In this context, Error Prone recommends resolving this problem by adding the throw keyword. We can also use Error Prone to modify the source code with the suggested replacements. This is useful when first adding Error Prone enforcement to our existing codebase. To activate this, we need to add two compiler flags to our compiler invocation:

在这种情况下,Error Prone 建议通过添加 throw 关键字来解决这个问题。我们还可以使用 Error Prone 根据建议的替换修改源代码。这在首次将 Error Prone 强制添加到现有代码库时非常有用。要激活此功能,我们需要在编译器调用中添加两个编译器标志:

  • XepPatchChecks: followed by the checks that we want to patch. If a check does not suggest fixes, then it won’t do anything.
  • -XepPatchLocation: the location where to generate the patch file containing the fixes

So, we can rewrite our compiler configuration like this:

因此,我们可以这样重写编译器配置:

<compilerArgs>
    <arg>-XDcompilePolicy=simple</arg>
    <arg>-Xplugin:ErrorProne -XepPatchChecks:DeadException,EmptyMethodCheck -XepPatchLocation:IN_PLACE</arg>
</compilerArgs>

We’ll tell the compiler to fix the DeadException and our custom EmptyMethodCheck. We set the location to IN_PLACE, meaning that it will apply the changes in our source code.

我们将告诉编译器修复 DeadException 和我们自定义的 EmptyMethodCheck. 我们将位置设置为 IN_PLACE,这意味着它将应用源代码中的更改。

Now, if we run the maven clean verify command on a buggy class:

现在,如果我们在一个有错误的类上运行 maven clean verify 命令:

public class BuggyClass {

    public static void main(String[] args) {
        if (args.length == 0 || args[0] != null) {
             new IllegalArgumentException();
        }
    }

    public void emptyMethod() {
    }
}

It will refactor the class:

它将重构该类:

public class BuggyClass {

    public static void main(String[] args) {
        if (args.length == 0 || args[0] != null) {
             throw new IllegalArgumentException();
        }
    }
}

6. Conclusion

6.结论

In summary, Error Prone is a versatile tool that combines effective error identification with customizable configurations. It empowers developers to enforce coding standards seamlessly and facilitates efficient code refactoring through automated suggested replacements. Overall, Error Prone is a valuable asset for enhancing code quality and streamlining the development process.

总之,Error Prone 是一款多功能工具,它将有效的错误识别与可定制的配置相结合。它使开发人员能够无缝地执行编码标准,并通过自动建议替换促进高效的代码重构。总之,Error Prone 是提高代码质量和简化开发流程的宝贵资产。

As always, the full code presented in this tutorial is available over on GitHub.

与往常一样,本教程中介绍的完整代码可在 GitHub. 上获取。