代码检测工具箱——checkstyle、findbugs、pmd-cpd

  • Post author:
  • Post category:其他



写在前面

好吧,我承认,我懒了,写了一天文档,到这里直接粘贴了,希望大家能看懂,如果需要一份格式完整的文档,请去我的百度文库下载,地址是:


http://wenku.baidu.com/view/d2849ff04693daef5ef73d34.html

下面,开始

一.目的:

最近要跟踪一个项目的代码质量,保障项目质量。个人总是认为,质量保障这个东西要用数据说话的。代码走查是一个方面,但要能使用工具完成部分标准化的代码走查,发现部分错误,也不失为一种不错的补充手段。结合之前自己用过、听过的几种开源工具,整合到一起,实现一个适合目前项目的简单工具。

二.取舍:

首先是工具的选择,经过多个工具的使用结合目前项目组成员的水平和状态,决定从几个方面进行检查:

常规bug

编码规范

重复代码

不想检查太多,以前没有开展过这方面的工作,领导是否支持,项目成员反响是否良好都很难说,如果大家都喜欢再完善更多的检查也不晚。

其实还差一个依赖的检测,但现在项目使用的是ssh的架构,现有依赖检查的工具中,对spring的支持都不是很好,遂放弃。

对于工具的选择,紧着自己熟悉的就选择了checkstyle、findbugs和pmd的cpd工具。

三.期望结果:

使用ant脚本,一步完成所有工作,针对项目直接产生检查报告。

四.步骤:

1.准备工具

ant、checkstyle、findbugs、pmd下载。还要下载cvs和eclipse,因为脚本中需要使用cvs更新代码,eclipse会让ant脚本的编辑和环境配置更加简单。

2.脚本规划

规划脚本,整个脚本分为几个部分,如下:

<?xml version=”1.0″ encoding=”utf-8″?>

<project name=”myProject” default=”start” >

<!– 变量定义 –>

<!– 环境初始化 –>

<!– checkout –>

<!– javac –>

<!– jar –>

<!– checkstyle –>

<!– findbugs –>

<!– pmd cpd –>

<!– run –>

<target name=”start”>

</target>

</project>

整个脚本规划描述了脚本执行的过程,每个注释都将添加具体的内容。系统执行start任务,这个任务通过依赖调用其他任务,任务间通过依赖定义过程。

l         首先“变量定义”中定义变量,便于将来多个项目复用。

l         “环境初始化”中初始化环境,所有环境清理和环境建立都在这里完成。

l         “checkout”完成cvs的更新工作。

l         由于findbugs需要检查二进制代码,“javac”完成代码编译工作。

l         同样为findbugs需要,“jar”将代码打包。

l         “checkstyle”完成代码拼写检查。

l         “findbugs”完成bug检查

l         “pmd cpd”使用pmd的cpd功能检查重复代码。

3.checkout

变量定义和环境初始化部分,在需要时随时添加,首先是checkout过程,将代码检出cvs库。

3.1脚本编写

checkout部分脚本如下:

<target name=”checkoutlib” depends=”mkDir”>

<cvs cvsroot=”${cvsroot}” package=”WorkingArea/Code/lib” />

</target>

<target name=”checkoutsrc” depends=”checkoutlib”>

<cvs cvsroot=”${cvsroot}” package=”WorkingArea/Code/project” />

</target>

checkoutlib检出公共库,checkoutsrc检出代码,同时在变量定义部分定义cvsroot变量,如下:

<property name=”cvsroot” value=”:pserver:wangjianxuan:password@scm1.domain.com:/repository/project” />

其中文字部分与我们常用的cvs写法相同,可以参考eclipse中的cvs视图的部分。


注意:上面脚本中依赖的mkDir任务是初始化部分的,为了建立相关的目录,后面有详细描述。

3.2执行

eclipse中执行ant就是有这点好,缺少什么不用去修改环境变量,eclipse内部就解决了。执行前有几个地方需要确认:

脚本上点右键-》Run As-》Ant Build…

注意,一定是带有省略号的菜单,这个菜单才能配置环境,打开界面如下图:

