写在前面
好吧,我承认,我懒了,写了一天文档,到这里直接粘贴了,希望大家能看懂,如果需要一份格式完整的文档,请去我的百度文库下载,地址是:
http://wenku.baidu.com/view/d2849ff04693daef5ef73d34.html
下面,开始
最近要跟踪一个项目的代码质量,保障项目质量。个人总是认为,质量保障这个东西要用数据说话的。代码走查是一个方面,但要能使用工具完成部分标准化的代码走查,发现部分错误,也不失为一种不错的补充手段。结合之前自己用过、听过的几种开源工具,整合到一起,实现一个适合目前项目的简单工具。
首先是工具的选择,经过多个工具的使用结合目前项目组成员的水平和状态,决定从几个方面进行检查:
常规bug
编码规范
重复代码
不想检查太多,以前没有开展过这方面的工作,领导是否支持,项目成员反响是否良好都很难说,如果大家都喜欢再完善更多的检查也不晚。
其实还差一个依赖的检测,但现在项目使用的是ssh的架构,现有依赖检查的工具中,对spring的支持都不是很好,遂放弃。
对于工具的选择,紧着自己熟悉的就选择了checkstyle、findbugs和pmd的cpd工具。
使用ant脚本,一步完成所有工作,针对项目直接产生检查报告。
ant、checkstyle、findbugs、pmd下载。还要下载cvs和eclipse,因为脚本中需要使用cvs更新代码,eclipse会让ant脚本的编辑和环境配置更加简单。
规划脚本,整个脚本分为几个部分,如下:
<?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功能检查重复代码。
变量定义和环境初始化部分,在需要时随时添加,首先是checkout过程,将代码检出cvs库。
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任务是初始化部分的,为了建立相关的目录,后面有详细描述。
eclipse中执行ant就是有这点好,缺少什么不用去修改环境变量,eclipse内部就解决了。执行前有几个地方需要确认:
脚本上点右键-》Run As-》Ant Build…
注意,一定是带有省略号的菜单,这个菜单才能配置环境,打开界面如下图:
这里Targets可以选择要执行的任务,执行时会连依赖的任务一起执行。ClassPath定义依赖的类库,JRE用来定义JRE的版本,Environment标签很重要,可以定义一些环境变量中未定义的变量,这里我因为一直没有找到cvs路径,但命令行中能够找到cvs,所以把Path环境变量复制定义到这里了,如下图:
脚本执行后,会将代码检出到工程目录下的“WorkingArea/Code/project”位置。
注意:环境变量和类路径等内容的设置在更改ant脚本名称后会丢失,需要重新设置。
ant调用cvs网上文章较少,问题的处理就更少了,也许是太简单了吧,但我还是碰到了一些问题。
首先,如果path路径中没有增加cvs路径,或者你没有安装cvs(windows系统需要安装cvsnt),找不到cvs路径,将报错。所以保证命令行中任意路径下调用“cvs”命令可以成功,如果还报错,可以将“path”环境变量增加到eclipse中。
另外,按照ant的cvs任务说明,cvs密码应该使用cvspass任务生成cvspass文件,在cvs任务中使用cvspass标签调用这个文件。但是,我在eclipse中这么做不会产生文件,同时cvs checkout会报告访问被拒绝(很明白,没密码)。处理办法就是将密码加在cvsroot属性中,在用户名后,“@”符号前增加“:密码”即可。
javac任务将刚刚检出的代码进行编译,编译后的代码放到bin目录下。
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”声明项目目录。
简单执行javac脚本可能会出现问题,需要使classpath包含类路径。同样,脚本上右键-》Run As-》Ant Build…打开窗口中选择Classpath页签,如下:
这里要注意,
1. 只有选择“User Entries”节点时,添加jar包的按钮才有效。
2. 不知道是不是我使用的问题,Add Folders添加目录后,并不能把目录下所有jar包都添加到类路径来,只能将所有jar包摊在这里。
编译后就是打包了,见下节。
与javac同样,由于findbugs的需要,将javac编译后的代码打包
<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包。
终于进入正题了,准备工作完成,可以开始检查了,首先是checkstyle检查并且生成报告。
检查脚本如下:
<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目录,目录中包含部分检查结果。
脚本如下:
<property name="checkstyledir" value="${projdir}/checkstyle" /> |
由于每次检查代码前要将之前的检查结果删除,保证结果是最新的。同时要保证文件目录存在,否则写报告时报错。所以有如下脚本:
<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。
脚本执行后会在checkstyledir目录下建立checkstyle_report.xml和checkstyle_report.html文件
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>
|
使用findbugs检查常见bug
脚本如下:
<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包的目录和文件名。
执行后查看报告即可。但findbugs生成的报告与findbugs eclipse插件检查的结果有些出入,没有查看具体问题发生在哪里。
cpd是pmd的一个组件,用来检查重复代码。
脚本如下:
<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指定剔除的文件。
执行后查看输出文件,虽然是xml格式,但使用IE打开很容易查看,由于只能使用xslt结合xml形式生成html,且初步试验,生成html文件有点问题,就放弃了。
完整脚本如下:
<?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>
|