一、前言
你會對你用到都技術,好奇嗎?
雖然我們都被稱為碼農(nóng),也都是寫著代碼,但因為所處場景需求的不同,所以各類碼農(nóng)也都做著不一樣都事情。
有些人統(tǒng)一規(guī)范、有些人開發(fā)組件、有些人編寫業(yè)務、有些人倒騰驗證,但越是工作內(nèi)容簡單如CRUD一樣的碼農(nóng),用到別人提供好的東西卻是越多。一會安裝個插件、一會引入個Jar包、一會調(diào)別人個接口,而自己的工作就像是裝配工,東拼拼西湊湊,就把產(chǎn)品需求寫完了。
壞了,這么干可能幾年下來,也不會有什么技術上都突破。因為你對那些使用都技術不好奇,不想知道它們是怎么實現(xiàn)的。就像阿里的P3C插件,是怎么檢查代碼分析出來我寫的拉胯的呢?
二、P3C 插件是什么
P3C 是阿里開源代碼庫的插件工程名稱,它以阿里巴巴Java開發(fā)手冊為標準,用于監(jiān)測代碼質(zhì)量的 IDEA/Eclipse 插件。
- 源碼:https://github.com/alibaba/p3c
插件安裝完成后,就可以按照編程規(guī)約,靜態(tài)分析代碼中出現(xiàn)的代碼:命名風格、常量定義、集合處理、并發(fā)處理、OOP、控制語句、注釋、異常等各項潛在風險,同時會給出一些優(yōu)化操作和實例。
- 在遵守開發(fā)手冊標準并按照插件檢查都情況下,還是可以非常好的統(tǒng)一編碼標準和風格都,也能剔除掉一些潛在都風險。如果你是新手編程用戶或者想寫出標準都代碼,那么非常建議你按照這樣都插件來輔助自己做代碼開發(fā)。當然如果你所在的公司也有相應都標準手冊和插件,也可以按照后遵守它都約定的。
三、P3C 插件源碼
在最開始使用這類代碼檢查都插件的時候,就非常好奇它是怎么發(fā)現(xiàn)我的屎山代碼的,用了什么樣都技術原理呢,如果我能分析下是不是也可以把這樣都技術手段用到其他地方。
在分析這樣一個代碼檢查插件前,先思考要從 IDEA 插件都源碼查起,看看它是什么個邏輯,之后分析具體是如何使用都。其實這與一些其他的框架性源碼學習都是類似的,拿到官網(wǎng)都文檔、GitHub 對應的源碼,按照步驟進行構(gòu)建、部署、測試、調(diào)試、分析,進而找到核心原理。
P3C 以 IDEA 插件開發(fā)為例,主要涉及到插件部分和規(guī)約部分,因為是把規(guī)約檢查的能力與插件技術結(jié)合,所以會涉及到一些 IDEA 開發(fā)的技術。另外 P3C 插件涉及到都技術語言不只是 Java 還有一部分 kotlin 它是一種在 Java 虛擬機上運行的靜態(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>
- 翻看源碼最重要是要找到入口,這個入口通常也是你在使用插件、程序、接口等時候,最直接進入都那部分。那么我們在使用 P3C 插件的時候,最明顯的就是
編碼規(guī)約掃描
通過源碼中找到這個關鍵字,看它都涉及了哪個類都配置。action 是 IDEA 插件中用于配置窗體事件入口都地方,以及把這個操作配置到哪個按鈕下和對應都快捷鍵。
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)
}
- 這是一個基于 kotlin 語言開發(fā)的插件代碼邏輯,它通過 actionPerformed 方法獲取到工程信息、類信息等,接下來就可以執(zhí)行代碼檢查了 doInspections
3. 規(guī)約 p3c-pmd
當我們再往下翻看閱讀的時候,就看到了一個關于 pmd 的東西。PMD 是一款采用 BSD 協(xié)議發(fā)布的Java 程序靜態(tài)代碼檢查工具,當使用PMD規(guī)則分析Java源碼時,PMD首先利用JavaCC和EBNF文法產(chǎn)生了一個語法分析器,用來分析普通文本形式的Java代碼,產(chǎn)生符合特定語法結(jié)構(gòu)的語法,同時又在JavaCC的基礎上添加了語義的概念即JJTree,通過JJTree的一次轉(zhuǎn)換,這樣就將Java代碼轉(zhuǎn)換成了一個AST,AST是Java符號流之上的語義層,PMD把AST處理成一個符號表。然后編寫PMD規(guī)則,一個PMD規(guī)則可以看成是一個Visitor,通過遍歷AST找出多個對象之間的一種特定模式,即代碼所存在的問題。該軟件功能強大,掃描效率高,是 Java 程序員 debug 的好幫手。
那么 p3c-pmd 是什么呢?
ViolationUtils.addViolationWithPrecisePosition(this, node, data,
I18nResources.getMessage("java.naming.ClassNamingShouldBeCamelRule.violation.msg",
node.getImage()));
- p3c-pmd 插件是基于 PMD 實現(xiàn)的,更具體的來說是基于 pmd-java 的,因為 PMD 不僅支持 Java 代碼分析,還支持其他多種語言。具體自定義規(guī)則的方式,通過自定義Java類和XPATH規(guī)則實現(xiàn)。
四、規(guī)約監(jiān)測案例
講道理,說一千道一萬,還得是拿出代碼跑一下,才知道 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
- 源碼:https://github.com/fuzhengwei/guide-pmd
這是一個類似 p3c-pmd 的測試工程,通過自行擴展重寫代碼監(jiān)測規(guī)約的方式,來處理自己關于代碼的審核標準處理。
- naming 下的類是用于處理一些和名稱相關的規(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 方法,使用正則的方式進行驗證。visit 方法都入?yún)㈩愋头浅6?,分別用于處理類、接口、方法、代碼等各項內(nèi)容的監(jiān)測處理,只要重寫需要的方法,在里面進行自己都處理就可以。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 用于配置規(guī)約處理類、priority 級別、message 提醒文字。同時還可以配置代碼示例,使用
<example>
標簽,在里面寫好標準代碼即可。
4. 測試驗證規(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ī)約的測試驗證可以直接使用 PMD.main 方法,在方法中提供字符串數(shù)組入?yún)?,這里的代碼監(jiān)測地址和規(guī)約配置需要是絕對路徑。
測試結(jié)果
TErrDto.java:3: 【TErrDto】不符合UpperCamelCase命名風格
TErrDto.java:5: 常量【max】命名應全部大寫并以下劃線分隔
TErrDto.java:7: 方法名【QueryUserInfo】不符合lowerCamelCase命名風格
Process finished with exit code 4
- 從測試結(jié)果可以看到,我們寫的三個代碼規(guī)約分別監(jiān)測出了代碼的命名風格、常量大寫、方法名不符合駝峰標識。同時你還可以測試
category/java/codestyle.xml
這個是 PMD 自身提供好的規(guī)約監(jiān)測。
五、擴展了解 Sonar
其實有了 PMD 靜態(tài)代碼檢查規(guī)約,能做都事情就很多,不是只對正在寫的代碼進行檢查,還可以對不同階段的代碼進行分析和風險提醒,比如:準備提測階段、已經(jīng)上線完成,都可以做相應的監(jiān)測處理。
而 Sonar 就是一個這樣都工具,它是一個Web系統(tǒng),可以展現(xiàn)靜態(tài)代碼掃描的結(jié)果,結(jié)果是可以自定義的,支持多種語言的原理是它的擴展性。https://www.sonarqube.org/
- 不遵循代碼標準:sonar可以通過PMD,CheckStyle,Findbugs等等代碼規(guī)則檢測工具規(guī)范代碼編寫。潛在的缺陷:sonar可以通過PMD,CheckStyle,Findbugs等等代碼規(guī)則檢測工具檢 測出潛在的缺陷。糟糕的復雜度分布:文件、類、方法等,如果復雜度過高將難以改變,這會使得開發(fā)人員 難以理解它們, 且如果沒有自動化的單元測試,對于程序中的任何組件的改變都將可能導致需要全面的回歸測試。重復:顯然程序中包含大量復制粘貼的代碼是質(zhì)量低下的,sonar可以展示 源碼中重復嚴重的地方。注釋不足或者過多:沒有注釋將使代碼可讀性變差,特別是當不可避免地出現(xiàn)人員變動 時,程序的可讀性將大幅下降 而過多的注釋又會使得開發(fā)人員將精力過多地花費在閱讀注釋上,亦違背初衷。缺乏單元測試:sonar可以很方便地統(tǒng)計并展示單元測試覆蓋率。糟糕的設計:通過sonar可以找出循環(huán),展示包與包、類與類之間的相互依賴關系,可以檢測自定義的架構(gòu)規(guī)則 通過sonar可以管理第三方的jar包,可以利用LCOM4檢測單個任務規(guī)則的應用情況, 檢測耦合。提高代碼質(zhì)量:了解自己在編碼過程中犯過的錯誤,讓自己的代碼更具有可讀性和維護性。
六、總結(jié)
- PMD 是一款采用 BSD 協(xié)議的代碼檢查工具,你可以擴展實現(xiàn)為自己的標準和規(guī)范以及完善個性的提醒和修復操作。另外基于 IDEA 插件實現(xiàn)的代碼檢查或者有審計要求的處理,也可以基于 IDEA 插件做更多的擴展,比如提醒修復、提供修復操作、自身業(yè)務邏輯的檢查。例如momo開源庫下的一款IDEA靜態(tài)代碼安全審計及漏洞一鍵修復插件 https://github.com/momosecurity/momo-code-sec-inspector-java這里補充一點,kotlin 語言可以在 IDEA 中轉(zhuǎn)換為 Java 語言,這樣你在閱讀類似這樣的代碼時候,如果不好看懂也可以轉(zhuǎn)換一下在閱讀。此外 IDEA 插件開發(fā)需要基于 Gradle 或者本身提供都模版進行創(chuàng)建,如果感興趣也可以閱讀我寫的 IDEA 插件開發(fā)文章。
七、系列推薦