p3c 插件,是怎麼檢查出你那屎山的程式碼?


作者:小傅哥
部落格://bugstack.cn
原文://mp.weixin.qq.com/s/RwzprbY2AhdgslY8tbVL-A

一、前言

你會對你用到都技術,好奇嗎?

雖然我們都被稱為碼農,也都是寫著程式碼,但因為所處場景需求的不同,所以各類碼農也都做著不一樣都事情。

有些人統一規範、有些人開發組件、有些人編寫業務、有些人倒騰驗證,但越是工作內容簡單如CRUD一樣的碼農,用到別人提供好的東西卻是越多。一會安裝個插件、一會引入個Jar包、一會調別人個介面,而自己的工作就像是裝配工,東拼拼西湊湊,就把產品需求寫完了。

壞了,這麼干可能幾年下來,也不會有什麼技術上都突破。因為你對那些使用都技術不好奇,不想知道它們是怎麼實現的。就像阿里的P3C插件,是怎麼檢查程式碼分析出來我寫的拉胯的呢?

二、P3C 插件是什麼

P3C 是阿里開源程式碼庫的插件工程名稱,它以阿里巴巴Java開發手冊為標準,用於監測程式碼品質的 IDEA/Eclipse 插件。

插件安裝完成後,就可以按照編程規約,靜態分析程式碼中出現的程式碼:命名風格、常量定義、集合處理、並發處理、OOP、控制語句、注釋、異常等各項潛在風險,同時會給出一些優化操作和實例。

  • 在遵守開發手冊標準並按照插件檢查都情況下,還是可以非常好的統一編碼標準和風格都,也能剔除掉一些潛在都風險。
  • 如果你是新手編程用戶或者想寫出標準都程式碼,那麼非常建議你按照這樣都插件來輔助自己做程式碼開發。當然如果你所在的公司也有相應都標準手冊和插件,也可以按照後遵守它都約定的。

三、P3C 插件源碼

在最開始使用這類程式碼檢查都插件的時候,就非常好奇它是怎麼發現我的屎山程式碼的,用了什麼樣都技術原理呢,如果我能分析下是不是也可以把這樣都技術手段用到其他地方。

在分析這樣一個程式碼檢查插件前,先思考要從 IDEA 插件都源碼查起,看看它是什麼個邏輯,之後分析具體是如何使用都。其實這與一些其他的框架性源碼學習都是類似的,拿到官網都文檔、GitHub 對應的源碼,按照步驟進行構建、部署、測試、調試、分析,進而找到核心原理。

P3C 以 IDEA 插件開發為例,主要涉及到插件部分和規約部分,因為是把規約檢查的能力與插件技術結合,所以會涉及到一些 IDEA 開發的技術。另外 P3C 插件涉及到都技術語言不只是 Java 還有一部分 kotlin 它是一種在 Java 虛擬機上運行的靜態類型程式語言。

1. 插件配置 p3c.xml

<action class="com.alibaba.p3c.idea.action.AliInspectionAction" id="AliP3CInspectionAction"
        popup="true" text="編碼規約掃描" icon="P3cIcons.ANALYSIS_ACTION">
    <keyboard-shortcut keymap="$default"
                       first-keystroke="shift ctrl alt J"/>
    <add-to-group group-id="MainToolBar" anchor="last"/>
    <add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
    <add-to-group group-id="ChangesViewPopupMenu" anchor="last"/>
    <add-to-group group-id="EditorPopupMenu" anchor="last"/>
</action>
  • 翻看源碼最重要是要找到入口,這個入口通常也是你在使用插件、程式、介面等時候,最直接進入都那部分。
  • 那麼我們在使用 P3C 插件的時候,最明顯的就是 編碼規約掃描 通過源碼中找到這個關鍵字,看它都涉及了哪個類都配置。
  • action 是 IDEA 插件中用於配置窗體事件入口都地方,以及把這個操作配置到哪個按鈕下和對應都快捷鍵。

2. 編碼規約掃描( AliInspectionAction)

class AliInspectionAction : AnAction() {

