一、前言
你會(huì)對(duì)你用到都技術(shù),好奇嗎?
雖然我們都被稱為碼農(nóng),也都是寫著代碼,但因?yàn)樗巿?chǎng)景需求的不同,所以各類碼農(nóng)也都做著不一樣都事情。
有些人統(tǒng)一規(guī)范、有些人開發(fā)組件、有些人編寫業(yè)務(wù)、有些人倒騰驗(yàn)證,但越是工作內(nèi)容簡(jiǎn)單如CRUD一樣的碼農(nóng),用到別人提供好的東西卻是越多。一會(huì)安裝個(gè)插件、一會(huì)引入個(gè)Jar包、一會(huì)調(diào)別人個(gè)接口,而自己的工作就像是裝配工,東拼拼西湊湊,就把產(chǎn)品需求寫完了。
壞了,這么干可能幾年下來(lái),也不會(huì)有什么技術(shù)上都突破。因?yàn)槟銓?duì)那些使用都技術(shù)不好奇,不想知道它們是怎么實(shí)現(xiàn)的。就像阿里的P3C插件,是怎么檢查代碼分析出來(lái)我寫的拉胯的呢?
二、P3C 插件是什么
P3C 是阿里開源代碼庫(kù)的插件工程名稱,它以阿里巴巴Java開發(fā)手冊(cè)為標(biāo)準(zhǔn),用于監(jiān)測(cè)代碼質(zhì)量的 IDEA/Eclipse 插件。
- 源碼:https://github.com/alibaba/p3c
插件安裝完成后,就可以按照編程規(guī)約,靜態(tài)分析代碼中出現(xiàn)的代碼:命名風(fēng)格、常量定義、集合處理、并發(fā)處理、OOP、控制語(yǔ)句、注釋、異常等各項(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)都代碼,那么非常建議你按照這樣都插件來(lái)輔助自己做代碼開發(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)建、部署、測(cè)試、調(diào)試、分析,進(jìn)而找到核心原理。
P3C 以 IDEA 插件開發(fā)為例,主要涉及到插件部分和規(guī)約部分,因?yàn)槭前岩?guī)約檢查的能力與插件技術(shù)結(jié)合,所以會(huì)涉及到一些 IDEA 開發(fā)的技術(shù)。另外 P3C 插件涉及到都技術(shù)語(yǔ)言不只是 Java 還有一部分 kotlin 它是一種在 Java 虛擬機(jī)上運(yùn)行的靜態(tài)類型編程語(yǔ)言。
- 插件源碼: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ī)約掃描
通過(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 語(yǔ)言開發(fā)的插件代碼邏輯,它通過(guò) actionPerformed 方法獲取到工程信息、類信息等,接下來(lái)就可以執(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è)語(yǔ)法分析器,用來(lái)分析普通文本形式的Java代碼,產(chǎn)生符合特定語(yǔ)法結(jié)構(gòu)的語(yǔ)法,同時(shí)又在JavaCC的基礎(chǔ)上添加了語(yǔ)義的概念即JJTree,通過(guò)JJTree的一次轉(zhuǎn)換,這樣就將Java代碼轉(zhuǎn)換成了一個(gè)AST,AST是Java符號(hào)流之上的語(yǔ)義層,PMD把AST處理成一個(gè)符號(hào)表。然后編寫PMD規(guī)則,一個(gè)PMD規(guī)則可以看成是一個(gè)Visitor,通過(guò)遍歷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)的,更具體的來(lái)說(shuō)是基于 pmd-java 的,因?yàn)?PMD 不僅支持 Java 代碼分析,還支持其他多種語(yǔ)言。具體自定義規(guī)則的方式,通過(guò)自定義Java類和XPATH規(guī)則實(shí)現(xiàn)。
四、規(guī)約監(jiān)測(cè)案例
講道理,說(shuō)一千道一萬(wàn),還得是拿出代碼跑一下,才知道 PMD 具體是什么個(gè)樣子。
1. 測(cè)試工程
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 的測(cè)試工程,通過(guò)自行擴(kuò)展重寫代碼監(jiān)測(cè)規(guī)約的方式,來(lái)處理自己關(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);
}
}
- 通過(guò)繼承 PMD 提供的 AbstractJavaRule 抽象類,重寫 visit 方法,使用正則的方式進(jìn)行驗(yàn)證。visit 方法都入?yún)㈩愋头浅6啵謩e用于處理類、接口、方法、代碼等各項(xiàng)內(nèi)容的監(jiān)測(cè)處理,只要重寫需要的方法,在里面進(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. 測(cè)試驗(yàn)證規(guī)約
問題類示例
public class TErrDto {
public static final Long max = 50000L;
public void QueryUserInfo(){
boolean baz = true;
while (baz)
baz = false;
}
}
單元測(cè)試
@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ī)約的測(cè)試驗(yàn)證可以直接使用 PMD.main 方法,在方法中提供字符串?dāng)?shù)組入?yún)?,這里的代碼監(jiān)測(cè)地址和規(guī)約配置需要是絕對(duì)路徑。
測(cè)試結(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
- 從測(cè)試結(jié)果可以看到,我們寫的三個(gè)代碼規(guī)約分別監(jiān)測(cè)出了代碼的命名風(fēng)格、常量大寫、方法名不符合駝峰標(biāo)識(shí)。同時(shí)你還可以測(cè)試
category/java/codestyle.xml
這個(gè)是 PMD 自身提供好的規(guī)約監(jiān)測(cè)。
五、擴(kuò)展了解 Sonar
其實(shí)有了 PMD 靜態(tài)代碼檢查規(guī)約,能做都事情就很多,不是只對(duì)正在寫的代碼進(jìn)行檢查,還可以對(duì)不同階段的代碼進(jìn)行分析和風(fēng)險(xiǎn)提醒,比如:準(zhǔn)備提測(cè)階段、已經(jīng)上線完成,都可以做相應(yīng)的監(jiān)測(cè)處理。
而 Sonar 就是一個(gè)這樣都工具,它是一個(gè)Web系統(tǒng),可以展現(xiàn)靜態(tài)代碼掃描的結(jié)果,結(jié)果是可以自定義的,支持多種語(yǔ)言的原理是它的擴(kuò)展性。https://www.sonarqube.org/
- 不遵循代碼標(biāo)準(zhǔn):sonar可以通過(guò)PMD,CheckStyle,Findbugs等等代碼規(guī)則檢測(cè)工具規(guī)范代碼編寫。潛在的缺陷:sonar可以通過(guò)PMD,CheckStyle,Findbugs等等代碼規(guī)則檢測(cè)工具檢 測(cè)出潛在的缺陷。糟糕的復(fù)雜度分布:文件、類、方法等,如果復(fù)雜度過(guò)高將難以改變,這會(huì)使得開發(fā)人員 難以理解它們, 且如果沒有自動(dòng)化的單元測(cè)試,對(duì)于程序中的任何組件的改變都將可能導(dǎo)致需要全面的回歸測(cè)試。重復(fù):顯然程序中包含大量復(fù)制粘貼的代碼是質(zhì)量低下的,sonar可以展示 源碼中重復(fù)嚴(yán)重的地方。注釋不足或者過(guò)多:沒有注釋將使代碼可讀性變差,特別是當(dāng)不可避免地出現(xiàn)人員變動(dòng) 時(shí),程序的可讀性將大幅下降 而過(guò)多的注釋又會(huì)使得開發(fā)人員將精力過(guò)多地花費(fèi)在閱讀注釋上,亦違背初衷。缺乏單元測(cè)試:sonar可以很方便地統(tǒng)計(jì)并展示單元測(cè)試覆蓋率。糟糕的設(shè)計(jì):通過(guò)sonar可以找出循環(huán),展示包與包、類與類之間的相互依賴關(guān)系,可以檢測(cè)自定義的架構(gòu)規(guī)則 通過(guò)sonar可以管理第三方的jar包,可以利用LCOM4檢測(cè)單個(gè)任務(wù)規(guī)則的應(yīng)用情況, 檢測(cè)耦合。提高代碼質(zhì)量:了解自己在編碼過(guò)程中犯過(guò)的錯(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開源庫(kù)下的一款I(lǐng)DEA靜態(tài)代碼安全審計(jì)及漏洞一鍵修復(fù)插件 https://github.com/momosecurity/momo-code-sec-inspector-java這里補(bǔ)充一點(diǎn),kotlin 語(yǔ)言可以在 IDEA 中轉(zhuǎn)換為 Java 語(yǔ)言,這樣你在閱讀類似這樣的代碼時(shí)候,如果不好看懂也可以轉(zhuǎn)換一下在閱讀。此外 IDEA 插件開發(fā)需要基于 Gradle 或者本身提供都模版進(jìn)行創(chuàng)建,如果感興趣也可以閱讀我寫的 IDEA 插件開發(fā)文章。
七、系列推薦