( 摘抄自 Clean Code 一书)
(有意义,读得出,可搜索)
包名全部小写;
类名以大写字母开头的单词组成,应该为名词或名词短语,不能为动词;
方法名,变量名,参数名均从第二单词开始首字母大写,其余小写;
常量以大写字母表示,若由多个单词组成,均以下划线分隔;
集合/数组用复数名称,所有非首个单词的第一个字母都大写;
25行封顶
30秒原则
只做一件事
别重复自己
尽量少写注释
魔数,巨类
便于自己开发,增加代码的可读性,易于与他人的交流,,代码风格前后一致,并且在不同的编辑器下风格一致。
子功能块当在其父功能块后缩进。
当功能块过多而导致缩进过深时当将子功能块提取出来做为子函数。
代码中以TAB(4个字符)缩进,在编辑器中请将TAB设置为以空格替代,否则在不同编辑器设置下会导致TAB长度不等而影响整个程序代码的格式。
为了便于阅读和理解,单个函数的行数不宜超过一个屏幕,单个类的行数不宜超过1500行。
尽量避免一行的长度超过80个字符。当一个表达式无法容纳在一行内时,可以依据如下一般规则断开:
以下是断开方法调用的一些例子:
someMethod(longExpression1, longExpression2, longExpression3,
longExpression4, longExpression5);
var = someMethod1(longExpression1,
someMethod2(longExpression2,
longExpression3));
以下是两个断开算术表达式的例子。前者更好,因为断开处位于括号表达式的外边,这是个较高级别的断开。
longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longname6; //PREFFER
longName1 = longName2 * (longName3 + longName4
- longName5) + 4 * longname6; //AVOID
以下是两个缩进方法声明的例子。前者是常规情形。后者若使用常规的缩进方式将会使第二行和第三行移得很靠右,所以代之以缩进8个空格
//CONVENTIONAL INDENTATION
someMethod(int anArg, Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
//INDENT 8 SPACES TO AVOID VERY DEEP INDENTS
private static synchronized horkingLongMethodName(int anArg,
Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
if语句的换行通常使用8个空格的规则,因为常规缩进(4个空格)会使语句体看起来比较费劲。比如:
//DON’T USE THIS INDENTATION
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) { //BAD WRAPS
doSomethingAboutIt(); //MAKE THIS LINE EASY TO MISS
}
//USE THIS INDENTATION INSTEAD
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
//OR USE THIS
if ((condition1 && condition2) || (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
a) 在名称中使用首字母缩写词时,只有缩写词的首字母缩写,其他部分小写,比如:
exportHtmlSource(); // NOT: exportHTMLSource();
openDvdPlayer(); // NOT: openDVDPlayer();
b) 在很大范围内有效的变量使用长名字,在小范围内有效的变量使用短名字(比如I/j/k/m/n/c/d等)
c) 在调用一个类的方法时,类的名称是隐含的,因此类的名称不应该出现在方法名中。
一个唯一包名的前缀总是全部小写字母并且是一个顶级域名。
类名必须是名词,并且每个单词的首字母必须大写,其余小写。
首字母小写,其他单词的首字母大写。
a) 常量所有字母大写,并且使用下划线分割单词。
方法名必须是动词,并且以小写字母作为首字母。
a) 必须使用get/set方法访问类变量。
b) Boolean变量和方法使用is前缀,也可以使用has/can/should,但是注意前后需要一致。
c) 提供计算功能的方法可以使用compute单词开头
d) 提供查找功能的方法可以使用find单词开头
e) 对象初始化的方法可以使用init单词开头
f) 声明集合对象时使用复数形式。
g) 表示对象个数的变量可以使用n前缀
h) 遍历使用的变量叫i/j/k,并且j/k必须在嵌套的循环里使用,比如
for (Iterator i = points.iterator(); i.hasNext(); ) {
:
}
for (int i = 0; i < nTables; i++) {
:
}
对应的名称需要成对使用。比如get/set, add/remove, create/destroy, start/stop, insert/delete, increment/decrement, old/new,
begin/end, first/last, up/down, min/max, next/previous, old/new, open/close, show/hide, suspend/resume等。
i) 常量声明时必须以一种类型作为前缀,比如
final int COLOR_RED = 1;
j) 异常类必须以Exception结尾。
Java程序有两类注释:实现注释(implementation comments)和文档注释(document comments)。实现注释是那些在C++中见过的,使用/.../和//界定的注释。文档注释(被称为“doc comments”)是Java独有的,并由/*.../界定。文档注释可以通过javadoc工具转换成HTML文件。
实现注释用以注释代码或者实现细节。文档注释从实现自由(implementation-free)的角度描述代码的规范。它可以被那些手头没有源码的开发人员读懂。
注释应被用来给出代码的总括,并提供代码自身没有提供的附加信息。注释应该仅包含与阅读和理解程序有关的信息。例如,相应的包如何被建立或位于哪个目录下之类的信息不应包括在注释中。
在注释里,对设计决策中重要的或者不是显而易见的地方进行说明是可以的,但应避免提供代码中己清晰表达出来的重复信息。多余的的注释很容易过时。通常应避免那些代码更新就可能过时的注释。
注意:频繁的注释有时反映出代码的低质量。当你觉得被迫要加注释的时候,考虑一下重写代码使其更清晰。
注释不应写在用星号或其他字符画出来的大框里。注释不应包括诸如制表符和回退符之类的特殊字符。
实现注释的格式(Implementation Comment Formats)
程序可以有4种实现注释的风格:块(block)、单行(single-line)、尾端(trailing)和行末(end-of-line)。
写作/修改人的姓名,创建 和修改时间,类的用途,需重点注意的要点。
块注释通常用于提供对文件,方法,数据结构和算法的描述。块注释被置于每个文件的开始处以及每个方法之前。它们也可以被用于其他地方,比如方法内部。在功能和方法内部的块注释应该和它们所描述的代码具有一样的缩进格式。
块注释之首应该有一个空行,用于把块注释和代码分割开来,比如:
/*
* Here is a block comment.
*/
块注释可以以/*-开头,这样indent(1)就可以将之识别为一个代码块的开始,而不会重排它。
/*-
* Here is a block comment with some very special
* formatting that I want indent(1) to ignore.
*
* one
* two
* three
*/
注意:如果你不使用indent(1),就不必在代码中使用/*-,或为他人可能对你的代码运行indent(1)作让步。
短注释可以显示在一行内,并与其后的代码具有一样的缩进层级。如果一个注释不能在一行内写完,就该采用块注释(参见“块注释”)。单行注释
之前应该有一个空行。以下是一个Java代码中单行注释的例子:
if (condition) {
/* Handle the condition. */
...
}
极短的注释可以与它们所要描述的代码位于同一行,但是应该有足够的空白来分开代码和注释。若有多个短注释出现于大段代码中,它们应该具有
相同的缩进。
以下是一个Java代码中尾端注释的例子:
if (a == 2) {
return TRUE; /* special case */
}
else {
return isPrime(a); /* works only for odd a */
}
注释界定符"//",可以注释掉整行或者一行中的一部分。它一般不用于连续多行的注释文本;然而,它可以用来注释掉连续多行的代码段。以下是
所有三种风格的例子:
if (foo > 1) {
// Do a double-flip.
...
}
else {
return false; // Explain why here.
}
//if (bar > 1) {
//
// // Do a triple-flip.
// ...
//}
//else {
// return false;
//}
注意:此处描述的注释格式之范例,参见"Java源文件范例"
若想了解更多,参见"How to Write Doc Comments for Javadoc",其中包含了有关文档注释标记的信息(@return, @param, @see):
http://java.sun.com/javadoc/writingdoccomments/index.html
若想了解更多有关文档注释和javadoc的详细资料,参见javadoc的主页:
http://java.sun.com/javadoc/index.html
文档注释描述Java的类、接口、构造器,方法,以及字段(field)。每个文档注释都会被置于注释定界符/*.../之中,一个注释对应一个类、接口或
成员。该注释应位于声明之前:
/**
* The Example class provides ...
*/
public class Example { ...
注意顶层(top-level)的类和接口是不缩进的,而其成员是缩进的。描述类和接口的文档注释的第一行(/**)不需缩进;随后的文档注释每行都缩进1
格(使星号纵向对齐)。成员,包括构造函数在内,其文档注释的第一行缩进4格,随后每行都缩进5格。
若你想给出有关类、接口、变量或方法的信息,而这些信息又不适合写在文档中,则可使用实现块注释或紧跟在声明后面的单行注释。例如,有关
一个类实现的细节,应放入紧跟在类声明后面的实现块注释中,而不是放在文档注释中。
文档注释不能放在一个方法或构造器的定义块中,因为Java会将位于文档注释之后的第一个声明与其相关联。
类注释使用如下格式:
/**
*
*
* Copyright:
* Company:
* @author
* @author
* @version 1.00 9999/99/99
* 9.99 9999/99/99
* 9.99 9999/99/99
* @see 1
* @see 2
*/
例子:
/**
* Title:111
* Description: 222
* Copyright: Copyright (c) 2010
* Company: cmbc
* @author 123
* @version 1.00
*/
方法使用如下方式的注释:
/**
*
*
* @param 1 1
* @param 2 2
* @param 3 3
* @return
* @throws .
* @throws
* @see 1
* @see 2#
*/
例子
/**
* Return lateral location of the specified position.
* If the position is unset, NaN is returned.
*
* @param x X coordinate of position.
* @param y Y coordinate of position.
* @param zone Zone of position.
* @return Lateral location.
* @throws IllegalArgumentException If zone is <= 0.
*/
public double computeLocation(double x, double y, int zone)
throws IllegalArgumentException
{ ... }
注意:
a) 开头在独立的一行以/**开头
b) 下面行中的*要与第一个*对齐
c) 每个*后面要有一个空格
d) 在方法描述部分和参数部分要有一个空白行
e) 每个参数的描述部分要对齐
f) 每个参数描述部分以标点符号结尾
a) 尽量使用明了的逻辑结构以及适当的名称使代码能够自解释来减少注释的使用。
b) 在注释分割符后添加一个空格。
c) 如果是集合类型没有指定对象类型,那么要在声明的后面使用注释说明装载的对象类型,比如:
private Vector points_; // of Point
private Set shapes_; // of Shape
d) 任何时候使用集合类型,都要尽可能地在声明时说明装载对象的类型。
e) 所有public类以及public类内的public和protected方法都要使用Javadoc进行注释。
f) 所有非javadoc注释都要使用//的方式进行注释,包括多行注释。
在多数Java源文件中,第一个非注释行是包语句。在它之后可以跟import语句。Package和import语句之间以空行分开。Import语句以基本包开始,
将相关的一类包归为一组,每组之间以空行隔开,且引入的类必须显示的指明(不能用import java.util.*这样的)。例如:
package java.awt;
import java.io.IOException;
import java.net.URL;
import java.rmi.RmiServer;
import java.rmi.server.Server;
import javax.swing.JPanel;
import javax.swing.event.ActionEvent;
import org.linux.apache.server.SoapServer;
类和接口的声明顺序如下:
类/接口文档
class或者interface语句
类(static)变量以public,protected,package(包可见),private的顺序声明
实例变量以public,protected,package(包可见),private的顺序声明
构造函数
方法
方法声明应该遵循以下顺序:
Access指方法的可见性,有public/protected/package/private四种,必须放在最前面
尽可能在变量声明的时候将变量初始化,并且保证变量的有效范围尽可能地小。
a) 只有循环控制语句能够在for()构造语句中出现,比如:
sum = 0; // NOT: for (i = 0, sum = 0; i < 100; i++) sum += value[i];
for (i = 0; i < 100; i++){
sum += value[i];
}
b) 循环变量应该在循环前初始化。
c) 避免使用do-while
避免使用复杂的条件表达式,可以使用临时变量替代,增加代码的可读性,例如:
bool isFinished = (elementNo < 0) || (elementNo > maxElement);
bool isRepeatedEntry = elementNo == lastElement;
if (isFinished || isRepeatedEntry){
:
}
// NOT:
if ((elementNo < 0) || (elementNo > maxElement)|| elementNo == lastElement) {
:
}
不要条件中包含执行语句,例如:
InputStream stream = File.open(fileName, "w");
if (stream != null) {
:
}
// NOT:
if (File.open(fileName, "w") != null)) {
:
}
一个switch语句应该具有如下格式:
switch (condition) {
case ABC:
statements;
// Fallthrough
case DEF:
statements;
break;
case XYZ:
statements;
break;
default:
statements;
break;
}
每当一个case顺着往下执行时(因为没有break语句),通常应在break语句的位置添加注释。上面的示例代码中就包含注释// Fallthrough。
有时为了调试方便,进行输出某一些部分的值。而采用
System.out.println(“”);
这种打印语句的,在程序完成后必须去掉。
如果需要换行的话,尽量用 println 来代替在字符串中使用"\n"。
例如:
System.out.print("Hello,world!\n"); //Not Good
System.out.println("Hello,world!"); //Right
或者你构造一个带换行符的字符串,至少要象这样:
String newline = System.getProperty("line.separator");
System.out.println("Hello world" + newline);
为防止维护人员错误理解以及在维护时修改错误,在语句块中,无论语句有多少,都不能省略”{}”. 例如:
if (null == obj) {
return;
}
longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longname6;
private static final int TEAM_SIZE = 11;
Player[] players = new Player[TEAM_SIZE]; // NOT: Player[] players = new Player[11];
double total = 0.0; // NOT: double total = 0;
double speed = 3.0e8; // NOT: double speed = 3e8;
double sum;
sum = (a + b) * 10.0;
double total = 0.5; // NOT: double total = .5;
禁止使用float和double进行金额加减乘除运算,必须使用java.math.BigDecimal对金额进行运算。两个构造函数:
BigDecimal(double val)和BigDecimal(String val)
强烈要求使用后者,因为前者的结果存在一些不可预测的结果,仍然没有达到精确计算的精度。在我们EMU的交易中,输入金额往往为double类型,
需要使用BigDecimal(String val)
来实现。进行加减乘除运算对应的方法为:add,subtract,multiply,divide.
如果需要对运算的结果按照约定的小数位数进行舍入,必须使用:
Java.math.BigDecimal.setScale(约定小数位数,RoundingMode.HALF_UP)
例如:
alpha = (aLongBooleanExpression) ? beta : gamma;
或
alpha = (aLongBooleanExpression)
? beta
: gamma;
异常用于通知系统出现了不正常的情况,这种不正常的情况需要传递给相应的处理者。异常的目的是为了对异常的处理。只有在程序处理范围之外
出现的不正常情况才属于异常,逻辑判断不属于异常范畴。
异常仅指从Exception和RuntimeException继承的类,程序通过异常类的不同来区分不同的异常。
使用Runtime异常,而不要使用Checked异常。这样只需要在方法注释中说明可能产生什么异常,但不需要在方法定义的时候声明,调用者也不需要强制捕获异常。
不要使用Error,Error用于JDK出现的错误,也不要实现Throwable接口。
如果可以通过返回null来说明不正常的情况,尽量采用此种方式,API中应有说明。
异常提供出的信息包括:异常的类型(类名)、异常信息、异常现场信息。
异常类型定义应恰如其分,异常信息应当准确而简约,异常现场信息是对异常信息的补充,用于描述异常发生的上下文信息,帮助通过日志进行排错。如:某个渠道某个交易拆包出错,那么“拆包出错”属于异常信息,而渠道和交易属于现场信息。
如果方法中会抛出异常,要在方法声明的注释中添加异常注释,RuntimeException不需要在方法声明中添加throws部分。
异常的处理通常会有如下几种情况:
a) 记录日志。
b) 对异常情况做恢复处理。
c) 把异常包装为另一个异常抛出,参见异常转换。
d) 忽略,永远不要忽略异常,如有特殊情况,至少要说明忽略的原因。
类的调用是有层次的,那么在高层方法调用底层方法时,底层方法抛出底层的异常,高层方法经常需要做异常的转换,即将底层的异常用高层的异常进行包装,必须将底层异常作为高层异常构造参数,然后抛出高层的异常。
捕获到底层异常的方法,可以选择处理并记录日志也可现在异常转换,如选择异常转换则不需要记录日志。
尽可能使用JDK中的已有异常类,如:IllegalArgumentException,IllegalStateException等。
任何异常都不应该改变对象调用方法之前的状态,如果有这种情况,则必须在API文档中说明。
除了get/set(有一定逻辑的除外)方法之外,其他public方法均须编写测试案例。
每次提交代码之前先进行单元测试,测试不通过的代码不得提交。
测试应当对数据库等资源不留或少留痕迹,例如,当测试添加一个用户时,在其成功后当及时从数据库中删除该记录,以避免脏数据的产生(由此衍生的一个经验是将添加、获取、删除一起测试)。