加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

p3c 插件,是怎么檢查出你那屎山的代碼?

2021/09/28
379
閱讀需 24 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

一、前言

你會(huì)對(duì)你用到都技術(shù),好奇嗎?

雖然我們都被稱為碼農(nóng),也都是寫著代碼,但因?yàn)樗巿鼍靶枨蟮牟煌?,所以各類碼農(nóng)也都做著不一樣都事情。

有些人統(tǒng)一規(guī)范、有些人開發(fā)組件、有些人編寫業(yè)務(wù)、有些人倒騰驗(yàn)證,但越是工作內(nèi)容簡單如CRUD一樣的碼農(nóng),用到別人提供好的東西卻是越多。一會(huì)安裝個(gè)插件、一會(huì)引入個(gè)Jar包、一會(huì)調(diào)別人個(gè)接口,而自己的工作就像是裝配工,東拼拼西湊湊,就把產(chǎn)品需求寫完了。

壞了,這么干可能幾年下來,也不會(huì)有什么技術(shù)上都突破。因?yàn)槟銓?duì)那些使用都技術(shù)不好奇,不想知道它們是怎么實(shí)現(xiàn)的。就像阿里的P3C插件,是怎么檢查代碼分析出來我寫的拉胯的呢?

二、P3C 插件是什么

P3C 是阿里開源代碼庫的插件工程名稱,它以阿里巴巴Java開發(fā)手冊(cè)為標(biāo)準(zhǔn),用于監(jiān)測代碼質(zhì)量的 IDEA/Eclipse 插件。

  • 源碼:https://github.com/alibaba/p3c

插件安裝完成后,就可以按照編程規(guī)約,靜態(tài)分析代碼中出現(xiàn)的代碼:命名風(fēng)格、常量定義、集合處理、并發(fā)處理、OOP、控制語句、注釋、異常等各項(xiàng)潛在風(fēng)險(xiǎn),同時(shí)會(huì)給出一些優(yōu)化操作和實(shí)例。

  • 在遵守開發(fā)手冊(cè)標(biāo)準(zhǔn)并按照插件檢查都情況下,還是可以非常好的統(tǒng)一編碼標(biāo)準(zhǔn)和風(fēng)格都,也能剔除掉一些潛在都風(fēng)險(xiǎn)。如果你是新手編程用戶或者想寫出標(biāo)準(zhǔn)都代碼,那么非常建議你按照這樣都插件來輔助自己做代碼開發(fā)。當(dāng)然如果你所在的公司也有相應(yīng)都標(biāo)準(zhǔn)手冊(cè)和插件,也可以按照后遵守它都約定的。

三、P3C 插件源碼

在最開始使用這類代碼檢查都插件的時(shí)候,就非常好奇它是怎么發(fā)現(xiàn)我的屎山代碼的,用了什么樣都技術(shù)原理呢,如果我能分析下是不是也可以把這樣都技術(shù)手段用到其他地方。

在分析這樣一個(gè)代碼檢查插件前,先思考要從 IDEA 插件都源碼查起,看看它是什么個(gè)邏輯,之后分析具體是如何使用都。其實(shí)這與一些其他的框架性源碼學(xué)習(xí)都是類似的,拿到官網(wǎng)都文檔、GitHub 對(duì)應(yīng)的源碼,按照步驟進(jìn)行構(gòu)建、部署、測試、調(diào)試、分析,進(jìn)而找到核心原理。

P3C 以 IDEA 插件開發(fā)為例,主要涉及到插件部分和規(guī)約部分,因?yàn)槭前岩?guī)約檢查的能力與插件技術(shù)結(jié)合,所以會(huì)涉及到一些 IDEA 開發(fā)的技術(shù)。另外 P3C 插件涉及到都技術(shù)語言不只是 Java 還有一部分 kotlin 它是一種在 Java 虛擬機(jī)上運(yùn)行的靜態(tài)類型編程語言。

  • 插件源碼:https://github.com/alibaba/p3c/blob/master/idea-plugin規(guī)約源碼:https://github.com/alibaba/p3c/tree/master/p3c-pmd

1. 插件配置 p3c.xml

<action class="com.alibaba.p3c.idea.action.AliInspectionAction" id="AliP3CInspectionAction"
        popup="true" text="編碼規(guī)約掃描" 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>
  • 翻看源碼最重要是要找到入口,這個(gè)入口通常也是你在使用插件、程序、接口等時(shí)候,最直接進(jìn)入都那部分。那么我們?cè)谑褂?P3C 插件的時(shí)候,最明顯的就是 編碼規(guī)約掃描 通過源碼中找到這個(gè)關(guān)鍵字,看它都涉及了哪個(gè)類都配置。action 是 IDEA 插件中用于配置窗體事件入口都地方,以及把這個(gè)操作配置到哪個(gè)按鈕下和對(duì)應(yīng)都快捷鍵。

