您当前的位置:首页 > 计算机 > 软件应用 > 开发工具(IDE)

详解CheckStyle的检查规则(共138条规则)

时间:02-06来源:作者:点击数:

本文主要介绍CheckStyle 5.6.1版本的各个规则配置项目,这个版本的CheckStyle将样式规则分为了以下十七种类型:

1. Annotations(注解:5个)

  • Annotation Use Style(注解使用风格)
这项检查可以控制要使用的注解的样式。
  • Missing Deprecated(缺少deprecad)
检查java.lang.Deprecated注解或@deprecated的Javadoc标记是否同时存在。
  • Missing Override(缺少override)
当出现{@inheritDoc}的Javadoc标签时,验证java.lang.Override注解是否出现。
  • Package Annotation(包注解)
这项检查可以确保所有包的注解都在package-info.java文件中。
  • Suppress Warnings(抑制警告)

这项检查允许你指定不允许SuppressWarnings抑制哪些警告信息。你还可以指定一个TokenTypes列表,其中包含了所有不能被抑制的警告信息。

2. Javadoc Comments(Javadoc注释:6个)

  • Package Javadoc(包注释)
检查每个Java包是否都有Javadoc注释。默认情况下,它只允许使用一个package-info.java文件,但是可以将其配置为允许使用package.html文件。
如果两个文件都存在的话,就会报告错误,因为Javadoc工具不允许这种情况发生。
  • Method Javadoc(方法注释)

检查方法或构造器的Javadoc。默认不会检查未使用的throws。想要将未声明的java.lang.RuntimeExceptions也纳入文档,则需要将allowUndeclaredRTE属性设置为true。想要验证的可见范围是由scope属性指定的,默认值为Scope.PRIVATE。想要验证其他的可见范围,则需要将scope属性设置为相应的值。

如果不想显示由于参数或类型参数没有在注释中使用param标记进行说明而产生的错误消息,就需要勾选allowMissingParamTags属性。如果不想显示由于声明了抛出异常但没有在注释中使用throws标记进行说明而产生的错误消息,就需要勾选allowMissingThrowsTags属性。如果不想显示由于return语句是非void类型但没有在注释中使用return标记进行说明而产生的错误消息,就需要勾选allowMissingReturnTag属性。

由@Override注解标记的方法,它的Javadoc不是必需的。但是在Java 5下,无法为接口中的方法加上标记(已经在Java 6中修正)。因此,CheckStyle支持使用单个的{@inheritDoc}标记来代替所有其他的标记。例如,如果下面的方法实现了接口中的某个方法,那么Javadoc可以这样编写:

/** {@inheritDoc} */

public int checkReturnTag(final int aTagIndex,

JavadocTag[] aTags,

int aLineNo)

  • Style Javadoc(风格注释)
验证Javadoc注释,以便于确保它们的格式。可以检查以下注释:接口声明、类声明、方法声明、构造器声明、变量声明。
  • Type Javadoc(类型注释)
检查方法或构造器的Javadoc。默认不会检查author和version标记。想要验证的可见范围是由scope属性指定的,默认值为Scope.PRIVATE。想要验证其他的可见范围,则需要将scope属性设置为相应的值。想要为author标记或version标记定义格式,将authorFormat属性或versionFormat属性设置为指定的正则表达式即可。
如果不想显示由于类型参数没有在注释中使用param标记进行说明而产生的错误消息,就需要勾选allowMissingParamTags属性。
  • Variable Javadoc(变量注释)
检查变量是否具有Javadoc注释。
  • Write Tag(输出标记)

将Javadoc作为信息输出。可以作为样式表来使用,根据作者名排序报告。想要为一个标记定义格式,将tagFormat属性设置为相应的正则表达式即可。这项检查会使用两种不同的严重级别。当标记缺失时,使用标准的严重级别(Severity)。当标记存在时,使用附加的严重级别(tagSeverity)作为报告等级。

3. Naming Conventions(命名规约:12个)

  • Abstract Class Name(抽象类名称)
检查抽象类的名称是否遵守命名规约。
  • Class Type Parameter Name(类的类型参数名称)
检查类的类型参数名称是否遵守命名规约。
  • Constant Names(常量名称)
检查常量(用static final修饰的字段)的名称是否遵守命名规约。
  • Local Final Variable Names(局部final变量名称)
检查局部final变量的名称是否遵守命名规约。
  • Local Variable Names(局部变量名称)
检查局部变量的名称是否遵守命名规约。
  • Member Names(成员名称)
检查成员变量(非静态字段)的名称是否遵守命名规约。
  • Method Names(方法名称)
检查方法名称是否遵守命名规约。
  • Method Type Parameter Name(方法的类型参数名称)
检查方法的类型参数名称是否遵守命名规约。
  • Package Names(包名称)
检查包名称是否遵守命名规约。
  • Parameter Names(参数名称)
检查参数名称是否遵守命名规约。
  • Static Variable Names(静态变量名称)
检查静态变量(用static修饰,但没用final修饰的字段)的名称是否遵守命名规约。
  • Type Names(类型名称)
检查类的名称是否遵守命名规约。

4. Headers(文件头:2个)

  • Header(文件头)
检查源码文件是否开始于一个指定的文件头。headerFile属性可以指定一个文件,该文件包含了需要的文件头。还有另一种方法,文件头的内容可以直接在header属性中设置,而不使用外部文件。
ignoreLines属性可以设置行号,指定检查时忽略头文件中的哪些行。如果想要支持包含版权日期的文件头,那么这个属性就显得非常有用。例如,考虑下面的文件头:
line 1:
line 2: // checkstyle:
line 3: // Checks Java source code for adherence to a set of rules.
line 4: // Copyright (C) 2002 Oliver Burn
line 5:
因为年份信息会随着时间改变,通过将ignoreLines属性设置为4,你就可以告诉CheckStyle忽略第4行。
  • Regular Expression Header(正则表达式文件头)

检查Java源码文件头部的每行是否匹配指定的正则表达式。

