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

 

 写在前面

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

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:[email protected]:/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:[email protected]:/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>

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(eclipse,Module,jar,脚本,cvs,工具)