使用 Java 中的 Error Prone Library 捕捉常見錯誤
一、簡介
確保程式碼品質對於成功部署應用程式至關重要。錯誤和錯誤的存在會嚴重影響軟體的功能和穩定性。這裡有一個有價值的工具可以幫助識別此類錯誤: Error Prone 。
Error Prone 是一個由 Google 內部維護和使用的函式庫。它幫助 Java 開發人員在編譯階段檢測和修復常見的程式錯誤。
在本教程中,我們將探討 Error Prone 庫的功能(從安裝到自訂),以及它在提高程式碼品質和穩健性方面提供的優勢。
2. 安裝
該庫可在Maven 中央儲存庫中找到。我們將添加一個新的建置配置來配置我們的應用程式編譯器以運行容易出錯的檢查:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</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>
由於版本 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
如果我們的maven-compiler-plugin
使用外部執行檔或啟用了maven-toolchains-plugin
,我們應該會新增exports
並opens
為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.錯誤模式
識別和理解常見錯誤模式對於維護軟體的穩定性和可靠性至關重要。透過在開發過程的早期識別這些模式,我們可以主動實施策略來防止它們並提高程式碼的整體品質。
3.1.預定義的錯誤模式
該外掛包含 500 多個預先定義的錯誤模式。這些錯誤之一是DeadException,我們將舉例說明:
public static void main(String[] args) {
if (args.length == 0 || args[0] != null) {
new IllegalArgumentException();
}
// other operations with args[0]
}
在上面的程式碼中,我們要確保我們的程式接收到一個非空參數。否則,我們要拋出IllegalArgumentException.
然而,由於粗心,我們只是創建了異常,卻忘了拋出它。在許多情況下,如果沒有錯誤檢查工具,這種情況可能會被忽略。
我們可以使用maven clean verify
指令對我們的程式碼執行容易出錯的檢查。如果我們這樣做,我們會得到以下編譯錯誤:
[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();'?
我們可以看到該插件不僅檢測到了我們的錯誤,還為我們提供了解決方案。
3.2.自訂錯誤模式
Error Prone 的另一個顯著功能是它支援建立自訂錯誤檢查器的能力。這些自訂錯誤檢查器使我們能夠根據特定的程式碼庫自訂工具並有效地解決特定於網域的問題。
要建立自訂檢查,我們需要初始化一個新專案。我們稱之為my-bugchecker-plugin
。我們首先新增錯誤檢查器的配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</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>
這次我們增加了更多依賴項。正如我們所看到的,除了 Error Prone 依賴項之外,我們還新增了 Google AutoService。 Google AutoService 是在 Google Auto 專案下開發的開源程式碼產生器工具。這將發現並加載我們的自訂檢查。
現在我們將建立自訂檢查,它將驗證程式碼庫中是否有任何空方法:
@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;
}
}
首先,註釋BugPattern
包含錯誤的名稱、簡短摘要和嚴重性。接下來,BugChecker 本身就是MethodTreeMatcher
的實現,因為我們想要匹配具有空主體的方法。最後,如果方法樹主體沒有任何語句,則matchMethod()
中的邏輯應傳回符合項。
要在另一個專案中使用我們的自訂錯誤檢查器,我們應該將其編譯成一個單獨的 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>
這樣,我們的錯誤檢查器也變得可重複使用。現在,如果我們寫一個帶有空方法的新類別:
public class ClassWithEmptyMethod {
public void theEmptyMethod() {
}
}
如果我們再次執行maven clean verify
命令,我們會得到一個錯誤:
[EmptyMethodCheck] Empty methods should be deleted
4. 自訂檢查
Google Error Prone 是一個很棒的工具,可以幫助我們消除許多錯誤,甚至在它們被引入程式碼之前。然而,有時我們的程式碼可能過於苛刻。假設我們想為空函數拋出異常,但僅限於此。我們可以新增SuppressWarnings
註解以及我們要繞過的檢查的名稱:
@SuppressWarnings("EmptyMethodCheck")
public void emptyMethod() {}
不建議抑制警告,但在某些情況下可能需要,例如使用未實現與我們的專案相同的程式碼標準的外部函式庫時。
除此之外,我們可以使用其他編譯器參數來控制所有檢查的嚴重性:
-
-Xep:EmptyMethodCheck:
使用 BugPattern 註解中的嚴重性等級開啟 EmptyMethodCheck -
-Xep:EmptyMethodCheck
: OFF 關閉 EmptyMethodCheck 檢查 -
-Xep:EmptyMethodCheck
: WARN 開啟 EmptyMethodCheck 檢查作為警告 -
-Xep:EmptyMethodCheck
: ERROR 將 EmptyMethodCheck 檢查當作錯誤開啟
我們還有一些針對所有檢查的全域嚴重性變更標誌:
-
-XepAllErrorsAsWarnings
-
-XepAllSuggestionsAsWarnings
-
-XepAllDisabledChecksAsWarnings
-
-XepDisableAllChecks
-
-XepDisableAllWarnings
-
-XepDisableWarningsInGeneratedCode
我們也可以將自訂編譯器標誌與全域編譯器標誌結合:
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne -XepDisableAllChecks -Xep:EmptyMethodCheck:ERROR</arg>
</compilerArgs>
透過如上所述配置我們的編譯器,我們將停用除我們建立的自訂檢查之外的所有檢查。
5. 重構程式碼
將我們的插件與其他靜態程式碼分析程式區分開來的一個功能是可以修補程式碼庫。除了在標準編譯階段識別錯誤之外,Error Prone 還可以提供建議的替換。正如我們在子點 3 中看到的,當 Error Prone 發現DeadException,
它還建議對其進行修復:
Did you mean 'throw new IllegalArgumentException();'?
在這種情況下,Error Prone 建議透過新增throw
關鍵字來解決此問題。我們也可以使用 Error Prone 使用建議的替換來修改原始程式碼。當第一次向我們現有的程式碼庫添加容易出錯的強制措施時,這非常有用。要啟動此功能,我們需要在編譯器呼叫中新增兩個編譯器標誌:
- –
XepPatchChecks:
後面是我們要修補的檢查。如果檢查沒有建議修復,那麼它不會做任何事情。 -
-XepPatchLocation:
產生包含修復程式的補丁檔案的位置
因此,我們可以像這樣重寫編譯器配置:
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne -XepPatchChecks:DeadException,EmptyMethodCheck -XepPatchLocation:IN_PLACE</arg>
</compilerArgs>
我們將告訴編譯器修復DeadException
和我們自訂的EmptyMethodCheck.
我們將位置設為IN_PLACE
,這意味著它將應用原始程式碼中的變更。
現在,如果我們在有問題的類別上執行 maven clean verify 命令:
public class BuggyClass {
public static void main(String[] args) {
if (args.length == 0 || args[0] != null) {
new IllegalArgumentException();
}
}
public void emptyMethod() {
}
}
它將重構該類別:
public class BuggyClass {
public static void main(String[] args) {
if (args.length == 0 || args[0] != null) {
throw new IllegalArgumentException();
}
}
}
六,結論
總之,Error Prone 是一種多功能工具,它將有效的錯誤識別與可自訂的配置相結合。它使開發人員能夠無縫地執行編碼標準,並透過自動建議的替換促進高效的程式碼重構。總體而言,Error Prone 是提高程式碼品質和簡化開發流程的寶貴資產。
與往常一樣,本教程中提供的完整程式碼可以在 GitHub 上取得。