1.前言 工作中難免會遇到維護別人代碼的情況,那麼首先就得看懂別人寫的代碼。如果對方寫的代碼混亂臃腫,維護成本必然很高,如果對方寫的代碼優雅清晰,那維護的人看起來必然心情愉悅。正所謂“前人栽樹,後人乘涼;前人埋坑,後人罵娘”。 代碼首先是給人看的,其次才是給機器看到,如何編寫出任何人都看到懂的代碼? ...
1.前言
工作中難免會遇到維護別人代碼的情況,那麼首先就得看懂別人寫的代碼。如果對方寫的代碼混亂臃腫,維護成本必然很高,如果對方寫的代碼優雅清晰,那維護的人看起來必然心情愉悅。正所謂“前人栽樹,後人乘涼;前人埋坑,後人罵娘”。
代碼首先是給人看的,其次才是給機器看到,如何編寫出任何人都看到懂的代碼?答案是制定規範!
每個公司都會有自己的編碼規範,但是往往的情況是趕項目進度或者懶惰或者個人水平習慣等原因,加上沒有code review,最後代碼就寫的千奇百怪了。原因就在於規範是有了,但是沒人遵守。所以,編碼規範需要強制執行,交給工具來強制執行。
本文將通過介紹java靜態代碼檢查工具PMD、阿裡巴巴p3c開源項目到最後編寫自定義編碼規約來學習如何規範代碼的編寫。
2.PMD靜態代碼掃描
2.1.PMD官網
2.2.概述
PMD是一種開源分析Java代碼錯誤的工具。它通過靜態分析獲知代碼錯誤。也就是說,在不運行Java程式的情況下報告錯誤。PMD附帶了許多可以直接使用的規則,利用這些規則可以找出Java源程式的許多問題,例如:
- 潛在的bug:空的try/catch/finally/switch語句
- 未使用的代碼:未使用的局部變數、參數、私有方法等
- 可選的代碼:String/StringBuffer的濫用
- 複雜的表達式:不必須的if語句、可以使用while迴圈完成的for迴圈
- 重覆的代碼:拷貝/粘貼代碼意味著拷貝/粘貼bugs
- 迴圈體創建新對象:儘量不要再for或while迴圈體內實例化一個新對象
- 資源關閉:Connect,Result,Statement等使用之後確保關閉掉
此外,用戶還可以自己定義規則,檢查Java代碼是否符合某些特定的編碼規範。例如,你可以編寫一個規則,要求PMD找出所有創建Thread和Socket對象的操作。
2.3.工作原理
PMD的核心是JavaCC解析器生成器。PMD結合運用JavaCC和EBNF(擴展巴科斯-諾爾範式,Extended Backus-Naur Formal)語法,再加上JJTree,把Java源代碼解析成抽象語法樹(AST,Abstract Syntax Tree)
從根本上看,Java源代碼只是一些普通的文本。不過,為了讓解析器承認 這些普通的文本是合法的Java代碼,它們必須符合某種特定的結構要求。這種結構可以用一種稱為EBNF的句法元語言表示,通常稱為“語法” (Grammar)。JavaCC根據語法要求生成解析器,這個解析器就可以用於解析用Java編程語言編寫的程式。
2.4.規則分類
- 最佳實踐:公認的最佳實踐的規則。
- 代碼風格:這些規則強制執行特定的編碼風格。
- 設計:幫助您發現設計問題的規則。
- 文檔:這些規則與代碼文檔有關。
- 容易出錯的規則:用於檢測被破壞的、非常混亂的或容易發生運行時錯誤的結構的規則。
- 多線程:這些規則在處理多個執行線程時標記問題。
- 性能:標記存在性能問題的代碼的規則。
- 安全:顯示潛在安全缺陷的規則。
2.5.編寫PMD自定義規則
- https://pmd.github.io/pmd-5.4.1/customizing/howtowritearule.html
- https://testerhome.com/topics/4918
- http://www.w3school.com.cn/xpath/index.asp
3.阿裡巴巴Java開發規約插件p3c
3.1.GITHUB地址
3.2.概述
阿裡巴巴p3c項目包含三個部分:
- p3c-pmd,提供大部分規則實現,基於PMD框架開發,如果想實現自己的規則,可以基於該模塊開發(該模塊基於maven編譯打包)
- IntelliJ IDEA插件,即idea-plugin模塊(該模塊基於gradle編譯打包)
- Eclipse插件,即eclipse-plugin,本文不介紹
3.3.阿裡編碼規約IDEA插件使用
4.基於p3c編寫自定義編碼規則
4.1.自定義規則
假設現在需要開發這麼一個規則:方法請求參數列表不允許超過(含)5個
4.2.開發步驟
4.2.1.找出問題代碼,使用pmd圖形化工具解析成抽象語法樹
代碼示例:
package org.p3c.demo;
public class Demo {
public void methodA(int a) {
}
public void methodB(int a, int b, int c, int d, int e) {
}
}
將源碼放入Source Code框,點擊Go按鈕,解析結果顯示在左下框
4.2.2.分析抽象語法樹
可以看到,整棵樹的根節點是CompilationUnit,即編譯單元,代表每個java源文件。我們首先要找到所有的方法聲明,根據樹節點名稱大概也能看出來是MethodDeclaration,點擊相應的節點,看看游標是否定位到源碼方法聲明位置。
仔細分析MethodDeclaration節點,可以看到他有以下幾個直接子節點:ResultType、MethodDeclarator、Block,即返回類型、方法聲明、方法體
MethodDeclarator是我們想找的節點XPATH表達式可以這麼寫:
//CompilationUnit//MethodDeclarator
驗證表達式是否正確,將它寫到PMD圖形界面XPATH Query框中,點擊Go按鈕
接下來,我們需要找到每個方法對應的參數列表,參數列表節點是方法節點的直接子節點,完整XPATH表達式為:
//CompilationUnit//MethodDeclarator/FormalParameters
獲取到參數列表節點後,我們查看該節點的屬性,找出參數個數的屬性,觀察可以發現是ParameterCount屬性。
到現在為止,抽象語法樹已經分析完,我們知道這麼找出代碼中參數列表大於等於5個的方法了。
4.2.3.p3c-pmd項目編寫自定義代碼規則
打開阿裡p3c-pmd工程,開始編寫我們的自定義規則。
阿裡已經寫了很多規則,我們現在要編寫的規則屬於面向對象範疇,可以把規則寫到opp包下,新建一個規則類MethodParameterCountRule,繼承自AbstractAliRule,重寫 visit方法:
package com.alibaba.p3c.pmd.lang.java.rule.oop;
import com.alibaba.p3c.pmd.lang.java.rule.AbstractAliRule;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
import java.util.List;
/**
* 方法參數列表個數不宜過長
*
* @auther qingjian.wu
* @create 2018-01-27 14:59
*/
public class MethodParameterCountRule extends AbstractAliRule{
private static final String METHOD_XPATH = "//MethodDeclarator";
private static final Integer PARAMETER_COUNT_LIMIT = 5;
@Override
public Object visit(ASTCompilationUnit node, Object data) {
try {
// 找到所方法節點
List<Node> methodNodes = node.findChildNodesWithXPath(METHOD_XPATH);
if (methodNodes != null && methodNodes.size() > 0) {
for (Node methodNode : methodNodes) {
// 找到每個方法的參數列表聲明
List<ASTFormalParameters> formalParameters = methodNode.findChildrenOfType(ASTFormalParameters.class);
if (formalParameters.get(0).getParameterCount() >= PARAMETER_COUNT_LIMIT) {
// 違反規則提示信息,第二個參數是提示信息位置,第三個參數是提示信息key,第四個參數用來替換提示信息
// 中的占位符,這裡獲取的節點image屬性就是方法名稱
addViolationWithMessage(data, methodNode,
"java.oop.MethodParameterCountRule.violation.msg",
new Object[]{methodNode.getImage()});
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return super.visit(node, data);
}
}
4.2.4.p3c-pmd項目配置規則
將編寫好規則配置到ali-oop.xml文件中
<rule name="MethodParameterCountRule"
language="java"
message="java.oop.MethodParameterCountRule.rule.msg"
class="com.alibaba.p3c.pmd.lang.java.rule.oop.MethodParameterCountRule">
<!--級別,1強制,2推薦,3參考-->
<priority>1</priority>
<example>
<![CDATA[
Negative example:
public void methodB(int a, int b, int c, int d, int e) {
}
]]>
</example>
<example>
<![CDATA[
Positive example:
public void methodA() {
}
]]>
</example>
</rule>
4.2.5.p3c-pmd項目編寫提示信息
上兩步使用的提示信息和規則信息需要編寫到message.xml配置文件中,message_en.xml中是英文提示,這裡就先不演示了
<entry key="java.oop.MethodParameterCountRule.violation.msg">
<![CDATA[方法【%s】參數列表過長。 ]]>
</entry>
<entry key="java.oop.MethodParameterCountRule.rule.msg">
<![CDATA[說明:方法參數列表不允許超過(含)5個,建議封裝到一個對象中。]]>
</entry>
4.2.6.單元測試
編寫測試樣例,將要測試的源代碼寫到test目錄對應的xml文件中
推薦一個 Spring Boot 基礎教程及實戰示例:
https://github.com/javastacks/spring-boot-best-practice
<?xml version="1.0" encoding="UTF-8"?>
<test-data>
<code-fragment id="測試樣例">
<![CDATA[
package org.p3c.demo;
public class Demo {
public void methodA(int a) {
}
public void methodB(int a, int b, int c, int d, int e) {
}
}
]]>
</code-fragment>
<test-code>
<!-- 預期問題個數 -->
<expected-problems>0</expected-problems>
<code-ref id="測試樣例" />
</test-code>
</test-data>
編寫單元測試
運行單元測試,因為樣例代碼中methodB不符合規範,但是我們預期問題個數寫的是0,所以單元測試會不通過:
4.3.配置插件
4.3.1.p3c-pmd打包安裝到本地maven倉庫
先把用不到的插件maven-javadoc-plugin和maven-gpg-plugin註釋掉,然後運行mvn命令:
mvn -DskipTests=true clean install
4.3.2.idea-plugin項目打包插件
idea-plugin項目基於gradle構建,配置根目錄下build.gradle,讓構建使用本地私有maven倉庫構建
然後運行開始gradle構建:
clean buildDependents build
打包成功後會在idea-plugin\p3c-idea\build\distributions\
目錄下生成Alibaba Java Coding Guidelines-1.0.0.zip文件,這個就是我們加入了自己拓展阿裡開發規約的插件,IDEA中安裝此插件
Settings->Plugins->Install plugin from disk
4.3.3.IDEA中使用編碼規約插件
安裝完插件重啟IDEA,用之前的代碼測試下插件是否生效。
右鍵點擊“編碼規約掃描”
結果:
5.Maven打包加入PMD校驗
到目前為止,我們已經做到了能在開發階段實時校驗自己的代碼了,最後我們需要把規約檢查加入到代碼打包中,這樣才能做到部署到生產環境的代碼都是符合規範的,如果不符合規範,打包會失敗。
考慮到大多數項目使用maven管理,可以把自定義pmd規則整合到maven,這樣就可以使用maven校驗代碼規則了
在maven項目中加入pmd插件,配置插件在package階段執行。通常我們的項目都有一個公共的父pom,那將插件加入到父pom中就行
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.8</version>
<configuration>
<rulesets>
<ruleset>rulesets/java/ali-comment.xml</ruleset>
<ruleset>rulesets/java/ali-concurrent.xml</ruleset>
<ruleset>rulesets/java/ali-constant.xml</ruleset>
<ruleset>rulesets/java/ali-exception.xml</ruleset>
<ruleset>rulesets/java/ali-flowcontrol.xml</ruleset>
<ruleset>rulesets/java/ali-naming.xml</ruleset>
<ruleset>rulesets/java/ali-oop.xml</ruleset>
<ruleset>rulesets/java/ali-orm.xml</ruleset>
<ruleset>rulesets/java/ali-other.xml</ruleset>
<ruleset>rulesets/java/ali-set.xml</ruleset>
</rulesets>
<printFailingErrors>true</printFailingErrors>
<!--掃描級別,小於等於這個級別的錯誤代碼將不通過掃描。不配預設是5-->
<minimumPriority>1</minimumPriority>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.alibaba.p3c</groupId>
<artifactId>p3c-pmd</artifactId>
<version>1.3.3</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
如果存在不符合規範代碼打包將失敗:
關於maven pmd插件更詳細介紹參考官網
6.總結
本文篇幅確實有點長,看懂需要有點耐心。不過其實也挺簡單,關鍵點就是分析抽象語法樹,找出問題代碼節點,剩下的工作就很簡單了。
PMD也有局限性,比如只能校驗java源文件,對於XML等配置規約就沒轍了。還有最最重要的,代碼邏輯等關鍵性問題是沒法校驗的,也沒法做。PMD只是一定程度上規範了我們的代碼,要寫出優雅的代碼,還得多思考多實踐吶。
來源:blog.csdn.net/u014513883/article/details/79186893
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
覺得不錯,別忘了隨手點贊+轉發哦!