一.目的
良好的编程风格是提高程序可靠性非常重要的手段,也是大型项目多人合作开发的技术基础。为了提高C/C++源程序的质量和可维护性,通过本规范定义来避免不好的编程风格,增强程序的易读性,便于自己及他人阅读。本规范的内容包括:排版、注释、标识符命名、可读性、变量、结构、函数、过程、可测性、质量保证等。
二.排版
1、相对独立的程序块之间要加空行分隔,在每个类声明之后、每个函数定义结束之后都要加空行,变量声明与执行代码之间加空行分隔(C++代码中变量声明与使用合在一起的可不加空行)。
2、函数或过程的开始、类或结构的定义、枚举的定义及循环、判断等语句中以及折行的代码都要采用缩进风格。每次缩进一个制表符宽度,或者缩进2个或4个空格宽度,代码中应统一使用制表符或空格来进行缩进,不可混用,否则在使用不同的源代码阅读工具时制表符将因为用户设置的不同而扩展为不同的宽度,造成显示混乱。制表符具有占用字节少、易定位、不容易错位(使用空格缩进容易出现多一个或少一个空格的现象,看上去不明显,但却是错位的)、扩展宽度可设置的优点,推荐使用。
3、较长的语句(>80字符,或以编辑屏幕可见范围为准)要分成多行书写(折行),长表达式要在低优先级操作符处划分新行,操作符放在新行之首。
4、循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分,长表达式要在低优先级操作符处划分新行,操作符放在新行之首。
5、若函数或过程中的参数列表较长,则要进行适当的划分。
6、特殊情况下,长代码的折行可采用灵活的方式,宗旨是使代码易读。
7、划分出的新行要进行适当的缩进,以便识别。将一行代码划分为多行时,划分出的新行的缩进量要一致。
8、一行代码只做一件事情,例如只定义一个变量,或只写一条语句,这样的代码容易阅读,并且方便于写注释。不允许把多个短语句写在一行中,特殊代码(例如宏)除外。
9、if、for、do、while、case、switch、default、continue、goto、extern、return、typedef等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要用大括号'{'和'}'括起来。
10、C/C++语言是用大括号'{'和'}'界定一段程序块的,编写程序块时'{'和'}'应各独占一行并且位于同一列,同时与引用它们的语句左对齐。空函数或简单的内联函数除外。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。
11、标识符和操作符之间加适当的空格,使代码错落有致,容易阅读。一元运算符(++、--、求址符&、求值符*、求非值符!、求反值符~、表示正负的+、-等)紧贴操作数,不加空格;二元运算符(算术运算符+、-、*、/、%、位运算符&、|、^、移位符<<、>>、
赋值符+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=、比较符&&、||等)与前后的操作数之间各保留一个空格;逗号、分号紧跟前面的标识符,后面保留一个空格;类、结构成员访问符.、->等前后不加空格;声明函数和单个变量时,类型修饰符&和*靠近类型名,与函数名或变量名之间留一个空格;在一条语句中声明多个变量时,类型修饰符&和*靠近变量名,避免阅读代码时产生误解。
12、修改代码时排版风格应与原代码风格保持一致,或者彻底修改整份代码的风格(代码基线后不提倡这样大改)。
三.注释
1、源程序有效注释量必须在20%以上。注释应尽量采用C++的注释风格,即使用// 注释。
2、在代码的逻辑含义的层次上进行注释。注释总是加在程序的需要一个概括性说明或不易理解或易理解错的地方。注释语言应该简练、易懂而又含义准确,避免二义性;所采用的语种首选是中文,如有输入困难、编译环境限制或特殊需求也可采用英文。
3、注释应与其描述的代码相近,对代码的注释应放在其上方或右方(针对单条语句的注释)相邻位置,不可放在下方,避免在一行代码或表达式中间使用注释,如放于上方则需与其上面的代码用空行隔开(较紧凑的代码除外)。
4、正确命名变量、结构、函数、过程以及合理地组织代码地结构,使代码成为自注释的,可减少不必要的注释。
5、数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右方。
6、在常量名字(或有宏机制的语言中的宏)声明后应对该名字作适当注释,注释说明的要点是:被保存值的含义(必须);合法取值的范围(可选)。
7、函数注释包括:函数功能描述(必须,除非函数非常简单明了),输入、输出等,复杂的函数需要加上变量用途说明。
8、说明文件(.h、.inc、.def、.cfg等)头部应进行注释,注释内容包括:作者名称、创建时间、模块用途等,复杂的算法需要加上流程说明,与其他文件有密切依赖关系的要进行说明。
9、程序中注释包括:修改时间和作者、方便理解的注释等。
10、当if、for等语句后的代码块比较长,特别是有多重嵌套时,应当在一些段落结束处的\符号后加注释,简单标明其对应起始位置,例如// end of if (...) 、// end of while (...),以便阅读。
11、边写代码边注释。代码比较复杂时,先写注释标识出程序的处理过程,然后再对每一个逻辑处理过程进行语句书写。修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
四.标识符命名
1、标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。命名中使用特殊约定或缩写时,要有注释说明。
2、命名规范必须与所使用的系统风格保持一致,UNIX系统下采用全小写加下划线的形式,Windows系统下采用大小写混排的形式。自己特有的命名风格,要自始至终保持一致,不可来回变化,且首先要符合项目组或产品组的命名规则。
3、对于变量命名,尽量使用完整的单词而不是缩写;禁止取单个字符(如i、j、k...),但i、j、k作局部循环变量、p作为指针、x、y作为坐标变量以及在数学公式中合理使用单字符变量是允许的,建议除了要有具体含义外,还能表明其变量类型、数据类型、有效范围等(采用匈牙利命名法或简化的匈牙利命名法)。静态变量要加前缀s_(表示static),全局变量要加前缀g_(表示global),类的数据成员要加前缀m_(表示member),常量要加前缀c_(表示const)或使用全大写加下划线的形式。
4、应尽量使用const定义常量而不使用宏定义常量。
5、程序中不要使用仅靠大小写区分的相似的标识符;也不要使用标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但容易使人误解。
6、复用一个变量时不要出于不同的目的,因为这样容易把读代码的人搞糊涂。
7、变量的名字应当使用“名词”或者“形容词+名词”。全局函数的名字应当使用“动词”、“动词+名词”或者“动词+副词(动词短语)”的形式。类的成员函数(如果可以的话)应当只使用“动词”,被省略掉的名词就是类本身。
8、用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。常用反义词组有: add/delete, add/remove, begin/end, create/destroy, cut/paste, first/last, get/put, get/set, increase/decrease, increment/decrement, insert/delete, insert/remove, lock/unlock,
min/max,
old/new,
open/close,
previous/next,
save/load,
send/receive, set/unset, show/hide, source/destination, source/target, start/finish, start/stop, up/down。
9、在Windows系统下编程时,类名、结构名用首字母大写的单词组合而成,结构名也可以用全大写单词加下划线分隔组成。类名以大写字母C为前缀,结构名以Stru为后缀,以区分函数名;全大写单词加下划线的结构名要以_STRU为后缀(或使用其他能体现结构含义的前/后缀),以区分全大写单词加下划线组成的常量名;函数名用首字母大写的单词组合而成。
五.可读性
1、避免使用不易理解的数字,用有意义的宏或常量标识符来替代。涉及物理状态或者含有物理意义的常量必须用有意义的枚举或宏来代替。被多处使用的数字应替换为宏以便修改。
2、注意运算符的优先级,表达式比较复杂时用括号明确表达式的操作顺序,避免使用默认优先级让阅读者容易产生误解。
3、程序中关系较为紧密的代码应尽可能相邻,结构成员赋值代码不应被其它代码隔开。不要使用难懂的技巧性很高的语句,除非很有必要。
六.变量、结构
1、去掉没有必要的公共变量以降低模块间的耦合度。定义公共变量时,应对其含义、作用、取值范围进行注释说明,若有必要还应说明与其它变量的关系。明确公共变量与操作此变量的函数或过程的关系,如:被哪个函数访问、修改等。对公共变量赋值应避免不合理的值或数组下标越界。避免局部变量与公共变量同名。
2、严禁使用未经初始化的变量作为右值,为避免不同的编译器在变量初始化上的差异,对变量,尤其是指针,在使用前将其初始化,尽可能在定义变量的同时初始化该变量(就近原则)。
3、使用可移植的数据类型,尽量不要使用与具体硬件或软件环境关系密切的变量。
4、结构的功能要单一,是针对一种事务的抽象。
说明:设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。如果两个结构间关系比较复杂、密切,应将它们合并为一个结构。
5、结构中元素的个数应适中。若结构中元素个数过多可考虑依据某种原则把元素组成不同的子结构,以减少原结构中元素的个数。
6、仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间(针对位域),并减少引起误用现象。考虑存取效率,结构元素尽量位于字节对齐的位置上,例如32位机上,如果结构成员在4字节倍数的位置上,CPU一个指令就可以存取,否则要耗
费更多的CPU指令。
7、结构的设计要尽量考虑向前兼容和以后的版本升级,并为某些未来可能的应用保留余地(如预留一些空间等)。
8、对自定义数据类型进行恰当命名,使它成为自描述性的,以提高代码可读性。注意其命名方式在同一产品中的统一。
9、当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的字节顺序、使用的位域及字节对齐等问题。
10、定义结构体成员变量应尽量使用基本的数据类型,如果使用结构体、类或指针为成员变量,应定义执行初始化的构造函数、拷贝构造函数和重载赋值操作符。声明不能自行初始化的结构或类的变量时,应及时初始化该变量。
11、定义以行为为主的类时,将public函数、方法声明写在前面,将private数据声明写在后面;定义以数据为主的结构体时,将数据声明写在前面,将辅助函数(执行初始化的构造函数、拷贝构造函数、赋值函数等)写在后面。
12、在类和结构声明中,复用public、protected、private关键字,将函数声明和成员变量声明分开。
七.函数、过程
1、对所调用函数的错误返回码要仔细、全面地处理。
2、编写可重入函数时,应注意局部变量的使用(例如编写C/C++语言的可重入函数时,应使用auto即缺省态局部变量或寄存器变量)。
说明:编写C/C++语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。
3、编写可重入函数时,若使用全局变量,则应通过设置互斥量、关中断、信号量(即P、V操作)等手段对其加以保护。
4、在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责。
说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。
5、函数的规模尽量限制在200行以内,一个函数仅完成一件功能,为简单功能编写函数,不要设计多用途面面俱到的函数。
6、函数的功能应该是可以预测的,也就是只要输入的数据相同就应产生同样的输出。
7、如果多段代码重复做同一件事情,应考虑函数重构。
8、设计高扇入、合理扇出(小于7)的函数,改进模块中函数的结构,降低函数间的耦合度,并提高函数的独立性以及代码可读性、效率和可维护性。
9、在多任务操作系统的环境下编程,要注意函数可重入性的考虑。
10、为了防止头文件被重复包含,应当用#include guards(#ifndef xx_h / #define xx_h / #endif)结构产生预处理块。#include应一律出现在文件开头, 如果是头文件则放在#include guards之后。尽量避免头文件对头文件的依赖,如果某个头文件中需要用到某个类的引用或指针,那么只需要一个forward declaration(例如class CMyClass;)就足够了。源程序应该首先#include对应的头文件 确保每个头文件都可以单独被include,而不需要额外的头文件依赖。
11、使用宏定义表达式时,要使用完备的括号。将宏定义的多条表达式放在大括号中,避免if、for等语句书写不规范造成代码漏执行。使用宏时,避免执行使参数值发生变化的操作,例如增1操作。
12、在主要的函数和过程的入口、出口及大的分支记录最低级别(DEBUGGING)的调试日志;在模块间互发消息时,发送方和接受方都要记录信息级别(INFORMATION)的日志;在调用系统函数发生错误处记录错误级别(ERROR)的日志;在与程序性能关系密切的函数调用处记录性能日志;进行多线程编程时在日志中记录线程ID以便定位错误。
八.可测性
1、在同一项目组或产品组内,调测打印出的信息串的格式要有统一的形式。信息串中至少要有所在模块名(或源文件名)及行号。
2、使用断言来发现软件问题,提高代码可测性,用断言确认函数的参数的有效性。
九.质量保证
1、代码质量保证优先原则:
1)正确性,指程序要实现设计要求的功能。
2)稳定性、安全性,指程序稳定、可靠、安全。
3)可测试性,指程序要具有良好的可测试性。
4)规范/可读性,指程序书写风格、命名规则等要符合规范。
5)全局效率,指软件系统的整体效率。
6)局部效率,指某个模块/子模块/函数的本身效率。
7)个人表达方式/个人方便性,指个人编程习惯。
2、防止引用已经释放的内存空间。
3、在每个要用到内存申请的模块文件头部定义#define new DEBUG_NEW宏以跟踪内存的申请和释放。过程/函数中分配的内存,在过程/函数退出之前要释放;过程/函数中申请的(为打开文件而使用的)文件句柄,在过程/函数退出之前要关闭。特殊需要保留的资源,应注释说明该资源将在什么时候释放。用free或delete释放了内存之后,如果指针生命周期未结束,应立即将指针设置为NULL,防止产生“野指针”。
4、系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用;要对加载到系统中的数据进行一致性检查。
5、使用断言(assert)来检测是否有未满足的前提条件和编程错误。
6、防止内存操作越界。
7、仅仅在没有适当或是有效的方法告知调用者错误情况时使用异常,如果可能的话,当函数抛出异常时,它必须没有任何效果,程序状态应该恢复到函数调用之前,仿佛没有执行函数一样。不要将那些拷贝构造函数会抛出异常的类型作为异常抛出,也不要在析构函数中抛出异常。
8、有可能的话,if语句尽量加上else分支,对没有else分支的语句要小心对待;switch语句必须有default分支。
因篇幅问题不能全部显示,请点此查看更多更全内容