解释:在某些项目中,检查固定的头部是不足够的,例如,文件头可能需要一行版权信息,但是其中的年份信息会随着时间变化。

例如,考虑下面的文件头:

line 1: ^/{71}$

line 2: ^// checkstyle:$

line 3: ^// Checks Java source code for adherence to a set of rules\.$

line 4: ^// Copyright \(C\) \d\d\d\d Oliver Burn$

line 5: ^// Last modification by \$Author.*\$$

line 6: ^/{71}$

line 7:

line 8: ^package

line 9:

line 10: ^import

line 11:

line 12: ^/\*\*

line 13: ^ \*([^/]|$)

line 14: ^ \*/

第1行和第6行说明了如何更紧凑地表示71个'/'符号。第4行表示版权信息中的年份的格式是四位数字。第5行示范了如何在文件头部添加校正控制关键字。第12-14行是Javadoc的模板(第13行非常复杂,它可以抑制Javadoc注释中的冲突)。

5. Imports(导入:7个)

  • Avoid Star (Demand) Imports(避免通配符导入)

检查没有import语句使用*符号。

解释:从一个包中导入所有的类会导致包之间的紧耦合,当一个新版本的库引入了命名冲突时,这样就有可能导致问题发生。

  • Avoid Static Imports(避免静态导入)

检查没有静态导入语句。

解释:导入静态成员可能会导致类成员之间的命名冲突。导入静态成员可能会导致代码的可读性很差,因为读者可能会搞不清楚成员到底位于哪个类中。

  • Illegal Imports(非法导入)
检查是否导入了指定的非法包。默认情况下,这项检查会拒绝所有的sun.*包,因为直接使用sun.*包的程序肯定不是100%的纯Java程序。想要拒绝其他的包,将illegalPkgs属性设置为你指定的非法包列表即可。
  • Import Order Check(导入顺序检查)
检查导入包的顺序/分组。确保导入包的分组按照指定的顺序排列(例如,java.排在首位,javax.排在第二,以此类推),并且每个分组内导入的包都是按照字典序排列的。静态导入必须放在最后,并且也是按照字典序排列的。
  • Redundant Imports(多余导入)

检查是否存在多余的导入语句。如果一条导入语句满足以下条件,那么就是多余的:

1. 它是另一条导入语句的重复。也就是,一个类被导入了多次。

2. 从java.lang包中导入类,例如,导入java.lang.String。

3. 从当前包中导入类。

  • Unused Imports(未使用导入)

检查未使用的导入语句。CheckStyle使用一种简单可靠的算法来报告未使用的导入语句。如果一条导入语句满足以下条件,那么就是未使用的:

1. 没有在文件中引用。这种算法不支持通配符导入,例如,java.io.*;。大多数IDE检查带有通配符的导入语句时,使用的算法非常复杂。

2. 它是另一条导入语句的重复。也就是,一个类被导入了多次。

3.从java.lang包中导入类,例如,导入java.lang.String。

4.从当前包中导入类。

5. 可选:在Javadoc注释中引用它。这项检查默认是关闭的,因为仅仅为了抽出文档而引入了一个编译时依赖是一个很坏的习惯。例如,当Javadoc注释中包含{@link Date}时,就会认为import java.util.Date被引用了。想要避免引入编译时依赖,可把Javadoc注释写成{@link java.util.Date}

  • Import Control(导入控制)

控制允许导入每个包中的哪些类。可用于确保应用程序的分层规则不会违法,特别是在大型项目中。

导入控制XML文档所使用的DTD文件位于

http://www.puppycrawl.com/dtds/import_control_1_0.dtd。它包含了上述XML文档的所有元素和属性。

6. Size Violations(尺寸超标:8个)

  • Anonymous inner classes lengths(匿名内部类长度)

检查匿名内部类的长度。

解释:如果一个匿名内部类变得非常长,那么就很难读懂它,并且难以跟踪方法的流程。因此,过长的匿名内部类通常应当被重构为一个具名内部类。可以参考Bloch编写的《Effective Java》的第93页。

  • Executable Statement Size(可执行语句数量)
将可执行语句的数量限制为一个指定的限值。
  • Maximum File Length(最大文件长度)

检查源码文件的长度。

解释:如果一个源码文件变得非常长,那么就会难以理解。因此,过长的类通常应当被重构成若干个较小的独立的类,每个类专注于一个特定的任务。

  • Maximum Line Length(最大行长度)

检查源码每行的长度。

解释:将源码打印出来,或者开发者限制了屏幕显示源码的空间(例如,IDE会显示额外的信息,诸如项目树、类层次等等),那么过长的行会显得难以阅读。

  • Maximum Method Length(最大方法长度)

检查方法和构造器的长度。

解释:如果一个方法变得非常长,那么就会难以理解。因此,过长的方法通常应当被重构成若干个较小的独立的方法,每个方法专注于一个特定的任务。

  • Maximum Parameters(最大参数数量)
检查一个方法或构造器的参数的数量。
  • Outer Type Number(外层类型数量)

检查在一个文件的外层(或根层)中声明的类型的数量。

解释:一般认为给每个文件只定义一个外层类型是较好的做法。

  • Method Count(方法总数)

检查每个类型中声明的方法的数量。

它包括了每种可见范围方法的总数(private、package、protected、public)。

7. Whitespace(空格:12个)

  • Generic Whitespace(范型标记空格)

检查范型标记<和>的周围的空格是否遵守标准规约。这项规约是不可配置的。

例如,以下代码都是合法的:

List x = new ArrayList();

List> y = new ArrayList>();

但是下面的示例代码是不合法的:

List < Integer > x = new ArrayList < Integer > ();

List < List < Integer > > y = new ArrayList < List < Integer > > ();

  • Empty For Initializer Pad(空白for初始化语句填充符)
