介绍
代码风格是指在编写代码时采用的一种惯例,如注释的格式、顺序的安排和变量的定义等等非处理逻辑上的考虑。遵循统一代码风格需要在
编程时花费一些时间,但是这种代价是值得的,因为:
软件在它生命周期的80%的时间里主要工作是维护。
通常维护软件的人并不是其原有的开发者。
统一的代码风格能大大提高源代码的可读性,使工程师能更快更彻底地理解新的代码。
如果将源代码转化成产品,需要象其他产品一样进行良好而干净的包装。
读者范围
主要为前端开发人员在写Java代码时参考。
内容范围
本文主要介绍了Sun发布的Java
编程的代码风格。
参考文献
[1] Code Conventions for the JavaTM Programming LanguageSun Microsystems, Inc.
文件命名
Java的文件主要有两种,
编程的源代码与解释后的字节码,前者的后缀是.java,后者是.class。使用JBuilder开发时,还会有.jpr表示Java的项目文件,每个项目一般都会带一个描述性质的html文件,Java的文件名是大小写敏感的。
需要说明的是Java的源代码文件名(不含后缀)需要和该文件中公共的类名字相同,否则编译器在编译时会报错。另外字节码文件是由JDK自动生成的,文件名与源代码相比只是后缀不同。对于一个源代码文件中定义了多个类或是在类中包含了匿名类的情况,JDK会自动将其余的类编译成文件名+$+数字的格式。
文件组织
Java源代码文件是由根据空行分隔的段落来组成的,有时每个段落还会有注释来标识。通常每个文件不超过2000行代码。
每个文件中包含一个唯一的公共类或公共接口,如果私有类或接口与一个公共类密切相关,可以将它放入公用类所在的文件中,但是公共类或公共接口应该在文件中最先定义。一个源代码文件通常由起始注释、包以及引用声明和类或接口的定义三部分组成。
起始注释
所有的源代码文件都应该有C语言风格的起始注释,列出类名、版本号、日期和版权信息等,如下所示:
/*
* Classname
*
* Version information
*
* Date
*
* Copyright notice
*/
包和引用声明
大部分Java源代码文件的非注释内容的第一行是包声明(如果有的话),然后是引用声明:
package java.awt;
import java.awt.peer.CanvasPeer;
需要注意的是,包的名字全部采用小写,一般均以顶级域名开始,如com、edu、net等或是1981年ISO3166中制定的两个英文字母的国家名称标准缩写。
类或接口定义
下表按先后顺序列出了定义体中的内容:
序号
内容
说明
类/接口的
文档注释(documentation comments)(/** ... */)
类/接口的声明
类/接口的
编程注释(implementation comments)(/* ... */)
包括所有非
文档注释的注释内容
类(静态)变量
从先到后按public,protected,package level(缺省类型,即无访问修饰符),private的顺序
对象(实例)变量
顺序同上
构造函数
方法
方法的编码顺序并不是按访问类型而是根据功能来安排,主要目的是使代码便于阅读
代码缩排
代码缩排的单位是4个空格,即缩排的空格必须是4的整数倍。Java并不指定用空格或是Tab键,但如果用Tab键的话,必须将Tab键设为8个空格。
每行代码的长度尽量少于80个字符,因为在很多终端或
工具中不能很好地处理超过80个字符的行。在
文档注释中的代码行则更短,一般每行不超过70个字符。
对于超长代码进行折行要遵循如下规则:
在逗号后折行
在操作符前折行
如果行中存在不同级别的表达式,尽可能在高一级的表达式中折行
折行后尽量与上一行中同级表达式的起始部分对齐
如果上述规则引起排版混乱,改用缩排8个空格的方法
如下所示:
someMethod(longExpression1, longExpression2, longExpression3,
longExpression4, longExpression5);
var = someMethod1(longExpression1,
someMethod2(longExpression2,
longExpression3));
对于不同级别表达式的折行:
longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longname6; // 正确的折行方法
longName1 = longName2 * (longName3 + longName4
- longName5) + 4 * longname6; // 不好的折行方法
if表达式的折行通常采用8个空格的缩排方法,因为4个空格往往使执行体难以区分出来,如:
//用4个空格缩排
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) { //不好的折行方式
doSomethingAboutIt();//它使这一行很容易被漏过
}
//改用8个空格缩排
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
//或者如此
if ((condition1 && condition2) || (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
对于三目运算符,可以有多种折行方式:
alpha = (aLongBooleanExpression) ? beta : gamma;
alpha = (aLongBooleanExpression) ? beta
: gamma;
alpha = (aLongBooleanExpression)
? beta
: gamma;
注释
Java的注释分为
文档注释和
编程注释两种,
编程注释与C++相同,由/*...*/或//...标识,
文档注释是Java独有的,用/**...*/标识,可以用java
文档工具将其抽取为HTML文件。
编程注释是用来对代码或特殊实现注释,它是与实现相关的,而
文档注释则是代码的说明书,是与实现无关的,供那些不必知道源代码的详细实现细节开发者阅读。
注释用来对代码提供简介或不能直接从代码中获得的附加信息,注释只提供为阅读或理解
程序所需的相关信息,至于如何建立相应的包或它们存在与哪个目录下等信息则不是注释所能包含的范围。
在注释中还可讨论不一致或不明显的设计决策,但要避免重复在代码中已提供或是显而易见的信息,冗余的注释很容易过时,通常,应避免任何类似于包含代码之类容易过时的注释。
需要注意的是,频繁的注释通常反映出代码的质量之差,当感觉重复注释时,可能需要重新组织代码使它更清晰。
Comments should not be enclosed in large boxes drawn with asterisks or other characters.
注释中不能包含如分页符与回退键等特殊字符。
编程注释
编程注释共有四种:块注释、单行注释、拖后注释(Trailing Comments)和行末注释。
块注释
块注释用来对文件、方法、数据结构和算法进行描述,它一般在文件的开始或在方法之前,它们也可放在其他地方如方法中。在方法或函数中的注释要与他们所注释的代码的缩进保持一致。块注释前保留一空行以便与其它代码分开。例如:
/*
* Here is a block comment.
*/
单行注释
单行注释与它随后的代码保持相同的缩进,当一行容不下时,它将变成块注释,单行注释前也需要保留一空行。例如:
if (condition) {
/* Handle the condition. */
...
}
拖后注释
对于很短的注释采用拖后注释的方法,将注释与代码放在同一行,与代码保持足够的距离。如果在一块代码中有多条拖后注释的话,它们应该保持相同的缩进。例如:
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;
//}
文档注释
文档注释比较复杂,详情请参见以下链接:
http://java.sun.com/javadoc/writingdoccomments/index.html
声明
声明的一个基本原则是每个声明一行,因为这样方便注释,如:
int level; // indentation level
int size;// size of table
就比
int level, size;
好,同时千万不能将不同类型的声明放在同一行,如:
int foo,fooarray[]; //错误!
上面的几个例子中声明的类型与变量用一个空格分开,另一种方法是用Tab键隔开,如:
intlevel;// indentation level
intsize;// size of table
ObjectcurrentEntry; // currently selected table entry
注意:尽量在声明时初始化变量,只有在变量初始值需要在第一次出现时计算的情况下才可以不用初始化。
声明的位置
只在块(由一对{}包围起来的代码)的开始声明变量,而不是等到要用变量时才声明它,那样会使没注意到的
程序员迷惑,并且妨碍了变量在它的适用范围内的使用。
void myMethod() {
int int1 = 0;// beginning of method block
if (condition) {
int int2 = 0;// beginning of "if" block
...
}
}
唯一的一个例外是for语句的循环索引,它可以在使用时声明,在退出循环后索引的生命周期也宣告结束,如:
for (int i = 0; i < maxLoops; i++) { ... }
要避免当前块中声明的变量已在高层定义的情况,虽然Java能将二者区分开来,但这严重地破坏了
程序的可读性,应该避免出现如下的情况:
int count;
...
myMethod() {
if (condition) {
int count = 0;// AVOID!
...
}
...
}
类和接口的声明
在声明类和接口时,需要注意以下事项:
在方法名和随后的括号中间不能留有空格
开始的花括号”{”与声明保持同一行
结束的花括号”}”另起一行,与它所对应的开始语句保持相同的缩进,但在中间无内容时可以与”{”在同一行上紧跟其后,如:
class Sample extends Object {
int ivar1;
int ivar2;
Sample(int i, int j) {
ivar1 = i;
ivar2 = j;
}
int emptyMethod() {}
...
}
方法的声明前保留一个空行。
表达式
对简单表达式每行至多包含一个,如:
argv++;// 正确
argc--;// 正确
argv++; argc--;// 要避免这种情况!
复合表达式
复合表达式是在一对花括号中包含多个表达式的表达式,对它需要做到如下几点:
内嵌的表达式要比复合表达式多缩进一层。
开始的花括号在开始行的末尾,结束的花括号另起一行并与开始行保持相同的缩进。
对于控制结构的表达式,即使只是简单的表达式,也使用花括号,这样不会因为添加后语句忘记加上花括号而导致错误。
Ruturn语句
一般情况下,返回值不用括号,除非为了使其更明显:
return;
return myDisk.size();
return (size ? size : defaultSize);
if, if-else, if else-if else 语句
全部加上{},格式如下:
if (condition) {
statements;
}
if (condition) {
statements;
} else {
statements;
}
if (condition) {
statements;
} else if (condition) {
statements;
} else{
statements;
}
for语句
for语句的格式如下:
for (initialization; condition; update) {
statements;
}
对于空循环语句可以不用{}:
for (initialization; condition; update);
注意:尽量避免在for语句中使用复杂的表达式和超过三个以上的变量,可以改在for开始之前或循环体末尾,也可达到相同的效果。
while语句
while语句的格式如下:
while (condition) {
statements;
}
对于空循环语句可以不用{}:
while (condition);
do-while语句
do-while语句的格式如下:
do {
statements;
} while (condition);
switch语句
switch语句的格式如下:
switch (condition) {
case ABC:
statements;
/* falls through */
case DEF:
statements;
break;
case XYZ:
statements;
break;
default:
statements;
break;
}
如上例所示,对于没有break语句的case分支,需要在最后一句添加一条注释。每个switch语句都要有default分支,default分支中的break是冗余的,但这样能防止以后又添加分支后出错。
try-catch语句
try-catch语句的格式如下:
try {
statements;
} catch (ExceptionClass e) {
statements;
}
如果有finally子句:
try {
statements;
} catch (ExceptionClass e) {
statements;
} finally {
statements;
}
空格
空行
空行通过将逻辑相关的代码分隔为段落以提高代码的可读性。
在下列情况下用两个空行:
源代码的段落之间,即起始注释、包和引用声明和类或接口定义体间。
类的定义和接口定义间。
在下列情况下用一个空行:
方法之间
方法内的变量定义与第一个语句间
在块或单行注释之前
在方法内逻辑段落间为提高可读性也用空行隔开
空格
在下列情况下用空格:
关键字与其后的括号需要用空格分开,如:
while (true) {
...
}
但方法与其后的括号不用空格分开,已此区别关键字与方法。
参数列表中逗号后面需要一个空格。
除了.外的所有二元运算符都要用空格与操作数分开,单目运算符(如负号-、++等)与操作数间不能用空格分开,如:
a += c + d;
a = (a + b) / (c * d);
while (d++ = s++) {
n++;
}
printSize("size is " + foo + "n");
for中的表达式间在;后用空格分开,如:
for (expr1; expr2; expr3)
类型转换需要在类型后添加一个空格,如
myMethod((byte) aNum, (Object) x);
myMethod((int) (cp + 5), ((int) (i + 3))
+ 1);
命名约定
命名约定通过使
程序易读而易于理解,好的命名同时也能提供它们所表示的方法或变量的信息。
标识类型
命名规则
示例
包
Packages
包的名字全部采用小写,一般均以顶级域名开始,如com、edu、net等或是1981年ISO3166中制定的两个英文字母的国家名称标准缩写。
包名中的其它部分根据组织自身的内部命名规则而不同,可能是根据目录、部门、项目、机器名甚至是登录名的层次来确定。
com.sun.eng
com.apple.quicktime.v2
edu.cmu.cs.bovik.cheese
类
Classes
类名应用大小写混合的名词,每个单词中的首字母大写,其余小写,名字应简单且具有描述性,也可用首字母大写或缩写的形式,除非该缩写已被广泛应用,如URL和HTML等。
class Raster;
class ImageSprite;
接口
Interfaces
命名方法同类名。
interface RasterDelegate;
interface Storing;
方法
Methods
方法应用大小写混合的动词,每个单词中的首字母大写,其余小写。
run();
runFast();
getBackground();
变量
Variables
除了变量,所有的实例、类和类的常量都是大小写混合并且首字母小写,内部其他单词的首字母大写。变量名不能由_或$开头,即使它们是合法的。
变量的名称要既简洁又有意义,还要便于记忆。尽量不用单个字母的变量,除非是用完即止的临时变量,在临时变量中通常用i, j, k, m和n表示整数,用c, d和e来表示字符变量。
inti;
charc;
floatmyWidth;
常量
Constants
常量应采用中间用_隔开的全部大写的单词来表示。
static final int
MIN_WIDTH = 4;
static final int
MAX_WIDTH = 999;
static final int
GET_THE_CPU = 1;
编程实践
访问控制
对于实例和类变量,除非有足够的理由,否则不要将其变为公有访问权限。Often, instance variables don''t need to be explicitly set or gotten-often that happens as a side effect of method calls.
一个适用的公用实例变量的例子是对于那些没有方法,只有变量的类,它其实是一个结构。换句话说,如果你需要的是一个结构而不是类的话,你可以将实例变量公有化。
类变量和类方法
对类变量和类方法的引用要用类名而不是对象名,如:
classMethod();//OK
AClass.classMethod();//OK
anObject.classMethod();//AVOID!
常量
除了在for循环中用到的-1, 0和1外,不要直接在
程序中使用数值与文字,而要先用常量来定义后使用。
变量分配
要避免在一个语句中给许多变量分配相同的值的情况,那样将很难读懂。如:
fooBar.fChar = barFoo.lchar = ''c''; // AVOID!
不要在容易与比较运算符混淆的地方使用赋值语句,这也是Java所不允许的,如:
if (c++ = d++) {// AVOID! (Java disallows)
...
}
上述的情况可以写为:
if ((c++ = d++) != 0) {
...
}
不要为了提高运行性能而使用内嵌的赋值语句,如下所示是没有必要的,因为编译器完全可以做到性能的优化:
d = (a = b + c) + r;// AVOID!
正确的写法是:
a = b + c;
d = a + r;
杂项实践
括号
通常在混合表达式中多用括号以避免运算符优先级的问题是个好主意,即使在你看来优先级的顺序十分明显时也是如此,因为也许在别人看来并不是如此。
返回值
尽可能使
程序的结构符合本意,如:
if (booleanExpression) {
return true;
} else {
return false;
}
就不如:
return booleanExpression
简洁明了。同样,
if (condition) {
return x;
}
return y;
可以写成:
return (condition ? x : y);
?前的表达式
在三目运算符?:的?前如果是一个表达式的话,最好给表达式加上括号,如:
return (condition ? x : y);
特殊注释
在注释中用”XXX”表示某些能正常工作的错误,用”FIXME”表示不能正常工作的错误。
代码示例
/*
* @(#)Blah.java1.82 99/03/18
*
* Copyright (c) 1994-1999 Sun Microsystems, Inc.
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information").You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*/
package java.blah;
import java.blah.blahdy.BlahBlah;
/**
* Class description goes here.
*
* @version 1.82 18 Mar 1999
* @author Firstname Lastname
*/
public class Blah extends SomeClass {
/* A class implementation comment can go here. */
/** classVar1 documentation comment */
public static int classVar1;
/**
* classVar2 documentation comment that happens to be
* more than one line long
*/
private static Object classVar2;
/** instanceVar1 documentation comment */
public Object instanceVar1;
/** instanceVar2 documentation comment */
protected int instanceVar2;
/** instanceVar3 documentation comment */
private Object[] instanceVar3;
/**
* ...constructor Blah documentation comment...
*/
public Blah() {
// ...implementation goes here...
}
/**
* ...method doSomething documentation comment...
*/
public void doSomething() {
// ...implementation goes here...
}
/**
* ...method doSomethingElse documentation comment...
* @param someParam description
*/
public void doSomethingElse(Object someParam) {
// ...implementation goes here...
}
}