明輝手游網(wǎng)中心:是一個免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

用PMD自動執(zhí)行Java代碼靜態(tài)區(qū)分

[摘要]作者:仙人掌工作室   一、基礎(chǔ)知識   PMD是一種分析Java代碼錯誤的工具。與其他分析工具不同的是,PMD通過靜態(tài)分析獲知代碼錯誤。也就是說,在不運(yùn)行Java程序的情況下報(bào)告錯誤。PMD附帶了...
作者:仙人掌工作室

  一、基礎(chǔ)知識

  PMD是一種分析Java代碼錯誤的工具。與其他分析工具不同的是,PMD通過靜態(tài)分析獲知代碼錯誤。也就是說,在不運(yùn)行Java程序的情況下報(bào)告錯誤。PMD附帶了許多可以直接使用的規(guī)則,利用這些規(guī)則可以找出Java源程序的許多問題,例如沒有用到的變量、多余的變量創(chuàng)建操作、空的catch塊,等等。此外,用戶還可以自己定義規(guī)則,檢查Java代碼是否符合某些特定的編碼規(guī)范。例如,你可以編寫一個規(guī)則,要求PMD找出所有創(chuàng)建Thread和Socket對象的操作。

  最初,PMD是為了支持Cougaar項(xiàng)目而開發(fā)的。Cougaar是美國國防高級研究計(jì)劃局(Defense Advanced Research Projects Agency,DARPA)的一個項(xiàng)目。DARPA開放了PMD的源代碼,所以PMD被發(fā)布到了SourceForge網(wǎng)站上。不久前,PMD的下載次數(shù)就超過了14000次,頁面瀏覽次數(shù)超過了130000次。更重要的是,在源代碼開放作者的努力下,越來越多的PMD規(guī)則和IDE插件被開發(fā)出來,然后加入到了PMD的核心項(xiàng)目之中。

  你可以從PMD的網(wǎng)站下載PMD的二進(jìn)制版本,或下載帶源代碼的版本,下載得到的都是ZIP文件。假設(shè)你下載了二進(jìn)制版本,先把它解壓縮到任意一個目錄。接下來怎么做,就要看你準(zhǔn)備怎么用它——最簡單的,如果要在一個Java源代碼目錄中運(yùn)行PMD,只需直接在命令行上運(yùn)行下面的命令:

C:\data\pmd\pmd>java -jar lib\pmd-1.02.jar c:\j2sdk1.4.1_01\src\java\util
   text rulesets/unusedcode.xml



  輸出結(jié)果類如:

c:\j2sdk1.4.1_01\src\java\util\AbstractMap.java 650
    Avoid unused local variables such as 'v'    
c:\j2sdk1.4.1_01\src\java\util\Date.java        438  
    Avoid unused local variables such as 'millis'



  除了直接在命令行上運(yùn)行PMD之外,還可以通過Ant、Maven或者各種集成開發(fā)環(huán)境(IDE)運(yùn)行PMD,例如jEdit、Netbeans、Eclipse、Emacs、IDEAJ和JBuilder等。

  二、內(nèi)建規(guī)則

  PMD本身就附帶了許多規(guī)則。下面是幾個例子。

沒有用到的代碼顯然是應(yīng)該被清除的。
public class Foo {
   // 下面這個實(shí)例變量沒有用到
   private List bar = new ArrayList(500);
}
如果用一個接口也能達(dá)到同樣的目標(biāo),為什么要返回一個具體的類?例如,下例可以改用List接口。
public ArrayList getList() {
   return new ArrayList();
}
當(dāng)if的條件為真時,if代碼塊其實(shí)不做任何事情。下面這段代碼其實(shí)可以寫得更加簡潔一些。
public void doSomething(int y) {
   if (y >= 2) {
   } else {
      System.out.println("Less than two");
   }
}
為什么要創(chuàng)建一個新的String對象?只要改用String x="x"就可以了。
String x = new String("x");



  PMD還包含其他許多內(nèi)建規(guī)則,但從上面幾個例子已經(jīng)可以看出PMD的基本工作方式。只要定義適當(dāng)?shù)撵o態(tài)規(guī)則,PMD就可以象一個富有經(jīng)驗(yàn)的程序員那樣,幫你指出代碼存在的問題。

  三、工作原理

  PMD的核心是JavaCC解析器生成器。PMD結(jié)合運(yùn)用JavaCC和EBNF(擴(kuò)展巴科斯-諾爾范式,Extended Backus-Naur Formal)語法,再加上JJTree,把Java源代碼解析成抽象語法樹(AST,Abstract Syntax Tree)。顯然,這句話不那么好懂,且看下文具體說明。

  從根本上看,Java源代碼只是一些普通的文本。不過,為了讓解析器承認(rèn)這些普通的文本是合法的Java代碼,它們必須符合某種特定的結(jié)構(gòu)要求。這種結(jié)構(gòu)可以用一種稱為EBNF的句法元語言表示,通常稱為“語法”(Grammar)。JavaCC根據(jù)語法要求生成解析器,這個解析器就可以用于解析用Java編程語言編寫的程序。

  不過實(shí)際運(yùn)行中的PMD還要經(jīng)過JJTree的一次轉(zhuǎn)換。JJTree是一個JavaCC的插件,通過AST擴(kuò)充JavaCC生成的解析器。AST是一個Java符號流之上的語義層。有了JJTree,語法分析的結(jié)果不再是“System, ., out, ., . println”之類的符號序列,而是一個由對象構(gòu)成的樹型層次結(jié)構(gòu)。例如,下面是一段簡單的Java代碼以及與之對應(yīng)的AST。