检查空的for循环初始化语句的填充符,也就是空格是否可以作为for循环初始化语句空位置的填充符。如果代码自动换行,则不会进行检查,正如以下代码所示:
for (
; i < j; i++, j--)
  • Empty For Iterator Pad(空白for迭代器填充符)

检查空的for循环迭代器的填充符,也就是空格是否可以作为for循环迭代器空位置的填充符。如果代码自动换行,则不会进行检查,正如以下代码所示:

for (Iterator foo = very.long.line.iterator();

foo.hasNext();

)

  • No Whitespace After(指定标记之后没有空格)
检查指定标记之后没有空格。若要禁用指定标记之后的换行符,将allowLineBreaks属性设为false即可。
  • No Whitespace Before(指定标记之前没有空格)
检查指定标记之前没有空格。若要允许指定标记之前的换行符,将allowLineBreaks属性设为true即可。
  • Operator Wrap(运算符换行)
检查代码自动换行时,运算符所处位置的策略。nl表示运算符必须在新行中,eol表示运算符必须在当前行的行末。
  • Method Parameter Pad(方法参数填充符)
检查方法定义、构造器定义、方法调用、构造器调用的标识符和参数列表的左圆括号之间的填充符。也就是,如果标识符和左圆括号位于同一行,那么就检查标识符之后是否需要紧跟一个空格。如果标识符和左圆括号不在同一行,那么就报错,除非将规则配置为允许使用换行符。想要在标识符之后使用换行符,将allowLineBreaks属性设置为true即可。
  • Paren Pad(圆括号填充符)
检查圆括号的填充符策略,也就是在左圆括号之后和右圆括号之前是否需要有一个空格。
  • Typecast Paren Pad(类型转换圆括号填充符)
检查类型转换的圆括号的填充符策略。也就是,在左圆括号之后和右圆括号之前是否需要有一个空格。
  • File Tab Character(文件制表符)

检查源码中没有制表符('\t')。

解释:

1. 为了能够更方便地阅读源码,开发者不应当在他们的文本编辑器中配置制表符的宽度。

2. 根据Apache Jakarta的编码标准:在一个分布式开发环境中,当提交的消息被发送到一个邮件列表中时,如果你使用了制表符,那么这些消息会变得几乎不可能阅读。

  • Whitespace After(指定标记之后有空格)
检查指定标记之后是否紧跟了空格。
  • Whitespace Around(指定标记周围有空格)

检查指定标记的周围是否有空格。以下形式的空构造器和方法的代码体(代码块):

public MyClass() {} // 空构造器

public void func() {} // 空方法

可以选择性地从检查策略中排除,通过设置allowEmptyMethods和allowEmptyConstructors属性即可。

8. Regexp(正则表达式:3个)

  • RegexpSingleline(正则表达式单行匹配)

检查单行是否匹配一条给定的正则表达式。可以处理任何文件类型。

解释:这项检查可以作为原型检查使用,能够发现常见的编码坏习惯,例如调用ex.printStacktrace()、System.out.println()、System.exit(),等等。

  • RegexpMultiline(正则表达式多行匹配)

检查多行是否匹配一条给定的正则表达式。可以处理任何文件类型。

解释:这项检查可以作为原型检查使用,能够发现常见的编码坏习惯,例如调用ex.printStacktrace()、System.out.println()、System.exit(),等等。
  • RegexpSingleLineJava(正则表达式单行Java匹配)
这项检查是RegexpSingleline的变种,用于检测Java文件中的单行是否匹配给定的正则表达式。它支持通过Java注释抑制匹配操作。

9. Modifiers(修饰符:2个)

  • Modifier Order(修饰符顺序)

检查代码中的标识符的顺序是否符合《Java Language Specification》中的第8.1.1、8.3.1章节所建议的顺序。正确的顺序应当如下:

1. public

2. protected

3. private

4. abstract

5. static

6. final

7. transient

8. volatile

9. synchronized

10. native

11. strictfp

  • Redundant Modifier(多余修饰符)

在以下部分检查是否有多余的修饰符:

1. 接口和注解的定义;

2. final类的方法的final修饰符;

3. 被声明为static的内部接口声明。

解释:《Java Language Specification》强烈不建议在接口定义中使用“public”和“abstract”来声明方法。

接口中的变量和注解默认就是public、static、final的,因此,这些修饰符也是多余的。

因为注解是接口的一种形式,所以它们的字段默认也是public、static、final的,正如它们的注解字段默认是public和abstract的。

定义为final的类是不能被继承的,因此,final类的方法的final修饰符也是多余的。

10. Blocks(代码块:5个)

  • Avoid Nested Blocks(避免嵌套代码块)

找到嵌套代码块,也就是在代码中无节制使用的代码块。

解释:内嵌代码块通常是调试过程的残留物,它们会使读者产生混淆。

  • Empty Block(空代码块)
检查空代码块。
  • Left Curly Brace Placement(左花括号位置)

检查代码块的左花括号的放置位置。

通过property选项指定验证策略。

若使用eol和nlow策略,则需要考虑maxLineLength属性。

  • Need Braces(需要花括号)
检查代码块周围是否有大括号,可以检查do、else、if、for、while等关键字所控制的代码块。
  • Right Curly Brace Placement(右花括号位置)

检查else、try、catch标记的代码块的右花括号的放置位置。

通过property选项指定验证策略。

11. Coding Problems(编码问题:43个)

  • Avoid Inline Conditionals(避免内联条件语句)

检测内联条件语句。内联条件语句的一个示例如下所示:

String a = getParameter("a");

String b = (a==null || a.length<1) ? null : a.substring(1);

解释:有些开发者发现内联条件语句很难读懂,因此他们公司的编码标准会禁止使用内联条件语句。

  • Covariant Equals(共变equals方法)

检查定义了共变equals()方法的类中是否同样覆盖了equals(java.lang.Object)方法。这项检查受到FindBugs的启发。

解释:错误地定义了一个共变equals()方法,而没有覆盖equals(java.lang.Object)方法,可能会产生不可预料的运行时行为。

  • Default Comes Last(默认分支置于最后)