2. 編碼規(guī)約掃描( 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)
}  
  • 這是一個(gè)基于 kotlin 語言開發(fā)的插件代碼邏輯,它通過 actionPerformed 方法獲取到工程信息、類信息等,接下來就可以執(zhí)行代碼檢查了 doInspections

3. 規(guī)約 p3c-pmd

當(dāng)我們?cè)偻路撮喿x的時(shí)候,就看到了一個(gè)關(guān)于 pmd 的東西。PMD 是一款采用 BSD 協(xié)議發(fā)布的Java 程序靜態(tài)代碼檢查工具,當(dāng)使用PMD規(guī)則分析Java源碼時(shí),PMD首先利用JavaCC和EBNF文法產(chǎn)生了一個(gè)語法分析器,用來分析普通文本形式的Java代碼,產(chǎn)生符合特定語法結(jié)構(gòu)的語法,同時(shí)又在JavaCC的基礎(chǔ)上添加了語義的概念即JJTree,通過JJTree的一次轉(zhuǎn)換,這樣就將Java代碼轉(zhuǎn)換成了一個(gè)AST,AST是Java符號(hào)流之上的語義層,PMD把AST處理成一個(gè)符號(hào)表。然后編寫PMD規(guī)則,一個(gè)PMD規(guī)則可以看成是一個(gè)Visitor,通過遍歷AST找出多個(gè)對(duì)象之間的一種特定模式,即代碼所存在的問題。該軟件功能強(qiáng)大,掃描效率高,是 Java 程序員 debug 的好幫手。

那么 p3c-pmd 是什么呢?

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

四、規(guī)約監(jiān)測案例

講道理,說一千道一萬,還得是拿出代碼跑一下,才知道 PMD 具體是什么個(gè)樣子。

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
  • 源碼:https://github.com/fuzhengwei/guide-pmd

這是一個(gè)類似 p3c-pmd 的測試工程,通過自行擴(kuò)展重寫代碼監(jiān)測規(guī)約的方式,來處理自己關(guān)于代碼的審核標(biāo)準(zhǔn)處理。

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

2. 駝峰命名規(guī)約

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 方法,使用正則的方式進(jìn)行驗(yàn)證。visit 方法都入?yún)㈩愋头浅6啵謩e用于處理類、接口、方法、代碼等各項(xiàng)內(nèi)容的監(jiān)測處理,只要重寫需要的方法,在里面進(jìn)行自己都處理就可以。ClassNamingShouldBeCamelRule、ConstantFieldShouldBeUpperCaseRule、LowerCamelCaseVariableNamingRule 三個(gè)類都功能類似,這里就不一一展示了,可以直接參考源碼。

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 用于配置規(guī)約處理類、priority 級(jí)別、message 提醒文字。同時(shí)還可以配置代碼示例,使用 <example> 標(biāo)簽,在里面寫好標(biāo)準(zhǔn)代碼即可。

4. 測試驗(yàn)證規(guī)約

問題類示例

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);
}
  • 規(guī)約的測試驗(yàn)證可以直接使用 PMD.main 方法,在方法中提供字符串?dāng)?shù)組入?yún)?,這里的代碼監(jiān)測地址和規(guī)約配置需要是絕對(duì)路徑。

測試結(jié)果

TErrDto.java:3: 【TErrDto】不符合UpperCamelCase命名風(fēng)格
TErrDto.java:5: 常量【max】命名應(yīng)全部大寫并以下劃線分隔
TErrDto.java:7: 方法名【QueryUserInfo】不符合lowerCamelCase命名風(fēng)格

Process finished with exit code 4
  • 從測試結(jié)果可以看到,我們寫的三個(gè)代碼規(guī)約分別監(jiān)測出了代碼的命名風(fēng)格、常量大寫、方法名不符合駝峰標(biāo)識(shí)。同時(shí)你還可以測試 category/java/codestyle.xml 這個(gè)是 PMD 自身提供好的規(guī)約監(jiān)測。

五、擴(kuò)展了解 Sonar

其實(shí)有了 PMD 靜態(tài)代碼檢查規(guī)約,能做都事情就很多,不是只對(duì)正在寫的代碼進(jìn)行檢查,還可以對(duì)不同階段的代碼進(jìn)行分析和風(fēng)險(xiǎn)提醒,比如:準(zhǔn)備提測階段、已經(jīng)上線完成,都可以做相應(yīng)的監(jiān)測處理。

