括号使用规则
If表达式必须使用{},无论有多少语句
错误实例:
public class Foo {
public void bar() {
int x = 0;
if (foo) x++;//if(foo){x++;}
}
}
While循环必须使用{},无论有多少语句
错误写法示意:
public void doSomething() {
while (true)
x++;
}
IfElse表达式必须使用{}
错误写法示意:
public void doSomething() {
// this is OK
if (foo) x++;
// but this is not
if (foo)
x=x+1;
else
x=x-1;
}
For循环语句必须使用{}
错误写法示意:
public void foo() {
for (int i=0; i<42;i++)
foo();
}
设计规则
如果方法返回boolean,那么注意避免不必要的if..then..else语句
示例:
public class Foo {
private int bar =2;
public boolean isBarEqualsTo(int x) {
// this bit of code
if (bar == x) {
return true;
} else {
return false;
}
// can be replaced with a simple
// return bar == x;
}
避免if语句嵌套过深(会导致代码可读性降低)
解决办法:建议if嵌套不要超过2层。使用工具方法封装更多的if语句或者把嵌套的if表达式放到同一个层次中。
示例:
public class Foo {
public void bar(int x, int y, int z) {
if (x>y) {
if (y>z) {
if (z==x) {
// whew, too deep
}
}
}
}
}
解决方案:把过深的if逻辑封装到独立的工具方法内,同时尽量让if语句“扁平化”,减少if嵌套
public class Foo {
public void bar(int x, int y, int z) {
if (x>y&&y>z) {//如果这里的判断逻辑比较复杂,则需要使用工具方法封装。时刻别忘了提高代码可读性。
doSomething(z,x);
}
}
Public void doSomething(int z,int x){
If(z==x){
//....
}
}
}
不要在方法内改变形参的值,这可能导致问题
示例:
public class Foo {
private void foo(String bar) {
bar = "something else";
}
}
注意Switch语句的密度,不要在switch语句的每个case中做太多事情,注意“责任分解”的原则,如果你一定要用switch语句,那让它成为一个控制者,每个case中的执行细节交给工具方法或者使用策略模式简化复杂度。
示例:
public class Foo {
public void bar(int x) {
switch (x) {
case 1: {
// lots of statements
break;
} case 2: {
// lots of statements
break;
}
}
}
}
不要在构造方法中调用Overridable方法,很显然这样存在风险。
示例:
public class SeniorClass {
public SeniorClass(){
toString(); //may throw NullPointerException if overridden
}
public String toString(){
return "IAmSeniorClass";
}
}
public class JuniorClass extends SeniorClass {
private String name;
public JuniorClass(){
super(); //Automatic call leads to NullPointerException
name = "JuniorClass";
}
public String toString(){
return name.toUpperCase();
}
}
AccessorClassGeneration
Since: PMD 1.04
Instantiation by way of private constructors from outside of the constructor's class often causes the generation of an accessor. A factory method, or non-privitization of the constructor can eliminate this situation. The generated class file is actually an interface. It gives the accessing class the ability to invoke a new hidden package scope constructor that takes the interface as a supplementary parameter. This turns a private constructor effectively into one with package scope, and is challenging to discern.
This rule is defined by the following Java class: net.sourceforge.pmd.rules.AccessorClassGeneration
Example:
public class Outer {
void method(){
Inner ic = new Inner();//Causes generation of accessor class
}
public class Inner {
private Inner(){}
}
}
final类型的域可以(应该)同时是static的
示例:
public class Foo {
public final int BAR = 42; // this could be static and save some space
}
记得及时关闭一些关键资源(Connection, Statement, ResultSet...)
示例:
public class Bar {
public void foo() {
Connection c = pool.getConnection();
try {
// do stuff
} catch (SQLException ex) {
// handle exception
} finally {
// oops, should close the connection using 'close'!
// c.close();
}
}
}
避免使用非静态初始化
非静态的初始化块将在构造器被调用的时候被访问(优先于调用构造器)。这是一个有效的语言结构,但使用很少且易造成迷惑
示例:
public class MyClass {
// this block gets run before any call to a constructor
{
System.out.println("I am about to construct myself");
}
}
优化toArray调用
调用Collection.toArray时使用集合的规模加上目标类型的空数组作为参数。
示例:
class Foo {
void bar(Collection x) {
// A bit inefficient
x.toArray(new Foo[0]);
// Much better; this one sizes the destination array, avoiding
// a reflection call in some Collection implementations
x.toArray(new Foo[x.size()]);
}
}
令人迷惑的三种性
在if表达式伴随else子句时,避免在if测试中使用否定表达。例如:不要使用if (x != y) diff(); else same()这种表述,而应该使用if (x == y) same(); else diff(),大多数时候使用if(x!=y)形式时不包含else分句,所以一贯地使用此规则能让代码更易于阅读。此外,这也解决了一个细节的排序问题,比如“应该是判断为false的代码块在前面?”,还是“判断通过的代码块在前?”
示例:
public class Foo {
boolean bar(int x, int y) {
return (x != y) ? diff : same;
}
}
在构建SimpleDateFormat时需要指定Locale
Example:
public class Foo {
// Should specify Locale.US (or whatever)
private SimpleDateFormat sdf = new SimpleDateFormat("pattern");
}
不变域
识别出一旦被声明就赋值或通过构造器赋值后就从不改变的私有域,它们将存在的类变成了不变类。这样的域可以是final的
Example:
public class Foo {
private int x; // could be final
public Foo() {
x = 7;
}
public void foo() {
int a = x + 2;
}
}
忽略大小写进行字符串比较时,使用String.equalsIgnoreCase,不要使用String.toLowerCase.前者有更好的性能而且还可以避免后者带来的本地化问题
Example:
class Foo {
// BAD
if (x.toLowerCase().equals("list"))...
/*
This will not match "LIST" when in Turkish locale
The above could be
if (x.toLowerCase(Locale.US).equals("list")) ...
or simply
if (x.equalsIgnoreCase("list")) ...
*/
// GOOD
String z = a.toLowerCase(Locale.EN);
}
避免在final类中使用protected域
因为final类型的class不能被继承,所以不要使用protected域,通过使用private或包访问符来代替以修正你的意图。
Example:
public final class Bar {
private int x;
protected int y; // <-- Bar cannot be subclassed, so is y really private or package visible???
Bar() {}
}
不要给一个非final的static类型赋值
不安全
Example:
public class StaticField {
static int x;
public FinalFields(int y) {
x = y; // unsafe
}
}
在不可实例化的class中丢失静态方法
一个class只有私有的构造函数,但是没有任何static方法,也没有任何可用的field。
Example:
/* This class is unusable, since it cannot be
instantiated (private constructor),
and no static method can be called.
*/
public class Foo {
private Foo() {}
void foo() {}
}
避免方法级的同步
块级别的同步可以确保内含真正需要同步的代码。
Example:
public class Foo {
// Try to avoid this
synchronized void foo() {
}
// Prefer this:
void bar() {
synchronized(this) {
}
}
}
用NotifyAll代替Notify
为了保证所有监听线程有均等的机会
Example:
public class Foo {
void bar() {
x.notify();
// If many threads are monitoring x, only one (and you won't know which) will be notified.
// use instead:
x.notifyAll();
}
}
避免在catch块中使用instanceof
每个产生的异常类型都应该在自己的catch块中被处理
Example:
try { // Avoid this
// do something
} catch (Exception ee) {
if (ee instanceof IOException) {
cleanup();
}
}
try { // Prefer this:
// do something
} catch (IOException ee) {
cleanup();
}
使用instanceof时不需要判断对象是否为null
当给予一个null作为参数时,instanceof返回false
Example:
class Foo {
void bar(Object x) {
if (x != null && x instanceof Bar) {
// just drop the "x != null" check
}
}
}
使用Equals来比较对象,不要用==
Example:
class Foo {
boolean bar(String a, String b) {
return a == b;
}
}
常量前置
Example:
class Foo {
boolean bar(String x) {
return x.equals("2"); // should be "2".equals(x)
}
}
在return之前不必要的本地变量
避免创建不必要的本地变量,不仅在return的场合需要注意这一点
Example:
public class Foo {
public int foo() {
int x = doSomething();
return x; // instead, just 'return doSomething();'
}
}
非线程安全的单例
Example:
private static Foo foo = null;
//multiple simultaneous callers may see partially initialized objects
public static Foo getFoo() {
if (foo==null)
foo = new Foo();
return foo;
}
不要定义只有常量没有行为的接口
接口只应该是对行为建模
Example:
public interface ConstantsInterface {
public static final int CONSTANT1=0;
public static final String CONSTANT2="1";
}
不要同步静态的DateFormat类
SimpleDateFormat是非同步的。Sun公司建议对每个线程单独的format实例。如果多线程必须访问一个静态formatter,formatter必须在方法或块级别同步。
代码示例:
public class Foo {
private static final SimpleDateFormat sdf = new SimpleDateFormat();
void bar() {
sdf.format(); // bad
}
synchronized void foo() {
sdf.format(); // good
}
}
保留追踪栈
在一个catch块中抛出一个新的异常却不把原始的异常传递给新的异常会导致真正的追踪信息栈丢失,而且导致难以有效的调试。
代码示例:
public class Foo {
void good() {
try{
Integer.parseInt("a");
} catch(Exception e){
throw new Exception(e);
}
}
void bad() {
try{
Integer.parseInt("a");
} catch(Exception e){
throw new Exception(e.getMessage());
}
}
}
使用集合类的isEmpty方法
java.util.Collection类的isEmpty方法提供判断一个集合类是否包含元素。不要是使用size()和0比较来重复类库已经提供的方法。 这条原则告诉我们一个普遍的原则:复用。尽量复用,充分利用已有的资源,不要重复自己(DRY)。比如apache提供了大量工具类供我们使用,我们没有必要再自己写了。
代码示例:
public class Foo {
void good() {
List foo = getList();
if (foo.isEmpty()) {
// blah
}
}
void bad() {
List foo = getList();
if (foo.size() == 0) {
// blah
}
}
}
类只包含私有的构造器应该是final的
一个类只包含私有的构造器应该是final的,除非私有构造器被一个内部类访问。
public class Foo { //Should be final
private Foo() { }
}
抽象类中的空方法也应该是抽象的
一个抽象类中的空方法也应该是抽象的,因为开发者有可能会信任这个空的实现而不去编写恰当的代码。
Example:
public abstract class ShouldBeAbstract
{
public Object couldBeAbstract()
{
// Should be abstract method ?
return null;
}
public void couldBeAbstract()
{
}
}
单数的域
域变量只在一个方法中被使用并且第一次使用时对这个域赋值。这种域可以改写为本地变量。
Example:
public class Foo {
private int x; //Why bother saving this?
public void foo(int y) {
x = y + 5;
return x;
}
}
返回空的数组而不要返回null
对于任何返回数组的方法,返回一个空的数组是一个比返回null引用更好的做法。
Example:
public class Example
{
// Not a good idea...
public int []badBehavior()
{
// ...
return null;
}
// Good behavior
public String[] bonnePratique()
{
//...
return new String[0];
}
}
没有任何方法的抽象类
如果抽象类没有提供任何的方法,它可能只是一个不可被实例化的数据容器,在这种状况下,更好的方法是使用私有的或受保护的构造器以阻止实例化可以让类避免带有欺骗性的抽象。 这条原则也适用于普通的数据容器类,比如我们常用的常量类,如果是常量类,那么请提供私有的或受保护的构造器。
Example:
public class abstract Example {
String field;
int otherField;
}
异常处理规则
声明以及捕获异常的指导原则
不要捕获Throwable
捕获Throwable意味着不能处理的异常(错误)你也想处理,比如OutOfMemoryError.
示例代码:
public class Foo {
public void bar() {
try {
// do something
} catch (Throwable th) { //Should not catch throwable
th.printStackTrace();
}
}
}
不要直接throws Exception
不要在方法签名处 throws Exception,除了增加调用者的反感外,没看到有什么实际的好处。正确的做法是要么抛出明确的checked exception要么抛出RuntimeException。Spring框架所有方法都只抛出RuntimeException,这样方便用户对异常的处理,很值得我们借鉴。
示例代码:
public void methodThrowingException() throws Exception {
}
不要用异常处理作为代码流程控制的手段
这样做会产生类似GOTO语句的效果,增加代码理解的难度。
示例代码:
public class Foo {
void bar() {
try {
try {
} catch (Exception e) {
throw new WrapperException(e);
// this is essentially a GOTO to the WrapperException catch block
}
} catch (WrapperException e) {
// do some more stuff
}
}
}
不要捕获NullPointerException
除非你想把真正的错误掩盖掉。
示例代码:
public class Foo {
void bar() {
try {
// do something
} catch (NullPointerException npe) {
}
}
}
不要抛出NullPointerException
用户总是会以为NullPointerException应该是由JVM抛出的。
示例代码:
public class Foo {
void bar() {
throw new NullPointerException();
}
}
不要捕获了某异常再把它抛出去
人们会怀疑你捕获它的目的
示例代码:
public class Foo {
void bar() {
try {
// do something
} catch (SomeException se) {
throw se;
}
}
}
不要继承JavaLangError
Errors是系统级别的异常,我们不要扩展他们。
示例代码:
public class Foo extends Error { }
不要在Finally里抛出异常
这样做会让人感到困惑,还可能掩盖代码的异常或者缺陷,也使代码的清理部分变得不可靠。
示例代码:
public class Foo
{
public void bar()
{
try {
// Here do some stuff
}
catch( Exception e) {
// Handling the issue
}
finally
{
// is this really a good idea ?
throw new Exception();
}
}
}
不要抛出同一种异常的新实例
不管你信不信,我是真的看到过这样的代码。
实例代码:
public class Foo {
void bar() {
try {
// do something
} catch (SomeException se) {
// harmless comment
throw new SomeException(se);
}
}
}
没有使用的代码
就一个原则:没有使用的就去掉,保持代码的干净、整洁。
没有使用的代码包括:
Ø 没有使用的私有成员
Ø 没有使用的本地变量
Ø 没有使用的私有方法
Ø 没有使用的方法参数(参数定义了,但是方法内没有使用此参数)
String 和StringBuffer
不要重复定义字符串
如果代码中包含多个重复的字符串,那么定义一个字符串常量来代替它。
示例代码:
public class Foo {
private void bar() {
buz("Howdy");
buz("Howdy");
buz("Howdy");
buz("Howdy");
}
private void buz(String x) {}
}
不要实例化String对象
通常情况下,这么做没有用处,只会破坏字符串的享元策略。
示例代码:
public class Foo {
private String bar = new String("bar"); // just do a String bar = "bar";
}
不要调用String.toString()
基本没人会这么做的。
示例代码:
public class Foo {
private String baz() {
String bar = "howdy";
return bar.toString();
}
}
没有必要的大小写转换
equalsIgnoreCase() 比 toUpperCase/toLowerCase().equals()更快。
示例:
public class Foo {
public boolean bar(String buz) {
// should be buz.equalsIgnoreCase("baz")
return buz.toUpperCase().equals("baz");
// another unnecessary toUpperCase()
// return buz.toUpperCase().equalsIgnoreCase("baz");
}
}
计算StringBuffer的长度用StringBuffer.length()别用StringBuffer.toString().length()
示例代码:
public class Foo {
void bar() {
StringBuffer sb = new StringBuffer();
// this is bad
if(sb.toString().equals("")) {}
// this is good
if(sb.length() == 0) {}
}
}
如果是单个字符,那么基于字符的操作比字符串更快
比如String.indexOf('a')比String.indexOf("a")快,StringBuilder.append('a')比StringBuilder.append("a")快
示例代码:
public class Foo {
void bar() {
String s = "hello world";
// avoid this
if (s.indexOf("d") {}
// instead do this
if (s.indexOf('d') {}
}
}
构建StringBuffer或StringBuilder时,如果知道长度,请指定,这样性能更好
不指定,则默认长度是16,这样当长度不够时,就会有扩容的动作了。
示例代码:
public class Foo {
void bar() {
StringBuffer bad = new StringBuffer();
bad.append("This is a long string, will exceed the default 16 characters");//bad
StringBuffer good = new StringBuffer(41);
good.append("This is a long string, which is pre-sized");//good
}
}
把某种类型的数据(例如整数)拼接到字符串,不需要调用String.valueOf方法
示例代码:
public String convert(int i) {
String s;
s = "a" + String.valueOf(i); // Bad
s = "a" + i; // Better
return s;
}
初始化StringBuffer或者StringBuilder时,别用字符,因为字符会被转化为整数,这样就会错误地认为你是想指定字符序列的长度而不是初始值。
示例代码:
class Foo {
StringBuffer sb1 = new StringBuffer('c'); //Bad
StringBuffer sb2 = new StringBuffer("c"); //Better
}
比较字符串也要用equals别用==
示例代码:
class Foo {
boolean test(String s) {
if (s == "one") return true; //Bad
if ("two".equals(s)) return true; //Better
return false;
}
}
别用StringBuffer作为类的私有成员
Example:
class Foo {
private StringBuffer memoryLeak;
}
优化规则
性能优化方面的一些最佳实践
如果本地变量只被赋值一次,那么把它声明为Final
示例代码:
public class Bar {
public void foo () {
String a = "a"; //if a will not be assigned again it is better to do this:
final String b = "b";
}
}
如果方法参数从来不会被重新赋值,那么把它声明为Final
示例代码:
public void foo (String param) {
// do stuff with param never assigning it
// better: public void foo (final String param) {
}
尽量避免在循环中实例化对象
示例代码:
public class Something {
public static void main( String as[] ) {
for (int i = 0; i < 10; i++) {
Foo f = new Foo(); //Avoid this whenever you can it's really expensive
}
}
}
如果可以,尽量使用非线程安全的集合类
示例代码:
public class SimpleTest extends TestCase {
public void testX() {
Collection c = new Vector();
// This achieves the same with much better performance
// Collection c = new ArrayList();
}
}
String.startWith性能不如String.charAt(0)
如果你的程序对性能要求很苛刻,那么可以考虑使用String.charAt(0)==x 来代替String.startWith
示例代码:
public class Foo {
boolean checkIt(String x) {
return x.startsWith("a");
}
}
用StringBuilder来进行字符串拼接而不是+
这个规则目前不靠谱,因为JDK5以上的版本已经对“+”进行了优化。
示例代码:
public class Foo {
void bar() {
String a;
a = "foo";
a += " bar";
// better would be:
// StringBuffer a = new StringBuffer("foo");
// a.append(" bar);
}
}
如果想由数组构建List,请使用Arrays.asList
示例代码:
public class Test {
public void foo(Integer[] ints) {
// could just use Arrays.asList(ints)
List l= new ArrayList(10);
for (int i=0; i< 100; i++) {
l.add(ints[i]);
}
for (int i=0; i< 100; i++) {
l.add(a[i].toString()); // won't trigger the rule
}
}
}
数组复制,请使用System.arraycopy,别用循环
示例代码:
public class Test {
public void bar() {
int[] a = new int[10];
int[] b = new int[10];
for (int i=0;i<10;i++) {
b[i]=a[i];
}
}
}
// this will trigger the rule
for (int i=0;i<10;i++) {
b[i]=a[c[i]];
}
}
}
没必要创建包装对象
Integer的各种解析方法应该直接调用。
示例代码:
public int convert(String s) {
int i, i2;
i = Integer.valueOf(s).intValue(); // this wastes an object
i = Integer.parseInt(s); // this is better
i2 = Integer.valueOf(i).intValue(); // this wastes an object
i2 = i; // this is better
String s3 = Integer.valueOf(i2).toString(); // this wastes an object
s3 = Integer.toString(i2); // this is better
return i2;
}
""+123的方式把数字转换为String,不够高效
示例代码:
String s = "" + 123; // bad
String t = Integer.toString(456); // ok
基本规则
人人都要遵守
避免代码中出现各种"空"的语句
空Catch,空If,空while,空try,空finally,空switch,空Synchronized块,空static块