Java代码质量检查工具及使用案例
在现在的软件开发中,由于软件的复杂度越来越高,业务也覆盖很广,各个业务模块业务错综复杂。这样就需要我们需要团队开发,在我们团队中开发人员的经验、代码风格样式都不一致,以及缺乏统一的标准,从而导致我们的整个项目的的代码难以阅读,不便于后期维护。这几天在研究代码质量管理,根据在网上搜集的资料及跟前辈学的一点经验整理一下,有需要的同学们可以查看,也便于以后自己回顾。
主要对下面的七块进行分析
编码格式规范
代码重复
代码覆盖率
依赖项分析
复杂度监控
Java模拟技术
代码评审和重构
接下来的使用Eclipse 插件来揭示这些分析领域:
编码格式规范:codeStyle和CheckStyle
代码重复:PMD 的 CPD
代码覆盖率:Coverlipse或者Emma
依赖项分析:JDepend
复杂度监控:Metrics
Java模拟技术:EasyMock、PowerMock
代码评审和重构:Jupiter
编码格式规范
codestyle介绍
统一的代码规范能提高代码的可读性、可维护性。
一般规则和格式规范:如代码缩进、程序块规范、每行最大代码长度;
命名规范:如包名、类名、接口名、枚举、属性名、方法名、参数名等命名规则;
文档规范:如类文件头注释、变量注释、方法注释等;
编程规范:如异常、并发、多线程等;
其它规范:如日志格式等。
图1
可以导入代码格式,实现统一。
checkstyle介绍
安装checkstyle 的Eclipse插件
1. 下载地址:
http://pan.baidu.com/s/1o6LOSwM
2. 解压net.sf.eclipsecs-updatesite_5.6.1.201306282206-bin.zip文件,到系统路径下。如:D:\geyouchao\eclipse-plugins\cs(注:一定不用起名为checkstyle,不知道为什么此名就是安装不成功),此文件夹下有两个文件夹features、plugins。
3. 我们使用link的方式安装。在Eclipse的dropins文件夹下新建checkstyle.link文件,内容为:
path=D:\\geyouchao\\eclipse-plugins\\cs
4. 关闭Eclipse,重启。然后在Eclipse的window》Preferences下就可以看到checkstyle菜单,安装成功,如下图
图2
使用checkstyle
自定义CheckStyle规则,下面是我定义的CheckStyle模板,然后导入
图3
把新导入的,设置为默认
图4
可以修改其中的值,点击“Configure…”按钮。
图5
下面是我自定义的CheckStyle.xml文件,供参考。
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//PuppyCrawl//DTD Check Configuration 1.2//EN""http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<module name="Checker">
<propertyname="severity" value="warning"/>
<property name="charset" value="UTF-8"/>
<!-- 长度方面的检查 -->
<!-- 文件长度不超过1500行 -->
<module name="FileLength">
<property name="max" value="1500" />
</module>
<module name="TreeWalker">
<!-- javadoc的检查 -->
<!-- 检查所有的interface和class -->
<module name="JavadocType" />
<!-- 命名方面的检查 -->
<!-- 局部的final变量,包括catch中的参数的检查 -->
<module name="LocalFinalVariableName" />
<!-- 局部的非final型的变量,包括catch中的参数的检查 -->
<module name="LocalVariableName" />
<!-- 包名的检查(只允许小写字母) -->
<module name="PackageName">
<property name="format"value="^[a-z]+(\.[a-z][a-z0-9]*)*$" />
</module>
<!--仅仅是static型的变量(不包括static final型)的检查 -->
<module name="StaticVariableName" />
<!-- 类型(Class或Interface)名的检查 -->
<module name="TypeName" />
<!-- 非static型变量的检查 -->
<module name="MemberName" />
<!-- 方法名的检查 -->
<module name="MethodName" />
<!-- 方法的参数名 -->
<modulename="ParameterName " />
<!-- 常量名的检查 -->
<module name="ConstantName" />
<!-- 没用的import检查,比如:1.没有被用到2.重复的3.import java.lang的4.import 与该类在同一个package的 -->
<module name="UnusedImports" />
<!-- 每行不超过150个字-->
<module name="LineLength">
<property name="max" value="150" />
</module>
<!-- 方法不超过150行 -->
<module name="MethodLength">
<property name="tokens" value="METHOD_DEF"/>
<property name="max"value="150" />
</module>
<!-- 方法的参数个数不超过5个。并且不对构造方法进行检查-->
<module name="ParameterNumber">
<property name="max" value="5" />
<property name="tokens" value="METHOD_DEF"/>
</module>
<!-- 空格检查 -->
<!-- 允许方法名后紧跟左边圆括号"(" -->
<module name="MethodParamPad" />
<!-- 在类型转换时,不允许左圆括号右边有空格,也不允许与右圆括号左边有空格 -->
<module name="TypecastParenPad" />
<!-- 关键字 -->
<!--
每个关键字都有正确的出现顺序。比如 public static final XXX 是对一个常量的声明。如果使用 static
public final 就是错误的
-->
<module name="ModifierOrder" />
<!-- 多余的关键字 -->
<module name="RedundantModifier" />
<!-- 对区域的检查 -->
<!-- 不能出现空白区域 -->
<module name="EmptyBlock" />
<!-- 所有区域都要使用大括号 -->
<module name="NeedBraces" />
<!-- 多余的括号 -->
<module name="AvoidNestedBlocks">
<property name="allowInSwitchCase" value="true"/>
</module>
<!-- 编码方面的检查 -->
<!-- 不许出现空语句 -->
<module name="EmptyStatement" />
<!-- 不允许魔法数 -->
<module name="MagicNumber">
<property name="tokens"value="NUM_DOUBLE, NUM_INT" />
</module>
<!-- 多余的throw -->
<module name="RedundantThrows" />
<!-- String的比较不能用!= 和 == -->
<module name="StringLiteralEquality" />
<!-- if最多嵌套3层 -->
<module name="NestedIfDepth">
<property name="max" value="3" />
</module>
<!-- try最多被嵌套2层 -->
<module name="NestedTryDepth">
<property name="max" value="2" />
</module>
<!-- clone方法必须调用了super.clone() -->
<module name="SuperClone" />
<!-- finalize 必须调用了super.finalize() -->
<module name="SuperFinalize" />
<!-- 不能catch java.lang.Exception -->
<module name="IllegalCatch">
<property name="illegalClassNames"value="java.lang.Exception" />
</module>
<!-- 确保一个类有package声明 -->
<module name="PackageDeclaration" />
<!-- 一个方法中最多有3个return -->
<modulename="ReturnCount">
<property name="max" value="3" />
<property name="format" value="^$" />
</module>
<!--
根据 Sun 编码规范, class 或 interface 中的顺序如下: 1.class 声明。首先是 public,
然后是protected , 然后是 package level (不包括access modifier )最后是private .
(多个class放在一个java文件中的情况) 2.变量声明。首先是 public, 然后是protected然后是 package
level (不包括access modifier )最后是private . (多个class放在一个java文件中的情况)
3.构造函数 4.方法
-->
<module name="DeclarationOrder" />
<!-- 同一行不能有多个声明 -->
<module name="MultipleVariableDeclarations" />
<!-- 不必要的圆括号 -->
<module name="UnnecessaryParentheses" />
<!-- 检查并确保所有的常量中的L都是大写的。因为小写的字母l跟数字1太象了 -->
<module name="UpperEll" />
<!-- 检查数组类型的定义是String[] args,而不是String args[] -->
<module name="ArrayTypeStyle" />
<!-- 检查java代码的缩进默认配置:基本缩进4个空格,新行的大括号:0。新行的case 4个空格
<module name="Indentation" /> -->
</module>
</module>
下面是使用CheckStyle检查过得代码
图6
常见错误分析
常见的CheckStyle错误有这些:
1.Type is missing a javadoc commentClass
缺少类型说明
2.“{” should be on the previous line
“{” 应该位于前一行
3.Methods is missing a javadoc comment
方法前面缺少javadoc注释
4.Expected @throws tag for “Exception”
在注释中希望有@throws的说明
5.“.” Is preceeded with whitespace “.”
前面不能有空格
6.“.” Is followed by whitespace“.”
后面不能有空格
7.“=” is not preceeded with whitespace
“=” 前面缺少空格
8.“=” is not followed with whitespace
“=” 后面缺少空格
9.“}” should be on the same line
“}” 应该与下条语句位于同一行
10.Unused @param tag for “unused”
没有参数“unused”,不需注释
11.Variable “CA” missing javadoc
变量“CA”缺少javadoc注释
12.Line longer than 80characters
行长度超过80
13.Line contains a tab character
行含有”tab” 字符
14.Redundant “Public” modifier
冗余的“public”modifier
15.Final modifier out of order with the JSL
suggestionFinal modifier的顺序错误
16.Avoid using the “.*” form of import
Import格式避免使用“.*”
17.Redundant import from the same package
从同一个包中Import内容
18.Unused import-java.util.list
Import进来的java.util.list没有被使用
19.Duplicate import to line 13
重复Import同一个内容
20.Import from illegal package
从非法包中 Import内容
21.“while” construct must use “{}”
“while” 语句缺少“{}”
22.Variable “sTest1” must be private and haveaccessor method
变量“sTest1”应该是private的,并且有调用它的方法
23.Variable “ABC” must match pattern“^[a-z][a-zA-Z0-9]*$”
变量“ABC”不符合命名规则“^[a-z][a-zA-Z0-9]*$”
24.“(” is followed by whitespace
“(” 后面不能有空格
25.“)” is proceeded by whitespace
“)” 前面不能有空格
代码重复
PMD介绍
安装PMD 的Eclipse插件
1. 下载地址:
http://jingyan.baidu.com/article/19192ad835de6ee53e57073c.html
2. 解压net.sourceforge.pmd.eclipse-3.2.6.v200903300643.zip文件,到系统路径下。如:D:\geyouchao\eclipse-plugins\pmd,此文件夹下有两个文件夹features、plugins。
3. 我们使用link的方式安装。在Eclipse的dropins文件夹下新建pmd.link文件,内容为:
path=D:\\geyouchao\\eclipse-plugins\\pmd
4. 关闭Eclipse,重启。然后在Eclipse的window》Preferences下就可以看到PMD菜单,安装成功,如下图
图7
使用PMD
图8
下面是PCD生成的重复代码,可以对其中的代码进行分析,修改
图9
代码覆盖率
Coverlipse介绍
安装coverlipse 的Eclipse插件
1. 下载地址:
https://sourceforge.net/projects/coverlipse/files/Coverlipse/
下载下图中5个文件
图10
1. 解压coverlipse-0.9.6.zip文件,到系统路径下。如:D:\geyouchao\eclipse-plugins\ coverlipse-0.9.6,此文件夹下有两个文件夹features、plugins。
2. 我们使用link的方式安装。在Eclipse的dropins文件夹下新建coverlipse.link文件,内容为:
path=D:\\geyouchao\\eclipse-plugins\\coverlipse
3. 关闭Eclipse,重启。右键java代码,点击dubug,安装成功,如下图
图11
使用coverlipse
Wait..
Emma介绍
Wait…
依赖项分析
jdepend介绍
安装jdepend 的Eclipse插件
1. 下载地址:
http://andrei.gmxhome.de/jdepend4eclipse/links.html
图12
2. 拷贝de.loskutov.eclipse.jdepend_1.2.4.201406241900.jar文件,到Eclipse的dropins目录下。如:D:\geyouchao\eclipse4.2-xu\dropins。
3. 关闭Eclipse,重启。通过右键单击源文件夹并选择 Run JDepend Analysis。一定要选择一个含源代码的源文件夹;否则看不到此菜单项。
图13
使用jdepend
图14
下面对jdepend的分析的结果简单介绍
图15
1. Selected objects():选择分析的包
2. Package:包全路径
3. CC(concr.cl.):当前行对应包的具体类的数量。
4. AC(abstr.cl.):当前行对应包的抽象类和接口的数量。
5. Ca(aff.):依赖于被分析package的其他package的数量,用于衡量pacakge的职责。即有多少包调用了它。(AfferentCouplings)
6. Ce(eff.):被分析package的类所依赖的其他package的数量,用于衡量package的独立性。即它调用了多少其他包。(EfferentCouplings)
7. A:被分析package中的抽象类和接口与所在package所有类数量的比例,取值范围为0-1。(Abstractness )
8. I:I=Ce/(Ce+Ca),用于衡量package的不稳定性,取值范围为0-1。I=0表示最稳定,I=1表示最不稳定。即如果这个类不调用任何其他包,则它是最稳定的。(Instability )
9. D:分析package和理想曲线A+I=1的垂直距离,用于衡量package在稳定性和抽象性之间的平衡。(Distance)
理想的package要么完全是抽象类和稳定(x=0,y=1),要么完全是具体类和不稳定(x=1,y=0)。取值范围为0-1,
D=0表示完全符合理想标准,
D=1表示package最大程度地偏离了理想标准。即你的包要么全是接口,不调用任何其他包(完全是抽象类和稳定),要么是具体类,不被任何其他包调用。
10. Cycle!:循环依赖
11. Package with cycle:包与包直接有循环调用
12. Depends upon-efferentdependencies:依赖的包
13. Used by-afferentdependencies:被引用的包
图16
Instability:不稳定
Abstractness:抽象性
问题分析
图17
针对上图中Cycle!列中有感叹号图标问题,是因为以上三个包中的类有传递依赖,故出现此警告。
解决办法:
把其中的某个或者某些类再单独抽出新包,解决此问题。
参考资料
1. http://www.ibm.com/developerworks/cn/java/j-ap01117/
2. http://www.clarkware.com/software/JDepend.html
复杂度监控
metrics(量度)介绍
安装metrics 的Eclipse插件
1. 下载地址:
https://sourceforge.net/projects/metrics/
图18
图19
图20
图21
2. 解压updatesite_1.3.6.zip文件,net.sourceforge.metrics.updatesite文件夹下有features和plugins分别拷贝到Eclipse的对应目录下。如:D:\geyouchao\eclipse4.2-xu\plugins等。
3. 关闭Eclipse,重启。然后在Eclipse的window》Preferences下就可以看到Metrics Preferences菜单,安装成功,如下图
图22
设置metrics参数
下图是metrics提供的配置项,下面对各个配置项进行解释
图23
number of overridden methods | 重载方法的数量 |
number of attributes | 属性的数量 |
number of children | 子类的数量 |
number of classes | 类的数量 |
method lines of code | 代码的方法行 |
number of methods | 方法的数量 |
nested block depth | 块嵌套深度 |
depth of inheritance tree | 继承树的深度 |
number of packages | 包的数量 |
afferent coupling | 传入耦合 |
number of Interfaces | 接口的数量 |
mccabe cyclomatic complexity | McCabe圈复杂度 |
total lines of code | 代码的总行数 |
instability | 不稳定 |
number of parameters | 参数的数量 |
lack of cohesion of methods | 方法缺乏凝聚力 |
efferent coupling | 传出耦合 |
number of static methods | 静态方法数 |
normalized distance | 正常距离 |
abstractness | 抽象性 |
specialization index | 专业化指数 |
weighted methods per class | 每类的加权方法 |
number of static attributes | 静态属性数 |
lack of cohesion of methods:
介于0-1之间,0表示最有凝聚力,1表示完全没有凝聚力。
1)如果所有方法都使用所有的实例字段,一个类是完全有凝聚力的
2)静态方法和实例方法计数,它还包括构造函数、属性的getter和setter,所有方法。
在Since Sonar 4.1中此量度已经被删除。
3) 子的数量NOC(Number ofchildren)子类在类的层次内,子类可以最直接地从属于一类。随着子类数量的增大,重用也增加了。但父类抽象的表示可能减少,即一些子类可能不是父类真正的成员,同时,测试数量(用来检查每个子类在操作前后的要求)也将增加。
4) 方法中聚合的不足LCOM(Lack ofcohesion in Methods)
一个类内的每种方法访问一个或多个属性(也称实例变量)。LCOM是访问一个或多个相同属性方法的数量
如果LCOM很大,则说明方法可以通过属性与其他方法耦合,这就增加了类设计的复杂性。通常,对LCOM值很大的类,可以把它分为两个或多个单独的类,这样每个类能的设计更方便。
这里讲的耦合和聚合与传统软件中讲的是一样的。我们希望高聚合和低耦合,即保持低的LCOM.但在某些时候,LCOM很大也是合理的。
5)每个类的加权方法WMC(Weighted Methodsper Class)
6)Out-of-range:超出范围,溢出
下面是metric的安全范围设置,在此页面中可以设置每项指标的安全范围。若警告启用,指标值超出我们设置的安全范围就好发出警告。
图24
使用metrics
1. 右键单击您的项目并选择 Properties 菜单。在结果窗口中,选择 EnableMetrics plugin 复选框并单击 OK:
图25
2. 从 Eclipse 中选择 Window 菜单打开 Metrics 视图,然后选择 Show View | Other...。
3. 选择 Metrics | Metrics View 打开如图 13 中显示的窗口。您需要使用Java 透视图并重新构建项目,从而显示这些度量值。
图26
注:一定要重新构建项目
图27
Java 模拟技术(mock)
Mock是什么
Mock通常是指,在测试一个对象A时,我们构造一些假的对象来模拟与A之间的交互,而这些Mock对象的行为是我们事先设定且符合预期。通过这些Mock对象来测试A在正常逻辑,异常逻辑或压力情况下工作是否正常。
引入Mock最大的优势在于:Mock的行为固定,它确保当你访问该Mock的某个方法时总是能够获得一个没有任何逻辑的直接就返回的预期结果。
Mock Object的使用通常会带来以下一些好处:
隔绝其他模块出错引起本模块的测试错误。
隔绝其他模块的开发状态,只要定义好接口,不用管他们开发有没有完成。
一些速度较慢的操作,可以用MockObject代替,快速返回。
对于分布式系统的测试,使用Mock Object会有另外两项很重要的收益:
通过Mock Object可以将一些分布式测试转化为本地的测试
将Mock用于压力测试,可以解决测试集群无法模拟线上集群大规模下的压力
mock技术的目的和作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。
Mock应用场景
在使用Mock的过程中,发现Mock是有一些通用性的,对于一些应用场景,是非常适合使用Mock的:
真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
真实对象很难被创建(比如具体的web容器)
真实对象的某些行为很难触发(比如网络错误)
真实情况令程序的运行速度很慢
真实对象有用户界面
测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)
当然,也有一些不得不Mock的场景:
一些比较难构造的Object:这类Object通常有很多依赖,在单元测试中构造出这样类通常花费的成本太大。
执行操作的时间较长Object:有一些Object的操作费时,而被测对象依赖于这一个操作的执行结果,例如大文件写操作,数据的更新等等,出于测试的需求,通常将这类操作进行Mock。
异常逻辑:一些异常的逻辑往往在正常测试中是很难触发的,通过Mock可以人为的控制触发异常逻辑。
在一些压力测试的场景下,也不得不使用Mock,例如在分布式系统测试中,通常需要测试一些单点(如namenode,jobtracker)在压力场景下的工作是否正常。而通常测试集群在正常逻辑下无法提供足够的压力(主要原因是受限于机器数量),这时候就需要应用Mock去满足。
在这些场景下,我们应该如何去做Mock的工作了,一些现有的Mock工具可以帮助我们进行Mock工作。
EasyMock应用
Easymock官网:
EasyMock 是早期比较流行的MocK测试框架。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。通过 EasyMock,我们可以方便的构造 Mock 对象从而使单元测试顺利进行。
EasyMock 是采用 MIT license 的一个开源项目
使用EasyMock大致可以划分为以下几个步骤:
① 使用 EasyMock 生成 Mock 对象;
② 录制 Mock 对象的预期行为和输出;
③ 将 Mock 对象切换到 播放 状态;
④ 调用 Mock 对象方法进行单元测试;
⑤ 对 Mock 对象的行为进行验证。
mockito应用
mockito官网
https://code.google.com/p/mockito/
由于官网上不了,可以到csdn上下载
http://download.csdn.net/download/wjjiang917/5519381
是EasyMock之后流行的mock工具。相对EasyMock学习成本低,而且具有非常简洁的API,测试代码的可读性很高。
使用mockito大致可以划分为以下几个步骤:
① 使用 mockito 生成 Mock 对象;
② 定义(并非录制) Mock 对象的行为和输出(expectations部分);
③ 调用 Mock 对象方法进行单元测试;
④ 对 Mock 对象的行为进行验证。
PowerMock应用
PowerMock官网
https://code.google.com/p/powermock/
这个工具是在EasyMock和Mockito上扩展出来的,目的是为了解决EasyMock和Mockito不能解决的问题,比如对static, final, private方法均不能mock。其实测试架构设计良好的代码,一般并不需要这些功能,但如果是在已有项目上增加单元测试,老代码有问题且不能改时,就不得不使用这些功能了。
PowerMock 在扩展功能时完全采用和被扩展的框架相同的API, 熟悉 PowerMock 所支持的模拟框架的开发者会发现 PowerMock 非常容易上手。PowerMock 的目的就是在当前已经被大家所熟悉的接口上通过添加极少的方法和注释来实现额外的功能。目前PowerMock 仅扩展了 EasyMock 和 mockito,需要和EasyMock或Mockito配合一起使用。
代码评审(review)和重构
Jupiter
Wait…
参考资料
2. http://www.ibm.com/developerworks/cn/java/j-ap01117/
3. http://www.blogjava.net/askcuix/archive/2009/02/08/253775.html
4. http://www.cnblogs.com/sunzhenchao/archive/2013/06/14/3136140.html
5. http://tieba.baidu.com/p/4304901637
6. http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/
7. http://www.ibm.com/developerworks/cn/java/j-easymock.html
8. http://www.cnblogs.com/huangbin/archive/2013/04/27/3047671.html
9.