目 录
- 序言
本规范的目的在于编写出可靠的,一致的,便于理解的JAVA源代码,并且写出的代码容易被其他人阅读,所有的JAVA代码保持良好的一致性,容易在以后的阶段中进行维护和升级。同时,也是为了通过一定时间的实践,增加软件产品的投入产出量,以提高工作效率。
- 代码命名的一般原则
在JAVA代码中,所有的代码命名的总原则是:
- 用全英文单词命名的方式,准确地描述变量、属性、类等。如:使用firstName, grandTotal等命名就比x1,y1,fn等更容易让人理解其含义,尽管它们的长度要大一些。
- 采用一些更加准确的术语来命名。例如,如果我们的用户称他们的clients为customers,那么我们就应该采用customer来命名,而不是采用client来命名。这是一些细微的地方,但也希望能注意。
- 采用大小写混合的方式来命名,以便命名有很好的可读性。在JAVA代码中,将采用如下原则:类或接口命名中每个单词的首字母均大写,其它单词的首字母均大写。其它像变量、成员方法、属性等除第一个单词全部小写以外,其它单词的首字母均大写,而单词的剩余部分均小写。????
- 尽量少用单词的缩写形式,但如果一定要用,则必须要选择大家通用的缩写方式,并且要在本JAVA源代码中坚持用它,而不要一会用这种缩写方式,一会用那种缩写方式。比如,如果要用"number"的缩写方式,则可用"no"或"num"两种缩写方式,而不要用"nu"这种大家不常用的缩写方式,并且要保持不变。
- 尽量避免太长的命名,一般以少于20个字符为宜。
- 尽量避免使用这样的命名:两个或多个命名仅仅是其中的有些字符大小写不一样,或者仅仅是其中有些单词是单复数之区别。例如:persistentObject 与 persistentObjects; anSqlDatabase 与 anSQLDatabase等。
- 尽量避免使用下划线,如要特殊需要可以使用。
- 代码注释的一般原则和类型
在JAVA代码中,我们经常要使用代码注释的方式来帮助理解代码的含义。代码注释的一般原则主要有以下几个方面:
- 代码中的注释应该以让人一目了然为目标。我们之所以要增加代码注释,其目的就是为了让编写代码的人、同一项目组的成员或以后的开发人员能很容易的理解其代码的意图。
- 避免使用标语式的、实际毫无用处的代码注释。
- 尽量采用简洁、易懂的注释代码,而不要长篇大论。
- 有些特殊的代码中要加以适当的注释,以说明其为什么要这样做。例如,一些特殊的商业规范的计算等等。
在JAVA代码中,经常使用三种类型的注释:文档型的注释,其一般格式是:以"/**"开头,以"*/"结尾; C语言类型的注释,它通常是以"/*"开头,以"*/"结尾;还有一种是通常用的单行注释的方法,即以"//"开头,对一个单行进行注释。下面以表格的方式,分别说明这三种方式在JAVA中如何更好的使用。
注释类型 | 使用方式 | 例程 |
文档型注释 | 文档型注释经常用在定义接口,类,成员方法,域的定义之前,并且这种注释可以被通过javadoc直接生成帮助文档。 | /** Customer- A customer is any person or …… @author S.W.Ambler */ public class Customer{} |
C风格型的注释 | 当要一次对多行代码进行注释,或者这些代码将不被执行,或者对这些行进行调试时,本注释是一种比较合适的注释方法。 | /* This code was commented .. ………… ……(the source code) */ |
单行注释 | 通常在成员方法内部注释商业逻辑,一小节代码,或者临时变量的声明。 | //Apply a 5% discount to all //invoices over $1000 as //defined by the ….. |
同时,为了编程人员所做的文档型注释,能更有效的生成HTML格式的帮助文件,将提供以下一些更多的内容:
标志 | 使用 | 目的 |
@author authorName | 一般在类、接口定义之前的文档型注释里面使用。 | 一般是指明编写本接口或本类的作者,当然可以有多个,每一个作者用@author标明。 |
@deprecated | 一般在类,成员方法定义之前的文档型注释里面使用。 | 它一般用于声明一个类或者一个成员方法被废弃,不再使用。 |
@exception name description | 成员方法定义之前的文档型注释里面使用。 | 用于声明本成员方法将可能会抛出的异常。每一个异常用一个@exception标明。 |
@param name description | 成员方法定义之前的文档型注释里面使用。 | 用于说明传递给该成员方法的参数,包括其类型和它的使用,每一个参数用@param标明。 |
@return description | 成员方法定义之前的文档型注释里面使用。 | 用于说明本成员方法的返回值类型,如果可能,适当的描述其可能的返回值 |
@see ClassName | 一般在类,接口,成员方法,域定义之前的文档型注释里面使用。 | 用于建立一个超文本链接到指定的类,接口。一般应该详细的描述其类名。 |
@see ClassName#member functionsName | 一般在类,接口,成员方法,域定义之前的文档型注释里面使用。 | 用于建立一个超文本链接到指定的成员方法。一般应详细描述类名。 |
@version text | 一般在类,接口定义之前的文档型注释里使用。 | 用于指定相应的版本信息。 |
- 方法命名和注释规范
- 成员方法的命名
成员方法的命名应该采用全英文单词且是大小写混合的方式来进行。方法名的第一个单词均小写,其它单词的首字母大写。而且方法名中第一个单词最好是一个动词形式的。如:openAccount() , printMailingLabel() , save() , delete() 等。
通过这样的命名规范,很容易看出这个方法是干什么用的,它的目的是什么。尽管有时方法名会比较长,但它能很容易让其他人理解本方法的意图。
get方法,它一般返回一个属性值,一般应将get放在方法名中第一个单词的位置。而当它返回的是判断其结果是true或者false的时候,一般用"is"来取代"get"。例如:getFirstName() , getAccountNumber() , isPersistent() , isAtEnd() 等等。
通过这种命名规范,就可以很清楚的知道本方法是返回一个对象的属性或返回一个boolean值。
同样,对于set方法,它一般是用于设置对象的属性值,同样也将set放在方法名中的第一个单词的位置。如:setFirstName(String aName), setAtEnd(boolean isAtEnd), setPersistent(boolean isPersistent)等等。
- 构造方法的命名
构造方法一般是用来创建一个对象时要执行的初始化操作。构造方法必须具有与类名完全一致的名字,其大小写也必须完全一致。这种命名规范是由sun公司确定,且必须遵守。
- 成员方法的头部注释文档规范
每一个成员方法的前面必须要有相应的注释文档说明,它主要包括以下内容,但不仅限于以下内容:
- 本成员方法的目的是什么。
- 本成员方法要传递的参数是什么。它们是如何被使用的。
- 本成员方法的返回值是什么。
- 本成员方法会抛出什么样的异常。
- 代码更改的一些历史信息,如什么时候被更改过,谁更改的,目的是什么等等一些信息。
- 成员方法的内部注释文档规范
为了更好的描述成员方法的目的,还会在成员方法的内部给予适当的注释,一般有两种格式:/*……..*/和//……….。一般而言,内部注释文档应该说明以下一些要素:
- 控制结构,描述每一个控制结构是什么样的。
- 在某些特殊的地方说明本代码为什么要这样编写。例如,在某些特殊的商业规范的处理代码中,给予说明。
- 局部变量的说明,通常用单行的注释说明它们是如何使用的,意义是什么。
- 对难以理解的,复杂的代码要有适当的说明,以便于其他开发人员理解。
- 对一些必须按照特定的顺序被执行,否则出错的代码段,对其处理顺序进行适当的文档说明。
- 成员方法的访问权限控制
成员方法的访问权限控制的基本原则是尽量的限制对成员方法的访问权限。如果没有必要设置为public,则将其设置为protected,否则设置为默认方式或者private。
- getXxxx方法和setXxxx方法的使用
getXxxx方法和setXxxx方法的名称一般是: get + 属性名/ set + 属性名 或 is +属性名。 例如:
属性名 | 类型 | get方法 | set方法 |
FirstName | String | getFirstName() | setFirstName(参数表) |
Persistent | Boolean | isPersistent() | setPersistent(参数表) |
OrderItems | Array of OrderItems Objects | getOrderItems() | setOrderItems(参数表) |
getXxxx方法和setXxxx方法一般对其有如下的使用方式:
- 设置对象属性的初始值;
- 更新对象的属性值;
- 得到属性值;
- 控制对属性值的访问和修改,特别是修改;
- 访问常量;
- 访问数组,vector等多值数据变量;
- 降低父、子类之间的耦合程度。 例如:
// Answer the branch number, which is the leftmost four digits of the full account number
protected int getBranchNumber(){
if (branchNumber = = 0){
//The default branch number is 1000,which is the main branch in downtown Bedrock
setBranchNumber(1000);
}
return branchNumber;
}
getXxxx方法和setXxxx方法的访问权限的控制:
一般情况,将它们设置为"protected",这样,只有同包类或子类的对象可以访问它们。但是通常将getXxxx方法设置为"public"方式,而setXxxx方法则是"protected"方式。只有在极少数情况下才将setXxxx方法设置为"public"方式。当然,有时也将setXxxx方法设置为"private"方式。
- 属性变量的命名和注释规范
- 属性变量的命名规范
属性变量的命名采用全英文单词的混合命名方式,第一个单词全部小写,以后单词的
第一个字母大写,其余小写。以便让它易于理解。而对于那些像数组或动态数组等一些多值的数据类型属性,则应该采用单词复数的方式命名(s加在最后一个单词上面),它的主要优点是可以方便的指明它们有多个值。例如: firstName, zipCode, orderItems等。
- 属性变量的访问权限控制
为了使对象具有更好的封闭性,一般不把属性的访问权限设置为public,建议一般把所有的属性访问权限设置为private,以防止其它类的对象直接访问本类对象的属性。而对属性的访问可以用getXxxx方法和setXxxx方法进行。
- 属性变量的注释
每一个属性变量都要给予注释,以便其他开发人员更好的理解其含义。一般从以下几个方面给予注释:
- 描述属性变量,以便其他开发人员知道如何使用它;
- 访问权限的说明。如果你的属性变量的访问权限不是private修饰的,则必须给予说明其为什么是这样的;
- 对于某些特殊取值(范围)的属性,其取值(范围)给予适当的说明,以便其他开发人员理解和使用;
- 适当的例子说明。对与某些特殊情况相关的属性,可适当的加以例子说明,以便于别人更好的理解和使用。
- 组件变量的命名规范
对于组件的命名,一般采用全英文单词的命名方式,但是采用将组件类型名作为后缀的方法。这样,就很容易识别这个组件变量的目的,从它的后缀类型就可以方便的看出。也很容易在列表中找到每一个组件(在很多可视化编程环境中,提供列表的方式来快速显示所有用到的组件变量)。如: okJButton, fileJMenu, newFileMenuItem等等。
- 常量命名规范
常量命名采用全英文单词的方式进行,且全是大写的字母,单词之间用下划线连接。如:MINIMUM_BALANCE, DEFAULT_START_DATE等等。 它的主要优点是可以很容易的与变量区分开来。
- 局部变量的命名和注释规范
一般而言,局部变量的命名规范遵循属性变量的命名规范,即全用英文单词命名,且除第一个单词以外,其它单词的首字母均大写。
但是,对于下面所列举的几种特殊类型的局部变量的命名有些特殊的规定:
1.streams
当一个单一的输入或输出流对象被定义和使用时,一般用in,inputStream表示输入流,而用out,outputStream表示输出流。而当一个流同时被使用为输入流和输出流的时候,则用inOut或者ioStream来命名。
2.循环计数器的使用
由于在成员方法中,我们经常会用一些循环结构,同时也就会使用循环计数器。在以前的C,C++中,已经习惯于使用像i,j,k等变量作为循环计数器,在JAVA编码中,也采用常用的,单一的小写字母来命名循环计数器。例如:for(i=0;i<10;i++)
3.异常对象的命名规范
在JAVA中,经常会碰到一些例外情况的处理,一般用一个字母"e"来命名相应的例外对象,如存在多个异常则采用属性的命名方式。
局部变量的文档注释一般涉及到以下几个方面:
- 说明局部变量是如何被使用的;
- 局部变量的声明与使用尽量靠近,也即等到要使用的时候才声明局部变量;
- 一个局部变量尽量只用在一件事物上,而不要用在多件事物上,以便于理解;
- 一般采用单行注释的方式。
- 成员方法参数命名和注释规范
成员方法参数的命名规范基本上与局部变量的命名规范相同。 如: customer, inventoryItem, in , e等。
成员方法参数的文档注释:
成员方法参数的文档注释在成员方法的头部注释文档中,它应包括以下内容:
- 该参数将被如何使用;
- 该参数的一些约束或其它前提条件;
- 如果仅凭以上的描述还不足以让人容易理解该参数是什么含义,可以举一个或多个例子加以说明。
- 类的命名和注释规范
类的命名也是采用全英文单词描述的方法,但它的第一个单词的首字母必须大写,其它单词的首字母也要大写。例如: public class Customer{….} public class OrderItems{…}
类的注释文档一般位于类的定义之前,它将包含如下内容:
- 本类的目的。其他开发人员常常需要知道一个类的通常目的,以便决定其是否是他所需要的;
- 一些预知的bugs。一般情况,如果有bugs存在,最好是对其进行相应的处理。但有时,我们没有更多的时间,或在本类中没有必要对其进行处理,在目前而言不是太重要。在这种情况下,应文档给予说明。比如,类中的一个成员方法在传递一个负数的参数时不能正常工作,而正好应用程序有不会传递负的参数过来,在这种情况下,就有一个预知的bugs,但没有相应的处理,则应文档说明,以便其他开发人员清楚;
- 对本类的一些开发/维护的历史信息的说明。比如日期、作者以及主要改变的内容等;
- 如果是线程类的话,应对其使用的并发控制策略作适当的描述,以及为什么采用这种策略而不是其它策略等信息。
- 接口的命名和注释规范
接口的命名,其第一个字母必须是大写的"I",以表明它是一个接口,与其它类进行区别。剩下的部分与类的命名规范一样,采用全英文单词描述的方法,第一个单词首字母大写,其它单词的首字母也大写。例如:public interface IOrderItems{……..}
public interface ICustomer{…..}
接口的文档注释必须在接口被定义前给予说明,主要包括以下内容:
- 本接口定义的目的。当其他开发人员使用一个接口的时候,他们常常需要了解本接口所封装的内容以及其目的;
- 它将被怎样的使用或者不能被怎样的使用。其他开发人员想知道本接口能被如何使用以及它不能被如何使用;
3.接口内的抽象成员方法的注释,参照类的成员方法的注释。
- 包的命名和文档说明规范
包的命名有以下几个规则:
- 包与子包之间或包与类之间用"ִ"来加以区别;
- 包名全部用小写字母来命名,如: persistence.mapping.relational等;
- 如果一个包有可能在多个组织中使用,则使用本组织域名的逆转方式来命名包。如:cn.com.mcm.www.persistence.mapping.relational 等。
- 包的命名需要有目的性,如:按模块名来划分,需要分层建立。
对于每一个包,应该有一个或多个外部文档来描述这个包。一般而言,文档中应描述如下内容:
- 描述这个包是一个什么样的包,它有一些什么样的相关性等等;
- 包中的类和接口的简单描述。应该有一个包中的类和接口的简单列表,并对每一个类和接口有相应的简单描述,以便其他开发人员都清楚的知道其包含的内容。
- 缩进规则
- 控制结构的缩进
程序应以缩进形式展现程序的块结构和控制结构,在不影响展示程序结构的前提下尽可能地减少缩进的层次。采用如下两种缩进方式之一:
1) |
if (expression ){ statements }else{ statements } |
- 缩进的限制
一个程序的宽度如果超出页宽或屏宽,这将是很难读的,所以本规范要求使用折行缩进的方法、合并表达式或编写子程序的方法来限制程序的宽度。
1)任何一个程序最大行宽不得超过80列,超过者应折行书写。
2)建议一个函数的缩进不得超过5级,超过者应将其子块写为子函数;
3)算法或程序本身的特性有特殊要求时,可以超过5级。
- jsp编码规范
- jsp 表示层应当尽可能的瘦和简单化。
- 在尽可能合理的情况下,把业务逻辑从JSP中移走。具体于HTTP的逻辑(如,对Cookie的处理)属于bean或支持类中,而不是JSP中。
- 应当在JSP中避免使用页面引入指令。import指令会促使类的实列化而不是jsp bean的实例化:
用:<%@ page import = "com.java.util.List" %>
而不用:<%@ page import = "com.java.util.*" %> - jsp不应该直接去访问请求参数。
- 应当避免设计既显示表单又处理结果的页面。
- jsp中应该避免使用out.println()发放来产生页面内容。
- jsp层不应该直接访问数据,这包括JDBC数据库访问和EJB访问。
- 应当使用隐藏的注释来阻止输出的HTML过大。<!-- --><%-- --%>
- 在jsp中避免进行异常处理,应当使用一个错误页面来处理不能够从中恢复的异常
- 如果没有充分的理由,就不要在jsp中定义方法
- 编写测试代码
- 凡是用于临时测试的所有成员方法均冠以前缀"test",这样将有利于快速的找到临时用于测试的所有成员方法;
- 所有用于测试其它成员方法的成员方法,命名规范和属性方法一致,但必须有详细注释。
- Main方法可以除外。
- 性能优化常识规范
- 前提约束
性能优化的原则是先让程序运行起来,再考虑变得更快——但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。进行优化时需使用专门的工具分析瓶颈。需要注意的是性能提升的隐含代价是自己的代码变得难于理解,而且难于维护。
对于像字符串的连接操作不使用"+"而使用专有方法 concat等其他方法,这类问题,则不能称为性能优化,而只能叫做基本常识。这类问题的解决注意不能影响程序的可读性和易维护性,以下是常见优化常识:
- 运算时间参考值
详见表9-1运算时间。
表 9-1 运算时间
运算 | 示例 | 标准时间 |
本地赋值 | i=n; | 1.0 |
实例赋值 | this.i=n; | 1.2 |
Int增值 | i++; | 1.5 |
Byte增值 | b++; | 2.0 |
Short增值 | s++; | 2.0 |
Float增值 | f++; | 2.0 |
Double增值 | d++; | 2.0 |
空循环 | while(true) n++; | 2.0 |
三元表达式 | (x<0)?-x:x; | 2.2 |
算术调用 | Math.abs(x); | 2.5 |
数组赋值 | a[0]=n; | 2.7 |
Long增值 | l++; | 3.5 |
方法调用 | funct(); | 5.9 |
Throw 或者catch 违例 | Try{throw e;} | 320 |
同步方法调用 | synchMethod(); | 570 |
新建对象 | new Object(); | 980 |
新建数组 | new int[10]; | 3100 |
- java.lang.String
字串的开销:字串连接运算符"+"看似简单,但实际需要消耗大量系统资源。编译器可高效地连接字串,但变量字串却要求可观的处理器时间。该操作要创建并拆除一个StringBuffer对象以及一个String对象。
上述问题的通常解决方法是新建一个StringBuffer(字串缓冲),用append方法追加自变量,然后用toString()将结果转换回一个字串。当要追加多个字串,则可考虑直接使用一个字串缓冲——特别是能在一个循环里重复利用它的时候。通过在每次循环里禁止新建一个字串缓冲,可节省980单位的对象创建时间(见表9-1)。
更有效的解决办法是:在构造 StringBuffer 时,应该粗略的估计出它最终的总长度。默认构造函数预设了16个字符的缓存容量。append()方法首先计算字符串追加完成后的总长度,如果这个总长度大于StringBuffer的存储能力,append()方法调用私有的expandCapacity()方法。expandCapacity()方法在每次被调用时使StringBuffer存储能力加倍,并把现有的字符数组内容复制到新的存储空间。存储能力的扩展,从而导致了两次代价昂贵的复制操作。因此,我们至少有一点可以做得比编译器更好,这就是分配一个初始存储容量大于或者等于最终字符长度StringBuffer。
因此,使用默认构造函数创建的StringBuffer在字符串连接操作上的效率其实和用"+"是一样的。如果首先估计出整个字符串最终的总长度,才会显著提高效率!
其他的字符串运算操作尽可能使用 String 已经提供的方法。比如,短字符串的连接可以使用 concat;子串的查找可以使用 indexOf,substring 等。
- java.util.Vector
一个Vector就是一个java.lang.Object实例的数组。Vector与数组相似,它的元素可以通过整数形式的索引访问。但是,Vector类型的对象在创建之后,对象的大小能够根据元素的增加或者删除而扩展、缩小。
(1)避免把新元素添加到Vector 的最前面
(2)避免从中间删除元素
(3)删除所有元素的最好方法是 removeAllElements()
(4)避免二次搜索
Vector类型的对象v包含字符串"Hello"。考虑下面的代码,它要从这个Vector中删除"Hello"字符串:
String s = "Hello";
int i = v.indexOf(s);
if(i != -1)
v.remove(s);
在这段代码中,indexOf()方法对v进行顺序搜索寻找字符串"Hello",remove(s)方法也要进行同样的顺序搜索。改进之后的版本是:
String s = "Hello";
int i = v.indexOf(s);
if(i!= -1) v.remove(i);
这个版本中我们直接在remove()方法中给出待删除元素的精确索引位置,从而避免了第二次搜索。一个更好的版本是:
String s = "Hello";
v.remove(s);
循环内部的代码不会以任何方式修改Vector类型对象大小时,应该提前取得Vector.size()
- 线程优化方案
- 防止过多的同步
不必要的同步常常会造成程序性能的下降。因此,如果程序是单线程,则一定不要使用同步。
- 避免同步整个代码段
对某个方法或函数进行同步比对整个代码段进行同步的性能要好。因为代码段的同步牵涉的范围比对某个方法或函数进行同步广。
- 对每个对象使用多"锁"的机制来增大并发
一般每个对象都只有一个"锁",这就表明如果两个线程执行一个对象的两个不同的同步方法时,会发生"死锁"。即使这两个方法并不共享任何资源。为了避免这个问题,可以对一个对象实行"多锁"的机制。
- 循环优化规范
- 边界
循环的边界是指完成所有循环操作的起点和终点。如果循环体内的操作不影响边界,那么应该在循环体外,计算并且求得边界值。例如:
for(int i = 0; i < array.length; i++)
{
array[i]=i;
}
上述代码中每次循环操作,都要计算一次 array.length。
- 循环体内避免构建新对象
如果在循环体内用到新对象,需要在循环体开始以前构建好该对象。由标准时间表可以看出构建对象有很大的系统消耗,并且在一次循环中还要清除掉该对象,下循环再重新构建。
- Break
遍历数组、集合时,如果满足条件的元素找到,一定要使用 break 语句退出循环。