检查switch语句中的default是否在所有的case分支之后。

解释:Java允许default位于switch语句中的任何地方。但是,如果default位于最后一个case分支之后,那么代码的可读性会更强。

  • Declaration Order Check(声明顺序检查)

根据Java编程语言的编码规约,一个类或接口的声明部分应当按照以下顺序出现:

1. 类(静态)变量。首先应当是public类变量,然后是protected类变量,然后是package类变量(没有访问标识符),最后是private类变量。

2. 实例变量。首先应当是public类变量,然后是protected类变量,然后是package类变量(没有访问标识符),最后是private类变量。

3. 构造器

4. 方法

  • Empty Statement(空语句)
检测代码中是否有空语句(也就是单独的;符号)。
  • Equals Avoid Null(避免调用空引用的equals方法)

检查equals()比较方法中,任意组合的String常量是否位于左边。

这项检查还会处理String.equalsIgnoreCase()调用(可以抑制这种警告)。

解释:调用String常量的equals()方法可以避免潜在的NullPointerException。同样,经常会发现在调用equals()方法之前,会进行空指针检查,不过在下面的示例中则没有必要这么做。

例如:

String nullString = null;

nullString.equals("My_Sweet_String");

这段代码应当重构为:

String nullString = null;

"My_Sweet_String".equals(nullString);

局限:如果覆盖了equals方法,或者定义了一个共变equals方法,并且没有正确地实现这个方法(也就是s.equals(t)返回的结果和t.equals(s)返回的结果不同),那么改写调用方法的对象和参数可能会产生无法预料的结果。

Java的Autoboxing特性会对这项检查如何实现产生影响。在Java 5之前的版本,所有的IDENT + IDENT对象拼接不会导致NullPointerException,即使它是空指针。这项检查已经包含了这些情况。它们会进行简单的处理,就好像使用String.valueof()方法包围起来一样,这个方法会拼接null字符串。

以下示例将会导致一个NullPointerException,这是Autoboxing功能所造成的结果:

Integer i = null, j = null;

String number = "5"

number.equals(i + j);

因为很难确定正在拼接的是哪种类型的对象,所以所有的IDENT拼接都会被认为是不安全的。

  • Equals and HashCode(equals方法和hashCode方法)

检查覆盖了equals()方法的类是否也覆盖了hashCode()方法。

解释:equals()方法和hashCode()方法约定,相等的对象必然具有相同的哈希码。因此,只要你覆盖了equals()方法,你就必须同时覆盖hashCode()方法,以确保可以在基于哈希的集合中使用你的类。

  • Explicit Initialization(显式初始化)

检查类或对象的成员是否显式地初始化为成员所属类型的默认值(对象引用的默认值为null,数值和字符类型的默认值为0,布尔类型的默认值为false)。

解释:每个实例变量都会被初始化两次,并且初始化为相同的值。在执行代码中指定的任何初始化操作之前,Java会初始化每个实例变量为它的默认值(0或null)。因此在这种情况下,x会被初始化为0两次,bar会被初始化为null两次。因此,这样稍微有些效率低下。这种编码风格是C/C++编码风格的延续,它表明开发者并不是真正有把握Java能够初始化实例变量为它的默认值。

  • Fall Through(跨越分支)

检查switch语句中是否存在跨越分支。如果一个case分支的代码中缺少break、return、throw或continue语句,那么就会导致跨越分支。

这项检查可以通过特殊的注释以抑制警告。默认情况下,在有跨越分支的case分支代码中添加“fallthru”、“fall through”、“fallthrough”、“falls through”、“fallsthrough”等注释(区分大小写)时,便可抑制警告。包含以上单词的注释必须在一行中,并且必须在当前case分支代码的最后一行中,或者与case语句在同一行,如以下代码所示:

switch (i){

case 0:

i++; // fall through

case 1:

i++;

// falls through

case 2: {

i++;

}

// fallthrough

case 3:

i++;

/* fallthru */case 4:

i++

break;

}

注意:这项检查假设case分支代码中没有不可达的代码。

  • Final Local Variable(final局部变量)

检查从未改变取值的局部变量是否被声明为final。这项检查还可以被配置为检查未修改过的参数是否被声明为final。

当配置为检查参数时,这项检查会忽略接口方法和抽象方法中的参数。

  • Hidden Field(隐藏字段)
检查局部变量或参数是否会遮蔽在相同类中定义的字段。
  • Illegal Instantiation(非法实例化)

检查是否有不合法的实例化操作,是否使用工厂方法更好。

解释:根据不同的项目,对于某些类来说,可能通过工厂方法来创建类实例更好,而不是调用类构造器。

一个简单的示例就是java.lang.Boolean类。为了节省内存和CPU周期,最好使用预定义的常量TRUE和FALSE。构造器的调用应当被替换为调用Boolean.valueOf()方法。

某些对性能有极端要求的项目可能需要其他的类也使用工厂方法,以便于提高缓存或对象池的使用效率。

  • Illegal Catch(非法异常捕捉)

从不允许捕捉java.lang.Exception、java.lang.Error、java.lang.RuntimeException的行为。

解释:缺乏经验的开发者经常会简单地捕捉Exception异常,试图处理多种异常类型。这会很不幸地使代码无意中捕捉到NullPointerException、OutOfMemoryErrors等系统异常。

  • Illegal Throws(非法异常抛出)
这项检查可以用来确保类型不能声明抛出指定的异常类型。从不允许声明抛出java.lang.Error或java.lang.RuntimeException。
  • Illegal Tokens(非法标记)

检查不合法的标记。

解释:某个语言特性经常会导致代码难以维护,或者开发新手难以理解。在某些框架中,其他特性可能不推荐使用,例如,在EJB组件中最好不要使用本地方法。

  • Illegal Tokens Text(非法标记文本)
检查是否有不合法的标记文本。
  • Illegal Type(非法类型)