这里Targets可以选择要执行的任务,执行时会连依赖的任务一起执行。ClassPath定义依赖的类库,JRE用来定义JRE的版本,Environment标签很重要,可以定义一些环境变量中未定义的变量,这里我因为一直没有找到cvs路径,但命令行中能够找到cvs,所以把Path环境变量复制定义到这里了,如下图:

脚本执行后,会将代码检出到工程目录下的“WorkingArea/Code/project”位置。


注意:环境变量和类路径等内容的设置在更改ant脚本名称后会丢失,需要重新设置。

3.3问题处理

ant调用cvs网上文章较少,问题的处理就更少了,也许是太简单了吧,但我还是碰到了一些问题。

首先,如果path路径中没有增加cvs路径,或者你没有安装cvs(windows系统需要安装cvsnt),找不到cvs路径,将报错。所以保证命令行中任意路径下调用“cvs”命令可以成功,如果还报错,可以将“path”环境变量增加到eclipse中。

另外,按照ant的cvs任务说明,cvs密码应该使用cvspass任务生成cvspass文件,在cvs任务中使用cvspass标签调用这个文件。但是,我在eclipse中这么做不会产生文件,同时cvs checkout会报告访问被拒绝(很明白,没密码)。处理办法就是将密码加在cvsroot属性中,在用户名后,“@”符号前增加“:密码”即可。

4. javac

javac任务将刚刚检出的代码进行编译,编译后的代码放到bin目录下。

4.1脚本

javac部分的脚本如下:

<target name=”javac” depends=”checkoutsrc”>

<javac srcdir=”${srcdir}” destdir=”${builddir}” source=”1.6″>

<compilerarg value=”-Xlint:unchecked” />

</javac>

</target>

其中,depends指定刚刚检出脚本,使脚本执行时先执行检出才会执行编译。source指定编译时使用的jdk版本,这里指定了1.6版本。由于代码编译需要,指定了一个编译参数“-Xlint:unchecked”,根据实际情况,可指定任何参数,可多次使用,与命令行编译对应。

在变量定义部分定义了两个变量:“srcdir”指定源代码路径;“builddir”指定编译后代码路径。脚本如下:

<property name=”projdir” value=”D:/work/quality_workspace/AntScript” />

<property name=”srcdir” value=”${projdir}/WorkingArea/Code/cginfo/src/main” />

<property name=”builddir” value=”${projdir}/WorkingArea/Code/cginfo/bin” />

srcdir和builddir变量共同以当前项目目录为基础,所以,又定义了一个变量“projdir”声明项目目录。

4.2执行

简单执行javac脚本可能会出现问题,需要使classpath包含类路径。同样,脚本上右键-》Run As-》Ant Build…打开窗口中选择Classpath页签,如下:

这里要注意,

1.       只有选择“User Entries”节点时,添加jar包的按钮才有效。

2.       不知道是不是我使用的问题,Add Folders添加目录后,并不能把目录下所有jar包都添加到类路径来,只能将所有jar包摊在这里。

编译后就是打包了,见下节。

5.jar

与javac同样,由于findbugs的需要,将javac编译后的代码打包

5.1脚本

<target name=”jar” depends=”javac”>

<jar destfile=”${packagedir}/app.jar” basedir=”${builddir}” />

</target>

脚本destfile指定了编译后的jar包路径和文件名,basedir指定原代码的路径。

这里在变量定义部分定义了变量packagedir用于指定jar包的路径,脚本如下:

<property name=”package” value=”WorkingArea/Code/commonMakeManage”/>

执行后会在destfile位置找到打好的jar包。

6.checkstyle

终于进入正题了,准备工作完成,可以开始检查了,首先是checkstyle检查并且生成报告。

6.1脚本

检查脚本如下:

<target name=”checkstyle” depends=”jar”>

<taskdef resource=”checkstyletask.properties” />

<checkstyle config=”lib/check_rules.xml”>

<formatter type=”xml” tofile=”${checkstyledir}/checkstyle_report.xml” />

