1 华为 Java 编程规范
1.1 排版
1.1.1 规则
规则1 、
程序块要采用缩进风格编写,缩进的空格数为4个,不允许使用TAB缩进。
说明:缩进使程序更易阅读,使用空格缩进可以适应不同操作系统与不同开发工具。
规则2
分界符(如大括号‘{’和‘}’)应各独占一行,同时与引用它们的语句左对齐。在函数体的开始、类和接口的定义、以及if、for、do、while、switch、case语句中的程序或者static、,synchronized等语句块中都要采用如上的缩进方式。
示例:
if (a>b)
{
doStart();
}
规则3
较长的语句、表达式或参数(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
示例:
if(logger.isDebugEnabled())
{
logger.debug(“Session destroyed,call-id”
+event.getSession().getCallId());
}
规则4 不允许把多个短语句写在一行中,即一行只写一条语句
说明:阅读代码更加清晰
示例:如下例子不符合规范。
Objecto = new Object(); Object b = null;
规则5 if, for, do, while, case, switch, default 等语句自占一行,且if, for, do, while,switch等语句的执行语句无论多少都要加括号{},case 的执行语句中如果定义变量必须加括号{}。
说明:阅读代码更加清晰,减少错误产生
示例:
if (a>b)
{
doStart();
}
case x:
{
inti = 9;
}
规则6 相对独立的程序块之间、变量说明之后必须加空行。
说明:阅读代码更加清晰
示例:
if(a > b)
{
doStart();
}
return;
规则7 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如.),后不应加空格。
说明:阅读代码更加清晰
示例:
if (a == b)
{
objectA.doStart();
}
a *= 2;
1.1.2 建议
建议1 类属性和类方法不要交叉放置,不同存取范围的属性或者方法也尽量不要交叉放置。
格式:
类定义
{
类的公有属性定义
类的保护属性定义
类的私有属性定义
类的公有方法定义
类的保护方法定义
类的私有方法定义
}
建议2 修饰词按照指定顺序书写:[访问权限][static][final] 。
示例:
publicstatic final String str = “abc”;
1.2 注释
1.2.1 规则
规则1 源程序注释量必须在30%以上。
说明:由于每个文件的代码注释不一定都可以达到30%,建议以一个系统内部模块作为单位进行检查
规则2 包的注释:写入一个名为 package.html的HTML格式的说明文件放入包所在路径。包的注释内容:简述本包的作用、详细描述本包的内容、产品模块名称和版本、公司版权。
说明:方便JavaDoc收集,方便对包的了解
示例:
com/huawei/iin/websmap/comm/package.html
一句话简述。
详细描述。
产品模块名称和版本
公司版权信息
示例:
为WEBSMAP 提供通信类,上层业务使用本包的通信类与SMP-B 进行通信。
详细描述。。。。。。。。
IINV100R001 WEBSMAP
(C)版权所有 2000-2001 华为技术有限公司
规则3 类和接口的注释放在class 或者 interface 关键字之前,import 关键字之后。注释主要是一句话功能简述与功能详细描述。类注释使用“/* /”注释方式
说明:方便JavaDoc收集,没有import可放在package之后。注释可根据需要列出:作者、内容、功能、与其它类的关系等。功能详细描述部分说明该类或者接口的功能、作用、使用方法和注意事项,每次修改后增加作者和更新版本号和日期,@since 表示从那个版本开始就有这个类或者接口,@deprecated 表示不建议使用该类或者接口。
/**
* 〈一句话功能简述〉
* 〈功能详细描述〉
* @author [作者](必须)
* @see [相关类/方法](可选)
* @since [产品/模块版本] (必须)
* @deprecated (可选)
*/
示例:
packagecom.huawei.iin.logwebsmap.comm;
importjava.util.*;
/**
* LogManager 类集中控制对日志读写的操作。
* 全部为静态变量和静态方法,对外提供统一接口。分配对应日志类型的读写器,
* 读取或写入符合条件的日志纪录。
* @author 张三,李四,王五
* @see LogIteraotor
* @see BasicLog
* @since CommonLog1.0
*/
publicclass LogManager
规则4 类属性(成员变量)、公有和保护方法注释:写在类属性、公有和保护方法上面,注释方式为“/* /”.
示例:
/**
* 注释内容
*/
privateString logType;
/**
* 注释内容
*/
publicvoid write()
规则5 公有和保护方法注释内容:列出方法的一句话功能简述、功能详细描述、输入参数、输出参数、返回值、异常等。
格式:
/**
* 〈一句话功能简述〉
* 〈功能详细描述〉
* @param [参数1] [参数1说明]
* @param [参数2] [参数2说明]
* @return [返回类型说明]
* @exception/throws [异常类型] [异常说明]
* @see [类、类#方法、类#成员]
* @since [起始版本]
* @deprecated
*/
说明:@since 表示从那个版本开始就有这个方法,如果是最初版本就存在的方法无需说明;@exception或throws 列出可能仍出的异常;@deprecated 表示不建议使用该方法。
示例:
/**
* 根据日志类型和时间读取日志。
* 分配对应日志类型的LogReader, 指定类型、查询时间段、条件和反复器缓冲数,
* 读取日志记录。查询条件为null或0的表示没有限制,反复器缓冲数为0读不到日志。
* 查询时间为左包含原则,即[startTime, endTime) 。
* @param logTypeName 日志类型名(在配置文件中定义的)
* @param startTime 查询日志的开始时间
* @param endTime 查询日志的结束时间
* @param logLevel 查询日志的级别
*@param userName 查询该用户的日志
* @param bufferNum 日志反复器缓冲记录数
* @return 结果集,日志反复器
* @since 1.2
*/
public static LogIterator read(StringlogType, Date startTime, Date endTime, intlogLevel, String userName, int bufferNum)
规则6 对于方法内部用throw语句抛出的异常,必须在方法的注释中标明,对于所调用的其他方法所抛出的异常,选择主要的在注释中说明。 对于非RuntimeException,即throws子句声明会抛出的异常,必须在方法的注释中标明。
说明:异常注释用@exception或@throws表示,在JavaDoc中两者等价,但推荐用@exception标注Runtime异常,@throws标注非Runtime异常。异常的注释必须说明该异常的含义及什么条件下抛出该异常。
规则7 注释应与其描述的代码相近,对代码的注释应放在其上方,并与其上面的代码用空行隔开,注释与所描述内容进行同样的缩排。
说明:可使程序排版整齐,并方便注释的阅读与理解。
示例:
/*
* 注释
*/
publicvoid example2( )
{
// 注释
CodeBlock One
// 注释
CodeBlock Two
}
/*
* 注释
*/
publicvoid example( )
{
// 注释
CodeBlock One
// 注释
CodeBlock Two
}
规则8 对于switch语句下的case语句,必须在每个case分支结束前加上break语句。
说明:break才能真正表示该switch执行结束,不然可能会进入该case以后的分支。至于语法上合法的场景“一个case后进入下一个case处理”,应该在编码设计上就避免。
规则9 修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
规则10 注释的内容要清楚、明了,含义准确,防止注释二义性。
说明:错误的注释不但无益反而有害。
规则11 避免在注释中使用缩写,特别是不常用缩写。
说明:在使用缩写时或之前,应对缩写进行必要的说明。
规则12 对重载父类的方法必须进行@Override声明(5.0+)
说明:可清楚说明此方法是重载父类的方法,保证重载父类的方法时不会因为单词写错而造成错误(写错方法名或者参数个数,类型都会编译无法通过)
示例:
@Override
public voiddoRequest(SipServletRequest req) throws ServletException,
IOException
1.2.2 建议
建议1 避免在一行代码或表达式的中间插入注释。
说明:除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。
建议2 在代码的功能、意图层次上进行注释,提供有用、额外的信息。
说明:注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。
示例:如下注释意义不大。
// 如果receiveFlag 为真
if(receiveFlag)
而如下的注释则给出了额外有用的信息。
//如果从连结收到消息
if(receiveFlag)
建议3 对关键变量的定义和分支语句(条件分支、循环语句等)必须编写注释。
说明:这些语句往往是程序实现某一特定功能的关键,对于维护人员来说,良好的注释帮助更好的理解程序,有时甚至优于看设计文档。
建议4 注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达。中文注释中需使用中文标点。方法和类描述的第一句话尽量使用简洁明了的话概括一下功能,然后加以句号。接下来的部分可以详细描述。
说明:注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。JavaDoc工具收集简介的时候使用选取第一句话。
建议5 方法内的单行注释使用 //。
说明:调试程序的时候可以方便的使用/* 。。。*/ 注释掉一长段程序。
建议6 一些复杂的代码需要说明。
示例:这里主要是对闰年算法的说明。
//1. 如果能被4整除,是闰年;
//2. 如果能被100整除,不是闰年;
//3. 如果能被400整除,是闰年。
建议7 使用Html标签使JavaDoc生成更加美观。
示例:
/**
* Returns a hash code for this string. Thehash code for a
* String
objectis computed as
*
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... +s[n-1]
*
* using
int
arithmetic, where s[i]
is the * ith character of thestring,
n
is the length * of * the string, and
^
indicates exponentiation. * (The hash value of the empty string iszero.)
*
* @return a hash code value for this object.
*/
public int hashCode()
生成后的JavaDoc
图1 生成后的JavaDoc
1.3 命名
1.3.1 规则
规则1 类名和接口使用类意义完整的英文描述,每个英文单词的首字母使用大写、其余字母使用小写的大小写混合法。
示例:OrderInformation,CustomerList, LogManager, LogConfig, SmpTransaction
规则2 方法名使用类意义完整的英文描述:第一个单词的字母使用小写、剩余单词首字母大写其余字母小写的大小写混合法。
示例:
privatevoid calculateRate();
publicvoid addNewOrder();
规则3 方法中,存取属性的方法采用setter 和 getter方法,动作方法采用动词和动宾结构。
格式:
get + 非布尔属性名()
is + 布尔属性名()
set + 属性名()
动词()
动词 + 宾语()
示例:
publicString getType();
publicboolean isFinished();
publicvoid setVisible(boolean);
publicvoid show();
publicvoid addKeyListener(Listener);
规则4 属性名使用意义完整的英文描述,第一个单词的字母使用小写,剩余单词首字母大写其余字母小写的大小写混合法。属性名不能与方法名相同。
示例:
privatecustomerName;
privateorderNumber;
privatesmpSession;
规则5 常量名使用全大写的英文描述,英文单词之间用下划线分隔开,并且使用 static final修饰。
示例:
publicstatic final int MAX_VALUE = 1000;
publicstatic final String DEFAULT_START_DATE = “2001-12-08”;
1.3.2 建议
建议1 包名采用域后缀倒置的加上自定义的包名,采用小写字母,都应该以com.huawei开头(不包括一些特殊原因)。在部门内部应该规划好包名的范围,防止产生冲突。部门内部产品使用部门的名称加上模块名称。产品线的产品使用产品的名称加上模块的名称。
说明:除特殊原因包结构都必须以com.huawei开头,如果因为OEM合作等关系,可以不做要求。
格式:
com.huawei.产品名.模块名称
示例:
融合WEBSMAP包名 com.huawei.iin.websmap
建议2 通过对函数或过程、变量、结构等正确的命名以及合理地组织代码的结构,使代码成为自注释的。
说明:清晰准确的函数、变量等的命名,可增加代码可读性,并减少不必要的注释。
建议3 常用组件类的命名以组件名加上组件类型名结尾。
示例:
Application 类型的,命名以App 结尾——MainApp
Frame 类型的,命名以Frame 结尾——TopoFrame
Panel 类型的,建议命名以Panel结尾——CreateCircuitPanel
Bean 类型的,建议命名以Bean 结尾——DataAccessBean
EJB 类型的,建议命名以EJB 结尾——DBProxyEJB
Applet 类型的,建议命名以Applet 结尾——PictureShowApplet
建议4 如果函数名超过15 个字母,可采用以去掉元音字母的方法或者以行业内约定俗成的缩写方式缩写函数名。
示例:
getCustomerInformation() 改为 getCustomerInfo()
建议5 准确地确定成员函数的存取控制符号:只是该类内部调用的函数使用 private 属性,继承类可以使用的使用protected属性,同包类可以调用的使用默认属性(不加属性控制符号),对外公开的函数使用public属性
示例:
protected void getUserName()
{
。。。。。。
}
private void calculateRate()
{
。。。。。。
}
建议6 含有集合意义的属性命名,尽量包含其复数的意义。
示例:
customers, orderItems
1.4 编码
1.4.1 规则
规则1 数据库操作、IO操作等需要使用结束close()的对象必须在try -catch-finally 的finally中close(),如果有多个IO对象需要close(),需要分别对每个对象的close()方法进行try-catch,防止一个IO对象关闭失败其他IO对象都未关闭。
示例:
try
{
// … …
}
catch(IOExceptionioe)
{
//… …
}
finally
{
try
{
out.close();
}
catch (IOException ioe)
{
//… …
}
try
{
in.close();
}
catch (IOException ioe)
{
//… …
}
}
规则2 系统非正常运行产生的异常捕获后,如果不对该异常进行处理,则应该记录日志。
说明:此规则指通常的系统非正常运行产生的异常,不包括一些基于异常的设计。若有特殊原因必须用注释加以说明。
示例:
try
{
//…. …
}
catch(IOException ioe)
{
logger.error(ioe);
}
规则3 自己抛出的异常必须要填写详细的描述信息。
说明:便于问题定位。
示例:
thrownew IOException(“Writing dataerror! Data: ” + data.toString());
规则4 运行时异常使用RuntimeException的子类来表示,不用在可能抛出异常的方法声明上加throws子句。非运行期异常是从Exception继承而来的,必须在方法声明上加throws子句。
说明:
非运行期异常是由外界运行环境决定异常抛出条件的异常,例如文件操作,可能受权限、磁盘空间大小的影响而失败,这种异常是程序本身无法避免的,需要调用者明确考虑该异常出现时该如何处理方法,因此非运行期异常必须有throws子句标出,不标出或者调用者不捕获该类型异常都会导致编译失败,从而防止程序员本身疏忽。
运行期异常是程序在运行过程中本身考虑不周导致的异常,例如传入错误的参数等。抛出运行期异常的目的是防止异常扩散,导致定位困难。因此在做异常体系设计时要根据错误的性质合理选择自定义异常的继承关系。
还有一种异常是Error 继承而来的,这种异常由虚拟机自己维护,表示发生了致命错误,程序无法继续运行例如内存不足。我们自己的程序不应该捕获这种异常,并且也不应该创建该种类型的异常。
规则5 在程序中使用异常处理还是使用错误返回码处理,根据是否有利于程序结构来确定,并且异常和错误码不应该混合使用,推荐使用异常。
说明:
一个系统或者模块应该统一规划异常类型和返回码的含义。
但是不能用异常来做一般流程处理的方式,不要过多地使用异常,异常的处理效率比条件分支低,而且异常的跳转流程难以预测。
注意:Java 5.0 程序内部的错误码可以使用枚举来表示。
规则6 注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。
说明:防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而导致程序出错。
示例:
下列语句中的表达式
word= (high << 8) | low (1)
if((a | b) && (a & c)) (2)
if((a | b) < (c & d)) (3)
如果书写为
high<< 8 | low
a | b&& a & c
a | b< c & d
(1)(2)虽然不会出错,但语句不易理解;(3)造成了判断条件出错。
规则7 避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的静态变量或者枚举来代替。使用异常来表示方法执行错误,而不是使用C++的错误返回码方式。
示例:如下的程序可读性差。
if(state == 0)
{
state = 1;
… // program code
}
应改为如下形式:
privatefinal static int TRUNK_IDLE = 0;
privatefinal static int TRUNK_BUSY = 1;
privatefinal static int TRUNK_UNKNOWN = -1;
if(state == TRUNK_IDLE)
{
state = TRUNK_BUSY;
… // program code
}
注意:Java5.0 下建议使用枚举来表示。
异常:
publicvoid function()
{
…
throw new RuntimeException(“。。。”);
}
规则8 数组声明的时候使用 int[] index ,而不要使用 int index[] 。
说明:使用int index[] 格式使程序的可读性较差,int [] index 表示声明了一个int数组(int [])叫做index
示例:
如下程序可读性差:
publicint getIndex()[]
{
….
}
如下程序可读性好:
publicint[] getIndex()
{
….
}
规则9 不要使用 System.out 与 System.err 进行控制台打印,应该使用工具类(如:日志工具)进行统一记录或者打印。
说明:代码发布的时候可以统一关闭控制台打印,代码调试的时候又可以打开控制台打印,方便调试。
规则10 用调测开关来切换软件的DEBUG版和正式版,而不要同时存在正式版本和DEBUG版本的不同源文件,以减少维护的难度。
规则11 集合必须指定模板类型(5.0+)
说明:方便程序阅读,除去强制转换代码
示例:
Map
define MAX_USR_NUM 10
unsigned char usr_login_flg[MAX_USR_NUM]= “”;
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no])
{
usr_login_flg[usr_no]= TRUE;
}
当usr_no 为10 时,将使用usr_login_flg 越界。可采用如下方式解决。
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no - 1])
{
usr_login_flg[usr_no - 1]= TRUE;
}
}
8:认真处理程序所能遇到的各种出错情况。
9:系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。
10:系统运行之初,要对加载到系统中的数据进行一致性检查。
说明:使用不一致的数据,容易使系统进入混乱状态和不可知状态。
11:严禁随意更改其它模块或系统的有关设置和配置。
说明:编程时,不能随心所欲地更改不属于自己模块的有关设置如常量、数组的大小等。
12:不能随意改变与其它模块的接口。
13:充分了解系统的接口之后,再使用系统提供的功能。
示例:在B 型机的各模块与操作系统的接口函数中,有一个要由各模块负责编写的初始化过程,此过程在软件系统加载完成后,由操作系统发送的初始化消息来调度。因此就涉及到初始化消息的类型与消息发送的顺序问题,特别是消息顺序,若没搞清楚就开始编程,很容易引起严重后果。以下示例引自B 型曾出现过的实际代码,其中使用了FID_FETCH_DATA与FID_INITIAL 初始化消息类型,注意B 型机的系统是在FID_FETCH_DATA 之前发送FID_INITIAL 的。
MID alarm_module_list[MAX_ALARM_MID];
int FAR SYS_ALARM_proc( FID function_id, int handle )
{
_UI i, j;
switch ( function_id )
{
… // program code
case FID_INITAIL:
for (i = 0; i < MAX_ALARM_MID; i++)
{
if (alarm_module_list[i]== BAM_MODULE // **)
|| (alarm_module_list[i]== LOCAL_MODULE)
{
for (j = 0; j < ALARM_CLASS_SUM; j++)
{
FAR_MALLOC( … );
}
}
}
… // program code
break;
case FID_FETCH_DATA:
… // program code
Get_Alarm_Module( ); // 初始化alarm_module_list
break;
… // program code
}
}
由于FID_INITIAL 是在FID_FETCH_DATA 之前执行的,而初始化alarm_module_list 是在FID_FETCH_DATA 中进行的,故在FID_INITIAL 中()处引用alarm_module_list 变量时,它还没有被初始化。这是个严重错误。应如下改正:要么把Get_Alarm_Module 函数放在FID_INITIAL 中()之前;要么就必须考虑(**)处的判断语句是否可以用(不使用alarm_module_list 变量的)其它方式替代,或者是否可以取消此判断语句。
14:编程时,要防止差1 错误。
说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后,应对这些操作符进行彻底检查。
15:要时刻注意易混淆的操作符。当编完程序后,应从头至尾检查一遍这些操作符,以防止拼写错误。
说明:形式相近的操作符最容易引起误用,如C/C++中的“=”与“==”、“|”与“||”、“&”与“&&”等,若拼写错了,编译器不一定能够检查出来。
示例:如把“&”写成“&&”,或反之。
ret_flg = (pmsg->ret_flg & RETURN_MASK);
被写为:
ret_flg = (pmsg->ret_flg && RETURN_MASK);
rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));
被写为:rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));
16:有可能的话,if 语句尽量加上else 分支,对没有else 分支的语句要小心对待;switch语句必须有default 分支。
17:Unix 下,多线程的中的子线程退出必需采用主动退出方式,即子线程应return 出口。
18:不要滥用goto 语句。
说明:goto 语句会破坏程序的结构性,所以除非确实需要,最好不使用goto 语句。
19:精心地构造、划分子模块,并按“接口”部分及“内核”部分合理地组织子模块,以提高“内核”部分的可移植性和可重用性。
说明:对不同产品中的某个功能相同的模块,若能做到其内核部分完全或基本一致,那么无论对产品的测试、维护,还是对以后产品的升级都会有很大帮助。
20:精心构造算法,并对其性能、效率进行测试。
21:对较关键的算法最好使用其它算法来确认。
22:时刻注意表达式是否会上溢、下溢。
示例:如下程序将造成变量下溢。
unsigned char size ;
while (size– >= 0) // 将出现下溢
{
… // program code
}
当size 等于0 时,再减1 不会小于0,而是0xFF,故程序是一个死循环。应如下修改。
char size; // 从unsigned char 改为char
while (size– >= 0)
{
… // program code
}
23:使用变量时要注意其边界值的情况。
示例:如C 语言中字符型变量,有效值范围为-128 到127。故以下表达式的计算存在一定风险。
char chr = 127;
int sum = 200;
chr += 1; // 127 为chr 的边界值,再加1 将使chr 上溢到-128,而不是128。
sum += chr; // 故sum 的结果不是328,而是72。
若chr 与sum 为同一种类型,或表达式按如下方式书写,可能会好些。
sum = sum + chr + 1;
24:留心程序机器码大小(如指令空间大小、数据空间大小、堆栈空间大小等)是否超出系统有关限制。
25:为用户提供良好的接口界面,使用户能较充分地了解系统内部运行状态及有关系统出错情况。
26:系统应具有一定的容错能力,对一些错误事件(如用户误操作等)能进行自动补救。
27:对一些具有危险性的操作代码(如写硬盘、删数据等)要仔细考虑,防止对数据、硬件等的安全构成危害,以提高系统的安全性。
28:使用第三方提供的软件开发工具包或控件时,要注意以下几点:
(1)充分了解应用接口、使用环境及使用时注意事项。
(2)不能过分相信其正确性。
3 Google Java编程风格
1.1 术语说明 在本文档中,除非另有说明:
1. 术语class可表示一个普通类,枚举类,接口或是annotation类型(@interface)
2. 术语comment只用来指代实现的注释(implementation comments),我们不使用“documentation comments”一词,而是用Javadoc。其他的术语说明会偶尔在后面的文档出现。
1.2 指南说明 本文档中的示例代码并不作为规范。也就是说,虽然示例代码是遵循Google编程风格,但并不意味着这是展 现这些代码的唯一方式。 示例中的格式选择不应该被强制定为规则。 源文件基础
2.1 文件名 源文件以其最顶层的类名来命名,大小写敏感,文件扩展名为.java。
2.2 文件编码:UTF-8 源文件编码格式为UTF-8。
2.3 特殊字符
2.3.1 空白字符 除了行结束符序列,ASCII水平空格字符(0x20,即空格)是源文件中唯一允许出现的空白字符,这意味着: 1. 所有其它字符串中的空白字符都要进行转义。 2. 制表符不用于缩进。
2.3.2 特殊转义序列 对于具有特殊转义序列的任何字符(\b, \t, \n, \f, \r, \“, \‘及),我们使用它的转义序列,而不是相 应的八进制(比如\012)或Unicode(比如\u000a)转义。
2.3.3 非ASCII字符 对于剩余的非ASCII字符,是使用实际的Unicode字符(比如∞),还是使用等价的Unicode转义符(比如 \u221e),取决于哪个能让代码更易于阅读和理解。 Tip: 在使用Unicode转义符或是一些实际的Unicode字符时,建议做些注释给出解释, 这有助于别人阅读和理解。
源文件结构 一个源文件包含(按顺序地):
1. 许可证或版权信息(如有需要)
2. package语句
3. import语句
4. 一个顶级类(只有一个) 以上每个部分之间用一个空行隔开。
3.1 许可证或版权信息 如果一个文件包含许可证或版权信息,那么它应当被放在文件最前面。
3.2 package语句 package语句不换行,列限制(4.4节)并不适用于package语句。(即package语句写在一行里)
3.3 import语句
3.3.1 import不要使用通配符即,不要出现类似这样的import语句:importjava.util.*;
3.3.2 不要换行 import语句不换行,列限制(4.4节)并不适用于import语句。(每个import语句独立成行)
3.3.3 顺序和间距 import语句可分为以下几组,按照这个顺序,每组由一个空行分隔:
1. 所有的静态导入独立成组
2. com.googleimports(仅当这个源文件是在com.google包下)
3. 第三方的包。每个顶级包为一组,字典序。例如:android, com, junit, org, sun
4. javaimports
5. javaximports 组内不空行,按字典序排列。
3.4 类声明
3.4.1 只有一个顶级类声明 每个顶级类都在一个与它同名的源文件中(当然,还包含.java后缀)。 例外:package-info.java,该文件中可没有package-info类。
3.4.2 类成员顺序 类的成员顺序对易学性有很大的影响,但这也不存在唯一的通用法则。不同的类对成员的排序可能是不同 的。 最重要的一点,每个类应该以某种逻辑去排序它的成员,维护者应该要能解释这种排序逻辑。比如, 新的方法不能总是习惯性地添加到类的结尾,因为这样就是按时间顺序而非某种逻辑来排序的。
3.4.2.1 重载:永不分离 当一个类有多个构造函数,或是多个同名方法,这些函数/方法应该按顺序出现在一起,中间不要放进其它函 数/方法。 格式 术语说明:块状结构(block-like construct)指的是一个类,方法或构造函数的主体。需要注意的是,数组 初始化中的初始值可被选择性地视为块状结构(4.8.3.1节)。
4.1 大括号
4.1.1 使用大括号(即使是可选的) 大括号与if,else,for,do,while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。
4.1.2 非空块:K & R 风格对于非空块和块状结构,大括号遵循Kernighan和Ritchie风格 (Egyptian brackets): 左大括号前不换行 左大括号后换行右大括号前换行 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号 后面是else或逗号,则不换行。 示例: returnnewMyClass(){ @Overridepublicvoidmethod(){ if(condition()){ try{something(); }catch(ProblemExceptione){ recover(); } } } };
4.1.3 空块:可以用简洁版本 一个空的块状结构里什么也不包含,大括号可以简洁地写成{},不需要换行。例外:如果它是一个多块语句 的一部分(if/else 或 try/catch/finally) ,即使大括号内没内容,右大括号也要换行。 示例: voiddoNothing(){}
4.2 块缩进:2个空格 每当开始一个新的块,缩进增加2个空格,当块结束时,缩进返回先前的缩进级别。缩进级别适用于代码和注 释。
4.3 一行一个语句 每个语句后要换行。
4.4 列限制:80或100 一个项目可以选择一行80个字符或100个字符的列限制,除了下述例外,任何一行如果超过这个字符数限制, 必须自动换行。
例外: 1. 不可能满足列限制的行(例如,Javadoc中的一个长URL,或是一个长的JSNI方法参考)。 2. package和import语句(见3.2节和3.3节)。 3. 注释中那些可能被剪切并粘贴到shell中的命令行。
4.5 自动换行 术语说明:一般情况下,一行长代码为了避免超出列限制(80或100个字符)而被分为多行,我们称之为自动 换行(line-wrapping)。 我们并没有全面,确定性的准则来决定在每一种情况下如何自动换行。很多时候,对于同一段代码会有好几 种有效的自动换行方式。 Tip: 提取方法或局部变量可以在不换行的情况下解决代码过长的问题(是合理缩短命 名长度吧)
4.5.1 从哪里断开 自动换行的基本准则是:更倾向于在更高的语法级别处断开。 1. 如果在非赋值运算符处断开,那么在该符号前断开(比如+,它将位于下一行)。注意:这一点与Google其 它语言的编程风格不同(如C++和JavaScript)。 这条规则也适用于以下“类运算符”符号:点分隔符 (.),类型界限中的&(
ifndef FOO_BAR_BAZ_H_
define FOO_BAR_BAZ_H_
…
endif // FOO_BAR_BAZ_H_
作用域
1. 命名空间(Namespaces)在.cc 文件中,提倡使用丌具名的命名空间(unnamed namespaces,注:丌具
名的命名空间就像丌具名 的类一样,似乎被介绍的徆少:-()。使用具名命名空间时,其名称可基亍项目戒路径名称,丌要使用 using 挃示符。
定义:命名空间将全尿作用域绅分为丌同的、具名的作用域,可有效防止全尿作用域的命名冲突。
优点:命名空间提供了(可嵌套)命名轰线(name axis,注:将命名分割在丌同命名空间内),当然,类 也提供了(可嵌套)的命名轰线(注:将命名分割在丌同类的作用域内)。 丼例来说,两个丌同项目的全尿作用域都有一个类 Foo,返样在编译戒运行时造成冲突。如果每个项目将 代码置亍丌同命名空间中,project1::Foo 和 project2::Foo 作为丌同符号自然丌会冲突。 缺点:命名空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轰线。在头文件中使用丌 具名的空间容易迗背 C++的唯一定义原则(One Definition Rule (ODR))。
结论:根据下文将要提到的策略合理使用命名空间。
1) 不具名命名空间(Unnamed Namespaces)
在.cc 文件中,允许甚至提倡使用丌具名命名空间,以避免运行时的命名冲突:
namespace
{ // .cc 文件中
// 命名空间的内容无需缩迕
enum { UNUSED, EOF, ERROR }; // 经常使用的符号
bool AtEof() { return pos_ == EOF; }// 使用本命名空间内的符号 EOF
} // namespace
然而,不特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员戒静态成员函数,而丌是丌 具名命名空间的成员。像上文展示的那样,丌具名命名空间结束时用注释// namespace 标识。
不能在.h 文件中使用丌具名命名空间。
2) 具名命名空间(Named Namespaces)
具名命名空间使用方式如下:
命名空间将除文件包吨、全尿标识的声明/定义以及类的前置声明外的整个源文件封装起来,以同其他命名 空间相区分。
// .h 文件
namespace mynamespace {
// 所有声明都置亍命名空间中, 注意丌要使用缩迕
class MyClass { public: … voidFoo(); }; } //
namespace mynamespace // .cc 文件
namespace mynamespace {
// 函数定义都置亍命名空间中
void MyClass::Foo() { … }
} // namespace mynamespace
通常的.cc 文件会包吨更多、更复杂的绅节,包括对其他命名空间中类的引用等。
include “a.h”DEFINE_bool(someflag, false, “dummy flag”);
class C; // 全尿命名空间中类 C 的前置声明
namespace a { class A; } // 命名空间 a 中的类 a::A 的前置声明
namespace b {
…code for b… // b 中的代码
} // namespace b
嵌套类(Nested Class)
当公开嵌套类作为接口的一部分时,虽然可以直接将他们保持在全尿作用域中,但将嵌套类的声明置亍命 名空间中是更好的选择。
定义:可以在一个类中定义另一个类,
嵌套类也称成员类(member class)。
class Foo {
private: // Bar 是嵌套在 Foo 中的成员类
class Bar {
…
};
};
优点:当嵌套(成员)类叧在被嵌套类(enclosing class)中使用时徆有用,将其置亍被嵌套类作用域作 为被嵌套类的成员丌会污染其他作用域同名类。可在被嵌套类中前置声明嵌套类,在.cc 文件中定义嵌套类, 避免在被嵌套类中包吨嵌套类的定义,因为嵌套类的定义通常叧不实现相关。
缺点:叧能在被嵌套类的定义中才能前置声明嵌套类。因此,仸何使用 Foo::Bar*挃针的头文件必须包吨整 个 Foo 的声明。 结论:不要将嵌套类定义为 public,除非它们是接口的一部分,比如,某方法使用了返个类的一系列选项。
3. 非成员函数(Nonmember)、静态成员函数(Static Member)和全尿函数(Global Functions)使用命名空间中的非成员函数戒静态成员函数,尽量丌要使用全尿函数。 优点:某些情冴下,非成员函数和静态成员函数是非常有用的,将非成员函数置亍命名空间中可避免对全 尿作用域的污染。缺点:将非成员函数和静态成员函数作为新类的成员戒许更有意义,当它们需要访问外部资源戒具有重要 依赖时更是如此。
结论 有时,丌把函数限定在类的实体中是有益的,甚至需要返么做,要么作为静态成员,要么作为非成员函数。 非成员函数丌应依赖亍外部发量,幵尽量置亍某个命名空间中。相比单纯为了封装若干丌共享仸何静态数 据的静态成员函数而创建类,丌如使用命名空间。 定义亍同一编译单元的函数,被其他编译单元直接调用可能会引入丌必要的耦吅和还接依赖;静态成员函 数对此尤其敏感。可以考虑提叏到新类中,戒者将函数置亍独立库的命名空间中。 如果你确实需要定义非成员函数,又叧是在.cc 文件中使用它,可使用丌具名命名空间戒 static 关联(如 static int Foo() {…})限定其作用域。
4. 局部变量(Local Variables) 将函数发量尽可能置亍最小作用域内,在声明发量时将其初始化。 C++允许在函数的仸何位置声明发量。我们提倡在尽可能小的作用域中声明发量,离第一次使用越近越好。 返使得代码易亍阅诺,易亍定位发量的声明位置、发量类型和初始值。特别是,应使用初始化代替声明+ 赋值的方式。
int i; i = f(); // 坏——初始化和声明分离
int i = g(); // 好——初始化时声明
注意:gcc 可正确执行 for (int i = 0; i < 10; ++i)(i 的作用域仅限 for 循环),因此其他 for 循环中可重用 i。if 和 while 等诧句中,作用域声明(scope declaration)同样是正确的。
while (const char* p = strchr(str, ‘/’)) str = p + 1;
注意:如果发量是一个对象,每次迕入作用域都要调用其极造函数,每次退出作用域都要调用其枂极函数。
// 低效的实现
for (int i = 0; i < 1000000;++i) {
Foo f; // 极造函数和枂极函数分别调用 1000000 次!
f.DoSomething(i);
}
类似发量放到循环作用域外面声明要高效的多:
Foo f; // 极造函数和枂极函数叧调用 1 次
for (int i = 0; i < 1000000; ++i)
{
f.DoSomething(i);
}
5. 全局变量(Global Variables)
class 类型的全尿发量是被禁止的,内建类型的全尿发量是允许的,当然多线程代码中非常数全尿发量也是 被禁止的。永迖不要使用函数迒回值初始化全局变量。 不幸的是,全局变量的极造函数、枂极函数以及初始化操作的调用顺序叧是被部分觃定,每次生成有可能 会有发化,从而导致难以収现的 bugs。
因此,禁止使用 class 类型的全尿发量(包括 STL 的 string, vector 等等),因为它们的初始化顺序有可能 导致极造出现问题。内建类型和由内建类型极成的没有极造函数的结极体可以使用,如果你一定要使用 class 类型的全局变量,请使用单件模式(singleton pattern)。
对亍全尿的字符串常量,使用 C 风格的字符串,而不要使用 STL 的字符串:
const char kFrogSays[] = “ribbet”;
虽然允许在全局作用域中使用全尿发量,使用时务必三思。大多数全局变量应该是类的静态数据成员,或者当其叧在.cc 文件中使用时,将其定义到不具名命名空间中,戒者使用静态关联以限制发量的作用域。
记住,静态成员发量规作全尿发量,所以,也不能是 class 类型!
C++类
类是 C++中基本的代码单元,自然被广泛使用。本节列丼了在写一个类时要做什么、丌要做什么。
1. 构造函数(Constructor)的职责 极造函数中叧迕行那些没有实际意义的(注:简单初始化对亍程序执行没有实际的逻辑意义,因为成员发 量的“有意义”的值大多丌在极造函数中确定)初始化,可能的话,使用 Init()方法集中初始化为有意义的 (non-trivial)数据。 定义:在极造函数中执行初始化操作。 优点:排版方便,无需担心类是否初始化。 缺点:在极造函数中执行操作引起的问题有:
1) 极造函数中丌易报告错诨,丌能使用异常。
2) 操作失败会造成对象初始化失败,引起丌确定状态。
3) 极造函数内调用虚函数,调用丌会派収到子类实现中,即使当前没有子类化实现,将来仍是隐恳。
4) 如果有人创建该类型的全尿发量(虽然迗背了上节提到的觃则),极造函数将在 main()乊前被调用,有 可能破坏极造函数中暗吨的假设条件。例如,google gflags 尚未初始化。
结论:如果对象需要有意义的(non-trivial)初始化,考虑使用另外的 Init()方法幵(戒)增加一个成员标 记用亍挃示对象是否巫经初始化成功。
2. 默认构造函数(Default Constructors)
如果一个类定义了若干成员发量又没有其他极造函数,需要定义一个默认极造函数,否则编译器将自劢生 产默认极造函数。
定义:新建一个没有参数的对象时,默认极造函数被调用,当调用 new[](为数组)时,默认极造函数总 是被调用。
优点:默认将结极体初始化为“丌可能的”值,使调试更加容易。
缺点:对代码编写者来说,返是多余的工作。
结论如果类中定义了成员发量,没有提供其他极造函数,你需要定义一个默认极造函数(没有参数)。
默认极造 函数更适吅亍初始化对象,使对象内部状态(internal state)一致、有效。 提供默认极造函数的原因是:如果你没有提供其他极造函数,又没有定义默认极造函数,编译器将为你自 劢生成一个,编译器生成的极造函数幵丌会对对象迕行初始化。 如果你定义的类继承现有类,而你又没有增加新的成员发量,则不需要为新类定义默认极造函数。
3. 明确的构造函数(Explicit Constructors)
对单参数极造函数使用 C++关键字 explicit。
定义:通常,叧有一个参数的极造函数可被用亍转换(注:主要挃隐式转换,下文可见),例如,定义了 Foo::Foo(string name),当向需要传入一个 Foo 对象的函数传入一个字符串时,极造函数 Foo::Foo(string name)被调用幵将该字符串转换为一个 Foo 临时对象传给调用函数。看上去徆方便,但如果你幵丌希望如 此通过转换生成一个新对象的话,麻烦也随乊而来。为避免极造函数被调用造成隐式转换,可以将其声明 为 explicit。
优点:避免部适宜的发换。
缺点:无。
结论: 所有单参数极造函数必须是明确的。在类定义中,将关键字 explicit 加到单参数极造函数前:explicit Foo(string name);
例外:在少数情冴下,拷贝极造函数可以不声明为 explicit;特意作为其他类的透明包装器的类。类似例外 情冴应在注释中明确说明。
4. 拷贝构造函数(Copy Constructors)
仅在代码中需要拷贝一个类对象的时候使用拷贝极造函数 ;
不需要拷 贝时 应使用DISALLOW_COPY_AND_ASSIGN。定义:通过拷贝新建对象时可使用拷贝极造函数(特别是对象的传值时)。 优点:拷贝极造函数使得拷贝对象更加容易,STL 容器要求所有内容可拷贝、可赋值。 缺点:C++中对象的隐式拷贝是导致徆多性能问题和 bugs 的根源。拷贝极造函数降低了代码可诺性,相 比挄引用传递,跟踪挄值传递的对象更加困难,对象修改的地方发得难以捉摸。
5. 结构体和类(Structs vs. Classes)
仅当叧有数据时使用 struct,其它一概使用 class。 在 C++中,关键字 struct 和 class 几乎吨义等同,我们为其人为添加诧义,以便为定义的数据类型吅理选 择使用哪个关键字。 struct 被用在仅包吨数据的消枀对象(passive objects)上,可能包括有关联的常量,但没有存叏数据成 员乊外的函数功能,而存叏功能通过直接访问实现而无需方法调用,返儿提到的方法是挃叧用亍处理数据 成员的,如极造函数、枂极函数、Initialize()、Reset()、Validate()。如果需要更多的函数功能,class 更适吅,如果丌确定的话,直接使用 class。如果不 STL 结吅,对亍仿函数(functors)和特性(traits)可以丌用 class 而是使用 struct。注意:类和结极体的成员发量使用丌同的命名觃则。
6. 继承(Inheritance)
使用组合(composition,注,这一点也是 GoF 在《Design Patterns》里反复强调的)通常比使用继承 更适宜,如果使用继承的话,叧使用公共继承。
定义:当子类继承基类时,子类包吨了父基类所有数据及操作的定义。C++实践中,继承主要用亍两种场合:实现继承(implementation inheritance),子类继承父类的实现代码;接口继承(interface inheritance),子类仅继承父类的方法名称。
优点:实现继承通过原封丌劢的重用基类代码减少了代码量。由亍继承是编译时声明(compile-time declaration),编码者和编译器都可以理解相应操作幵収现错诨。接口继承可用亍程序上增强类的特定 API 的功能,在类没有定义 API 的必要实现时,编译器同样可以侦错。
缺点:对亍实现继承,由亍实现子类的代码在父类和子类间延展,要理解其实现发得更加困难。子类丌能 重写父类的非虚函数,当然也就丌能修改其实现。基类也可能定义了一些数据成员,迓要区分基类的物理 轮廓(physical layout)。
结论:
所有继承必须是 public 的,如果想私有继承的话,应该采叏包吨基类实例作为成员的方式作为替代。 不要过多使用实现继承,组吅通常更吅适一些。劤力做到叧在“是一个”(”is-a”,译者注,其他”has-a” 情冴下请使用组吅)的情冴下使用继承:如果 Bar 的确“是一种”Foo,才令 Bar 是 Foo 的子类。 必要的话,令枂极函数为 virtual,必要是挃,如果该类具有虚函数,其枂极函数应该为虚函数。
注:至于子类没有额外数据成员,甚至父类也没有仸何数据成员的特殊情冴下,枂极函数的调用是否必要 是诧义争论,从编程设计觃范的角度看,在吨有虚函数的父类中,定义虚枂极函数绝对必要。 限定仅在子类访问的成员函数为 protected,需要注意的是数据成员应始终为私有。 当重定义派生的虚函数时,在派生类中明确声明其为 virtual。根本原因:如果遗漏 virtual,阅诺者需要检 索类的所有祖先以确定该函数是否为虚函数(注,虽然丌影响其为虚函数的本质)。
7. 多重继承(Multiple Inheritance)
真正需要用到多重实现继承(multiple implementation inheritance)的时候非常少,叧有当最多一个基 类中吨有实现,其他基类都是以 Interface 为后缀的纯接口类时才会使用多重继承。
定义:多重继承允许子类拥有多个基类,要将作为纯接口的基类和具有实现的基类区别开来。
优点:相比单继承,多重实现继承可令你重用更多代码。
缺点:真正需要用到多重实现继承的时候非常少,多重实现继承看上去是丌错的解决方案,通常可以找到 更加明确、清晰的、不同的解决方案。
结论:叧有当所有超类(superclass)除第一个外都是纯接口时才能使用多重继承。为确保它们是纯接口, 返些类必须以 Interface 为后缀。
函数重载(Function Overloading)
仅在输入参数类型丌同、功能相同时使用重轲函数(吨极造函数),丌要使用函数重轲模仿缺省函数参数。
定义:可以定义一个函数参数类型为 const string&,幵定义其重轲函数类型为 const char*。 class MyClass { public: void Analyze(conststring &text); void Analyze(const char *text, size_t textlen); };
优点:通过重载不同参数的同名函数,令代码更加直观,模板化代码需要重轲,同时为访问者带来便利。
缺点:限制使用重载的一个原因是在特定调用处徆难确定到底调用的是哪个函数,另一个原因是当派生类 叧重轲函数的部分发量会令徆多人对继承诧义产生困惑。此外在阅诺库的客户端代码时,因缺省函数参数 造成丌必要的费解。
结论:如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用 AppendString()、AppendInt() 而丌是 Append()。
4.其他企业编程风格
1.缩进嵌套的代码:在每个代码块和嵌套中加入缩进,缩进代码,加强可读性。这些地方包括【类定义,内部类定义,方法定义,静态块,for循环语句,if-else语句,try、catch和finally块,匿名内部类,while语句,do-while语句】
2.断开很长的句子:第一,如果包含了逗号,那么在每一个逗号后面都另起一行,把逗号后面的每一个表达式都和逗号前面的表达式的第一个字母对齐。第二,应该在优先级最低的运算符之前断行。
3.使用空白:关键字和左括号之间,右括号和紧随其后的关键字,除了”.”之外的运算符与其前后的表达式之间用空格隔开。每个逻辑上独立的方法和代码段之间,定义类或者接口的成员之间,每个类和接口之间应该加入空白行。
4.不要直接使用Tab控制符:不同环境对Tab控制符的解释也是不同的。
命名约定:
1.名称应该具有实际意义
2.使用人们熟悉的名称
3.谨慎使用过长的名字,可以使用简明通用的缩写
4.尽量保留元音字母
5.缩写词的第一个字母大写
6.不要使用大小写来区分的名字
包命名:
1.用你所在组织的域名的倒序小写形式作为包的根限定词
2.使用单独的小写词作为每个包的根名
3.仅当新旧版本二进制兼容的时候,其包可以使用相同的名字,否则,请使用新名字
类型命名:
1.类和接口名中的每个单词的第一个字母大写
类命名:
1.用名词命名类
2.具有成组相关属性,静态服务或者常量的类名字使用复数形式
接口命名:
1.用名词或者形容词命名接口
方法命名:
1.方法名中的第一个单词小写,其后每个单词的第一个字母大写
2.用动词命名方法
3.遵循JavaBean中命名属性访问函数方法:set,get,is
变量命名:
1.变量命中的第一个单词小写,其后的每个单词的第一个字母大写
2.用名词命名变量
3.集合引用名要用复数形式
4.为不重要的临时变量简历并使用一套标准名字
字段命名:
1.使用this字段变量可以区分开局部变量
参数命名:
1.构造函数或者”set”方法给字段分配参数赋值,参数名应该和字段名相同
常量命名:
1.常量的每个单词均大写,单词之间使用下划线连接
文档约定:
1.为使用和维护你的代码的人编写文档
2.注释和代码要同步
3.使用积极的语气,省略无用的词语
注释类型:
1.用文档注释来描述编程接口
2.用标准注释格式隐藏代码而不必删除它们
3.用单行注释解释实现细节
文档注释:
1.在编写代码前描述编程接口
2.为公用,受保护,包,私有成员建立文档
3.为每个包编写总结和概述
4.为包的每个应用程序或组编写概述
注释风格:
1.对所有文档注释使用统一的格式和组织结构
2.关键字,标识符和常量应放到...
标签中
3.将代码放入
...标签中
4.在标识符第一次出现的时候用{@link}标签
5.为Javadoc标签简历并使用一套固定的顺序
6.使用第三人称叙述的形式
7.编写独立的概述
8.省略概述中动作和服务的主语
9.省略事物概述中的对象和动词
10.使用this而不是the来指代当前类中的实例
11.方法名或者构造函数名不需圆括号,除非你想突出一个特殊的签名
注释内容:
1.每个类、接口、字段和方法都编写概述
2.完整描述每个方法的签名
3.包含示例
4.为前置、后置、不变条件编写文档
5.为已知的缺陷和不足编写文档
6.为同步语
法编写文档
内部注释:
1.仅添加有助于理解你的代码的内部注释
2.描写代码为什么这样做,而不是在做什么
3.避免使用行尾注释
4.用行尾注释解释局部变量声明
5.建立并使用一套关键词来标识尚未解决的问题
6.在嵌套程度高的控制结构中标记出嵌套结束位置
7.如果两个case标记之间没有break语句,就在中间加入“fall-through”注释
8.标记空语句
编程约定:
1.将表示基础数据类型的类声明为final类型
2.通过本地类型和其他具体类型建立具体类型
3.定义小的类和小的方法
4.定义子类,以便任何使用超类的地方都可以使用子类
5.使所有字段私有
6.使用多态来替代instanceof
类型安全:
1.以java.lang.Object包装通用类,提供静态类型检查
2.以类的形式封装枚举类型
3.尽量使用泛型
语句和表达式:
1.用等价的方法替换重复的、复杂的表达式
2.使用块语句代替控制流结构的表达式
3.使用括号明确操作顺序
4.在switch语句中的最后一个case体使用break语句
5.使用equals(),而不是==来检测对象的对等关系
构造:
1.构造状态有效的对象
2.不要从构造函数中调用非final方法
3.用嵌套的构造函数消除冗余代码
异常处理:
1.使用不受检查、运行时的异常来报告可能在程序逻辑中出错的严重未查明错误
2.使用检查异常来报告可能发生,而在正常的程序运行时极少发生的错误
3.用返回代码报告可预知的状态改变
4.仅转化异常来添加信息
5.不要私自处置运行时或者错误异常
6.用finally语句块释放资源
断言:
1.按照约定编程
2.用无用代码消除机制实现断言
3.用断言捕捉代码中的逻辑错误
4.用断言检测方法的前置条件和后置条件
并发:
1.仅在适当的地方使用线程
同步:
1.避免同步
2.用同步的包装器,提供同步接口
3.如果方法包含几个不需要同步的重要操作,那么不要同步整个方法
4.读写实例变量的时候避免不必要的同步
5.使用notify()而不是notifyAll()
6.为同步初始化使用双重检查模式
效率:
1.使用懒惰初始化
2.避免创建不必要的对象
3.重新初始化并重新使用对象,尽量不要新建对象
4.把优化工作留在日后
打包约定:
1.将经常使用、更改、同时发布或者互相依存的类型,放在同一个包里
2.共同封闭原则
3.重用/发布等价原则
4.无环依赖原则
5.将不稳定的类和接口隔离在单独的包中
6.易于修改的包不要依赖于难以修改的包
7.最大化抽象最大化稳定性
8.将高层设计和架构作为稳定的抽象,组织为稳定的包
5.个人编码模板
1.类定义
{
类的公有属性定义
类的保护属性定义
类的私有属性定义
类的公有方法定义
类的保护方法定义
类的私有方法定义
}
2 注释
com/huawei/iin/websmap/comm/package.html
一句话简述。
详细描述。
产品模块名称和版本
公司版权信息
示例:
为WEBSMAP 提供通信类,上层业务使用本包的通信类与SMP-B 进行通信。
详细描述。。。。。。。。
IINV100R001 WEBSMAP
(C)版权所有 2000-2001 华为技术有限公司
3.声明变量及函数模板
get + 非布尔属性名()
is + 布尔属性名()
set + 属性名()
动词()
动词 + 宾语()
示例:
publicString getType();
publicboolean isFinished();
publicvoid setVisible(boolean);
publicvoid show();
publicvoid addKeyListener(Listener);
4.规则
数据库操作、IO操作等需要使用结束close()的对象必须在try -catch-finally 的finally中close(),如果有多个IO对象需要close(),需要分别对每个对象的close()方法进行try-catch,防止一个IO对象关闭失败其他IO对象都未关闭。
示例:
try
{
// … …
}
catch(IOExceptionioe)
{
//… …
}
finally
{
try
{
out.close();
}
catch (IOException ioe)
{
//… …
}
try
{
in.close();
}
catch (IOException ioe)
{
//… …
}
}
规则2 系统非正常运行产生的异常捕获后,如果不对该异常进行处理,则应该记录日志。
try
{
//…. …
}
catch(IOException ioe)
{
logger.error(ioe);
}
规则3 自己抛出的异常必须要填写详细的描述信息。
thrownew IO1 华为 Java 编程规范
1.1 排版
1.1.1 规则
规则1 、
程序块要采用缩进风格编写,缩进的空格数为4个,不允许使用TAB缩进。
说明:缩进使程序更易阅读,使用空格缩进可以适应不同操作系统与不同开发工具。
规则2
分界符(如大括号‘{’和‘}’)应各独占一行,同时与引用它们的语句左对齐。在函数体的开始、类和接口的定义、以及if、for、do、while、switch、case语句中的程序或者static、,synchronized等语句块中都要采用如上的缩进方式。
示例:
if (a>b)
{
doStart();
}
规则3
较长的语句、表达式或参数(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
示例:
if(logger.isDebugEnabled())
{
logger.debug(“Session destroyed,call-id”
+event.getSession().getCallId());
}
规则4 不允许把多个短语句写在一行中,即一行只写一条语句
说明:阅读代码更加清晰
示例:如下例子不符合规范。
Objecto = new Object(); Object b = null;
规则5 if, for, do, while, case, switch, default 等语句自占一行,且if, for, do, while,switch等语句的执行语句无论多少都要加括号{},case 的执行语句中如果定义变量必须加括号{}。
说明:阅读代码更加清晰,减少错误产生
示例:
if (a>b)
{
doStart();
}
case x:
{
inti = 9;
}
规则6 相对独立的程序块之间、变量说明之后必须加空行。
说明:阅读代码更加清晰
示例:
if(a > b)
{
doStart();
}
return;
规则7 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如.),后不应加空格。
说明:阅读代码更加清晰
示例:
if (a == b)
{
objectA.doStart();
}
a *= 2;
1.1.2 建议
建议1 类属性和类方法不要交叉放置,不同存取范围的属性或者方法也尽量不要交叉放置。
格式:
类定义
{
类的公有属性定义
类的保护属性定义
类的私有属性定义
类的公有方法定义
类的保护方法定义
类的私有方法定义
}
建议2 修饰词按照指定顺序书写:[访问权限][static][final] 。
示例:
publicstatic final String str = “abc”;
1.2 注释
1.2.1 规则
规则1 源程序注释量必须在30%以上。
说明:由于每个文件的代码注释不一定都可以达到30%,建议以一个系统内部模块作为单位进行检查
规则2 包的注释:写入一个名为 package.html的HTML格式的说明文件放入包所在路径。包的注释内容:简述本包的作用、详细描述本包的内容、产品模块名称和版本、公司版权。
说明:方便JavaDoc收集,方便对包的了解
示例:
com/huawei/iin/websmap/comm/package.html
一句话简述。
详细描述。
产品模块名称和版本
公司版权信息
示例:
为WEBSMAP 提供通信类,上层业务使用本包的通信类与SMP-B 进行通信。
详细描述。。。。。。。。
IINV100R001 WEBSMAP
(C)版权所有 2000-2001 华为技术有限公司
规则3 类和接口的注释放在class 或者 interface 关键字之前,import 关键字之后。注释主要是一句话功能简述与功能详细描述。类注释使用“/* /”注释方式
说明:方便JavaDoc收集,没有import可放在package之后。注释可根据需要列出:作者、内容、功能、与其它类的关系等。功能详细描述部分说明该类或者接口的功能、作用、使用方法和注意事项,每次修改后增加作者和更新版本号和日期,@since 表示从那个版本开始就有这个类或者接口,@deprecated 表示不建议使用该类或者接口。
/**
* 〈一句话功能简述〉
* 〈功能详细描述〉
* @author [作者](必须)
* @see [相关类/方法](可选)
* @since [产品/模块版本] (必须)
* @deprecated (可选)
*/
示例:
packagecom.huawei.iin.logwebsmap.comm;
importjava.util.*;
/**
* LogManager 类集中控制对日志读写的操作。
* 全部为静态变量和静态方法,对外提供统一接口。分配对应日志类型的读写器,
* 读取或写入符合条件的日志纪录。
* @author 张三,李四,王五
* @see LogIteraotor
* @see BasicLog
* @since CommonLog1.0
*/
publicclass LogManager
规则4 类属性(成员变量)、公有和保护方法注释:写在类属性、公有和保护方法上面,注释方式为“/* /”.
示例:
/**
* 注释内容
*/
privateString logType;
/**
* 注释内容
*/
publicvoid write()
规则5 公有和保护方法注释内容:列出方法的一句话功能简述、功能详细描述、输入参数、输出参数、返回值、异常等。
格式:
/**
* 〈一句话功能简述〉
* 〈功能详细描述〉
* @param [参数1] [参数1说明]
* @param [参数2] [参数2说明]
* @return [返回类型说明]
* @exception/throws [异常类型] [异常说明]
* @see [类、类#方法、类#成员]
* @since [起始版本]
* @deprecated
*/
说明:@since 表示从那个版本开始就有这个方法,如果是最初版本就存在的方法无需说明;@exception或throws 列出可能仍出的异常;@deprecated 表示不建议使用该方法。
示例:
/**
* 根据日志类型和时间读取日志。
* 分配对应日志类型的LogReader, 指定类型、查询时间段、条件和反复器缓冲数,
* 读取日志记录。查询条件为null或0的表示没有限制,反复器缓冲数为0读不到日志。
* 查询时间为左包含原则,即[startTime, endTime) 。
* @param logTypeName 日志类型名(在配置文件中定义的)
* @param startTime 查询日志的开始时间
* @param endTime 查询日志的结束时间
* @param logLevel 查询日志的级别
*@param userName 查询该用户的日志
* @param bufferNum 日志反复器缓冲记录数
* @return 结果集,日志反复器
* @since 1.2
*/
public static LogIterator read(StringlogType, Date startTime, Date endTime, intlogLevel, String userName, int bufferNum)
规则6 对于方法内部用throw语句抛出的异常,必须在方法的注释中标明,对于所调用的其他方法所抛出的异常,选择主要的在注释中说明。 对于非RuntimeException,即throws子句声明会抛出的异常,必须在方法的注释中标明。
说明:异常注释用@exception或@throws表示,在JavaDoc中两者等价,但推荐用@exception标注Runtime异常,@throws标注非Runtime异常。异常的注释必须说明该异常的含义及什么条件下抛出该异常。
规则7 注释应与其描述的代码相近,对代码的注释应放在其上方,并与其上面的代码用空行隔开,注释与所描述内容进行同样的缩排。
说明:可使程序排版整齐,并方便注释的阅读与理解。
示例:
/*
* 注释
*/
publicvoid example2( )
{
// 注释
CodeBlock One
// 注释
CodeBlock Two
}
/*
* 注释
*/
publicvoid example( )
{
// 注释
CodeBlock One
// 注释
CodeBlock Two
}
规则8 对于switch语句下的case语句,必须在每个case分支结束前加上break语句。
说明:break才能真正表示该switch执行结束,不然可能会进入该case以后的分支。至于语法上合法的场景“一个case后进入下一个case处理”,应该在编码设计上就避免。
规则9 修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
规则10 注释的内容要清楚、明了,含义准确,防止注释二义性。
说明:错误的注释不但无益反而有害。
规则11 避免在注释中使用缩写,特别是不常用缩写。
说明:在使用缩写时或之前,应对缩写进行必要的说明。
规则12 对重载父类的方法必须进行@Override声明(5.0+)
说明:可清楚说明此方法是重载父类的方法,保证重载父类的方法时不会因为单词写错而造成错误(写错方法名或者参数个数,类型都会编译无法通过)
示例:
@Override
public voiddoRequest(SipServletRequest req) throws ServletException,
IOException
1.2.2 建议
建议1 避免在一行代码或表达式的中间插入注释。
说明:除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。
建议2 在代码的功能、意图层次上进行注释,提供有用、额外的信息。
说明:注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。
示例:如下注释意义不大。
// 如果receiveFlag 为真
if(receiveFlag)
而如下的注释则给出了额外有用的信息。
//如果从连结收到消息
if(receiveFlag)
建议3 对关键变量的定义和分支语句(条件分支、循环语句等)必须编写注释。
说明:这些语句往往是程序实现某一特定功能的关键,对于维护人员来说,良好的注释帮助更好的理解程序,有时甚至优于看设计文档。
建议4 注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达。中文注释中需使用中文标点。方法和类描述的第一句话尽量使用简洁明了的话概括一下功能,然后加以句号。接下来的部分可以详细描述。
说明:注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。JavaDoc工具收集简介的时候使用选取第一句话。
建议5 方法内的单行注释使用 //。
说明:调试程序的时候可以方便的使用/* 。。。*/ 注释掉一长段程序。
建议6 一些复杂的代码需要说明。
示例:这里主要是对闰年算法的说明。
//1. 如果能被4整除,是闰年;
//2. 如果能被100整除,不是闰年;
//3. 如果能被400整除,是闰年。
建议7 使用Html标签使JavaDoc生成更加美观。
示例:
/**
* Returns a hash code for this string. Thehash code for a
* String
objectis computed as
*
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... +s[n-1]
*
* using
int
arithmetic, where s[i]
is the * ith character of thestring,
n
is the length * of * the string, and
^
indicates exponentiation. * (The hash value of the empty string iszero.)
*
* @return a hash code value for this object.
*/
public int hashCode()
生成后的JavaDoc
图1 生成后的JavaDoc
1.3 命名
1.3.1 规则
规则1 类名和接口使用类意义完整的英文描述,每个英文单词的首字母使用大写、其余字母使用小写的大小写混合法。
示例:OrderInformation,CustomerList, LogManager, LogConfig, SmpTransaction
规则2 方法名使用类意义完整的英文描述:第一个单词的字母使用小写、剩余单词首字母大写其余字母小写的大小写混合法。
示例:
privatevoid calculateRate();
publicvoid addNewOrder();
规则3 方法中,存取属性的方法采用setter 和 getter方法,动作方法采用动词和动宾结构。
格式:
get + 非布尔属性名()
is + 布尔属性名()
set + 属性名()
动词()
动词 + 宾语()
示例:
publicString getType();
publicboolean isFinished();
publicvoid setVisible(boolean);
publicvoid show();
publicvoid addKeyListener(Listener);
规则4 属性名使用意义完整的英文描述,第一个单词的字母使用小写,剩余单词首字母大写其余字母小写的大小写混合法。属性名不能与方法名相同。
示例:
privatecustomerName;
privateorderNumber;
privatesmpSession;
规则5 常量名使用全大写的英文描述,英文单词之间用下划线分隔开,并且使用 static final修饰。
示例:
publicstatic final int MAX_VALUE = 1000;
publicstatic final String DEFAULT_START_DATE = “2001-12-08”;
1.3.2 建议
建议1 包名采用域后缀倒置的加上自定义的包名,采用小写字母,都应该以com.huawei开头(不包括一些特殊原因)。在部门内部应该规划好包名的范围,防止产生冲突。部门内部产品使用部门的名称加上模块名称。产品线的产品使用产品的名称加上模块的名称。
说明:除特殊原因包结构都必须以com.huawei开头,如果因为OEM合作等关系,可以不做要求。
格式:
com.huawei.产品名.模块名称
示例:
融合WEBSMAP包名 com.huawei.iin.websmap
建议2 通过对函数或过程、变量、结构等正确的命名以及合理地组织代码的结构,使代码成为自注释的。
说明:清晰准确的函数、变量等的命名,可增加代码可读性,并减少不必要的注释。
建议3 常用组件类的命名以组件名加上组件类型名结尾。
示例:
Application 类型的,命名以App 结尾——MainApp
Frame 类型的,命名以Frame 结尾——TopoFrame
Panel 类型的,建议命名以Panel结尾——CreateCircuitPanel
Bean 类型的,建议命名以Bean 结尾——DataAccessBean
EJB 类型的,建议命名以EJB 结尾——DBProxyEJB
Applet 类型的,建议命名以Applet 结尾——PictureShowApplet
建议4 如果函数名超过15 个字母,可采用以去掉元音字母的方法或者以行业内约定俗成的缩写方式缩写函数名。
示例:
getCustomerInformation() 改为 getCustomerInfo()
建议5 准确地确定成员函数的存取控制符号:只是该类内部调用的函数使用 private 属性,继承类可以使用的使用protected属性,同包类可以调用的使用默认属性(不加属性控制符号),对外公开的函数使用public属性
示例:
protected void getUserName()
{
。。。。。。
}
private void calculateRate()
{
。。。。。。
}
建议6 含有集合意义的属性命名,尽量包含其复数的意义。
示例:
customers, orderItems
1.4 编码
1.4.1 规则
规则1 数据库操作、IO操作等需要使用结束close()的对象必须在try -catch-finally 的finally中close(),如果有多个IO对象需要close(),需要分别对每个对象的close()方法进行try-catch,防止一个IO对象关闭失败其他IO对象都未关闭。
示例:
try
{
// … …
}
catch(IOExceptionioe)
{
//… …
}
finally
{
try
{
out.close();
}
catch (IOException ioe)
{
//… …
}
try
{
in.close();
}
catch (IOException ioe)
{
//… …
}
}
规则2 系统非正常运行产生的异常捕获后,如果不对该异常进行处理,则应该记录日志。
说明:此规则指通常的系统非正常运行产生的异常,不包括一些基于异常的设计。若有特殊原因必须用注释加以说明。
示例:
try
{
//…. …
}
catch(IOException ioe)
{
logger.error(ioe);
}
规则3 自己抛出的异常必须要填写详细的描述信息。
说明:便于问题定位。
示例:
thrownew IOException(“Writing dataerror! Data: ” + data.toString());
规则4 运行时异常使用RuntimeException的子类来表示,不用在可能抛出异常的方法声明上加throws子句。非运行期异常是从Exception继承而来的,必须在方法声明上加throws子句。
说明:
非运行期异常是由外界运行环境决定异常抛出条件的异常,例如文件操作,可能受权限、磁盘空间大小的影响而失败,这种异常是程序本身无法避免的,需要调用者明确考虑该异常出现时该如何处理方法,因此非运行期异常必须有throws子句标出,不标出或者调用者不捕获该类型异常都会导致编译失败,从而防止程序员本身疏忽。
运行期异常是程序在运行过程中本身考虑不周导致的异常,例如传入错误的参数等。抛出运行期异常的目的是防止异常扩散,导致定位困难。因此在做异常体系设计时要根据错误的性质合理选择自定义异常的继承关系。
还有一种异常是Error 继承而来的,这种异常由虚拟机自己维护,表示发生了致命错误,程序无法继续运行例如内存不足。我们自己的程序不应该捕获这种异常,并且也不应该创建该种类型的异常。
规则5 在程序中使用异常处理还是使用错误返回码处理,根据是否有利于程序结构来确定,并且异常和错误码不应该混合使用,推荐使用异常。
说明:
一个系统或者模块应该统一规划异常类型和返回码的含义。
但是不能用异常来做一般流程处理的方式,不要过多地使用异常,异常的处理效率比条件分支低,而且异常的跳转流程难以预测。
注意:Java 5.0 程序内部的错误码可以使用枚举来表示。
规则6 注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。
说明:防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而导致程序出错。
示例:
下列语句中的表达式
word= (high << 8) | low (1)
if((a | b) && (a & c)) (2)
if((a | b) < (c & d)) (3)
如果书写为
high<< 8 | low
a | b&& a & c
a | b< c & d
(1)(2)虽然不会出错,但语句不易理解;(3)造成了判断条件出错。
规则7 避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的静态变量或者枚举来代替。使用异常来表示方法执行错误,而不是使用C++的错误返回码方式。
示例:如下的程序可读性差。
if(state == 0)
{
state = 1;
… // program code
}
应改为如下形式:
privatefinal static int TRUNK_IDLE = 0;
privatefinal static int TRUNK_BUSY = 1;
privatefinal static int TRUNK_UNKNOWN = -1;
if(state == TRUNK_IDLE)
{
state = TRUNK_BUSY;
… // program code
}
注意:Java5.0 下建议使用枚举来表示。
异常:
publicvoid function()
{
…
throw new RuntimeException(“。。。”);
}
规则8 数组声明的时候使用 int[] index ,而不要使用 int index[] 。
说明:使用int index[] 格式使程序的可读性较差,int [] index 表示声明了一个int数组(int [])叫做index
示例:
如下程序可读性差:
publicint getIndex()[]
{
….
}
如下程序可读性好:
publicint[] getIndex()
{
….
}
规则9 不要使用 System.out 与 System.err 进行控制台打印,应该使用工具类(如:日志工具)进行统一记录或者打印。
说明:代码发布的时候可以统一关闭控制台打印,代码调试的时候又可以打开控制台打印,方便调试。
规则10 用调测开关来切换软件的DEBUG版和正式版,而不要同时存在正式版本和DEBUG版本的不同源文件,以减少维护的难度。
规则11 集合必须指定模板类型(5.0+)
说明:方便程序阅读,除去强制转换代码
示例:
Map
define MAX_USR_NUM 10
unsigned char usr_login_flg[MAX_USR_NUM]= “”;
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no])
{
usr_login_flg[usr_no]= TRUE;
}
当usr_no 为10 时,将使用usr_login_flg 越界。可采用如下方式解决。
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no - 1])
{
usr_login_flg[usr_no - 1]= TRUE;
}
}
8:认真处理程序所能遇到的各种出错情况。
9:系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。
10:系统运行之初,要对加载到系统中的数据进行一致性检查。
说明:使用不一致的数据,容易使系统进入混乱状态和不可知状态。
11:严禁随意更改其它模块或系统的有关设置和配置。
说明:编程时,不能随心所欲地更改不属于自己模块的有关设置如常量、数组的大小等。
12:不能随意改变与其它模块的接口。
13:充分了解系统的接口之后,再使用系统提供的功能。
示例:在B 型机的各模块与操作系统的接口函数中,有一个要由各模块负责编写的初始化过程,此过程在软件系统加载完成后,由操作系统发送的初始化消息来调度。因此就涉及到初始化消息的类型与消息发送的顺序问题,特别是消息顺序,若没搞清楚就开始编程,很容易引起严重后果。以下示例引自B 型曾出现过的实际代码,其中使用了FID_FETCH_DATA与FID_INITIAL 初始化消息类型,注意B 型机的系统是在FID_FETCH_DATA 之前发送FID_INITIAL 的。
MID alarm_module_list[MAX_ALARM_MID];
int FAR SYS_ALARM_proc( FID function_id, int handle )
{
_UI i, j;
switch ( function_id )
{
… // program code
case FID_INITAIL:
for (i = 0; i < MAX_ALARM_MID; i++)
{
if (alarm_module_list[i]== BAM_MODULE // **)
|| (alarm_module_list[i]== LOCAL_MODULE)
{
for (j = 0; j < ALARM_CLASS_SUM; j++)
{
FAR_MALLOC( … );
}
}
}
… // program code
break;
case FID_FETCH_DATA:
… // program code
Get_Alarm_Module( ); // 初始化alarm_module_list
break;
… // program code
}
}
由于FID_INITIAL 是在FID_FETCH_DATA 之前执行的,而初始化alarm_module_list 是在FID_FETCH_DATA 中进行的,故在FID_INITIAL 中()处引用alarm_module_list 变量时,它还没有被初始化。这是个严重错误。应如下改正:要么把Get_Alarm_Module 函数放在FID_INITIAL 中()之前;要么就必须考虑(**)处的判断语句是否可以用(不使用alarm_module_list 变量的)其它方式替代,或者是否可以取消此判断语句。
14:编程时,要防止差1 错误。
说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后,应对这些操作符进行彻底检查。
15:要时刻注意易混淆的操作符。当编完程序后,应从头至尾检查一遍这些操作符,以防止拼写错误。
说明:形式相近的操作符最容易引起误用,如C/C++中的“=”与“==”、“|”与“||”、“&”与“&&”等,若拼写错了,编译器不一定能够检查出来。
示例:如把“&”写成“&&”,或反之。
ret_flg = (pmsg->ret_flg & RETURN_MASK);
被写为:
ret_flg = (pmsg->ret_flg && RETURN_MASK);
rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));
被写为:rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));
16:有可能的话,if 语句尽量加上else 分支,对没有else 分支的语句要小心对待;switch语句必须有default 分支。
17:Unix 下,多线程的中的子线程退出必需采用主动退出方式,即子线程应return 出口。
18:不要滥用goto 语句。
说明:goto 语句会破坏程序的结构性,所以除非确实需要,最好不使用goto 语句。
19:精心地构造、划分子模块,并按“接口”部分及“内核”部分合理地组织子模块,以提高“内核”部分的可移植性和可重用性。
说明:对不同产品中的某个功能相同的模块,若能做到其内核部分完全或基本一致,那么无论对产品的测试、维护,还是对以后产品的升级都会有很大帮助。
20:精心构造算法,并对其性能、效率进行测试。
21:对较关键的算法最好使用其它算法来确认。
22:时刻注意表达式是否会上溢、下溢。
示例:如下程序将造成变量下溢。
unsigned char size ;
while (size– >= 0) // 将出现下溢
{
… // program code
}
当size 等于0 时,再减1 不会小于0,而是0xFF,故程序是一个死循环。应如下修改。
char size; // 从unsigned char 改为char
while (size– >= 0)
{
… // program code
}
23:使用变量时要注意其边界值的情况。
示例:如C 语言中字符型变量,有效值范围为-128 到127。故以下表达式的计算存在一定风险。
char chr = 127;
int sum = 200;
chr += 1; // 127 为chr 的边界值,再加1 将使chr 上溢到-128,而不是128。
sum += chr; // 故sum 的结果不是328,而是72。
若chr 与sum 为同一种类型,或表达式按如下方式书写,可能会好些。
sum = sum + chr + 1;
24:留心程序机器码大小(如指令空间大小、数据空间大小、堆栈空间大小等)是否超出系统有关限制。
25:为用户提供良好的接口界面,使用户能较充分地了解系统内部运行状态及有关系统出错情况。
26:系统应具有一定的容错能力,对一些错误事件(如用户误操作等)能进行自动补救。
27:对一些具有危险性的操作代码(如写硬盘、删数据等)要仔细考虑,防止对数据、硬件等的安全构成危害,以提高系统的安全性。
28:使用第三方提供的软件开发工具包或控件时,要注意以下几点:
(1)充分了解应用接口、使用环境及使用时注意事项。
(2)不能过分相信其正确性。
3 Google Java编程风格
1.1 术语说明 在本文档中,除非另有说明:
1. 术语class可表示一个普通类,枚举类,接口或是annotation类型(@interface)
2. 术语comment只用来指代实现的注释(implementation comments),我们不使用“documentation comments”一词,而是用Javadoc。其他的术语说明会偶尔在后面的文档出现。
1.2 指南说明 本文档中的示例代码并不作为规范。也就是说,虽然示例代码是遵循Google编程风格,但并不意味着这是展 现这些代码的唯一方式。 示例中的格式选择不应该被强制定为规则。 源文件基础
2.1 文件名 源文件以其最顶层的类名来命名,大小写敏感,文件扩展名为.java。
2.2 文件编码:UTF-8 源文件编码格式为UTF-8。
2.3 特殊字符
2.3.1 空白字符 除了行结束符序列,ASCII水平空格字符(0x20,即空格)是源文件中唯一允许出现的空白字符,这意味着: 1. 所有其它字符串中的空白字符都要进行转义。 2. 制表符不用于缩进。
2.3.2 特殊转义序列 对于具有特殊转义序列的任何字符(\b, \t, \n, \f, \r, \“, \‘及),我们使用它的转义序列,而不是相 应的八进制(比如\012)或Unicode(比如\u000a)转义。
2.3.3 非ASCII字符 对于剩余的非ASCII字符,是使用实际的Unicode字符(比如∞),还是使用等价的Unicode转义符(比如 \u221e),取决于哪个能让代码更易于阅读和理解。 Tip: 在使用Unicode转义符或是一些实际的Unicode字符时,建议做些注释给出解释, 这有助于别人阅读和理解。
源文件结构 一个源文件包含(按顺序地):
1. 许可证或版权信息(如有需要)
2. package语句
3. import语句
4. 一个顶级类(只有一个) 以上每个部分之间用一个空行隔开。
3.1 许可证或版权信息 如果一个文件包含许可证或版权信息,那么它应当被放在文件最前面。
3.2 package语句 package语句不换行,列限制(4.4节)并不适用于package语句。(即package语句写在一行里)
3.3 import语句
3.3.1 import不要使用通配符即,不要出现类似这样的import语句:importjava.util.*;
3.3.2 不要换行 import语句不换行,列限制(4.4节)并不适用于import语句。(每个import语句独立成行)
3.3.3 顺序和间距 import语句可分为以下几组,按照这个顺序,每组由一个空行分隔:
1. 所有的静态导入独立成组
2. com.googleimports(仅当这个源文件是在com.google包下)
3. 第三方的包。每个顶级包为一组,字典序。例如:android, com, junit, org, sun
4. javaimports
5. javaximports 组内不空行,按字典序排列。
3.4 类声明
3.4.1 只有一个顶级类声明 每个顶级类都在一个与它同名的源文件中(当然,还包含.java后缀)。 例外:package-info.java,该文件中可没有package-info类。
3.4.2 类成员顺序 类的成员顺序对易学性有很大的影响,但这也不存在唯一的通用法则。不同的类对成员的排序可能是不同 的。 最重要的一点,每个类应该以某种逻辑去排序它的成员,维护者应该要能解释这种排序逻辑。比如, 新的方法不能总是习惯性地添加到类的结尾,因为这样就是按时间顺序而非某种逻辑来排序的。
3.4.2.1 重载:永不分离 当一个类有多个构造函数,或是多个同名方法,这些函数/方法应该按顺序出现在一起,中间不要放进其它函 数/方法。 格式 术语说明:块状结构(block-like construct)指的是一个类,方法或构造函数的主体。需要注意的是,数组 初始化中的初始值可被选择性地视为块状结构(4.8.3.1节)。
4.1 大括号
4.1.1 使用大括号(即使是可选的) 大括号与if,else,for,do,while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。
4.1.2 非空块:K & R 风格对于非空块和块状结构,大括号遵循Kernighan和Ritchie风格 (Egyptian brackets): 左大括号前不换行 左大括号后换行右大括号前换行 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号 后面是else或逗号,则不换行。 示例: returnnewMyClass(){ @Overridepublicvoidmethod(){ if(condition()){ try{something(); }catch(ProblemExceptione){ recover(); } } } };
4.1.3 空块:可以用简洁版本 一个空的块状结构里什么也不包含,大括号可以简洁地写成{},不需要换行。例外:如果它是一个多块语句 的一部分(if/else 或 try/catch/finally) ,即使大括号内没内容,右大括号也要换行。 示例: voiddoNothing(){}
4.2 块缩进:2个空格 每当开始一个新的块,缩进增加2个空格,当块结束时,缩进返回先前的缩进级别。缩进级别适用于代码和注 释。
4.3 一行一个语句 每个语句后要换行。
4.4 列限制:80或100 一个项目可以选择一行80个字符或100个字符的列限制,除了下述例外,任何一行如果超过这个字符数限制, 必须自动换行。
例外: 1. 不可能满足列限制的行(例如,Javadoc中的一个长URL,或是一个长的JSNI方法参考)。 2. package和import语句(见3.2节和3.3节)。 3. 注释中那些可能被剪切并粘贴到shell中的命令行。
4.5 自动换行 术语说明:一般情况下,一行长代码为了避免超出列限制(80或100个字符)而被分为多行,我们称之为自动 换行(line-wrapping)。 我们并没有全面,确定性的准则来决定在每一种情况下如何自动换行。很多时候,对于同一段代码会有好几 种有效的自动换行方式。 Tip: 提取方法或局部变量可以在不换行的情况下解决代码过长的问题(是合理缩短命 名长度吧)
4.5.1 从哪里断开 自动换行的基本准则是:更倾向于在更高的语法级别处断开。 1. 如果在非赋值运算符处断开,那么在该符号前断开(比如+,它将位于下一行)。注意:这一点与Google其 它语言的编程风格不同(如C++和JavaScript)。 这条规则也适用于以下“类运算符”符号:点分隔符 (.),类型界限中的&(
ifndef FOO_BAR_BAZ_H_
define FOO_BAR_BAZ_H_
…
endif // FOO_BAR_BAZ_H_
作用域
1. 命名空间(Namespaces)在.cc 文件中,提倡使用丌具名的命名空间(unnamed namespaces,注:丌具
名的命名空间就像丌具名 的类一样,似乎被介绍的徆少:-()。使用具名命名空间时,其名称可基亍项目戒路径名称,丌要使用 using 挃示符。
定义:命名空间将全尿作用域绅分为丌同的、具名的作用域,可有效防止全尿作用域的命名冲突。
优点:命名空间提供了(可嵌套)命名轰线(name axis,注:将命名分割在丌同命名空间内),当然,类 也提供了(可嵌套)的命名轰线(注:将命名分割在丌同类的作用域内)。 丼例来说,两个丌同项目的全尿作用域都有一个类 Foo,返样在编译戒运行时造成冲突。如果每个项目将 代码置亍丌同命名空间中,project1::Foo 和 project2::Foo 作为丌同符号自然丌会冲突。 缺点:命名空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轰线。在头文件中使用丌 具名的空间容易迗背 C++的唯一定义原则(One Definition Rule (ODR))。
结论:根据下文将要提到的策略合理使用命名空间。
1) 不具名命名空间(Unnamed Namespaces)
在.cc 文件中,允许甚至提倡使用丌具名命名空间,以避免运行时的命名冲突:
namespace
{ // .cc 文件中
// 命名空间的内容无需缩迕
enum { UNUSED, EOF, ERROR }; // 经常使用的符号
bool AtEof() { return pos_ == EOF; }// 使用本命名空间内的符号 EOF
} // namespace
然而,不特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员戒静态成员函数,而丌是丌 具名命名空间的成员。像上文展示的那样,丌具名命名空间结束时用注释// namespace 标识。
不能在.h 文件中使用丌具名命名空间。
2) 具名命名空间(Named Namespaces)
具名命名空间使用方式如下:
命名空间将除文件包吨、全尿标识的声明/定义以及类的前置声明外的整个源文件封装起来,以同其他命名 空间相区分。
// .h 文件
namespace mynamespace {
// 所有声明都置亍命名空间中, 注意丌要使用缩迕
class MyClass { public: … voidFoo(); }; } //
namespace mynamespace // .cc 文件
namespace mynamespace {
// 函数定义都置亍命名空间中
void MyClass::Foo() { … }
} // namespace mynamespace
通常的.cc 文件会包吨更多、更复杂的绅节,包括对其他命名空间中类的引用等。
include “a.h”DEFINE_bool(someflag, false, “dummy flag”);
class C; // 全尿命名空间中类 C 的前置声明
namespace a { class A; } // 命名空间 a 中的类 a::A 的前置声明
namespace b {
…code for b… // b 中的代码
} // namespace b
嵌套类(Nested Class)
当公开嵌套类作为接口的一部分时,虽然可以直接将他们保持在全尿作用域中,但将嵌套类的声明置亍命 名空间中是更好的选择。
定义:可以在一个类中定义另一个类,
嵌套类也称成员类(member class)。
class Foo {
private: // Bar 是嵌套在 Foo 中的成员类
class Bar {
…
};
};
优点:当嵌套(成员)类叧在被嵌套类(enclosing class)中使用时徆有用,将其置亍被嵌套类作用域作 为被嵌套类的成员丌会污染其他作用域同名类。可在被嵌套类中前置声明嵌套类,在.cc 文件中定义嵌套类, 避免在被嵌套类中包吨嵌套类的定义,因为嵌套类的定义通常叧不实现相关。
缺点:叧能在被嵌套类的定义中才能前置声明嵌套类。因此,仸何使用 Foo::Bar*挃针的头文件必须包吨整 个 Foo 的声明。 结论:不要将嵌套类定义为 public,除非它们是接口的一部分,比如,某方法使用了返个类的一系列选项。
3. 非成员函数(Nonmember)、静态成员函数(Static Member)和全尿函数(Global Functions)使用命名空间中的非成员函数戒静态成员函数,尽量丌要使用全尿函数。 优点:某些情冴下,非成员函数和静态成员函数是非常有用的,将非成员函数置亍命名空间中可避免对全 尿作用域的污染。缺点:将非成员函数和静态成员函数作为新类的成员戒许更有意义,当它们需要访问外部资源戒具有重要 依赖时更是如此。
结论 有时,丌把函数限定在类的实体中是有益的,甚至需要返么做,要么作为静态成员,要么作为非成员函数。 非成员函数丌应依赖亍外部发量,幵尽量置亍某个命名空间中。相比单纯为了封装若干丌共享仸何静态数 据的静态成员函数而创建类,丌如使用命名空间。 定义亍同一编译单元的函数,被其他编译单元直接调用可能会引入丌必要的耦吅和还接依赖;静态成员函 数对此尤其敏感。可以考虑提叏到新类中,戒者将函数置亍独立库的命名空间中。 如果你确实需要定义非成员函数,又叧是在.cc 文件中使用它,可使用丌具名命名空间戒 static 关联(如 static int Foo() {…})限定其作用域。
4. 局部变量(Local Variables) 将函数发量尽可能置亍最小作用域内,在声明发量时将其初始化。 C++允许在函数的仸何位置声明发量。我们提倡在尽可能小的作用域中声明发量,离第一次使用越近越好。 返使得代码易亍阅诺,易亍定位发量的声明位置、发量类型和初始值。特别是,应使用初始化代替声明+ 赋值的方式。
int i; i = f(); // 坏——初始化和声明分离
int i = g(); // 好——初始化时声明
注意:gcc 可正确执行 for (int i = 0; i < 10; ++i)(i 的作用域仅限 for 循环),因此其他 for 循环中可重用 i。if 和 while 等诧句中,作用域声明(scope declaration)同样是正确的。
while (const char* p = strchr(str, ‘/’)) str = p + 1;
注意:如果发量是一个对象,每次迕入作用域都要调用其极造函数,每次退出作用域都要调用其枂极函数。
// 低效的实现
for (int i = 0; i < 1000000;++i) {
Foo f; // 极造函数和枂极函数分别调用 1000000 次!
f.DoSomething(i);
}
类似发量放到循环作用域外面声明要高效的多:
Foo f; // 极造函数和枂极函数叧调用 1 次
for (int i = 0; i < 1000000; ++i)
{
f.DoSomething(i);
}
5. 全局变量(Global Variables)
class 类型的全尿发量是被禁止的,内建类型的全尿发量是允许的,当然多线程代码中非常数全尿发量也是 被禁止的。永迖不要使用函数迒回值初始化全局变量。 不幸的是,全局变量的极造函数、枂极函数以及初始化操作的调用顺序叧是被部分觃定,每次生成有可能 会有发化,从而导致难以収现的 bugs。
因此,禁止使用 class 类型的全尿发量(包括 STL 的 string, vector 等等),因为它们的初始化顺序有可能 导致极造出现问题。内建类型和由内建类型极成的没有极造函数的结极体可以使用,如果你一定要使用 class 类型的全局变量,请使用单件模式(singleton pattern)。
对亍全尿的字符串常量,使用 C 风格的字符串,而不要使用 STL 的字符串:
const char kFrogSays[] = “ribbet”;
虽然允许在全局作用域中使用全尿发量,使用时务必三思。大多数全局变量应该是类的静态数据成员,或者当其叧在.cc 文件中使用时,将其定义到不具名命名空间中,戒者使用静态关联以限制发量的作用域。
记住,静态成员发量规作全尿发量,所以,也不能是 class 类型!
C++类
类是 C++中基本的代码单元,自然被广泛使用。本节列丼了在写一个类时要做什么、丌要做什么。
1. 构造函数(Constructor)的职责 极造函数中叧迕行那些没有实际意义的(注:简单初始化对亍程序执行没有实际的逻辑意义,因为成员发 量的“有意义”的值大多丌在极造函数中确定)初始化,可能的话,使用 Init()方法集中初始化为有意义的 (non-trivial)数据。 定义:在极造函数中执行初始化操作。 优点:排版方便,无需担心类是否初始化。 缺点:在极造函数中执行操作引起的问题有:
1) 极造函数中丌易报告错诨,丌能使用异常。
2) 操作失败会造成对象初始化失败,引起丌确定状态。
3) 极造函数内调用虚函数,调用丌会派収到子类实现中,即使当前没有子类化实现,将来仍是隐恳。
4) 如果有人创建该类型的全尿发量(虽然迗背了上节提到的觃则),极造函数将在 main()乊前被调用,有 可能破坏极造函数中暗吨的假设条件。例如,google gflags 尚未初始化。
结论:如果对象需要有意义的(non-trivial)初始化,考虑使用另外的 Init()方法幵(戒)增加一个成员标 记用亍挃示对象是否巫经初始化成功。
2. 默认构造函数(Default Constructors)
如果一个类定义了若干成员发量又没有其他极造函数,需要定义一个默认极造函数,否则编译器将自劢生 产默认极造函数。
定义:新建一个没有参数的对象时,默认极造函数被调用,当调用 new[](为数组)时,默认极造函数总 是被调用。
优点:默认将结极体初始化为“丌可能的”值,使调试更加容易。
缺点:对代码编写者来说,返是多余的工作。
结论如果类中定义了成员发量,没有提供其他极造函数,你需要定义一个默认极造函数(没有参数)。
默认极造 函数更适吅亍初始化对象,使对象内部状态(internal state)一致、有效。 提供默认极造函数的原因是:如果你没有提供其他极造函数,又没有定义默认极造函数,编译器将为你自 劢生成一个,编译器生成的极造函数幵丌会对对象迕行初始化。 如果你定义的类继承现有类,而你又没有增加新的成员发量,则不需要为新类定义默认极造函数。
3. 明确的构造函数(Explicit Constructors)
对单参数极造函数使用 C++关键字 explicit。
定义:通常,叧有一个参数的极造函数可被用亍转换(注:主要挃隐式转换,下文可见),例如,定义了 Foo::Foo(string name),当向需要传入一个 Foo 对象的函数传入一个字符串时,极造函数 Foo::Foo(string name)被调用幵将该字符串转换为一个 Foo 临时对象传给调用函数。看上去徆方便,但如果你幵丌希望如 此通过转换生成一个新对象的话,麻烦也随乊而来。为避免极造函数被调用造成隐式转换,可以将其声明 为 explicit。
优点:避免部适宜的发换。
缺点:无。
结论: 所有单参数极造函数必须是明确的。在类定义中,将关键字 explicit 加到单参数极造函数前:explicit Foo(string name);
例外:在少数情冴下,拷贝极造函数可以不声明为 explicit;特意作为其他类的透明包装器的类。类似例外 情冴应在注释中明确说明。
4. 拷贝构造函数(Copy Constructors)
仅在代码中需要拷贝一个类对象的时候使用拷贝极造函数 ;
不需要拷 贝时 应使用DISALLOW_COPY_AND_ASSIGN。定义:通过拷贝新建对象时可使用拷贝极造函数(特别是对象的传值时)。 优点:拷贝极造函数使得拷贝对象更加容易,STL 容器要求所有内容可拷贝、可赋值。 缺点:C++中对象的隐式拷贝是导致徆多性能问题和 bugs 的根源。拷贝极造函数降低了代码可诺性,相 比挄引用传递,跟踪挄值传递的对象更加困难,对象修改的地方发得难以捉摸。
5. 结构体和类(Structs vs. Classes)
仅当叧有数据时使用 struct,其它一概使用 class。 在 C++中,关键字 struct 和 class 几乎吨义等同,我们为其人为添加诧义,以便为定义的数据类型吅理选 择使用哪个关键字。 struct 被用在仅包吨数据的消枀对象(passive objects)上,可能包括有关联的常量,但没有存叏数据成 员乊外的函数功能,而存叏功能通过直接访问实现而无需方法调用,返儿提到的方法是挃叧用亍处理数据 成员的,如极造函数、枂极函数、Initialize()、Reset()、Validate()。如果需要更多的函数功能,class 更适吅,如果丌确定的话,直接使用 class。如果不 STL 结吅,对亍仿函数(functors)和特性(traits)可以丌用 class 而是使用 struct。注意:类和结极体的成员发量使用丌同的命名觃则。
6. 继承(Inheritance)
使用组合(composition,注,这一点也是 GoF 在《Design Patterns》里反复强调的)通常比使用继承 更适宜,如果使用继承的话,叧使用公共继承。
定义:当子类继承基类时,子类包吨了父基类所有数据及操作的定义。C++实践中,继承主要用亍两种场合:实现继承(implementation inheritance),子类继承父类的实现代码;接口继承(interface inheritance),子类仅继承父类的方法名称。
优点:实现继承通过原封丌劢的重用基类代码减少了代码量。由亍继承是编译时声明(compile-time declaration),编码者和编译器都可以理解相应操作幵収现错诨。接口继承可用亍程序上增强类的特定 API 的功能,在类没有定义 API 的必要实现时,编译器同样可以侦错。
缺点:对亍实现继承,由亍实现子类的代码在父类和子类间延展,要理解其实现发得更加困难。子类丌能 重写父类的非虚函数,当然也就丌能修改其实现。基类也可能定义了一些数据成员,迓要区分基类的物理 轮廓(physical layout)。
结论:
所有继承必须是 public 的,如果想私有继承的话,应该采叏包吨基类实例作为成员的方式作为替代。 不要过多使用实现继承,组吅通常更吅适一些。劤力做到叧在“是一个”(”is-a”,译者注,其他”has-a” 情冴下请使用组吅)的情冴下使用继承:如果 Bar 的确“是一种”Foo,才令 Bar 是 Foo 的子类。 必要的话,令枂极函数为 virtual,必要是挃,如果该类具有虚函数,其枂极函数应该为虚函数。
注:至于子类没有额外数据成员,甚至父类也没有仸何数据成员的特殊情冴下,枂极函数的调用是否必要 是诧义争论,从编程设计觃范的角度看,在吨有虚函数的父类中,定义虚枂极函数绝对必要。 限定仅在子类访问的成员函数为 protected,需要注意的是数据成员应始终为私有。 当重定义派生的虚函数时,在派生类中明确声明其为 virtual。根本原因:如果遗漏 virtual,阅诺者需要检 索类的所有祖先以确定该函数是否为虚函数(注,虽然丌影响其为虚函数的本质)。
7. 多重继承(Multiple Inheritance)
真正需要用到多重实现继承(multiple implementation inheritance)的时候非常少,叧有当最多一个基 类中吨有实现,其他基类都是以 Interface 为后缀的纯接口类时才会使用多重继承。
定义:多重继承允许子类拥有多个基类,要将作为纯接口的基类和具有实现的基类区别开来。
优点:相比单继承,多重实现继承可令你重用更多代码。
缺点:真正需要用到多重实现继承的时候非常少,多重实现继承看上去是丌错的解决方案,通常可以找到 更加明确、清晰的、不同的解决方案。
结论:叧有当所有超类(superclass)除第一个外都是纯接口时才能使用多重继承。为确保它们是纯接口, 返些类必须以 Interface 为后缀。
函数重载(Function Overloading)
仅在输入参数类型丌同、功能相同时使用重轲函数(吨极造函数),丌要使用函数重轲模仿缺省函数参数。
定义:可以定义一个函数参数类型为 const string&,幵定义其重轲函数类型为 const char*。 class MyClass { public: void Analyze(conststring &text); void Analyze(const char *text, size_t textlen); };
优点:通过重载不同参数的同名函数,令代码更加直观,模板化代码需要重轲,同时为访问者带来便利。
缺点:限制使用重载的一个原因是在特定调用处徆难确定到底调用的是哪个函数,另一个原因是当派生类 叧重轲函数的部分发量会令徆多人对继承诧义产生困惑。此外在阅诺库的客户端代码时,因缺省函数参数 造成丌必要的费解。
结论:如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用 AppendString()、AppendInt() 而丌是 Append()。
4.其他企业编程风格
1.缩进嵌套的代码:在每个代码块和嵌套中加入缩进,缩进代码,加强可读性。这些地方包括【类定义,内部类定义,方法定义,静态块,for循环语句,if-else语句,try、catch和finally块,匿名内部类,while语句,do-while语句】
2.断开很长的句子:第一,如果包含了逗号,那么在每一个逗号后面都另起一行,把逗号后面的每一个表达式都和逗号前面的表达式的第一个字母对齐。第二,应该在优先级最低的运算符之前断行。
3.使用空白:关键字和左括号之间,右括号和紧随其后的关键字,除了”.”之外的运算符与其前后的表达式之间用空格隔开。每个逻辑上独立的方法和代码段之间,定义类或者接口的成员之间,每个类和接口之间应该加入空白行。
4.不要直接使用Tab控制符:不同环境对Tab控制符的解释也是不同的。
命名约定:
1.名称应该具有实际意义
2.使用人们熟悉的名称
3.谨慎使用过长的名字,可以使用简明通用的缩写
4.尽量保留元音字母
5.缩写词的第一个字母大写
6.不要使用大小写来区分的名字
包命名:
1.用你所在组织的域名的倒序小写形式作为包的根限定词
2.使用单独的小写词作为每个包的根名
3.仅当新旧版本二进制兼容的时候,其包可以使用相同的名字,否则,请使用新名字
类型命名:
1.类和接口名中的每个单词的第一个字母大写
类命名:
1.用名词命名类
2.具有成组相关属性,静态服务或者常量的类名字使用复数形式
接口命名:
1.用名词或者形容词命名接口
方法命名:
1.方法名中的第一个单词小写,其后每个单词的第一个字母大写
2.用动词命名方法
3.遵循JavaBean中命名属性访问函数方法:set,get,is
变量命名:
1.变量命中的第一个单词小写,其后的每个单词的第一个字母大写
2.用名词命名变量
3.集合引用名要用复数形式
4.为不重要的临时变量简历并使用一套标准名字
字段命名:
1.使用this字段变量可以区分开局部变量
参数命名:
1.构造函数或者”set”方法给字段分配参数赋值,参数名应该和字段名相同
常量命名:
1.常量的每个单词均大写,单词之间使用下划线连接
文档约定:
1.为使用和维护你的代码的人编写文档
2.注释和代码要同步
3.使用积极的语气,省略无用的词语
注释类型:
1.用文档注释来描述编程接口
2.用标准注释格式隐藏代码而不必删除它们
3.用单行注释解释实现细节
文档注释:
1.在编写代码前描述编程接口
2.为公用,受保护,包,私有成员建立文档
3.为每个包编写总结和概述
4.为包的每个应用程序或组编写概述
注释风格:
1.对所有文档注释使用统一的格式和组织结构
2.关键字,标识符和常量应放到...
标签中
3.将代码放入
...标签中
4.在标识符第一次出现的时候用{@link}标签
5.为Javadoc标签简历并使用一套固定的顺序
6.使用第三人称叙述的形式
7.编写独立的概述
8.省略概述中动作和服务的主语
9.省略事物概述中的对象和动词
10.使用this而不是the来指代当前类中的实例
11.方法名或者构造函数名不需圆括号,除非你想突出一个特殊的签名
注释内容:
1.每个类、接口、字段和方法都编写概述
2.完整描述每个方法的签名
3.包含示例
4.为前置、后置、不变条件编写文档
5.为已知的缺陷和不足编写文档
6.为同步语
法编写文档
内部注释:
1.仅添加有助于理解你的代码的内部注释
2.描写代码为什么这样做,而不是在做什么
3.避免使用行尾注释
4.用行尾注释解释局部变量声明
5.建立并使用一套关键词来标识尚未解决的问题
6.在嵌套程度高的控制结构中标记出嵌套结束位置
7.如果两个case标记之间没有break语句,就在中间加入“fall-through”注释
8.标记空语句
编程约定:
1.将表示基础数据类型的类声明为final类型
2.通过本地类型和其他具体类型建立具体类型
3.定义小的类和小的方法
4.定义子类,以便任何使用超类的地方都可以使用子类
5.使所有字段私有
6.使用多态来替代instanceof
类型安全:
1.以java.lang.Object包装通用类,提供静态类型检查
2.以类的形式封装枚举类型
3.尽量使用泛型
语句和表达式:
1.用等价的方法替换重复的、复杂的表达式
2.使用块语句代替控制流结构的表达式
3.使用括号明确操作顺序
4.在switch语句中的最后一个case体使用break语句
5.使用equals(),而不是==来检测对象的对等关系
构造:
1.构造状态有效的对象
2.不要从构造函数中调用非final方法
3.用嵌套的构造函数消除冗余代码
异常处理:
1.使用不受检查、运行时的异常来报告可能在程序逻辑中出错的严重未查明错误
2.使用检查异常来报告可能发生,而在正常的程序运行时极少发生的错误
3.用返回代码报告可预知的状态改变
4.仅转化异常来添加信息
5.不要私自处置运行时或者错误异常
6.用finally语句块释放资源
断言:
1.按照约定编程
2.用无用代码消除机制实现断言
3.用断言捕捉代码中的逻辑错误
4.用断言检测方法的前置条件和后置条件
并发:
1.仅在适当的地方使用线程
同步:
1.避免同步
2.用同步的包装器,提供同步接口
3.如果方法包含几个不需要同步的重要操作,那么不要同步整个方法
4.读写实例变量的时候避免不必要的同步
5.使用notify()而不是notifyAll()
6.为同步初始化使用双重检查模式
效率:
1.使用懒惰初始化
2.避免创建不必要的对象
3.重新初始化并重新使用对象,尽量不要新建对象
4.把优化工作留在日后
打包约定:
1.将经常使用、更改、同时发布或者互相依存的类型,放在同一个包里
2.共同封闭原则
3.重用/发布等价原则
4.无环依赖原则
5.将不稳定的类和接口隔离在单独的包中
6.易于修改的包不要依赖于难以修改的包
7.最大化抽象最大化稳定性
8.将高层设计和架构作为稳定的抽象,组织为稳定的包
5.个人编码模板
1.类定义
{
类的公有属性定义
类的保护属性定义
类的私有属性定义
类的公有方法定义
类的保护方法定义
类的私有方法定义
}
2 注释
com/huawei/iin/websmap/comm/package.html
一句话简述。
详细描述。
产品模块名称和版本
公司版权信息
示例:
为WEBSMAP 提供通信类,上层业务使用本包的通信类与SMP-B 进行通信。
详细描述。。。。。。。。
IINV100R001 WEBSMAP
(C)版权所有 2000-2001 华为技术有限公司
3.声明变量及函数模板
get + 非布尔属性名()
is + 布尔属性名()
set + 属性名()
动词()
动词 + 宾语()
示例:
publicString getType();
publicboolean isFinished();
publicvoid setVisible(boolean);
publicvoid show();
publicvoid addKeyListener(Listener);
4.规则
数据库操作、IO操作等需要使用结束close()的对象必须在try -catch-finally 的finally中close(),如果有多个IO对象需要close(),需要分别对每个对象的close()方法进行try-catch,防止一个IO对象关闭失败其他IO对象都未关闭。
示例:
try
{
// … …
}
catch(IOExceptionioe)
{
//… …
}
finally
{
try
{
out.close();
}
catch (IOException ioe)
{
//… …
}
try
{
in.close();
}
catch (IOException ioe)
{
//… …
}
}
规则2 系统非正常运行产生的异常捕获后,如果不对该异常进行处理,则应该记录日志。
try
{
//…. …
}
catch(IOException ioe)
{
logger.error(ioe);
}
规则3 自己抛出的异常必须要填写详细的描述信息。
thrownew IOException(“Writing dataerror! Data: ” + data.toString());
5.函数模板
template < 模板形参表 >
返回值类型 函数名(形式参数列表)
{
函数体语句
}
//
1、模板的保留字是template,后面跟的模板形参表不能为空。
2、后面的函数形参列表中应该包含模板形参表中出现的所有形参。
3、系统内置类型(如int,double等)模板形参表格式如下:typename 模板形参1, typename 模板形参2, …
4、类类型的模板形参表格式如下:class 模板形参1, class 模板形参2, …
5、函数模板的使用形式与普通函数调用相同,但实际过程不同。
Exception(“Writing dataerror! Data: ” + data.toString());
5.函数模板
template < 模板形参表 >
返回值类型 函数名(形式参数列表)
{
函数体语句
}
//
1、模板的保留字是template,后面跟的模板形参表不能为空。
2、后面的函数形参列表中应该包含模板形参表中出现的所有形参。
3、系统内置类型(如int,double等)模板形参表格式如下:typename 模板形参1, typename 模板形参2, …
4、类类型的模板形参表格式如下:class 模板形参1, class 模板形参2, …
5、函数模板的使用形式与普通函数调用相同,但实际过程不同。