静态分析工具PMD使用说明
靜態分析工具PMD使用說明
目錄
1. 編寫目的
質量是衡量一個軟件是否成功的關鍵要素。而對于商業軟件系統,尤其是企業應用軟件系統來說,除了軟件運行質量、文檔質量以外,代碼的質量也是非常重要的。軟件開發進行到編碼階段的時候,最大的風險就在于如何保證代碼的易讀性和一致性,從而使得軟件的維護的代價不會很高。
在軟件開發的過程中,以下幾種情形隨處可見:
1) 軟件維護時間長,而且維護人員的積極性不高:
做過軟件維護的開發人員,尤其是在接手不是自己開發產品的源碼的時候,即使有良好的文檔說明,仍然會對代碼中冗長、沒有注釋的段落“嘆為觀止”。理解尚且如此困難,何況要修改或者增加新的功能。因此,很多開發人員不愿意進行軟件維護的工作。
2)新的開發人員融入團隊的時間比較長:
除了沒有良好的培訓、文檔等有效的機制以外,每個人一套的編碼風格,也容易造成新成員對于已有代碼的理解不夠,甚至出現偏差。
提高代碼的質量,除了要提高邏輯上的控制以及業務流程的理解外,代碼本身也存在提高的空間,例如一些潛在的問題可以很早的就避免。類似于編碼規范上的內容,如果全靠編碼人員進行自行檢查,那么無疑需要很大的工作量,如果可以使用代碼的靜態檢查工具進行檢查的話,那么將大大的提高編碼的效率。
項目組目前代碼檢查的工作基本上都是通過人工的方式,實行起來比較困難,檢查的效果也不是很明顯。PMD正是這樣一種工具,可以直接使用它自帶的規則(當然也可以使用自己的規則)對Java源程序進行分析找出程序存在的問題,可以很大程度上的減輕代碼檢查工作的繁瑣,為項目組今后的維護和開發工作起到指導的作用。
本文主要介紹了如何使用pmd工具進行代碼的自動化檢查,以規避一些潛在的問題并找出代碼的邏輯錯誤。
2. PMD簡介
PMD是一種開源分析Java代碼錯誤的工具。與其他分析工具不同的是,PMD通過靜態分析獲知代碼錯誤。也就是說,在不運行Java程序的情況下報告錯誤。PMD附帶了許多可以直接使用的規則,利用這些規則可以找出Java源程序的許多問題,例如:
® 潛在的bug:空的try/catch/finally/switch語句
® 未使用的代碼:未使用的局部變量、參數、私有方法等
® 可選的代碼:String/StringBuffer的濫用
® 復雜的表達式:不必須的if語句、可以使用while循環完成的for循環
® 重復的代碼:拷貝/粘貼代碼意味著拷貝/粘貼bugs
® 循環體創建新對象:盡量不要再for或while循環體內實例化一個新對象
@ 資源關閉:Connect,Result,Statement等使用之后確保關閉掉
此外,用戶還可以自己定義規則,檢查Java代碼是否符合某些特定的編碼規范。例如,你可以編寫一個規則,要求PMD找出所有創建Thread和Socket對象的操作。
3. 工作原理
PMD的核心是JavaCC解析器生成器。PMD結合運用JavaCC和EBNF(擴展巴科斯-諾爾范式,Extended Backus-Naur Formal)語法,再加上JJTree,把Java源代碼解析成抽象語法樹(AST,Abstract Syntax Tree)。顯然,這句話不那么好懂,且看下文具體說明。
從根本上看,Java源代碼只是一些普通的文本。不過,為了讓解析器承認這些普通的文本是合法的Java代碼,它們必須符合某種特定的結構要求。這種結構可以用一種稱為EBNF的句法元語言表示,通常稱為“語法”(Grammar)。JavaCC根據語法要求生成解析器,這個解析器就可以用于解析用Java編程語言編寫的程序。
不過實際運行中的PMD還要經過JJTree的一次轉換。JJTree是一個JavaCC的插件,通過AST擴充JavaCC生成的解析器。AST是一個Java符號流之上的語義層。有了JJTree,語法分析的結果不再是“System, ., out, ., . println”之類的符號序列,而是一個由對象構成的樹型層次結構。例如,下面是一段簡單的Java代碼以及與之對應的AST。
Java源代碼:
public class Foo {
public void bar() {
System.out.println("hello world");
}
}
對應的抽象語法樹
CompilationUnit
TypeDeclaration
ClassDeclaration
UnmodifiedClassDeclaration
ClassBody
ClassBodyDeclaration
MethodDeclaration
ResultType
MethodDeclarator
FormalParameters
Block
BlockStatement
Statement
StatementEXPression
PrimaryExpression
PrimaryPrefix
Name
PrimarySuffix
Arguments
ArgumentList
Expression
PrimaryExpression
PrimaryPrefix
Literal
4. PMD的安裝和運行
4.1安裝并從命令行運行PMD
你可以從PMD的網站下載PMD的二進制版本,或下載帶源代碼的版本,下載得到的都是ZIP文件。假設你下載了二進制版本,先把它解壓縮到任意一個目錄。接下來怎么做,就要看你準備怎么用它——最簡單的,如果要在一個Java源代碼目錄中運行PMD,只需直接在命令行上運行下面的命令:
E:\SoftWare\pmd-bin-4.2.1\pmd-4.2.1\bin>java -jar ..\lib\pmd-4.2.1.jar D:\ebsser
vice\ebsservice\src text rulesets/unusedcode.xml
輸出結果類如:
D:\ebsservice\ebsservice\src\com\sinosoft\service\policy\ebs\SMPolicyInput.java:
51 Avoid unused private fields such as 'logger'.
D:\ebsservice\ebsservice\src\com\sinosoft\service\policy\ebs\SMPolicyShow.java:2
5 Avoid unused private fields such as 'logger'.
D:\ebsservice\ebsservice\src\com\sinosoft\service\policy\ebs\SMQueryPolicyByPoli
cyNo.java:32 Avoid unused local variables such as 'visaStatus'.
D:\ebsservice\ebsservice\src\com\sinosoft\service\policy\ebs\SMQueryPolicyByPoli
cyNo.java:44 Avoid unused local variables such as 'temp'.
D:\ebsservice\ebsservice\src\com\sinosoft\service\policy\ebs\erisk\ESMPolicyInpu
t.java:28 Avoid unused private fields such as 'logger'.
D:\ebsservice\ebsservice\src\com\sinosoft\service\policy\ebs\jrisk\JSMPolicyInpu
t.java:22 Avoid unused private fields such as 'logger'.
一些可以加載必須參數前面或者后面的可選參數如下:
-debug: 打印debug日志信息
-targetjdk: 指定目標源代碼的版本- 1.3, 1.4, 1.5, 1.6 or 1.7;
默認是1.5
-cpus: 指定創建的線程數
-encoding: 指定PMD檢查的代碼的編碼方式
-excludemarker: 指定PMD需要忽略的行的標記,默認為NOPMD
-shortnames: 在報告中顯示縮短的文件名
-linkprefix: HTML源文件的路徑,只是為了HTML顯示
-lineprefix: 自定義的錨,用于影響源文件中的行,只是用于HTML顯示
-minimumpriority: 規則的優先級限制,低于優先級的規則將不被使用
-nojava: 不檢查java文件,默認是檢查java文件
-jsp: 檢查JSP/JSF文件,默認不檢查
-reportfile: 將報告輸出到文件,默認是打印在控制臺
-benchmark: 輸出一個基準清單,默認輸出到控制臺
-xslt: 覆蓋默認的xslt
-auxclasspath: 指定源代碼文件使用的類路徑
例如在windows系統中,例子如下:
c:\> java -jar pmd-4.2.1.jar c:\my\source\code text unusedcode,imports -targetjd
k 1.5 -debug
c:\> java -jar pmd-4.2.1.jar c:\my\source\code xml basic,design -encoding UTF-8
c:\> java -jar pmd-4.2.1.jar c:\my\source\code html typeresolution -auxclasspath
commons-collections.jar;derby.jar
4.2在Eclipse中安裝PMD插件運行方式
PMD可以作為插件集成到很多流行的IDE中,很多的插件中都包含了PMD的jar文件,這個jar文件中包含了規則集。所以雖然一些插件中使用rulesets/unusedcode.xml來作為參數引用規則集,但是實際上是使用getResourceAsStream()方法來從PMD的jar文件中加載。
由于Eclipse是比較流行的開源Java/J2EE開發IDE,所以本文主要介紹如何在Eclipse中使用PMD工具進行代碼的檢查。
4.2.1 安裝基于Eclipse IDE的插件
安裝Eclipse的PMD插件的過程如下:
® 啟動Eclipse
® 選擇Help-->Software Updates-->Find and Install
® 選擇Next,選擇New remote site
® 在Name框中輸入PMD,URL框中輸入http://pmd.sf.net/eclipse
® 在之后的對話框中一直點擊下一步或者接受協議,完成Eclipse的PMD插件的安裝
也可以通過下載最新的zip文件按,然后執行上述過程,只是使用New locale site來代替New remote site,并使用下載的zip文件。
可以通過Windows-->Preferences來配置PMD。
通過右鍵一個項目,然后選擇PMD-->Check node with PMD,即可使用PMD工具檢查代碼。如果要進行重復代碼檢測,那么右鍵一個項目后,選擇PMD-->Find suspect cut and paste。檢查結果會放在reports目錄下,文件名為cpd-report.txt。
可以通過使用Eclipse的幫助系統來查看PMD插件的文檔。
在安裝完更新后,如果發生了一個異常,例如”java.lang.RuntimeException: Could not find that class xxxx”,這時試著刪除workspace中的.metadata/plugins/net.sourceforge.pmd.eclipse目錄下的ruleset.xml文件。
4.2.2 使用PMD
1、啟動Eclipse IDE,打開工程,選擇 "Windows"->"Preferences"下的PMD項,其中Rules Configuration 項目可以配置PMD的檢查規則,自定義檢查規則也可以在此通過Import的方式導入到PMD中
2、配置好后,鼠標右鍵點擊工程中需要檢查的JavaSource,選擇"PMD"->"Check Code With PMD" ,之后PMD就會通過規則檢查你的JavaSource了并且將信息顯示在PMD自己的視圖上
3、示例
import java.util.*;
public class Test {
public static void main(String[] args) {
try{
if(true) {}
System.out.println("Hello World!");
} catch(Exception e) {
}
}
}
以上代碼PMD會檢查出:catch塊中沒有內容、if判斷塊中沒有內容、代碼中出現System.out.println等警告描述
4.3 使用Ant進行調用
下面是主要的Ant配置信息
<path id="pmd.path">
<fileset dir="${lib.dir}/pmd-3.8">
<include name="**/*.jar" />
</fileset>
</path>
<taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpathref="pmd.path"/>
<taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" classpathref="pmd.path"/>
<target name="pmd">
<pmd shortFilenames="true">
<ruleset>rulesets/favorites.xml</ruleset>
<formatter type="html" toFile="d:\foo.html" toConsole="false"/>
<fileset dir="${src.dir}">
<include name="**/*.java"/>
</fileset>
</pmd>
</target>
<target name="cpd">
<cpd minimumTokenCount="100" outputFile="d:/cpd.txt">
<fileset dir="${src.dir}">
<include name="**/*.java"/>
</fileset>
</cpd>
</target>
用Ant命令運行build.xml,PMD就會按照你設定好的規則自動執行代碼檢查了。
5. 關于PMD規則
選擇合適的規則
運行所有的規則集中的規則會產生非常多的沖突,這些沖突中的很多是不重要的。在這么多的沖突中尋找你關心的部分結果就沒有什么效率可言了。
所以需要從明顯的規則集,也就是說必須要改的地方開始是比較好的一個選擇,例如只是運行unusedcode檢查,然后修改沒有使用的局部變量和成員變量。然后運行基本的檢查,修改所有的空語句,例如if語句等。最后可以執行與設計相關的或者存在一定爭議的規則集,或者自定義的規則集。
自帶規則的介紹: (PMD插件分析代碼規則(中文).xls)
PMD 自帶了很多規則集合,并且分類寫入不同的 ruleset 文件,如
Basic 包含每人都必須遵守的代碼最佳實踐,如EmptyCatchBlock
Braces 關于條件分支的規則,如IfStmtsMustUseBraces
Code Size 關于代碼大小的規則,如方法的長度,參數的長度,屬性的個數等
Clone 克隆實現的規則,如是否有super.clone()
Controversial 一些有爭議的規則,如UnnecessaryConstructor不必要的構造器
Coupling 對象連接有關的規則
Design 可以檢查有問題的設計,如SwitchStmtsShouldHaveDefault
Finalizers 使用finalizers時需遵循的規則,如FinalizeOnlyCallsSuperFinalize
Import Statements 和import有關的規則,如DuplicateImports重復import
J2EE 唯一規則UseProperClassLoader,class.getClassLoader()可能不正確,用
Thread.currentThread().getContextClassLoader() 代替
Javabeans 和javabean規范有關的規則,有BeanMembersShouldSerialize屬性必須
序列化和MissingSerialVersionUID缺少序列化ID
JUnit Tests 和JUnit測試有關的,如JUnitSpelling拼寫檢查等
Logging (Java) 檢查Logger的一些錯誤用法,如MoreThanOneLogger多個Logger
Logging (Jakarta) 使用Jakarta Logger的一些規則,有UseCorrectExceptionLogging
異常處理不當和ProperLogger是否正確定義Logger
Migrating JDK 版本移植的規則,如ReplaceVectorWithList用List代替Vector
Naming 和命名有關的規則,名稱太短或太長,命名的約定等
Optimizations 優化性能的一些規則,如LocalVariableCouldBeFinal本地變量如果
只賦值一次,則應該聲明為final
Strict Exceptions 比較嚴格的異常處理方針,如AvoidCatchingThrowable
Strings 使用String和StringBuffer時應遵守的規則,如StringToString
Sun Security 編寫安全的代碼,有MethodReturnsInternalArray直接返回內部的數組,
更安全的做法是返回一個拷貝和ArrayIsStoredDirectly
Unused Code 檢查未使用的代碼,如UnusedPrivateField未使用的私有屬性
Java Server Pages 編寫jsp的一些方針,如NoLongScripts
Java Server Faces 編寫jsf的一些方針,有DontNestJsfInJstlIteration,在Jsf
里使用jstl的標簽
自定義規則:
有兩個辦法來自定義規則,可以編寫java類和編寫XPath,編寫java類的一般步驟是,先確定要查找的代碼形式,利用PMD自帶的designer.bat工具查看AST(抽象語法樹),然后編寫規則類(繼承net.sourceforge.pmd.AbstractRule),然后編寫一個ruleset的XML文件,最后就可以運行PMD進行檢查。編寫XPath比編寫java類要容易些,但也需要掌握AST的含義,利用designer.bat工具可以查看AST,比如 //ClassBody [count(//VariableDeclarator[../Type/Name[@Image='Logger']])>1] ,這個表達式就是查找類的代碼里是否聲明了多個 Logger ,然后編寫一個 ruleset 的 XML 文件,最后運行 PMD 進行檢查。這里是一個 ruleset 的 XML 文件的例子。
自定義規則集合:
PMD 自帶了很多代碼規范的規則,還可以自定義規則,我們可以把這些規則整合到一起,按照我們的需求進行代碼檢查。
<!-- 使用整個strings規則集 -->
<rule ref="rulesets/strings.xml"/>
<!-- 使用某個規則集里的某個規則 -->
<rule ref="rulesets/unusedcode.xml/UnusedLocalVariable"/>
<!-- 指定某個規則集里的某個規則的優先級 -->
<rule ref="rulesets/basic.xml/EmptyCatchBlock" message="Must handle exceptions">
<priority>2</priority>
</rule>
<!-- 去除某個規則集里的某個規則 -->
<rule ref="rulesets/braces.xml">
<exclude name="WhileLoopsMustUseBracesRule"/>
</rule>
最后,我們運行PMD的時候就可以指定這個 ruleset 文件。
PMD 里面還有一個寫好的ruleset文件,在pmd-3.8.jar里面的rulesets文件夾下,名稱是favorites.xml,以下是主要部分:
< rule ref ="rulesets/basic.xml" />
< rule ref ="rulesets/basic.xml/EmptyCatchBlock" message ="Must handle exceptions" >
< priority > 2 </ priority >
</ rule >
< rule ref ="rulesets/unusedcode.xml" />
< rule ref ="rulesets/braces.xml/WhileLoopsMustUseBraces" />
< rule ref ="rulesets/braces.xml/ForLoopsMustUseBraces" />
< rule ref ="rulesets/design.xml/SimplifyBooleanReturns" />
< rule ref ="rulesets/design.xml/SwitchStmtsShouldHaveDefault" />
< rule ref ="rulesets/strings.xml/StringToString" />
< rule ref ="rulesets/strings.xml/StringInstantiation" />
< rule ref ="rulesets/controversial.xml/UnnecessaryConstructor" />
< rule ref ="rulesets/controversial.xml/NullAssignment" />
< rule ref ="rulesets/controversial.xml/UnusedModifier" />
< rule ref ="rulesets/codesize.xml/CyclomaticComplexity" >
< properties >< property name ="reportLevel" value ="5" /></ properties >
</ rule >
其它事項:
1. 可以使用JDK1.5的聲明 @SuppressWarnings(""),禁止PMD的警告。
2. 可以使用//NOPMD來標記行或塊代碼,禁止PMD警告。
3. 有兩種方法自定義Rule,編寫java類和編寫XPath。
6. 編寫自定義的PMD規則
前提:
1.了解XPath:http://www.w3.org/TR/xpath
2. 對PMD 的實現原理有一定的了解
實現過程:
首先傳一個文件名或者Ruleset給pmd
Pmd把該文件流傳給自己生成的javaCC分析器
分析完畢后,pmd獲得了分析生成的AST的一個引用
PMD把AST處理成一個符號表,你可以在符號表里面查詢一些有用的信息
每個pmd規則都會遍歷整個AST并檢驗是否發生了錯誤
接著pmd產生一個報表,上面說明了有哪些地方違反了pmd規則
編寫pmd規則有兩種方法:
一.用java code,需要了解pmd的api,需要進行深入研究,也常常用于一些比較復雜的pmd規則
二.用xpath,對著產生的AST樹,寫就行了,上手比較快,寫起來也比較簡單
下面舉一個用XPath實現的一個PMD規則:
在項目中,我們不希望Application的開發人員手動的調用Toplink UnitOfWork的commit,
commitAndResume, commitAndResumeOnFailure'方法,因為每次提交都會映像performa,我們的提交是放在自己編寫的framework里面,
在指定的位置提交。所以我們把規則的優先級設置為3. 在eclipse的pmd plugin中,優先級為3會產生一個警告。
1首先將D:"local_lib"pmd-bin-4.2"bin 加到系統環境變量的path中
2打開cmd 運行 designer 分析器
3左上角source code可以把你寫好的java source copy過來主要就在這個java source code基礎上不斷修正你的pmd規則。
4xpath query:用來編寫自定義的xpath expression(先不忙寫xpath expression)
5點擊go,就會在左下角的Abstract syntax Tree中產生AST,你可以選擇AST上的某個節點,左下角的下面一個框中就會出現該節點的一些信息。
是在符號表中查詢得到的。
6.DFA是pmd4的新功能,用于編寫更復雜的pmd規則,不光是某個source code級別了,pmd4使用了asm讀取字節碼,并作分析,處理類文件之間的依賴性。
在實際使用中,特別是在特定應用中,這個功能是相當有用的。還可以用來簡化一些現有的規則。
7根據生成的AST編寫xpath expression。對于上文提到的source檢查規則編寫了一個xpath
Expression,在編寫xpath expression的過程中需要反復的修改源代碼并且反復的修改xpath expression這樣才能滿足所有的需要,反復的點擊go。
最后寫好的規則大致如下:
//PrimaryExpression[(PrimaryPrefix/Name[ends-with(@Image,'commit') or ends-with(@Image, 'commitAndResume') or ends-with(@Image, 'commitAndResumeOnFailure')] and substring-before(PrimaryPrefix/Name/@Image, '.') =
//VariableDeclaratorId[../..//ClassOrInterfaceType[@Image =
'UnitOfWork']]/@Image) or (PrimarySuffix[ends-with(@Image, 'commit') or
ends-with(@Image,'commitAndResume') or ends-with(@Image, 'commitAndResumeOnFailure')] and (PrimarySuffix[ends-with(@Image,
'getActiveUnitOfWork')] or PrimarySuffix[ends-with(@Image, 'acquireUnitOfWork')]))
and //ImportDeclaration/Name[contains(@Image,'oracle.toplink.sessions.UnitOfWork') or contains(@Image, 'oracle.toplink.sessions')]
]
8.將寫好的xpath expression轉換成pmd rule。Designer可以自動生成點擊菜單actions下面的create rule xml。
9.最后將生成的rule添加到ruleset中,并最好在大批量的代碼中進行驗證。
7. 結束語
即使只使用內置規則(內容相當全面),PMD 也可以找到您的代碼中的一些真正問題。某些問題可能很小,但有些問題則可能很大。PMD 不可能找到每個 bug,您仍然需要做單元測試和接受測試,在查找已知 bug 時,即使是 PMD 也無法替代一個好的調試器。但是,PMD 確實可以幫助您發現未知的問題。這是一個便宜、簡易、有意思的改進程序的方式。如果您以前從未用過 PMD,那么您以及您的客戶應該試試它。
8. 參考資料
PMD 官方文檔(http://pmd.sourceforge.net/)
請參閱 Tom Copeland 撰寫的文章“ Static Analysis with PMD”,這篇文章對 PMD 進行了介紹。
(http://www.onjava.com/pub/a/onjava/2003/02/12/static_analysis.html?page=1)
從 Tom Copeland 撰寫的文章“ Custom PMD Rules”中學習如何編寫定制 PMD 規則。
(http://www.onjava.com/pub/a/onjava/2003/04/09/pmd_rules.html)
XOM 在本文的示例中多次引用到,它是一個開源的、基于樹的 API,用于處理 Java 中的 XML。 (http://www.xom.nu/)
XPath語法:(http://www.w3.org/TR/xpath)