    override fun actionPerformed(e: AnActionEvent) {
        val project = e.project ?: return
        val analysisUIOptions = ServiceManager.getService(project, AnalysisUIOptions::class.java)!!
        analysisUIOptions.GROUP_BY_SEVERITY = true

        val managerEx = InspectionManager.getInstance(project) as InspectionManagerEx
        val toolWrappers = Inspections.aliInspections(project) {
            it.tool is AliBaseInspection
        }
        val psiElement = e.getData<PsiElement>(CommonDataKeys.PSI_ELEMENT)
        val psiFile = e.getData<PsiFile>(CommonDataKeys.PSI_FILE)
        val virtualFile = e.getData<VirtualFile>(CommonDataKeys.VIRTUAL_FILE)
        
		...
		
		createContext(
    	toolWrappers, managerEx, element,
    	projectDir, analysisScope
		).doInspections(analysisScope)
}		
  • 這是一個基於 kotlin 語言開發的插件程式碼邏輯,它通過 actionPerformed 方法獲取到工程資訊、類資訊等,接下來就可以執行程式碼檢查了 doInspections

3. 規約 p3c-pmd

當我們再往下翻看閱讀的時候,就看到了一個關於 pmd 的東西。PMD 是一款採用 BSD 協議發布的Java 程式靜態程式碼檢查工具,當使用PMD規則分析Java源碼時,PMD首先利用JavaCC和EBNF文法產生了一個語法分析器,用來分析普通文本形式的Java程式碼,產生符合特定語法結構的語法,同時又在JavaCC的基礎上添加了語義的概念即JJTree,通過JJTree的一次轉換,這樣就將Java程式碼轉換成了一個AST,AST是Java符號流之上的語義層,PMD把AST處理成一個符號表。然後編寫PMD規則,一個PMD規則可以看成是一個Visitor,通過遍歷AST找出多個對象之間的一種特定模式,即程式碼所存在的問題。該軟體功能強大,掃描效率高,是 Java 程式設計師 debug 的好幫手。

那麼 p3c-pmd 是什麼呢?

ViolationUtils.addViolationWithPrecisePosition(this, node, data,
    I18nResources.getMessage("java.naming.ClassNamingShouldBeCamelRule.violation.msg",
        node.getImage()));
  • p3c-pmd 插件是基於 PMD 實現的,更具體的來說是基於 pmd-java 的,因為 PMD 不僅支援 Java 程式碼分析,還支援其他多種語言。
  • 具體自定義規則的方式,通過自定義Java類和XPATH規則實現。

四、規約監測案例

講道理,說一千道一萬,還得是拿出程式碼跑一下,才知道 PMD 具體是什麼個樣子。

1. 測試工程

guide-pmd
└── src
    ├── main
    │   ├── java
    │   │   └── cn.itedus.guide.pmd.rule
    │   │       ├── naming
    │   │       │   ├── ClassNamingShouldBeCamelRule.java
    │   │       │   ├── ConstantFieldShouldBeUpperCaseRule.java
    │   │       │   └── LowerCamelCaseVariableNamingRule.java
    │   │       ├── utils
    │   │       │   ├── StringAndCharConstants.java
    │   │       │   └── ViolationUtils.java    
    │   │       └── I18nResources
    │   └── resources
    │       ├── rule 
    │       │   └── ali-naming.xml  
    │       ├── messages.xml   
    │       └── namelist.properties  
    └── test
        └── java
            └── cn.itedus.demo.test
                ├── ApiTest.java
                └── TErrDto.java

這是一個類似 p3c-pmd 的測試工程,通過自行擴展重寫程式碼監測規約的方式,來處理自己關於程式碼的審核標準處理。

  • naming 下的類是用於處理一些和名稱相關的規則,類名、屬性名、方法名等
  • resources 下 ali-naming.xml 是規約的配置文件

2. 駝峰命名規約

public class ClassNamingShouldBeCamelRule extends AbstractJavaRule {

    private static final Pattern PATTERN
            = Pattern.compile("^I?([A-Z][a-z0-9]+)+(([A-Z])|(DO|DTO|VO|DAO|BO|DAOImpl|YunOS|AO|PO))?$");

    @Override
    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
        if (PATTERN.matcher(node.getImage()).matches()) {
            return super.visit(node, data);
        }
        
        ViolationUtils.addViolationWithPrecisePosition(this, node, data,
                I18nResources.getMessage("java.naming.ClassNamingShouldBeCamelRule.violation.msg",
                        node.getImage()));

        return super.visit(node, data);
    }
}
  • 通過繼承 PMD 提供的 AbstractJavaRule 抽象類,重寫 visit 方法,使用正則的方式進行驗證。
  • visit 方法都入參類型非常多,分別用於處理類、介面、方法、程式碼等各項內容的監測處理,只要重寫需要的方法,在裡面進行自己都處理就可以。
  • ClassNamingShouldBeCamelRule、ConstantFieldShouldBeUpperCaseRule、LowerCamelCaseVariableNamingRule 三個類都功能類似,這裡就不一一展示了,可以直接參考源碼。

3. ali-naming.xml 配置

<rule name="ClassNamingShouldBeCamelRule"
      language="java"
      since="1.6"
      message="java.naming.ClassNamingShouldBeCamelRule.rule.msg"
      class="cn.itedus.guide.pmd.rule.naming.ClassNamingShouldBeCamelRule">
    <priority>3</priority>