检查代码中是否有在变量声明、返回值、参数中都没有作为类型使用过的特定类。包括一种格式检查功能,默认情况下不允许抽象类。
解释:帮助减少和实体类之间的耦合。另外,抽象类应当被认为是接口的一种简便的基类实现,因此不能是类型本身。
  • Inner Assignment(内部赋值)

检查子表达式中是否有赋值语句,例如String s = Integer.toString(i = 2);。

解释:这项检查会忽略for循环代码,其余所有的赋值操作都应当在它们自己的顶层语句中,以便于增强可读性。在上述的内部赋值代码中,很难看到变量是在哪儿赋值的。

  • JUnit Test Case(JUnit测试用例)

确保setUp()、tearDown()方法的名称正确,没有任何参数,返回类型为void,是public或protected的。

同样确保suite()方法的名称正确,没有参数,返回类型为junit.framewotk.Test,并且是public和static的。

解释:开发者时常会错误地命名这些方法,并且不会意识到这些方法没有被调用。

  • Magic Number(幻数)
检查代码中是否含有“幻数”,幻数就是没有被定义为常量的数值文字。默认情况下,-1、0、1、2不会被认为是幻数。
  • Missing Constructor(缺少构造器)
检查类(除了抽象类)是否定义了一个构造器,而不是依赖于默认构造器。
  • Missing Switch Default(缺少switch默认分支)

检查switch语句是否含有default子句。

解释:在每个switch语句中引入一条默认分支通常是一个很好的主意。即使开发者确信所有当前可能的分支都能覆盖到,这也应当在default分支中表达出来,例如,使用一条断言。这种方法使得代码可以应付以后的修改,例如,在一个枚举类型中引入新的类型。

  • Modified Control Variable(修改控制变量)

检查确保for循环的控制变量没有在for代码块中被修改。示例代码如下:

for (int i = 0; i < 1; i++) {

i++;

}

解释:如果在循环体中修改了控制变量,程序流程就会变得更加难以跟踪。可以用while循环替换for循环。

  • Multiple String Literals(多重字符串常量)

检查在单个文件中,相同的字符串常量是否出现了多次。

解释:重复代码会使得维护工作变得更加困难,因此最好用一个常量来替换多次出现。

  • Multiple Variable Declaration(多重变量声明)

检查每个变量是否使用一行一条语句进行声明。

解释:《SUN编码规约》的第6.1章节推荐应当使用一行一条语句声明一个变量。

  • Nested For Depth(for嵌套深度)
限制for循环的嵌套层数(默认值为1)。
  • Nested If Depth(if嵌套深度)
限制if-else代码块的嵌套层数(默认值为1)。
  • Nested Try Depth(try嵌套深度)
限制try代码块的嵌套层数(默认值为1)。
  • No Clone(没有clone方法)

检查是否覆盖了Object类中的clone()方法。

解释:clone()方法依赖于一套奇怪且难以遵循的规则,这套规则并不是在所有情况下都起作用。因此,很难正确地覆盖clone()方法。下面是一些说明为何应当避免使用clone()方法的原因。

支持clone方法的类应当事先Cloneable接口,但是Cloneable结构并不包含clone方法。因此,它并不会强制覆盖clone方法。

Cloneable接口会强迫对象的clone方法正确地工作。如果不实现这个接口,那么对象的clone方法会抛出CloneNotSupportedException。没有用final关键字修饰的类必须返回由调用super.clone()方法所返回的对象。用final关键字修饰的类可以使用一个构造器创建一个克隆对象,这个对象和非final类的有所不同。

如果一个父类没有正确地实现clone方法,那么所有的子类调用super.clone()方法时,都会注定失败。

如果一个类含有可变对象的引用,那么在这个类的clone方法中调用super.clone()方法之后,必须使用前述可变对象的拷贝来替换这些对象引用。

clone方法不能正确地处理用final关键字修饰的可变对象引用,因为final引用不能被重新赋值。

如果一个父类覆盖了clone方法,那么所有的子类都必须提供一个正确的clone实现。

在某些情况下,有两种clone方法的替代方案可以使用,一种是使用一个拷贝构造器,另一种是使用静态工厂方法来返回某个对象的拷贝。这两种方法都更加简单,并且不会和final关键字修饰的字段产生冲突。它们不会强迫调用的客户端处理CloneNotSupportException。它们都是具有类型的,因此不需要进行任何类型转换。最后,它们更加灵活,因为它们可以处理接口类型,而不仅仅是实体类。

有时候,不能使用拷贝构造器或静态工厂作为clone方法的替代方案。以下示例说明了拷贝构造器或静态工厂的局限性。假设Square是Shape的一个子类。

Shape s1 = new Square();

System.out.println(s1 instanceof Square); //true

...假设此处的代码不知道s1是一个Square类型的对象,这是多态的优美之处,但是代码想要拷贝这个被声明为Shape类型的Square对象,Shape是Square的父类...

Shape s2 = new Shape(s1); //using the copy constructor

System.out.println(s2 instanceof Square); //false

有效的解决办法(不用知道所有的子类,并且不用执行大量的类型转换)应当如下列代码所示(假设实现了正确的clone方法):

Shape s2 = s1.clone();

System.out.println(s2 instanceof Square); //true

你只需要记住,如果需要这种多态克隆的类型,那么一个正确实现的clone方法可能才是最好的选择。

这项检查和{@link NoFinalizerCheck}几乎完全相同。

  • No Finalizer(没有finalize方法)
验证类中是否定义了finalize()方法。
  • Package Declaration(包声明)

确保一个类具有一个包声明,并且(可选地)包名要与源代码文件所在的目录名相匹配。

解释:位于空包中的类是不能够被导入的。很多开发新手并没有注意到这一点。

  • Parameter Assignment(参数赋值)

不允许对参数进行赋值。

解释:对参数的赋值通常被认为是缺乏编程实践经验。强迫开发者将参数声明为final通常是非常麻烦的。这项检查可以确保参数从不会被赋值,这对于双方都是好事。

  • Redundant Throws(多余的throws)
