1、清晰第一
清晰性是易于维护、易于重构的程序必需具备的特征。代码首先是给人读的,好的代码应当可以像文 章一样发声朗诵出来。
目前软件维护期成本占整个生命周期成本的40%~90%。根据业界经验,维护期变更代码的成本,小型系 统是开发期的5倍,大型系统(100万行代码以上)可以达到100倍。业界的调查指出,开发组平均大约 一半的人力用于弥补过去的错误,而不是添加新的功能来帮助公司提高竞争力。
“程序必须为阅读它的人而编写,只是顺便用于机器执行。”——Harold Abelson 和 Gerald Jay Sussman
“编写程序应该以人文本,计算机第二。”——Steve McConnell
本规范通过后文中的原则(如头优秀的代码可以自我解释,不通过注释即可轻易读懂/头文件中适合放 置接口的声明,不适合放置实现/除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音)、
规则(如防止局部变量与全局变量同名)等说明清晰的重要性。
一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。
2、简洁为美
简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。写的代码越 多,意味着出错的地方越多,也就意味着代码的可靠性越低。因此,我们提倡大家通过编写简洁明了 的代码来提升代码可靠性。
废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。
本规范通过后文中的原则(如文件应当职责单一/一个函数仅完成一件功能)、规则(重复代码应该尽 可能提炼成函数/避免函数过长,新增函数不超过50行)等说明简洁的重要性。
3、选择合适的风格,与代码原有风格保持一致
产品所有人共同分享同一种风格所带来的好处,远远超出为了统一而付出的代价。在公司已有编码规 范的指导下,审慎地编排代码以使代码尽可能清晰,是一项非常重要的技能。如果重构/修改其他风格 的代码时,比较明智的做法是根据现有代码的现有风格继续编写代码,或者使用格式转换工具进行转
2011-05-10 华为机密,未经许可不得扩散 Huawei Confidential 第6页,共60页Page 6 , Total60 换成公司内部风格。
0.3 规范实施、解释
本规范制定了编写C语言程序的基本原则、规则和建议。
本规范适用于公司内使用C语言编码的所有软件。本规范自发布之日起生效,对以后新编写的和修改 的代码应遵守本规范。
本规范由质量体系发布和维护。实施中遇到问题,可以到论坛 http://hi3ms.huawei.com/group/1735/threads.html上讨论。
在某些情况下(如BSP软件)需要违反本文档给出的规则时,相关团队必须通过一个正式的流程来评 审、决策规则违反的部分,个体程序员不得违反本规范中的相关规则。
0.4 术语定义
原则:编程时必须坚持的指导思想。
规则:编程时强制必须遵守的约定。
建议:编程时必须加以考虑的约定。
说明:对此原则/规则/建议进行必要的解释。
示例:对此原则/规则/建议从正、反两个方面给出例子。
延伸阅读材料:建议进一步阅读的参考材料。
1 头文件
背景
对于C语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根 因,不合理的头文件实际上不合理的设计。
术语定义:
依赖:本章节特指编译依赖。若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文件的一种破坏。
大部分产品修改一处代码,都得需要编译整个工程,对于TDD之类的实践,要求对于模块级别的编译时 间控制在秒级,即使使用分布式编译也难以实现,最终仍然需要合理的划分头文件、以及头文件之间 的包含关系,从根本上降低编译时间。
2011-05-10 华为机密,未经许可不得扩散 Huawei Confidential 第7页,共60页Page 7 , Total60
《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语言接口与实现》(英文版: "C Interfaces and Implementations")
原则1.2 头文件应当职责单一。
说明:头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大,
职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。
示例:如下是某平台定义WORD类型的头文件:
#include <VXWORKS.H>
#include <KERNELLIB.H>
#include <SEMLIB.H>
#include <INTLIB.H>
#include <TASKLIB.H>
#include <MSGQLIB.H>
#include <STDARG.H>
#include <FIOLIB.H>
#include <STDIO.H>
#include <STDLIB.H>
#include <CTYPE.H>
#include <STRING.H>
#include <ERRNOLIB.H>
#include <TIMERS.H>
#include <MEMLIB.H>
#include <TIME.H>
#include <WDLIB.H>
#include <SYSLIB.H>
#include <TASKHOOKLIB.H>
#include <REBOOTLIB.H>
…
2011-05-10 华为机密,未经许可不得扩散 Huawei Confidential 第8页,共60页Page 8 , Total60 typedef unsigned short WORD;
…
这个头文件不但定义了基本数据类型WORD,还包含了stdio.h syslib.h等等不常用的头文件。如果工 程中有10000个源文件,而其中100个源文件使用了stdio.h的printf,由于上述头文件的职责过于庞大,
而WORD又是每一个文件必须包含的,从而导致stdio.h/syslib.h等可能被不必要的展开了9900次,大 大增加了工程的编译时间。
原则1.3 头文件应向稳定的方向包含。
说明:头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳 定的模块发生变化时,不会影响(编译)稳定的模块。
就我们的产品来说,依赖的方向应该是:产品依赖于平台,平台依赖于标准库。某产品线平台的代码 中已经包含了产品的头文件,导致平台无法单独编译、发布和测试,是一个非常糟糕的反例。
除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口,这样任何一个模块 的内部实现更改都不需要重新编译另外一个模块。在这里,我们假设接口本身是最稳定的。
编者推荐开发人员使用“依赖倒置”原则,即由使用者制定接口,服务提供者实现接口,更具体的描述 可以参见《敏捷软件开发 原则 模式与实践》的第二部分“敏捷设计”章节。
规则1.1 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。
说明:如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main 函数所在的文件。
现有某些产品中,习惯一个.c文件对应两个头文件,一个用于存放对外公开的接口,一个用于存放内 部需要用到的定义、声明等,以控制.c文件的代码行数。编者不提倡这种风格。这种风格的根源在于 源文件过大,应首先考虑拆分.c文件,使之不至于太大。另外,一旦把私有定义、声明放到独立的头 文件中,就无法从技术上避免别人include之,难以保证这些定义最后真的只是私有的。
本规则反过来并不一定成立。有些特别简单的头文件,如命令ID定义头文件,不需要有对应的.c存在。
示例:对于如下场景,如在一个.c中存在函数调用关系:
void foo() {
bar();
}
void bar() {
Do something;
}
必须在foo之前声明bar,否则会导致编译错误。
这一类的函数声明,应当在.c的头部声明,并声明为static的,如下:
static void bar();
void foo() {
bar();
}
2011-05-10 华为机密,未经许可不得扩散 Huawei Confidential 第9页,共60页Page 9 , Total60 void bar()
2011-05-10 华为机密,未经许可不得扩散 Huawei Confidential 第9页,共60页Page 9 , Total60 void bar()