</rule>
  • 在 ali-naming.xml 用於配置規約處理類、priority 級別、message 提醒文字。
  • 同時還可以配置程式碼示例,使用 <example> 標籤,在裡面寫好標準程式碼即可。

4. 測試驗證規約

問題類示例

public class TErrDto {

    public static final Long max = 50000L;

    public void QueryUserInfo(){
        boolean baz = true;
        while (baz)
            baz = false;
    }

}

單元測試

@Test
public void test_naming(){
    String[] str = {
            "-d",
            "E:\\itstack\\git\\github.com\\guide-pmd\\src\\test\\java\\cn\\itedus\\demo\\test\\TErrDto.java",
            "-f",
            "text",
            "-R",
            "E:\\itstack\\git\\github.com\\guide-pmd\\src\\main\\resources\\rule\\ali-naming.xml"
            // "category/java/codestyle.xml"
    };
    PMD.main(str);
}
  • 規約的測試驗證可以直接使用 PMD.main 方法,在方法中提供字元串數組入參,這裡的程式碼監測地址和規約配置需要是絕對路徑。

測試結果

TErrDto.java:3:	【TErrDto】不符合UpperCamelCase命名風格
TErrDto.java:5:	常量【max】命名應全部大寫並以下劃線分隔
TErrDto.java:7:	方法名【QueryUserInfo】不符合lowerCamelCase命名風格

Process finished with exit code 4
  • 從測試結果可以看到,我們寫的三個程式碼規約分別監測出了程式碼的命名風格、常量大寫、方法名不符合駝峰標識。
  • 同時你還可以測試 category/java/codestyle.xml 這個是 PMD 自身提供好的規約監測。

五、擴展了解 Sonar

其實有了 PMD 靜態程式碼檢查規約,能做都事情就很多,不是只對正在寫的程式碼進行檢查,還可以對不同階段的程式碼進行分析和風險提醒,比如:準備提測階段、已經上線完成,都可以做相應的監測處理。

而 Sonar 就是一個這樣都工具,它是一個Web系統,可以展現靜態程式碼掃描的結果,結果是可以自定義的,支援多種語言的原理是它的擴展性。//www.sonarqube.org/

  • 不遵循程式碼標準:sonar可以通過PMD,CheckStyle,Findbugs等等程式碼規則檢測工具規範程式碼編寫。
  • 潛在的缺陷:sonar可以通過PMD,CheckStyle,Findbugs等等程式碼規則檢測工具檢 測出潛在的缺陷。
  • 糟糕的複雜度分布:文件、類、方法等,如果複雜度過高將難以改變,這會使得開發人員 難以理解它們, 且如果沒有自動化的單元測試,對於程式中的任何組件的改變都將可能導致需要全面的回歸測試。
  • 重複:顯然程式中包含大量複製粘貼的程式碼是品質低下的,sonar可以展示 源碼中重複嚴重的地方。
  • 注釋不足或者過多:沒有注釋將使程式碼可讀性變差,特別是當不可避免地出現人員變動 時,程式的可讀性將大幅下降 而過多的注釋又會使得開發人員將精力過多地花費在閱讀注釋上,亦違背初衷。
  • 缺乏單元測試:sonar可以很方便地統計並展示單元測試覆蓋率。
  • 糟糕的設計:通過sonar可以找出循環,展示包與包、類與類之間的相互依賴關係,可以檢測自定義的架構規則 通過sonar可以管理第三方的jar包,可以利用LCOM4檢測單個任務規則的應用情況, 檢測耦合。
  • 提高程式碼品質:了解自己在編碼過程中犯過的錯誤,讓自己的程式碼更具有可讀性和維護性。

六、總結

  • PMD 是一款採用 BSD 協議的程式碼檢查工具,你可以擴展實現為自己的標準和規範以及完善個性的提醒和修復操作。
  • 另外基於 IDEA 插件實現的程式碼檢查或者有審計要求的處理,也可以基於 IDEA 插件做更多的擴展,比如提醒修復、提供修復操作、自身業務邏輯的檢查。例如momo開源庫下的一款IDEA靜態程式碼安全審計及漏洞一鍵修復插件 //github.com/momosecurity/momo-code-sec-inspector-java
  • 這裡補充一點,kotlin 語言可以在 IDEA 中轉換為 Java 語言,這樣你在閱讀類似這樣的程式碼時候,如果不好看懂也可以轉換一下在閱讀。此外 IDEA 插件開發需要基於 Gradle 或者本身提供都模版進行創建,如果感興趣也可以閱讀我寫的 IDEA 插件開發文章。

七、系列推薦