检查throws子句中是否声明了多余的异常,例如重复异常、未检查的异常或一个已声明抛出的异常的子类。
  • Require This(需要this)
检查代码是否使用了“this.”,也就是说,在默认情况下,引用当前对象的实例变量和方法时,应当显式地通过“this.varName”或“this.methodName(args)”这种形式进行调用。
  • Return Count(return总数)

限制return语句的数量。默认值为2。可以忽略检查指定的方法(默认忽略equals()方法)。

解释:过多的返回点可能表明代码尝试处理过多的业务,可能会难以理解。

  • Simplify Boolean Expression(简化布尔表达式)

检查是否有过于复杂的布尔表达式。现在能够发现诸如if (b == true)、b || true、!false等类型的代码。

解释:复杂的布尔逻辑会使得代码难以理解和维护。

  • Simplify Boolean Return(简化布尔返回值)

检查是否有过于复杂的布尔类型return语句。例如下面的代码:

if (valid())

return false;

else

return true;

可以写成:

return !valid();

这项检查是从PMD规则中借鉴而来的。

  • String Literal Equality(严格的常量等式比较)

检查字符串对象的比较是否使用了==或!=运算符。

解释:Java新手程序员经常会使用类似于下面的代码:

if (x == "something")

其实他们是想表达如下的意思:

if ("something".equals(x))

  • SuperClone(父类clone方法)

检查一个覆盖的clone()方法是否调用了super.clone()方法。

参考:Object.clone()。

  • SuperFinalize(父类finalize方法)
检查一个覆盖的finalize()方法是否调用了super.finalize()方法。
参考:清理未使用对象。
  • Trailing Array Comma(数组尾随逗号)

检查数组的初始化是否包含一个尾随逗号。

int[] a = new int[]

{

1,

2,

3,

};

如果左花括号和右花括号都位于同一行,那么这项检查允许不添加尾随逗号。如下所示:

return new int[] { 0 };

解释:添加尾随逗号可以使得改变元素顺序,或者在末尾添加新的元素变得更加方便。

  • Unnecessary Parentheses(不必要的圆括号)
检查代码中是否使用了不必要的圆括号。
  • One Statement Per Line(每行一条语句)

检查每行是否只有一条语句。下面的一行将会被标识为出错:

x = 1; y = 2; // 一行中有两条语句

12. Class Design(类设计:8个)

  • Designed For Extension(设计扩展性)

检查类是否具有可扩展性。更准确地说,它强制使用一种编程风格,父类必须提供空的“句柄”,以便于子类实现它们。

确切的规则是,类中可以由子类继承的非私有、非静态方法必须是:

1. abstract方法,或

2. final方法,或

3. 有一个空的实现

解释:这种API设计风格可以保护父类不会被子类破坏。不利之处在于子类的灵活性会受到限制,特别是它们不能够阻止父类代码的执行,但是这也意味着子类不会由于忘记调用父类的方法而破坏父类的状态。(个人理解:不允许类的方法被子类覆盖)

  • Final Class(final类)
检查一个只有私有构造器的类是否被声明为final。
  • Inner Type Last(最后声明内部类型)
检查嵌套/内部的类型是否在当前类的最底部声明(在所有的方法/字段的声明之后)。
  • Hide Utility Class Constructor(隐藏工具类构造器)

确保工具类(在API中只有静态方法和字段的类)没有任何公有构造器。

解释:实例化工具类没有任何意义。因此,工具类的构造器应当是私有的或者受保护的(如果你打算以后扩展子类)。一个常见的错误便是忘记隐藏默认构造器。

如果你打算将工具类的构造器声明为受保护的,那么你可以考虑下面的构造器实现技术,借此可以禁止子类的实例化:

public class StringUtils // 不是final类,允许子类继承

{

protected StringUtils() {

throw new UnsupportedOperationException(); // 防止子类调用

}

public static int count(char c, String s) {

// ...

}

}

  • Interface Is Type(接口是类型)

Bloch编写的《Effective Java》中提到,接口应当描述为一个类型。因此,定义一个只包含常量,但是没有包含任何方法的接口是不合适的。标准类javax.swing.SwingConstants是一个会被这项检查标记的示例类。

这项检查还可以配置为禁用标记接口,例如java.io.Serializable,这种接口不会包含任何方法或常量。

  • Mutable Exception(可变异常)

确保异常(异常类的名称必须匹配指定的正则表达式)是不可变的。也就是说,异常只能有final字段。

这项检查当前使用的算法非常简单,它会检查异常的所有成员是否是final的。用户仍然可以修改一个异常实例(例如,Throwable使用setStackTrace(StackTraceElement[] stackTrace)方法修改堆栈跟踪)。但是,至少这种异常类型所提供的信息是不可修改的。

解释:异常实例应当表示一个错误状态。异常类中含有非final的字段,不仅仅会导致异常状态会由于偶然的因素被修改,这样便会遮蔽原始的异常状态,还会使得开发者偶尔会忘记初始化异常状态,这样便会导致代码捕捉到异常之后,根据异常状态推导出不正确的结论。

  • Throws Count(抛出计数)

将异常抛出语句的数量配置为一个指定的限值(默认值为1)。

解释:异常是方法接口的组成部分之一。如果一个方法声明抛出过多不同的异常,就会使得异常处理非常繁重,并且会导致不好的编程习惯,例如catch (Exception)。这项检查会强制开发者将异常处理变得具有层次性,举个最简单的例子,调用者只需要检查一种类型的异常,但是必要时也允许捕捉上述异常的任何子类。

  • Visibility Modifier(可见性标识符)

检查类成员的可见性。只有static final的类成员可以是公有的,其他的类成员必须是私有的,除非设置了protectedAllowed属性或packageAllowed属性。

如果类成员的名称和指定的公有成员正则表达式匹配,那么这项检查就不会标记这个类成员(默认包含“^serialVersionUID$”)。

解释:强制封装。