而 Sonar 就是一個(gè)這樣都工具,它是一個(gè)Web系統(tǒng),可以展現(xiàn)靜態(tài)代碼掃描的結(jié)果,結(jié)果是可以自定義的,支持多種語言的原理是它的擴(kuò)展性。https://www.sonarqube.org/

 

  • 不遵循代碼標(biāo)準(zhǔn):sonar可以通過PMD,CheckStyle,Findbugs等等代碼規(guī)則檢測工具規(guī)范代碼編寫。潛在的缺陷:sonar可以通過PMD,CheckStyle,Findbugs等等代碼規(guī)則檢測工具檢 測出潛在的缺陷。糟糕的復(fù)雜度分布:文件、類、方法等,如果復(fù)雜度過高將難以改變,這會(huì)使得開發(fā)人員 難以理解它們, 且如果沒有自動(dòng)化的單元測試,對(duì)于程序中的任何組件的改變都將可能導(dǎo)致需要全面的回歸測試。重復(fù):顯然程序中包含大量復(fù)制粘貼的代碼是質(zhì)量低下的,sonar可以展示 源碼中重復(fù)嚴(yán)重的地方。注釋不足或者過多:沒有注釋將使代碼可讀性變差,特別是當(dāng)不可避免地出現(xiàn)人員變動(dòng) 時(shí),程序的可讀性將大幅下降 而過多的注釋又會(huì)使得開發(fā)人員將精力過多地花費(fèi)在閱讀注釋上,亦違背初衷。缺乏單元測試:sonar可以很方便地統(tǒng)計(jì)并展示單元測試覆蓋率。糟糕的設(shè)計(jì):通過sonar可以找出循環(huán),展示包與包、類與類之間的相互依賴關(guān)系,可以檢測自定義的架構(gòu)規(guī)則 通過sonar可以管理第三方的jar包,可以利用LCOM4檢測單個(gè)任務(wù)規(guī)則的應(yīng)用情況, 檢測耦合。提高代碼質(zhì)量:了解自己在編碼過程中犯過的錯(cuò)誤,讓自己的代碼更具有可讀性和維護(hù)性。

六、總結(jié)

  • PMD 是一款采用 BSD 協(xié)議的代碼檢查工具,你可以擴(kuò)展實(shí)現(xiàn)為自己的標(biāo)準(zhǔn)和規(guī)范以及完善個(gè)性的提醒和修復(fù)操作。另外基于 IDEA 插件實(shí)現(xiàn)的代碼檢查或者有審計(jì)要求的處理,也可以基于 IDEA 插件做更多的擴(kuò)展,比如提醒修復(fù)、提供修復(fù)操作、自身業(yè)務(wù)邏輯的檢查。例如momo開源庫下的一款I(lǐng)DEA靜態(tài)代碼安全審計(jì)及漏洞一鍵修復(fù)插件 https://github.com/momosecurity/momo-code-sec-inspector-java這里補(bǔ)充一點(diǎn),kotlin 語言可以在 IDEA 中轉(zhuǎn)換為 Java 語言,這樣你在閱讀類似這樣的代碼時(shí)候,如果不好看懂也可以轉(zhuǎn)換一下在閱讀。此外 IDEA 插件開發(fā)需要基于 Gradle 或者本身提供都模版進(jìn)行創(chuàng)建,如果感興趣也可以閱讀我寫的 IDEA 插件開發(fā)文章。

七、系列推薦

  • 基于IDEA插件開發(fā)和字節(jié)碼插樁技術(shù),實(shí)現(xiàn)研發(fā)交付質(zhì)量自動(dòng)分析技術(shù)調(diào)研,IDEA 插件怎么開發(fā)「腳手架、低代碼可視化編排、接口生成測試」?《SpringBoot 中間件設(shè)計(jì)和開發(fā)》筆記整理:技術(shù)架構(gòu)涵蓋內(nèi)容和演變過程總結(jié)給學(xué)習(xí)加點(diǎn)實(shí)踐,開發(fā)一個(gè)分布式IM(即時(shí)通信)系統(tǒng)!

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

作者小傅哥多年從事一線互聯(lián)網(wǎng)Java開發(fā),從19年開始編寫工作和學(xué)習(xí)歷程的技術(shù)匯總,旨在為大家提供一個(gè)較清晰詳細(xì)的核心技能學(xué)習(xí)文檔。如果本文能為您提供幫助,請(qǐng)給予支持(關(guān)注、點(diǎn)贊、分享)!