<fileset dir=”${srcdir}” includes=”**/*.java” />

</checkstyle>

<style in=”${checkstyledir}/checkstyle_report.xml” out=”${checkstyledir}/checkstyle_report.html” style=”lib/checkstyle-frames.xsl” />

</target>

脚本有点长,逐个解说:

首先,taskdef定义了checkstyle的ant task,以及声明了属性文件,这就需要ant的类路径中有checkstyle的jar包,这里我用的是checkstyle-5.4-all.jar。

下面,checkstyle定义了检查的各方面,“config”定义了检查规则,规则可以自定义编写,随后说明。

formatter子元素定义了输出格式以及输出文件的位置,这里定义了xml格式,同时定义了“checkstyledir”变量,代表checkstyle报告目录。后面说明具体变量定义。

fileset子元素定义了源代码位置,dir指定了源代码目录,includes指定文件类型,也可以使用excludes排除一些文件,详见checkstyle文档。

执行后生成xml报告文件,使用style标签结合checkstyle中自带的xsl文件将xml生成html,便于查看,checkstyle自带了很多xsl文件,多尝试一下,看看哪个适合。


注意:这里使用的是框架样式,生成的html,生成后有时会在“${checkstyledir}”的同级目录生成files目录,目录中包含部分检查结果。

6.2变量定义

脚本如下:

<property name=”checkstyledir” value=”${projdir}/checkstyle” />

6.3环境初始化脚本

由于每次检查代码前要将之前的检查结果删除,保证结果是最新的。同时要保证文件目录存在,否则写报告时报错。所以有如下脚本:

<target name=”delDir”>

<delete dir=”${builddir}” />

<delete dir=”${checkstyledir}” />

<delete dir=”${findbugsdir}” />

<delete dir=”${cpddir}” />

</target>

<target name=”mkDir” depends=”delDir”>

<mkdir dir=”${builddir}” />

<mkdir dir=”${checkstyledir}” />

<mkdir dir=”${findbugsdir}” />

<mkdir dir=”${cpddir}” />

</target>

这里将所有需要删除和建立的脚本都写出来了,包括编译的目录builddir;代码检查结果目录checkstyledir;bug检查结果目录findbugsdir以及代码重复检查目录cpddir。

6.3执行

脚本执行后会在checkstyledir目录下建立checkstyle_report.xml和checkstyle_report.html文件

6.4自定义规则

checkstyle规则可以自定义,详细规则可以参考checkstyle文档,这里我只需要检查很少的规则,从checkstyle规则中删除不必要的规则,保留一部分需要的即可。规则如下:

<?xml version=

“1.0”

?>

<!DOCTYPE module PUBLIC

“-//Puppy Crawl//DTD Check Configuration 1.3//EN”

“http://www.puppycrawl.com/dtds/configuration_1_3.dtd”>

<!–


Checkstyle

configuration that checks the sun coding conventions from:

– the Java Language Specification at

http://java.sun.com/docs/books/jls/second_edition/html/index.html

– the Sun Code Conventions at http://java.sun.com/docs/codeconv/

– the

Javadoc

guidelines at

http://java.sun.com/j2se/javadoc/writingdoccomments/index.html

– the JDK

Api

documentation http://java.sun.com/j2se/docs/api/index.html

– some best practices


Checkstyle

is very configurable. Be sure to read the documentation at

http://checkstyle.sf.net (or in your

downloaded

distribution).

Most Checks are configurable, be sure to consult the documentation.

To completely disable a check, just comment it out or delete it from the file.

Finally, it is worth reading the documentation.

–>

<module name=

“Checker”

>

<!–

If you set the

basedir

property below, then all reported file

names will be relative to the specified directory. See

http://checkstyle.sourceforge.net/5.x/config.html#Checker

<property name=”

basedir

” value=”${


basedir

}”/>

–>

<!– Checks that property files contain the same keys.         –>

<!– See http://checkstyle.sf.net/config_misc.html#Translation –>

<module name=

“Translation”

/>

<module name=

“TreeWalker”

>

<!– Checks for

Javadoc

comments.                     –>

<!– See http://checkstyle.sf.net/config_javadoc.html –>

<module name=

“JavadocMethod”

/>

<module name=

“JavadocType”

/>

<module name=

“JavadocVariable”

/>

<!– Checks for Naming Conventions.                  –>

<!– See http://checkstyle.sf.net/config_naming.html –>

<module name=

“ConstantName”

/>

<module name=

“LocalFinalVariableName”

/>

<module name=

“LocalVariableName”

/>

<module name=

“MemberName”

/>

<module name=

“MethodName”

/>

<module name=

“PackageName”

/>

<module name=

“ParameterName”

/>

<module name=

“StaticVariableName”

/>

<module name=

“TypeName”

/>

<!– Checks for Headers                                –>

<!– See http://checkstyle.sf.net/config_header.html   –>

<!– <module name=”Header”>                            –>

<!– The follow property value demonstrates the ability     –>

<!– to have access to ANT properties. In this case it uses –>

<!– the ${


basedir

} property to allow

Checkstyle

to be run  –>

<!– from any directory within a project. See property      –>

<!– expansion,                                             –>

<!– http://checkstyle.sf.net/config.html#properties        –>

<!– <property                                              –>

<!–     name=”headerFile”                                  –>

<!–     value=”${


basedir

}/java.header”/>                   –>

<!– </module> –>

<!– Following interprets the header file as regular expressions. –>

<!– <module name=”RegexpHeader”/>                                –>

<!– Checks for imports                              –>

<!– See http://checkstyle.sf.net/config_import.html –>

<module name=

“AvoidStarImport”

/>

<module name=

“IllegalImport”

/> <!– defaults to sun.* packages –>

<module name=

“RedundantImport”

/>

<module name=

“UnusedImports”

/>

<!– Checks for common coding problems               –>

<!– See http://checkstyle.sf.net/config_coding.html –>

<module name=

“SimplifyBooleanExpression”

/>

<module name=

“SimplifyBooleanReturn”

/>

</module>

</module>

7.findbugs

使用findbugs检查常见bug

7.1脚本

脚本如下:

<target name=”findbugs” depends=”checkstyle”>

<taskdef name=”findbugs” classname=”edu.umd.cs.findbugs.anttask.FindBugsTask” />

<findbugs home=”${findbugs.home}” output=”html” outputFile=”${findbugsdir}/findbugs_report.html”>

<sourcePath path=”${srcdir}” />

<class location=”${projdir}/WorkingArea/Code/cginfo/app.jar” />

</findbugs>

</target>

同样,taskdef定义了findbugs的ant task,需要classpath中增加findbugs的jar包。一个findbugs-ant.jar基本就够了。

findbugs标签,home指定findbug安装目录。

output定义输出样式,一般输出html形式,xml形式也可,但xml形式再用样式转html总是显示不了bug,没仔细查问题发生在哪里。

outputFile指定报告的路径和文件名。

sourcePath指定源代码目录

class指定jar包的目录和文件名。

7.2执行

执行后查看报告即可。但findbugs生成的报告与findbugs eclipse插件检查的结果有些出入,没有查看具体问题发生在哪里。

8.cpd

cpd是pmd的一个组件,用来检查重复代码。

8.1脚本

脚本如下:

<target name=”pmdcpd” depends=”findbugs”>

<taskdef name=”cpd” classname=”net.sourceforge.pmd.cpd.CPDTask” />

<cpd minimumTokenCount=”100″ encoding=”UTF-8″ format=”xml” outputFile=”${cpddir}/cpd.xml”>

<fileset dir=”${srcdir}”>

<include name=”**/*.java” />