Java源代碼:
public class Foo {
    public void bar() {
        System.out.println("hello world");
    }
}
對應(yīng)的抽象語法樹
CompilationUnit
TypeDeclaration
  ClassDeclaration
   UnmodifiedClassDeclaration
    ClassBody
     ClassBodyDeclaration
      MethodDeclaration
       ResultType
       MethodDeclarator
        FormalParameters
       Block
        BlockStatement
         Statement
          StatementExpression
           PrimaryExpression
            PrimaryPrefix
             Name
            PrimarySuffix
             Arguments
              ArgumentList
               Expression
                PrimaryExpression
                 PrimaryPrefix
                  Literal



  四、編寫規(guī)則

  前面我們看到了Java源代碼以及與之對應(yīng)的對象層次結(jié)構(gòu)。下面我們就要利用這些對象編寫PMD規(guī)則檢查代碼存在的問題。

  一般地,一個PMD規(guī)則可以看成一個Visitor,它遍歷AST,尋找多個對象之間的一種特定模式,這種模式表示代碼存在的問題。問題模式可能簡單也可能復(fù)雜,簡單的如查找代碼中是否包含new Thread關(guān)鍵詞,復(fù)雜的如確定一個類是否正確覆蓋了equals和hashcode。

  下面是一個尋找空if語句的簡單PMD規(guī)則。

//擴(kuò)展AbstractRule,以啟用Visitor模式
public class EmptyIfStmtRule extends AbstractRule implements Rule {
   //當(dāng)源代碼中出現(xiàn)一個Block,下面的方法被調(diào)用
   public Object visit(ASTBlock node, Object data){
      //如果父節(jié)點(diǎn)是一個if語句且代碼塊里面沒有任何內(nèi)容
      if ((node.jjtGetParent().jjtGetParent() instanceof ASTIfStatement)
         && node.jjtGetNumChildren()==0) {
         //肯定代碼存在問題。把一個RuleViolation加入到Report。
         RuleContext ctx = (RuleContext)data;
         ctx.getReport().addRuleViolation(createRuleViolation(ctx,
            node.getBeginLine()));
      }
      //繼續(xù)檢查樹的下一個節(jié)點(diǎn)
      return super.visit(node, data);
   }
}



  也許你不能一下子掌握這段代碼,其實(shí)它的思路還是比較簡單的:

  #擴(kuò)展AbstractRule基類。

  #聲明一個“鉤子”,一旦我們感興趣的節(jié)點(diǎn)出現(xiàn),它就會被調(diào)用(稱為“回調(diào)”)。在上面的例子中,我們要求在每一個ASTBlock出現(xiàn)時得到通知,所以聲明visit(ASTBlock node, Object data)。

  #在回調(diào)函數(shù)中,判斷是否出現(xiàn)了我們正在檢查的問題。本例我們檢查是否存在空的if塊,所以先判斷當(dāng)前是否在ASTIfStatement之內(nèi),然后判斷它是否有子節(jié)點(diǎn)。

  當(dāng)然,我們還可以按照另一種方法進(jìn)行檢查:聲明一個要求檢查ASTIfStatement的回調(diào)函數(shù),然后在回調(diào)函數(shù)中檢查是否存在子節(jié)點(diǎn)。

  五、配置規(guī)則

  寫好自定義規(guī)則之后,接下來要把它加入到某個PMD規(guī)則集。所謂PMD規(guī)則集,就是由一組PMD規(guī)則構(gòu)成的集合。每個PMD規(guī)則集由一個XML文件定義,下面是一個PMD規(guī)則的配置信息的例子:

<rule name="EmptyIfStmt"
   message="避免使用空的if語句"
   class="net.sourceforge.pmd.rules.EmptyIfStmtRule">
   <description>
      找到空的if語句:if檢查了條件,但if塊里面沒有任何內(nèi)容。
   </description>
   <priority>3</priority>
   <example>
      <![CDATA[
         if (absValue < 1) {
            // not good
         }
      </XMLCDATA>
   </example>
</rule>



  可以看出,規(guī)則配置文件包含了許多有用的信息。要運(yùn)行新添加的規(guī)則,只需把規(guī)則集XML文件和Java源代碼文件放入CLASSPATH,然后運(yùn)行PMD。

  結(jié)束語:本文介紹了PMD如何在不編譯代碼的情況下分析和尋找代碼存在的問題,通過幾個簡單的例子了解了EBNF語法、JavaCC和AST,以及如何用PMD檢查代碼存在的問題、如何編寫和運(yùn)用定制PMD規(guī)則等。愿PMD能夠助你一臂之力!