通用代码编码规范
左锋义【草】2010-2-1 锋恩科学技术发展有限公司
1 项目信息格式
l 版权信息
l 文件名称,标识符,摘要
l 项目名称,当前版本号,作者/修改者,完成日期
l 版本历史信息
2 文件及文件夹结构
l 定义文件开头处的版权和版本声明
l 对一些头文件的引用
l 程序的实现体
l 按模块命名文件夹名称
l 按功能命名文件夹名称
l 文件夹名称使用具有明确含义的英文单词按驼峰命名规则命名
l 按功能命名文件名称
l 文件名称使用具有明确含义的英文单词按驼峰命名规则命名
l 文件名要体现出min-length and max-information的原则
3.1 空行
l 在每个类声明之后、每个函数定义结束之后都要加空行
l 在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔
l 关键字之后要留空格 象const、virtual、inline、case 等关键字之后至少要留一个空格,否则无法辨析关键字 象if、for、while等关键字之后应留一个空格再跟左括号‘(’,以突出关键字
l 函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别
l ‘(’向后紧跟,‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格
l ‘,’之后要留空格,如Function(x, y, z) 如果‘;’不是一行的结束符号,其后要留空格
l 赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格
l 一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不加空格
l 象“[]”、“.”、“->”这类操作符前后不加空格
l 对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉空格
l 一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释
l if、for、while、do等语句自占一行,执行语句不得紧跟其后 不论执行语句有多少都要加{} 这样可以防止书写失误
l 尽可能在定义变量的同时初始化该变量;尽可能在使用变量的地方之前定义变量
l 程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐
l { }之内的代码块在‘{’右边数格处左对齐
3.4 行拆分
l 代码行最大长度宜控制在70至80个字符以内 代码行不要过长,否则眼睛看不过来,也不便于打印
l 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符) 拆分出的新行要进行适当的缩进,使排版整齐,语句可读
l 将private类型的数据写在前面,而将public类型的函数写在后面,采用这种版式的程序主张类的设计“以数据为中心”,重点关注类的内部结构
l 将public类型的函数写在前面,而将private类型的数据写在后面,采用这种版式的程序主张类的设计“以行为为中心”,重点关注的是类应该提供什么样的接口(或服务)
l 建议采用“以行为为中心”的书写方式,即首先考虑类应该提供什么样的函数 这是很多人的经验——“这样做不仅让自己在设计类时思路清晰,而且方便别人阅读 因为用户最关心的是接口,谁愿意先看到一堆私有数据成员!”
4 注释
l 注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱 注释的花样要少
l 如果代码本来就是清楚的,则不必加注释。否则多此一举,令人厌烦
l 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除
l 注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害
l 尽量避免在注释中使用缩写,特别是不常用缩写
l 注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方
l 当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读
l 虽然注释有助于理解代码,但注意不可过多地使用注释
5 类型定义
5.1 共性规则
l 标识符应当直观且可以拼读,可望文知意,不必进行“解码”;即标识符应具有自解释性
l 标识符最好采用英文单词或其组合,便于记忆和阅读 切忌使用汉语拼音来命名 程序中的英文单词一般不会太复杂,用词应当准确
l 标识符的长度应当符合“min-length && max-information”原则
l 命名规则尽量与所采用的操作系统或开发工具的风格保持一致
l 程序中不要出现仅靠大小写区分的相似的标识符
l 程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解
l 变量的名字应当使用“名词”或者“形容词+名词”
l 全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组) 类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身
l 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等
l 尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)
5.2 简单的Windows应用程序命名规则
l 类名和函数名用大写字母开头的单词组合而成
l 变量和参数用小写字母开头的单词组合而成
l 常量全用大写的字母,用下划线分割单词
l 静态变量加前缀s_(表示static)
l 如果不得已需要全局变量,则使全局变量加前缀g_(表示global)
l 类的数据成员加前缀m_(表示member),这样可以避免数据成员与成员函数的参数同名
l 为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为各种标识符加上能反映软件性质的前缀
6 运算符
l 如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级
7 表达式
l 不要编写太复杂的复合表达式
l 不要有多用途的复合表达式
l 不要把程序中的复合表达式与“真正的数学表达式”混淆
8 逻辑控制
8.1 if语句
l 布尔变量与零值比较
不可将布尔变量直接与TRUE、FALSE或者1、0进行比较;而是直接使用TRUE、FALSE
l 整型变量与零值比较
应当将整型变量用“==”或“!=”直接与0比较
l 浮点变量与零值比较
不可将浮点变量用“==”或“!=”与任何数字比较;而是使用 “<=”“>=”“<”“>”与某一精度比较
l 指针变量与零值比较
应当将指针变量用“==”或“!=”与NULL比较
l 变量与常量比较
l else子句
不要忘记else子句,以防止忘记要处理的情况,这样有利于测试
8.2 循环语句
l 在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数
l 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面
l 不可在for 循环体内修改循环变量,防止for 循环失去控制
l 建议for语句的循环控制变量的取值采用“半开半闭区间”写法
8.3 switch语句
l 每个case语句的结尾不要忘了加break,否则将导致多个分支重叠(除非有意使多个分支重叠)
l 不要忘记最后那个default分支,即使程序真的不需要default处理,也应该保留语句default : break; 这样做并非多此一举,而是为了防止别人误以为你忘了default处理
l 不要在case分支中定义变量;如果定义变量,应在所有case分支之前定义
l 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串
l 通用的常量定义在一个单独的文件中
8.5 参数声明
l 参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字 如果函数没有参数,则用void填充
l 参数命名要恰当,顺序要合理
l 如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改
l 如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率
l 避免函数有太多的参数,参数个数尽量控制在7个左右 如果参数太多,在使用时容易将参数类型或顺序搞错
l 尽量不要使用类型和数目不确定的参数
l 参数缺省值只能出现在函数的声明中,而不能出现在定义体中
l 如果函数有多个参数,参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样
l 不要省略返回值的类型
l 函数名字与返回值类型在语义上不可冲突
l 不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回
l 有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值
l 如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率 而有些场合只能用“值传递”而不能用“引用传递”,否则会出错
l 函数的功能要单一,不要设计多用途的函数
l 函数体的规模要小,尽量控制在50行代码之内
l 尽量避免函数带有“记忆”功能 相同的输入应当产生相同的输出
l 带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态” 这样的函数既不易理解又不利于测试和维护
l 不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等
l 用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况
l 在函数内部要有防止错误处理程序
l 在函数内部要有异常处理程序
l 在函数体的“入口处”,对参数的有效性进行检查 很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误
l 在函数体的“出口处”,对return语句的正确性和效率进行检查 如果函数有返回值,那么函数的“出口处”是return语句 我们不要轻视return语句 如果return语句写得不好,函数要么出错,要么效率低下
注意事项如下:
(1)return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁
(2)要搞清楚返回的究竟是“值”、“指针”还是“引用”
(3)如果函数返回值是一个对象,要考虑return语句的效率
l 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)
l 不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)
l 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)
8.9 内存错误及其对策
l 内存分配未成功,却使用了它。常用解决办法是,在使用内存之前检查指针是否为NULL
l 内存分配虽然成功,但是尚未初始化就引用它。犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有;所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦
l 内存分配成功并且已经初始化,但操作越过了内存的边界
l 在使用数组时经常发生下标“多1”或者“少1”的操作,特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界
l 忘记了释放内存,造成内存泄露。含有这种错误的函数每被调用一次就丢失一块内存,刚开始时系统的内存充足,你看不到错误,终有一次程序突然死掉,系统出现提示:内存耗尽。
l 释放了内存却继续使用它。(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。(2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁
8.10 类
8.10.1 重载、覆盖、隐藏
l 当心隐式类型转换导致重载函数产生二义性
l 区别成员函数的重载、覆盖(override)与隐藏
重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无
覆盖特征:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字
隐藏特征:
(1)如果派生类的函数与基类的函数同名,但是参数不同 此时,不论有无virtual关键字,基类的函数将被隐藏
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字
8.10.2 inline内联
l 关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用 inline是实现的关键字,而不是声明的关键字
l 如果函数体内的代码比较长,那么不能使用inline
l 如果函数体内出现循环,那么不能使用inline
l 不要随便地将构造函数和析构函数的定义体放在类声明中
8.10.3 成员初始化列表
l 如果类存在继承关系,派生类必须在其初始化列表里调用基类的构造函数
l 类的const常量只能在初始化列表里被初始化,因为它不能在函数体内用赋值的方式来初始化
l 类的数据成员的初始化可以采用初始化列表或函数体内赋值两种方式,这两种方式的效率不完全相同
l 非内部数据类型的成员对象应当采用函数体内赋值方式初始化,以获取更高的效率
l 成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定 这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表 如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序
8.10.4 构造函数
l 拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用
l 派生类的构造函数应在其初始化表里调用基类的构造函数
l 基类与派生类的析构函数应该为虚(即加virtual关键字)
l 在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值
8.10.5 继承
l 如果类A和类B毫不相关,不可以为了使B的功能更多些而让B继承A的功能和属性
l 若在逻辑上B是A的“一种”(is a),则允许B继承A的功能和属性 即若在逻辑上B是A的“一种”,并且A的所有功能和属性对B而言都有意义,则允许B继承A的功能和属性(继承关系)
l 若在逻辑上A是B的“一部分”(has a),则不允许B从A派生,而是要用A和其它东西组合出B(聚合关系)
9 效率
l 不要一味地追求程序的效率,应当在满足正确性、可靠性、健壮性、可读性等质量因素的前提下,设法提高程序的效率
l 以提高程序的全局效率为主,提高局部效率为辅
l 在优化程序的效率时,应当先找出限制效率的“瓶颈”,不要在无关紧要之处优化
l 先优化数据结构和算法,再优化执行代码
l 有时候时间效率和空间效率可能对立,此时应当分析那个更重要,作出适当的折衷
l 不要追求紧凑的代码,因为紧凑的代码并不能产生高效的机器码
l 避免编写技巧性很高代码
l 不要设计面面俱到、非常灵活的数据结构
l 如果原有的代码质量比较好,尽量复用它 但是不要修补很差劲的代码,应当重新编写
l 尽量使用标准库函数,不要“发明”已经存在的库函数
l 尽量不要使用与具体硬件或软件环境关系密切的变量
l 把编译器的选择项设置为最严格状态