</fileset>

</cpd>

</target>

taskdef定义了cpd的ant任务,这与pmd任务是分开的。classpath中需要增加pmd解压目录的lib目录下的所有jar包。

cpd标签的minimumTokenCount指定了最小重复行数,当重复函数大于这个量时产生报告。

encoding指定文件编码。

format指定输出文件格式。

outputFile指定输出文件路径和文件名称。

include指定检查的文件。可以使用exclude指定剔除的文件。

8.2执行

执行后查看输出文件,虽然是xml格式,但使用IE打开很容易查看,由于只能使用xslt结合xml形式生成html,且初步试验,生成html文件有点问题,就放弃了。

9.完整脚本

完整脚本如下:

<?xml version=”1.0″ encoding=”utf-8″?>

<project name=”myProject” default=”start”>

<!– 变量定义 –>

<property name=”cvsroot” value=”:pserver:wangjianxuan:password@scm1.domain.com:/repository/opermanage” />

<property name=”projdir” value=”D:/work/quality_workspace/AntScript” />

<property name=”srcdir” value=”${projdir}/WorkingArea/Code/cginfo/src/main” />

<property name=”builddir” value=”${projdir}/WorkingArea/Code/cginfo/bin” />

<property name=”checkstyledir” value=”${projdir}/checkstyle” />

