软件开发代码规范
历史记录
版本号
V1.0.0
日期
制/修订人
内容描述
目录
1 规范制订说明 .......................................................................................................................................................... 1
1.1 前言 ............................................................................................................................................................... 1
1.2 代码总体原则 ...............................................................................................................................................1
1.3 术语定义 ....................................................................................................................................................... 1
2 头文件...................................................................................................................................................................... 2
2.1 背景 ............................................................................................................................................................... 2
2.2 术语定义 ....................................................................................................................................................... 2
3 函数 .......................................................................................................................................................................... 7
4 标识符命名与定义 ................................................................................................................................................16
4.1 通用命名规则 .............................................................................................................................................16
4.2 文件命名规则 .............................................................................................................................................18
4.3 变量命名规则 .............................................................................................................................................18
4.4 函数命名规则 .............................................................................................................................................19
4.5 宏的命名规则 .............................................................................................................................................19
5 变量 ........................................................................................................................................................................ 20
6 宏、常量 ................................................................................................................................................................ 23
7 质量保证 ................................................................................................................................................................ 26
8 程序效率 ................................................................................................................................................................ 30
9 注释 ........................................................................................................................................................................ 32
10 排版与格式.......................................................................................................................................................... 37
11 表达式.................................................................................................................................................................. 39
12 代码编辑、编译..................................................................................................................................................42
13 可测性.................................................................................................................................................................. 42
14 安全性.................................................................................................................................................................. 43
14.1 字符串操作安全.......................................................................................................................................44
14.2 整数安全 ...................................................................................................................................................45
14.3 格式化输出安全.......................................................................................................................................48
14.4 文件 I/O 安全............................................................................................................................................50
14.5 其它 ........................................................................................................................................................... 51
15 单元测试 .............................................................................................................................................................. 52
16 可移植性 .............................................................................................................................................................. 52
17 版本说明 .............................................................................................................................................................. 53
18 业界编程规范 ......................................................................................................................................................53
第 1页/共 53页
1 规范制订说明
1.1 前言
为了提高产品代码质量,指导软件开发人员编写出简洁、可维护、可靠、可测试、高效、可移植的代码,
编写了本规范。在本规范的最后,列出了一些业界比较优秀的编程规范,作为延伸阅读参考材料。
1.2 代码总体原则
1、清晰第一
清晰性是易于维护、易于重构的程序必需具备的特征。代码首先是给人读的,好的代码应当可以像文章一
样发声朗诵出来。
目前软件维护期成本占整个生命周期成本的 40%~90%。根据业界经验,维护期变更代码的成本,小型系
统是开发期的 5 倍,大型系统(100 万行代码以上)可以达到 100 倍。业界的调查指出,开发组平均大约
一半的人力用于弥补过去的错误,而不是添加新的功能来帮助公司提高竞争力。
“程序必须为阅读它的人而编写,只是顺便用于机器执行。”——Harold Abelson 和 Gerald Jay Sussman
“编写程序应该以人为本,计算机第二。”——Steve McConnell
本规范通过后文中的原则(如头优秀的代码可以自我解释,不通过注释即可轻易读懂/头文件中适合放置接
口的声明,不适合放置实现/除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音)、规则(如
防止局部变量与全局变量同名)等说明清晰的重要性。
一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。
2、简洁为美
简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。写的代码越多,
意味着出错的地方越多,也就意味着代码的可靠性越低。因此,我们提倡大家通过编写简洁明了的代码来
提升代码可靠性。
废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。
本规范通过后文中的原则(如文件应当职责单一/一个函数仅完成一件功能)、规则(重复代码应该尽可能
提炼成函数/避免函数过长,新增函数不超过 50 行)等说明简洁的重要性。
3、选择合适的风格,与代码原有风格保持一致
产品所有人共同分享同一种风格所带来的好处,远远超出为了统一而付出的代价。在公司已有编码规范的
指导下,审慎地编排代码以使代码尽可能清晰,是一项非常重要的技能。如果重构/修改其他风格的代码时,
比较明智的做法是根据现有代码的现有风格继续编写代码,或者使用格式转换工具进行转换成公司内部风
格。
1.3 术语定义
原则:编程时必须坚持的指导思想。
规则:编程时强制必须遵守的约定。
建议:编程时必须加以考虑的约定。
说明:对此原则/规则/建议进行必要的解释。
示例:对此原则/规则/建议从正、反两个方面给出例子。
延伸阅读材料:建议进一步阅读的参考材料。
第 2页/共 53页
2 头文件
2.1 背景
对于 C 语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根因,
不合理的头文件实际上不合理的设计。
2.2 术语定义
依赖:本章节特指编译依赖。若 x.h 包含了 y.h,则称作 x 依赖 y。依赖关系会进行传导,如 x.h 包含 y.h,
而 y.h 又包含了 z.h,则 x 通过 y 依赖了 z。依赖将导致编译时间的上升。虽然依赖是不可避免的,也是必
须的,但是不良的设计会导致整个系统的依赖关系无比复杂,使得任意一个文件的修改都要重新编译整个
系统,导致编译时间巨幅上升。
在一个设计良好的系统中,修改一个文件,只需要重新编译数个,甚至是一个文件。
某产品曾经做过一个实验,把所有函数的实现通过工具注释掉,其编译时间只减少了不到 10%,究其原因,
在于 A 包含 B,B 包含 C,C 包含 D,最终几乎每一个源文件都包含了项目组所有的头文件,从而导致绝
大部分编译时间都花在解析头文件上。
某产品更有一个“优秀实践”,用于将.c 文件通过工具合并成一个比较大的.c 文件,从而大幅度提高编译
效率。其根本原因还是在于通过合并.c 文件减少了头文件解析次数。但是,这样的“优秀实践” 是对合
理划分.c 文件的一种破坏。
《google C++ Style Guide》1.2 头文件依赖 章节也给出了类似的阐述:
若包含了头文件 aa.h,则就引入了新的依赖:一旦 aa.h 被修改,任何直接和间接包含 aa.h 代码都会被重新
编译。如果 aa.h 又包含了其他头文件如 bb.h,那么 bb.h 的任何改变都将导致所有包含了 aa.h 的代码被重
新编译,在敏捷开发方式下,代码会被频繁构建,漫长的编译时间将极大的阻碍频繁构建。因此,我们倾
向于减少包含头文件,尤其是在头文件中包含头文件,以控制改动代码后的编译时间。
合理的头文件划分体现了系统设计的思想,但是从编程规范的角度看,仍然有一些通用的方法,用来合理
规划头文件。本章节介绍的一些方法,对于合理规划头文件会有一定的帮助。
原则 1.1 头文件中适合放置接口的声明,不适合放置实现。
说明:头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供
的函数声明、宏定义、类型定义等。
内部使用的函数(相当于类的私有方法)声明不应放在头文件中。内部使用的宏、枚举、结构定义不应放
入头文件中。
变量定义不应放在头文件中,应放在.c 文件中。
变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现
细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。即使必须
使用全局变量,也只应当在.c 中定义全局变量,在.h 中仅声明变量为全局的。
延伸阅读材料:《C 语言接口与实现》(David R. Hanson 著 傅蓉 周鹏 张昆琪 权威 译 机械工业出版社
2004 年 1 月)(英文版: "C Interfaces and Implementations")
原则 1.2 头文件应当职责单一。
说明:头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大, 职
责过多,再加上循环依赖的问题,可能导致为了在.c 中使用一个宏,而包含十几个头文件。
第 3页/共 53页
示例:如下是某平台定义 WORD 类型的头文件:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
…
typedef unsigned short WORD;
…
这个头文件不但定义了基本数据类型 WORD,还包含了 stdio.h syslib.h 等等不常用的头文件。如果工程中
有 10000 个源文件,而其中 100 个源文件使用了 stdio.h 的 printf,由于上述头文件的职责过于庞大,而 WORD
又是每一个文件必须包含的,从而导致 stdio.h/syslib.h 等可能被不必要的展开了 9900 次,大大增加了工程
的编译时间。
原则 1.3 头文件应向稳定的方向包含。
说明:头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的
模块发生变化时,不会影响(编译)稳定的模块。
就我们的产品来说,依赖的方向应该是:产品依赖于平台,平台依赖于标准库。某产品线平台的代码中已
经包含了产品的头文件,导致平台无法单独编译、发布和测试,是一个非常糟糕的反例。
除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口,这样任何一个模块的内
部实现更改都不需要重新编译另外一个模块。在这里,我们假设接口本身是最稳定的。
延伸阅读材料:编者推荐开发人员使用“依赖倒置”原则,即由使用者制定接口,服务提供者实现接口,
更具体的描述可以参见《敏捷软件开发:原则、模式与实践》(Robert C.Martin 著 邓辉 译 清华大学出
版社 2003 年 9 月) 的第二部分“敏捷设计”章节。
规则 1.1 每一个.c 文件应有一个同名.h 文件,用于声明需要对外公开的接口。
说明:如果一个.c 文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如 main 函
数所在的文件。
现有某些产品中,习惯一个.c 文件对应两个头文件,一个用于存放对外公开的接口,一个用于存放内部需
要用到的定义、声明等,以控制.c 文件的代码行数。编者不提倡这种风格。这种风格的根源在于源文件过
第 4页/共 53页
大,应首先考虑拆分.c 文件,使之不至于太大。另外,一旦把私有定义、声明放到独立的头文件中,就无
法从技术上避免别人 include 之,难以保证这些定义最后真的只是私有的。
本规则反过来并不一定成立。有些特别简单的头文件,如命令 ID 定义头文件,不需要有对应的.c 存在。
示例:对于如下场景,如在一个.c 中存在函数调用关系:
void foo()
{
bar();
}
void bar()
{
Do something;
}
必须在 foo 之前声明 bar,否则会导致编译错误。
这一类的函数声明,应当在.c 的头部声明,并声明为 static 的,如下:
static void bar();
void foo()
{
bar();
}
void bar()
{
Do something;
}
规则 1.2 禁止头文件循环依赖。
说明:头文件循环依赖,指 a.h 包含 b.h,b.h 包含 c.h,c.h 包含 a.h 之类导致任何一个头文件修改,都导致
所有包含了 a.h/b.h/c.h 的代码全部重新编译一遍。而如果是单向依赖,如 a.h 包含 b.h,b.h 包含 c.h,而 c.h
不包含任何头文件,则修改 a.h 不会导致包含了 b.h/c.h 的源代码重新编译。
规则 1.3 .c/.h 文件禁止包含用不到的头文件。
说明:很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想
到的头文件,甚至有些产品干脆发布了一个 god.h,其中包含了所有头文件,然后发布给各个项目组使用,
这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。
规则 1.4 头文件应当自包含。
说明:简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另
外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。
示例:
如果 a.h 不是自包含的,需要包含 b.h 才能编译,会带来的危害:
第 5页/共 53页
每个使用 a.h 头文件的.c 文件,为了让引入的 a.h 的内容编译通过,都要包含额外的头文件 b.h。额外的头
文件 b.h 必须在 a.h 之前进行包含,这在包含顺序上产生了依赖。
注意:该规则需要与“.c/.h 文件禁止包含用不到的头文件”规则一起使用,不能为了让 a.h 自包含,而在
a.h 中包含不必要的头文件。a.h 要刚刚可以自包含,不能在 a.h 中多包含任何满足自包含之外的其他头文
件。
规则 1.5 总是编写内部#include 保护符(#define 保护)。
说明:多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内
容被包含多于一次的机制。
通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时
使用它以排除文件内容。
所有头文件都应当使用#define 防止头文件被多重包含,命名格式为 FILENAME_H,为了保证唯一性,更
好的命名是 PROJECTNAME_PATH_FILENAME_H。
注:没有在宏最前面加上“_”,即使用 FILENAME_H 代替_FILENAME_H_,是因为一般以“_”开头的
标识符为系统保留或者标准库使用,在有些静态检查工具中,若全局可见的标识符以“_”开头会给出告
警。
定义包含保护符时,应该遵守如下规则:
1)保护符使用唯一名称;
2)不要在受保护部分的前后放置代码或者注释。
示例:假定 VOS 工程的 timer 模块的 timer.h,其目录为 VOS/include/timer/timer.h,应按如下方式保护:
#ifndef VOS_INCLUDE_TIMER_TIMER_H
#define VOS_INCLUDE_TIMER_TIMER_H
...
#endif
也可以使用如下简单方式保护:
#ifndef TIMER_H
#define TIMER_H
..
#endif
例外情况:头文件的版权声明部分以及头文件的整体注释部分(如阐述此头文件的开发背景、使用注意事
项等)可以放在保护符(#ifndef XX_H)前面。
规则 1.6 禁止在头文件中定义变量。
说明:在头文件中定义变量,将会由于头文件被其他.c 文件包含而导致变量重复定义。
规则 1.7 只能通过包含头文件的方式使用其他.c 提供的接口,禁止在.c 中通过 extern 的方式使用外部函数
接口、变量。
说明:若 a.c 使用了 b.c 定义的 foo()函数,则应当在 b.h 中声明 extern int foo(int input);并在 a.c 中通过#include
来使用 foo。禁止通过在 a.c 中直接写 extern int foo(int input);来使用 foo,后面这种写法容易在 foo 改
变时可能导致声明和定义不一致。
规则 1.8 禁止在 extern "C"中包含头文件。