13. Duplicates(重复:1个)

  • Strict Duplicate Code(严格重复代码)

逐行地比较所有的代码行,如果有若干行只有缩进有所不同,那么就报告存在重复代码。Java代码中的所有的import语句都会被忽略,任何其他的行 —— 包括Javadoc、方法之间的空白行,等等 —— 都会被检查(这也是为什么这项检查被称作是严格的)。

14. Metrics(度量:6个)

  • Boolean Expression Complexity(布尔表达式复杂度)

限制一个表达式中的&&、||、&、|、^等逻辑运算符的数量。

解释:过多的条件会导致代码难以读懂、调试和维护。

注意,&和|运算符并不仅仅是整数的位运算符,它们还是布尔运算符&&和||的非快捷版本。

  • Class Data Abstraction Coupling(类的数据抽象耦合)
这项度量会测量给定类中的其他类的实例化操作的次数。这种类型的耦合并不是由于继承或者面向对象范型而产生的。一般而言,任何将其他抽象数据类型作为成员的抽象数据类型都具有数据抽象耦合;因此,如果一个类中的某个局部变量是另一个类的实例(对象),那么就存在数据抽象耦合(DAC)。DAC越高,系统的数据结构(类)就会越复杂。
  • Class Fan Out Complexity(类的扇出复杂度)
一个给定类所依赖的其他类的数量。这个数量的平方还可以用于表示函数式程序(基于文件)中需要维护总量的最小值。
  • Cyclomatic Complexity(循环复杂度)
检查循环复杂度是否超出了指定的限值。该复杂度由构造器、方法、静态初始化程序、实例初始化程序中的if、while、do、for、?:、catch、switch、case等语句,以及&&和||运算符的数量所测量。它是遍历代码的可能路径的一个最小数量测量,因此也是需要的测试用例的数量。通常1-4是很好的结果,5-7较好,8-10就需要考虑重构代码了,如果大于11,则需要马上重构代码!
  • Non Commenting Source Statements(非注释源码语句)
通过对非注释源码语句(NCSS)进行计数,确定方法、类、文件的复杂度。这项检查遵守Chr. Clemens Lee编写的JavaNCSS-Tool中的规范。
粗略地说,NCSS度量就是不包含注释的源代码行数,(近似)等价于分号和左花括号的计数。一个类的NCSS就是它所有方法的NCSS、它的内部类的NCSS、成员变量声明数量的总和。一个文件的NCSS就是它所包含的所有顶层类的NCSS、imports语句和包声明语句数量的总和。
解释:太大的方法和类会难以阅读,并且维护成本会很高。一个较大的NCSS数值通常意味着对应的方法或类承担了过多的责任和/或功能,应当分解成多个较小的单元。
  • NPath Complexity(NPath复杂度)

NPATH度量会计算遍历一个函数时,所有可能的执行路径的数量。它会考虑嵌套的条件语句,以及由多部分组成的布尔表达式(例如,A && B,C || D,等等)。

解释:在Nejmeh的团队中,每个单独的例程都有一个取值为200的非正式的NPATH限值;超过这个限值的函数可能会进行进一步的分解,或者至少一探究竟。

15. Miscellaneous(杂项:12个)

  • Array Type Style(数组类型风格)
检查数组定义的风格。有的开发者使用Java风格:public static void main(String[] args);有的开发者使用C风格:public static void main(String args[])。
  • Descendent Token Check(后续标记检查)

检查在其他标记之下的受限标记。

警告:这是一项非常强大和灵活的检查,但是与此同时,它偏向于底层技术,并且非常依赖于具体实现,因为,它的结果依赖于我们用来构建抽象语法树的语法。因此,当其他检查项目提供了你想要用的功能时,我们建议你使用这些检查项目。总之,这项检查只能在抽象语法树的层面上工作,它并不了解任何语言结构。

  • Final Parameters(final参数)
检查方法/构造器的参数是否是final的。这项检查会忽略接口方法的检查 —— final关键字不会理会接口方法的参数,因为没有任何代码能够修改这些参数。
解释:在方法算法的执行期间改变参数值会使读者产生混淆,因此应当避免这种情况的发生。有个很好的方法可以使得Java的编译器预防这种编码风格,那就是将方法的参数声明为final的。
  • Indentation(代码缩进)

检查Java代码的缩进是否正确。

尽管有些格式精美的打印机有时可以很方便地批量重排原始代码的格式,但是它们通常不是没有足够的可配置性,就是不能够达到预期的排版格式。有时这是个人喜好的问题,有时这是实际经验的问题。无论如何,这项检查应当只确保代码遵守缩进规则的一个最小集合。

  • New Line At End Of File(文件末尾的新行)

检查文件是否以新行结束。

解释:通常,任何源码文件和文本文件都应当以一个新行符结束,特别是使用诸如CVS这样的SCM系统时。当文件没有以新行结束时,CVS甚至会打印出一个警告。

  • Todo Comment(TODO注释)
这项检查负责TODO注释的检查。实际上,这是一种检查Java注释的通用正则表达式匹配器。想要检查其他格式的Java注释,那么设置format属性即可。
  • Translation(语言转换)

这是一项FileSetCheck检查,通过检查关键字的一致性属性文件,它可以确保代码的语言转换的正确性。可以使用两个描述同一个上下文环境的属性文件来保证一致性,如果它们包含相同的关键字。

考虑下面的属性文件,它们在同一个目录中:

#messages.properties

hello=Hello

cancel=Cancel

#messages_de.properties

hell=Hallo

ok=OK

转换检查将会发现德语hello关键字的拼写错误、默认资源文件(messages.properties)缺少ok关键字、德语资源文件(messages_de.properties)缺少cancel关键字等错误:

messages_de.properties: Key 'hello' missing.

messages_de.properties: Key 'cancel' missing.

messages.properties: Key 'hell' missing.

messages.properties: Key 'ok' missing.

  • Trailing Comment(行尾注释)