<property name=”findbugsdir” value=”${projdir}/findbugs” />

<property name=”findbugs.home” value=”D:/work/quality_workspace/findbugs-1.3.9″ />

<property name=”cpddir” value=”${projdir}/cpd” />

<!– init –>

<target name=”delDir”>

<delete dir=”${builddir}” />

<delete dir=”${checkstyledir}” />

<delete dir=”${findbugsdir}” />

<delete dir=”${cpddir}” />

</target>

<target name=”mkDir” depends=”delDir”>

<mkdir dir=”${builddir}” />

<mkdir dir=”${checkstyledir}” />

<mkdir dir=”${findbugsdir}” />

<mkdir dir=”${cpddir}” />

</target>

<!– checkout –>

<target name=”checkoutlib” depends=”mkDir”>

<cvs cvsroot=”${cvsroot}” package=”WorkingArea/Code/lib” />

</target>

<target name=”checkoutsrc” depends=”checkoutlib”>

<cvs cvsroot=”${cvsroot}” package=”WorkingArea/Code/cginfo” />

</target>

<!– javac –>

<target name=”javac” depends=”checkoutsrc”>

<javac srcdir=”${srcdir}” destdir=”${builddir}” source=”1.6″>

<compilerarg value=”-Xlint:unchecked” />

</javac>

</target>

<!– jar –>

<target name=”jar” depends=”javac”>

<jar destfile=”${projdir}/WorkingArea/Code/cginfo/app.jar” basedir=”${builddir}” />

</target>

<!– checkstyle –>

<target name=”checkstyle” depends=”jar”>

<taskdef resource=”checkstyletask.properties” />

<checkstyle config=”lib/check_rules.xml” failureProperty=”checkstyle.failure” failOnViolation=”false”>

<formatter type=”xml” tofile=”checkstyle/checkstyle_report.xml” />

<fileset dir=”${srcdir}” includes=”**/*.java” />

</checkstyle>

<style in=”checkstyle/checkstyle_report.xml” out=”checkstyle/checkstyle_report.html” style=”lib/checkstyle-frames.xsl” />

</target>

<!– findbugs –>

<target name=”findbugs” depends=”checkstyle”>

<taskdef name=”findbugs” classname=”edu.umd.cs.findbugs.anttask.FindBugsTask” />

<findbugs home=”${findbugs.home}” output=”html” outputFile=”${findbugsdir}/findbugs_report.html”>

<sourcePath path=”${srcdir}” />

<class location=”${projdir}/WorkingArea/Code/cginfo/app.jar” />

</findbugs>

</target>

<!– pmd cpd –>

<target name=”pmdcpd” depends=”findbugs”>

<taskdef name=”cpd” classname=”net.sourceforge.pmd.cpd.CPDTask” />

<cpd minimumTokenCount=”100″ encoding=”UTF-8″ format=”xml” outputFile=”${cpddir}/cpd.xml”>

<fileset dir=”${srcdir}”>

<include name=”**/*.java” />

</fileset>

</cpd>

</target>

<!– run –>

<target name=”start” depends=”pmdcpd”>

</target>

</project>



版权声明:本文为jianxuan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。