1. PMD简介
PMD是一款采用BSD协议发布的Java程序代码检查工具。该工具可以做到检查Java代码中是否含有未使用的变量、是否含有空的抓取块、是否含有不必要的对象等。该软件功能强大,扫描效率高,是Java程序员debug的好帮手。
PMD是扫描 Java 源码并查找以下潜在问题:
空捕捉块(catch block)
- 从未用过的参数
- 空if声明
- 重复的导入声明
- 从未用过的私有方法
- 孤立的类
- 短型或长型变量及方法名
PMD 具有下列软件的插件:JEdit, JBuilder, NetBeans/Sun ONE Studio, IntelliJ IDEA, TextPad, Maven, Ant, Eclipse, Gel, Emacs
1.1 PMD 的含义
坦率地说,我们其实也不知道PMD的真正意义所在 (我们只是认为这三个字母拼在一起较为好听) 。但是,我们提出了计算行业的几种解释以供参考。
PMD:
- Pretty Much Done (几乎无所不能)
- Project Mess Detector (项目故障探测器)
- Project Monitoring Directives (项目监视器)
- Protein Mutant Database (基因突变数据库)
- Project Meets Deadline (项目到期)
- Programming Mistake Detector (程序错误检测器)
- Pounds Mistakes Dead (彻底纠错)
- PMD Meaning Discovery (PMD含义探索)
1.2 PMD运行机制
PMD 根据规则核对源码并产生一个报告。具体如下:
- 有文件名和RuleSet传入PMD;
- PMD将通过该文件的InputStream传递给由JavaCC-生成的解析器;
- PMD 从解析器取得指向抽象语法树(AST)的引用;
- RuleSet 中的每个规则都遍历AST 并检查错误;
- 报告内容包括RuleViolations以及符合XML 、HTML 或其它格式的代码/文件;
1.3 PMD用法
PMD之后有几个选项:
Generate reports:生成报告,生成在你的工程目录下的reports里;
Clear violation reviews
Find suspect Cut And Paste..:是检查指种后缀的文件。如JAVA,JSP等;
Check Code With PMD:就是检查代码。
做好以上步骤以后就会在项目中静态的检测你每一行的代码,如果有不符合要求的会出现警告和错误信息。
2. 集成PMD至规则插件
NC规则检查工具原来的代码检查使用的是JavaParser,但是JavaParser毕竟功能上比较弱,能够分析出来的结果不够全面,而且最近一段时间没有更新,仅支持Java1.5。
因此,考虑到有些部门提出的规则对代码检查要求比较复杂,JavaParser很难进行编程处理,考虑集成PMD中的源码,改造其API入口方法,使其能够在实际的规则中使用,并能够以PMD规则的方式对其进行扩展。
集成后的PMD功能包括:
l 仅保留支持Java1.7代码的检查,将其他文件(xml,html,jsp,cpp等)的支持抛弃;
l 去除用于代码检查时进行性能测试的Benchmark功能;
l 仍然保持了整个规则执行的整理流程,去掉了原有规则集合(RuleSetFactory, RuleSet)的概念,仅支持单个规则的检查;
l 保留了PMD中原有的各类规则;
l 去除了用于生成报告的过程;
最后进行规则检查只需要执行SourceCodeProcessor中的如下方法即可:
/**
* 解析规则文件,得出解析结果上下文
* @param filePath -源代码文件路径
* @param rule -要执行的规则实现
* @return
* @throws FileNotFoundException
*/
publicstatic RuleContext parseRule(String filePath, Rule rule) throws FileNotFoundException{
其中,用户需要做的事情就是执行规则检查时,扩展Rule接口,其中扩展部分内容见下节。
用户使用pmd工具需要事先依赖pmd的bundle:com.yonyou.nc.codevalidator.code.pmdadaptor
3. PMD扩展
在介绍完PMD的使用后,下面重点介绍该如何对PMD进行扩展,如何编写自己的规则用在代码检查上。
3.1 规则Rule类结构
规则的最基础接口是net.sourceforge.pmd.Rule,其中有一系列的抽象类,下面主要介绍几个重要的抽象类:
AbstractStatisticJavaRule:顾名思义,该抽象类主要就是用于处理一些统计规则的实现,比如计算方法体中代码行数超出一定范围,过多的方法参数,过长的class文件等。如果用户需要计算一些统计信息,可以从此类中派生。
AbstractJavaRule: 一般的Java源码分析规则都是从本类中继承,包括前面所说的统计规则;当前其中也包括一些包含某些特殊帮助方法的规则抽象类,用户可参考其实现。总之,用户如果没有找到特殊适合的实现类型,那么从该类中继承并实现规则。
3.2 Java Node的Visitor访问
PMD中的源码分析与JavaParser大体类似,都是将整个Java文件分析成各种子Node,且Node之间存在着Composite关系,采用访问者模式(Visitor模式,参考设计模式),这样就可以在解析Java文件的时候,选择切入点,关注感兴趣的具体Node实现(后面详细介绍Node的结构)。
3.3 Java Node的结构
在进行Java代码检查的时候,经常会使用的便是PMD中提供的JavaNode结构,只有详细了解了JavaNode的结构,才能够有针对性的编写检查代码中特定问题的规则。
编译单元(.java)分析
下面就从最顶层的一个编译单元说起,详细介绍一下JavaNode中的结构,一个编译单元就是对应一个.java文件(其中可能包含多个类,包括内部类)。
ASTCompilationUnit包括三个基本部分:ASTPackageDeclaration(包声明),ASTImportDeclaration(引用其他类声明)和ASTTypeDeclaration类型声明。
其中包声明和引用列表声明中引用的类都以ASTName形式存在;
如果类中包含非内部类,会存在多个ASTTypeDeclaration。
类型声明分析
对于具体的类型声明,包括ASTExtendsList(继承的类,java中只能继承单个),ASTImplementsList(实现的接口,可多个)和ASTClassOrInterfaceBody(实现类或接口体)。
当类型中存在着内部类时(不区分静态或非静态内部类),ASTClassOrInterfaceBody中会包含多个ASTClassOrInterfaceBodyDeclaration,其中内部类的子仍然聚合了对应的ASTClassOrInterfaceDeclaration,主类向下延伸,见下文。
具体类型元素分析
具体的类型实现体中,包含下面的内容:
ASTFieldDeclaration:字段声明,其中有着ASTType字段的类型(根据情况分为基本类型ASTPrimaryType和引用类型ASTReferenceType),以及ASTVariableDeclarator字段变量的定义。
ASTConstructorDeclaration:构造方法声明,其中包括ASTFormalParameters构造函数参数和对应ASTBlockStatement构造函数语句
ASTMethodDeclaration:方法声明,包含ASTReturnType返回值定义,ASTMethodDeclarator方法定义(其中可包含多个方法参数),ASTNameList可能抛出的异常列表,ASTBlock方法体。
实现AccessNode接口的Node(都是从AbstractJavaAccessNode中继承),其中都有着访问符(modifiers)的API,即可以访问该字段或方法声明中修饰符(static, final, private等)。
而对于类似字段声明的部门,都存在着一颗类似上图中的结构(ASTFieldDeclaration结构)。
常见方法内语句结构图
下面介绍一下比较常见的方法内语句结构图(do while,for循环类似,暂不介绍):
try语句
try语句statement包含ASTBlock(try块中的内容),ASTCatchStatement(catch块中的内容),ASTFinallyStatement(finally语句中的内容),其中catch和finally块递归也包含相应ASTBlock和ASTBlockStatement。
If语句
解析完成的结构:
其中,第一个ASTExpression为判断语句,第二个ASTStatement为if内部体,第三个ASTStatement中又包含了一个ASTIf语句,此为else子句,以此类推,最后的else嵌入在ASTStatement中。
至于其他类型分支,分析过程都比较类似,这里就不一一介绍了。
3.4 规则编写常用的方法
编写规则需要直接从net.sourceforge.pmd.lang.java.rule.AbstractJavaRule中(或其子类)继承,严格遵守Visitor模式(参考设计模式),仅关注需要实现的方法。
可以参考pmd中内置的规则实现,写出自己的规则,内置的规则实现在bundle:/com.yonyou.nc.codevalidator.code.pmdadaptor中,net.sourceforge.pmd.lang.java.rule包下,已按照规则的分类和使用情况分成不同的实现包进行管理,并在每个包中都有一个对应的xml文件用于描述本包中的规则意义。
此外,还有一些常用的方法,用来在规则中使用,没介绍的方法,自己查阅,有问题沟通。
String net.sourceforge.pmd.lang.ast.Node.getImage()
在某些类型的节点中,用来获得定义的方法名,变量名,参数名等名称类信息,具体请查看相应的定义;
<T> List<T> net.sourceforge.pmd.lang.ast.Node.findDescendantsOfType(Class<T> targetType)
在某个节点上,查找其子节点中是否包含某个特定类型的子节点(递归向下查询)。
<T> List<T> net.sourceforge.pmd.lang.ast.AbstractNode.getParentsOfType(Class<T> parentType)
在某个节点上,查找其父节点是否包含某个特定类型(递归向上查询)。
Node net.sourceforge.pmd.lang.ast.Node.jjtGetChild(int index)
得到节点的子节点,可通过node.jjtGetNumChildren()获取子节点的数量来协助工作。
结束
最后,由于项目中用到了PMD中的分析过程,而剥离掉原有的例如报表生成等功能,抽象出了一个简单可轻易调用的类库及其源码,具体信息请参考github:
https://github.com/clamaa/pmd-adaptor