这项检查可以确保代码中含有注释的行中只包含注释。在使用//注释的场合下,这意味着//符号之前只能有空格。如果行不是以注释结束的,那么就不会检查这些注释。例如,下面的代码是可接受的

Thread.sleep( 10 );

format属性会处理“} // while”这样的注释。

解释:Steve McConnell编写的《Code Complete》认为行尾注释是一个不好的编程习惯。行尾注释就是那些和实际代码位于同一行的注释。例如:

a = b + c; // 一条常见的注释

d = e / f; // 这一行的另一条注释

《Code Complete》为此给出了以下几条论证:

1. 注释必须对齐,这样便不会干扰代码的可视结构。如果你不将它们整洁地对齐,它们将会使你的代码看起来就像刚从洗衣机里出来一样乱糟糟的。

2. 行尾注释会很难格式化,需要花费大量的时间来对齐它们。这样的时间并不是花在深入理解代码上的,你只能乏味地敲击空格键或制表键来重新格式化这些注释。

3. 行尾注释非常难以维护。如果某一行包含行尾注释的代码增加了,就会使这行的注释被挤得更远,并且所有其他的行尾注释为了排版对齐,不得不被放置的同样远。难以维护的代码风格就是不可维护的。

4. 行尾注释可能会意义不明确。每行的右侧通常不能提供足够的空间来放置注释,将注释和代码放在同一行就意味着注释可能会比较短。按照这种习惯,你编写代码时就会专注于每行尽可能的短,而不是每行尽可能的清晰。因此,这种注释经常会意义不明确清晰。

5. 行尾注释还会带来一个系统性问题,你会发现很难仅仅在一行中写出意义明确的注释。大多数行尾注释仅仅重复了一下这行的代码,这种行为带来的危害性远比带来的帮助要大。

当使用自动化重构技术时,源码每行的长度会经常变化,这就使得包含大量行尾注释的代码变得非常难以维护。

  • Uncommented Main(未注释main方法)

检查源码中是否有未注释的main()方法(调试的残留物)。

解释:调试时经常会在代码中利用main()方法。当调试结束时,开发者经常会忘记删除这些main()方法,这样会改变API,并且会增大生成的class/jar文件的尺寸。除了程序真正的入口点之外,源码中其他所有的main()方法都应当被删除或注释掉。

  • Upper Ell(大写“L”)

检查long类型的常量在定义时是否由大写的“L”开头。注意,是“L”,不是“l”。这是由《Java Language Specification》的第3.10.1章节所建议的编码规约。

解释:小写字母“l”看起来非常像数字“1”。

  • Regexp(正则表达式)

这项检查可以确保指定的格式串在文件中存在,或者允许出现几次,或者不存在。

这项检查结合了RegexpHeader、GenericIllegalRegexp和RequiredRegexp的所有功能,但是并不支持使用文件中正则表达式。

这项检查的不同之处在于,它是工作在多行模式下的。它的正则表达式可以跨越多行,并且可以一次性检查完整个文件。上面提到的其他三项检查工作在单行模式下。它们的单行或多行正则表达式一次只能检查一行。它们只能依次检查文件中的每一行。

注意:由于工作模式有所不同,所以需要对使用的正则表达式做一些修改才行。

在多行模式下:

1. “^”符号表示一行的开始,而不是输入的开始。

2. “\A”表示输入的开始。

3. “$”符号表示一行的结束,而不是输入的结束。

4. “\Z”表示输入的结束。

5. 文件中的每行都是以新行符号结束。

注意:并不是所有的正则表达式引擎创建正则表达式都是相同的。有些引擎提供了额外的功能,但是其他的引擎不支持,并且语法元素可能也有所不同。这项检查使用了java.util.regex包,请查看相关文档,以便于学习如何构建一个符合使用目标的正则表达式。

注意:当你在XML配置文件中键入一个正则表达式作为参数时,你必须考虑到XML文件的规则,例如,如果你想要匹配一个“<”符号,那么你需要键入“&lt;”。一条正则表达式应当在同一行中键入。

  • Outer Type File Name(外部类型文件名)
检查外部类型名称是否与文件名称匹配。例如,类Foo必须在文件Foo.java中。

16. Other(其他:2个)

  • Checker(检查器)
每个checkstyle配置的根模块。不能被删除。
  • TreeWalker(树遍历器)
FileSetCheck TreeWalker会检查单个的Java源码文件,并且定义了适用于检查这种文件的属性。

17. Filters(过滤器:4个)

  • Severity Match Filter(严重度匹配过滤器)
SeverityMatchFilter过滤器会根据事件的严重级别决定是否要接受审计事件。
  • Suppression Filter(抑制过滤器)
在检查错误时,SuppressionFilter过滤器会依照一个XML格式的策略抑制文件,选择性地拒绝一些审计事件。如果没有配置好的策略抑制文件可用,那么这个过滤器会接受所有的审计事件。
  • Suppression Comment Filter(抑制注释过滤器)

SuppressionCommentFilter过滤器使用配对的注释来抑制审计事件。

解释:有时候,违反一项检查是有正当理由的。当问题出在代码本身,而不是个人喜好时,最好在代码中覆盖检查策略。半结构化的注释可以和检查关联起来。这种做法有时比一个独立的策略抑制文件更好,因为这个文件必须随着源码文件的变化而保持更新。

  • Suppress With Nearby Comment Filter(抑制附近注释过滤器)
SuppressWithNearbyCommentFilter过滤器使用独立的注释来抑制审计事件。
解释:和SuppressionCommentFilter相同。然而,SuppressionCommentFilter使用配对的过滤器来打开/关闭注释匹配,SuppressWithNearbyCommentFilter则使用单个注释过滤器。这样可以使用更少的行数来标记一块区域,在某些环境中会显得风格很优美。
用法:这个过滤器需要和FileContentsHolder协同使用,因为检查器可以在.java文件中不公开地使用抑制注释。包含这个过滤器的配置文件必须将FileContentsHolder配置为TreeWalker的一个子模块。
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门