简介:《中兴通讯软件编程规范——C/C++》是一份面向 C/C++ 开发工程师、代码评审人员及团队规范制定者的企业级编码标准,可用于统一编程风格、提升源码可读性与可维护性,也适合作为新人培训与代码走查的参考依据。文档围绕布局、注释、命名规则、变量常量与类型、表达式与语句、函数与过程等主题展开,并进一步讨论可靠性、可测试性、断言与错误处理,正文以规则、建议、说明、正例和反例组织,便于对照落地。压缩包内为 1 个 doc 文件,约 486KB,包含规范正文及附录中的编程模板和规范检查表,可直接用于项目编码约定或评审清单。目前已有 301 人学习,适合需要建立 C/C++ 编码底线、减少常见缺陷并促进团队协作的开发与测试人员查阅。
1. 一句话说清 C/C++ 编码规范:为什么你的代码别人读着像天书
C/C++ 编码规范,说白了就是一份“代码该怎么写”的集体约定:缩进几格、注释放哪、变量怎么起名、函数写到多长就该拆。它管的不是程序能不能跑通——那是语法和逻辑的事——而是另一个人接手时,能不能在十分钟内看懂。打个比方,语法规则决定了你会不会说这门语言,编码规范决定的是一屋子人开会时能不能听懂彼此说话。
很多人第一次听说“编码规范”会以为是给新手补语法课,其实恰恰相反:越是写过几年代码的人,越容易被自己三个月前写的东西绊住。函数里那个i到底是循环下标还是索引缓存?这段if为什么没加大括号?全靠回忆。规范存在的意义,就是把这类“回忆成本”提前消灭掉。
2. 为什么规范要分“规则”和“建议”两档:强制与弹性的边界
一份动不动上百条的编码规范,如果每条都写成“必须”,用不了多久就会被集体绕过——写的人嫌烦,审的人挑不完,最后整份文档变成摆设。2003 年初,某通信设备企业把自己内部对 C/C++ 的要求整理成了一份企业标准,发布两天后即开始执行,规定此后新写和修改的代码都要照办。它采用的办法不是一刀切,而是给每条内容标上“档位”。
2.1 五个关键词:原则、规则、建议、说明、正例反例
这套文档在术语部分就把话说死了,一共五个词,分工明确:
- 原则:编程时应该坚持的指导思想。它是方向,不是判据。
- 规则:编程时必须遵守的约定。违反了就是要改。
- 建议:编程时必须加以考虑的约定。考虑了可以不用,但要有理由。
- 说明:对某条规则或建议的必要解释,告诉你“为什么这么定”。
- 正例 / 反例:一对代码片段,把抽象条文落成看得见的样子。
别看只有五个词,它解决的是一个很实际的问题:评审时怎么吵架才算有效。没有这套档位,两个人争论缩进几格只能拼嗓门;有了档位,一句“这一行违反规则 2-3-1,改用 4 个空格”就把讨论收敛了。而遇到“建议”类条目,双方也可以坦然地写下“本项目不采纳,原因是历史代码量大”。
配套的还有编号体系,条目普遍写成【规则 2-1-2】【建议 2-3-1】这种形式,第一位数字对应章节。编号看起来枯燥,实际是这套规范能被引用的前提——口头说“你这里缩进不对”是主观意见,说“违反规则 2-3-1”是可核查的结论。
2.2 为什么必须分档:一刀切的规范注定被绕过
打个比方,交通法规里“红灯停”和“建议保持安全车距”都写在同一个本子里,但性质完全不同:前者违反就是违法,后者是你能动脑筋判断的余地。如果连“车距必须恰好五十米”都写成硬规则,那所有人在堵车时都得违规。
软件项目也是这个道理。同一条“函数不超过 80 行”,在写设备驱动的模块里可能绰绰有余,在写协议解析状态机的模块里可能处处受限。规范的制定者显然清楚这一点,原文在适用范围里就写明:考虑到不同产品和项目的实际开发特性,规范分成规则性和建议性两种,规则性要求所有开发人员严格执行,建议性由各项目编程人员根据实际情况选择。
这里藏着一个成熟团队的取舍:把“底线”和“偏好”分开。底线部分不讨论,讨论就是浪费时间;偏好部分允许讨论,但必须留下结论。反过来说,如果你所在团队把一百多条全部当成硬规则,结果往往是重要的几条没人记得,边角的几条天天有人争——这就是很多团队推行规范翻车的原因:他们把弹性当成了软弱,又把强制用错了地方。
2.3 八条基本原则:整份规范的“宪法”
文档在正文最前面列了八条原则,后面所有的布局、注释、命名条款,基本都能追溯到这八条里的某一条。理解了它们,很多“为什么要这么规定”就不用死记。
原则一:首先是为人编写程序,其次才是计算机。原文给出的理由是,软件的生命周期贯穿开发、测试、生产、用户使用、版本升级和后期维护,只有易读、易维护的代码才有生命力。翻译成大白话:机器不挑食,编译器什么样的代码都能吃下去,但下一个维护的人会挑。代码被读的次数远多于被写的次数,这个账要先算明白。
原则二:保持代码的简明清晰,避免过分的编程技巧。原文用了四个字——“简单是最美”。这不是审美问题,而是工程问题:技巧型的代码作者自己一周后都未必看得懂,而它换来的那点性能收益,往往在整体性能里察觉不到。把i = i++ + ++i这种靠副作用顺序取巧的写法写进产品代码,是典型的自找麻烦。
原则三:所有代码尽量遵循 ANSI C 标准。尽量不使用 ANSI C 未定义的、或编译器扩展的功能。这条在跨平台项目里是保命的:某个编译器“好心”支持的扩展语法,换一个编译环境就报错,而这种错误往往在最不该出问题的时候冒出来。
原则四:编程时首先达到正确性,其次考虑效率。原文的表述是,先满足正确性、健壮性、可维护性、可移植性等质量因素,最后才考虑程序的效率和资源占用。这条常被误解成“效率不重要”,实际是“顺序不能反”。先让程序对,再让它快;先让它能被改,再让它跑得省。倒过来做的结果,通常是又快又错的代码。
原则五:避免或少用全局变量。理由写得很直接:过多使用全局变量会导致模块间紧耦合,违反模块化要求。全局变量的问题不在于“占内存”,而在于它把模块之间原本清晰的边界搅浑了——任何一个函数都能改它,于是没人知道它是在哪一刻变成现在这个值的。
原则六:尽量避免使用 GOTO 语句。注意是“尽量”。这四个字留了口子:能靠结构改写的就改写,实在改不动的地方,至少把跳转收敛到一处。C 语言里“出错就跳到函数末尾统一清理”是一种常见写法:
/* 反例思路:跳转分散在函数各处时,路径一多就没人敢改 */ int DoWork(void) { int iRet = 0; if (!Init()) { iRet = -1; goto EXIT; /* 跳转到函数末尾统一清理 */ } if (!Prepare()) { iRet = -2; goto EXIT; } iRet = Run(); EXIT: CleanUp(); return iRet; }这段代码里goto的落点只有一处,阅读时能一眼看到出口,属于相对可控的用法;真正麻烦的是跳转目标散落在函数各处、形成网状路径的写法。判断标准很简单:你是不是得在脑子里画一张流程图才能读完这个函数。如果是,那这段结构就该重写了。
原则七:尽可能重用、修正老的代码。原文说,尽量选择可借用的代码,对其修改优化以满足自身要求。这句话有点反直觉——重用老代码听起来保守,但它省下的是重新踩一遍别人踩过的坑的成本。
原则八:尽量减少同样的错误出现的次数。原文很坦诚:事实上无法完全消除错误,但通过不懈努力,可以减少同样的错误出现的次数。这条几乎是整份规范的元原则——规范不能让你不犯错,但能让你不重复犯同一个错。
3. 布局与注释:最像“表面功夫”的部分,恰恰最影响可读性
一提到缩进、空格、空行,很多人第一反应是“这不就是排版嘛,编辑器一键格式化就完事”。但排版在代码里的作用,跟排版在书里的作用一样:它不改变内容,它决定内容能不能被吸收。一份行距混乱、段落不分、注释全挤在角落的说明书,哪怕技术内容再对,读者也读不下去。
3.1 布局管的是三件事:分区、缩进、留白
文档把布局拆成文件布局、基本格式、对齐、空行空格、断行五块。其中最容易被忽略但收益最大的是文件布局:它规定了一个文件里各部分的固定先后顺序,头文件和实现文件各有各的顺序。
头文件的顺序是:文件头注释、防止重复包含的宏、其它条件编译选项、#include、常量定义、全局宏、全局数据类型、类定义、模板、全局函数原型。实现文件的顺序是:文件头注释、#include、常量定义、文件内部使用的宏、文件内部使用的数据类型、全局变量、本地变量(静态全局变量)、局部函数原型、类的实现、全局函数、局部函数。原文特别注明:不需要的节可以省略,但保留的节必须保持这个次序。
/* 文件头:模块名、创建日期、功能说明、修改记录(由团队模板统一) */ #ifndef MODULE_NAME_H /* 文件名全大写加下划线,防止重复包含 */ #define MODULE_NAME_H #include <stdio.h> /* 标准库头文件用尖括号 */ #include "module_cfg.h" /* 项目内头文件用双引号 */ #define MAX_BUF_SIZE 256 /* 常量定义区 */ typedef unsigned char BOOLEAN; /* 类型定义区 */ int DoSomething(void); /* 全局函数原型区 */ #endif这段示例对应的规则有三条:头文件必须避免重复包含;标准库头文件用尖括号、非标准库头文件用双引号;多个节之间用注释块分隔。顺序固定的好处是可预测:任何人打开一个陌生的头文件,都知道常量在上面、函数原型在下面,不需要每次都从头扫一遍。这就是“降低回忆成本”的具体形态。
3.2 基本格式:80 列、大括号、指针星号的位置
三条基本格式规则,条条都在解决具体的阅读障碍。
第一,一行的代码和注释不超过 80 列(含空格)。这条源自终端年代的限制,在今天依然有用:它逼着人把过长的表达式拆开,而拆开的过程往往就是发现“这个条件怎么这么绕”的过程。打印、并排比对、代码评审的屏幕空间,都受益于这个限制。
第二,if、else、else if、for、while、do等语句自占一行,执行语句不得紧跟其后,不论执行语句有多少都要加{}。原文给的理由是防止书写失误、便于阅读。这条的血泪经验在于“省略大括号”:
/* 反例:执行语句紧跟 if 条件,且没有大括号 */ if (variable1 < variable2) variable1 = variable2;看起来省了一行,代价是后来的人往里加第二句时极易加错位置——尤其当代码被合并、被自动格式化、被某人手工调整缩进之后,那句“新增的语句”可能悄悄跑到if外面去了。而编译器不会报错,因为语法完全合法。
第三,定义指针类型变量时,*放在变量前:
float *pfBuffer; /* 正例:星号紧贴变量名 */ float* pfBuffer; /* 反例:星号贴着类型,容易误读成“float 指针类型” */这条纯属约定,两种写法编译器一视同仁。但float* pfBuffer, pfOther;这种写法会诱导人误以为两个变量都是指针——实际上pfOther只是普通float。星号贴变量名,等于时刻提醒“星号是修饰这个变量的”。
3.3 缩进与对齐:为什么禁用 TAB、为什么大括号独占一行
缩进部分有三条硬规则。禁止使用 TAB 键,必须使用空格缩进,缩进量为 4 个空格;分界符{和}各自独占一行、位于同一列,并与引用它们的语句左对齐;声明类时,public、protected、private与分界符{}对齐,这些部分的内容缩进。
void Function(int iVar) { /* { 独占一行并与引用它的语句左对齐;块内统一缩进 4 格 */ while (iVar > 0) { DoSomething(); iVar--; } }禁用 TAB 的理由原文写得很实在:消除不同编辑器对 TAB 处理的差异。TAB 到底是 4 格还是 8 格,取决于看代码的那个人用什么工具、什么设置。同一份文件在两个人屏幕上呈现两种结构,这在评审时是纯粹的噪声。有些编辑器可以设置“按 TAB 键输入空格”,这是个实用的小开关。
{}独占一行的规则,本质是把“块的边界”变成视觉上可数的东西:左边缘那一列竖着扫下去,每个{都能找到对应的}。类声明的对齐规则同理,public:和private:与{对齐,是为了让“这段是接口、那段是实现细节”一眼可见。
还有一些不那么显眼但很实用的对齐约定:结构型数组、多维数组在定义时初始化,按矩阵结构分行书写;相关的连续赋值语句把等号对齐。后者看起来只是好看,实际是让人能快速比对一串赋值的右值是同一量级还是天差地别。
/* 按矩阵结构分行,行列关系一眼可见 */ int aiNumbers[4][3] = { 1, 1, 1, 2, 4, 8, 3, 9, 27, 4, 16, 64 };另外,switch语句里每一个case分支和default都要用{}括起来,块内内容缩进。这在 C 里不是必须的,但一旦某个分支里定义了局部变量,缺大括号的写法在某些编译器下会直接翻车,加上就没有这类悬念。
3.4 空行、空格与断行:细节里的可读性
空行分隔的是逻辑块。原文明确:不同逻辑程序块之间要使用空行分隔——两个函数的实现是两个逻辑块,中间就该空一行。同类的还有:关系较为紧密的代码尽可能相邻,比如矩形的长和宽两个赋值放一起,而不是被一句无关的标题字符串插在中间。
空格规则按操作符的“目数”区分,逻辑很统一:
- 一元操作符(
!、~、++、--、取地址&、指针*)前后不加空格。 .、->、[]这类操作符前后不加空格。- 多元操作符两侧至少留一个空格,比如
fTotal + fValue。 - 关键字(
if、for、while)之后留一个空格再跟左括号,以突出它是关键字;函数名之后紧跟左括号不留空格,以区分“这是函数调用”。 - 左括号
(向后紧跟,)、,、;向前紧跟,紧跟处不留空格;,之后和作为分隔用的;之后要留空格。
断行的规则更考验判断力:长表达式(超过 80 列)要在低优先级操作符处拆分成新行,操作符放在新行之首,拆分出的新行做适当缩进,使排版整齐。条件表达式的续行在第一个条件处对齐,赋值语句的续行在赋值号处对齐。
if ((iFormat == CH_A_FORMAT_M) && (iOfficeType == CH_BSC_M)) /* 在 && 处断开,操作符放行首 */ { DoSomething(); }操作符放行首而不是行尾,是因为人读多行条件时是“竖着扫”的:&&、||排在左边缘,布尔结构立刻清晰;如果放行尾,它们会散落在各行的最右侧,视线得来回横跳。
3.5 注释:写“为什么”,而不是把代码翻译一遍
文档对注释的要求只有一句核心判断:有效的注释是指在代码的功能、意图层次上进行注释,提供有用、额外的信息,而不是代码表面意义的简单重复。换句话说,iCount++; // iCount 加一这种注释等于没写,它没有给出任何代码里读不出来的东西。
格式上也有硬规定:C 语言注释符为/* … */;C++ 中多行注释用/* … */,单行注释用// …;注释符与注释内容之间用一个空格分隔。最后这条看着琐碎,但它和缩进是一个道理——统一了,眼睛就不用每次重新校准。
真正值钱的注释通常回答三类问题:这段代码为什么这么写(而不是别的方式)、这个魔法数字从哪来、这个函数在什么前提下会被调用。文件头注释则承担另一件事:让打开文件的人在三十秒内知道这个文件属于哪个模块、谁维护、改过几次。
4. 命名、类型与表达式:把“看得懂”落到每个标识符上
如果说布局决定了代码的“长相”,命名决定的则是代码的“词汇量”。一个函数里出现i、j、tmp、data、flag这五个变量,你读它的时候要不停往回翻;换成iRetryCount、pstConnInfo、bIsTimeout,阅读就变成了顺读。
4.1 命名:名字是代码里被读次数最多的东西
这套规范单列了一章讲命名规则,覆盖变量、常量、函数、类、宏等各类标识符。从文档给出的示例里,可以反推出它采用的是一套前缀表意的命名法——名字里前几个字母就交代了类型和身份。
#define MAX_RETRY_COUNT 3 /* 宏:全大写,单词间下划线分隔 */ typedef unsigned char BOOLEAN; /* 类型定义:全大写 */ class CCounter /* 类名以 C 开头,后接名词 */ { public: CCounter(void); ~CCounter(void); int GetCount(void); /* 函数名:动词开头,首字母大写 */ void SetCount(int iCount); private: int m_iCount; /* 成员变量:m_ 表示成员,i 表示 int */ }; int iLength; /* i 前缀:整型 */ char *pcName; /* pc 前缀:指向字符的指针 */ float *pfBuffer; /* pf 前缀:指向 float 的指针 */ char strCaption[32]; /* str 前缀:字符串数组 */这套写法的好处在声明处就体现出来了:看到m_iCount,不用往回翻就知道它是成员变量、是整型;看到pcName,就知道它是个指针而不是数组。代价是名字变长,且新人得先学一遍前缀表——这也是为什么前缀命名法在有些团队里被弃用,转而用编辑器的高亮和类型提示来替代。这两种选择没有绝对对错,关键是一个团队内部必须一致:一半人用前缀一半人不用,那才是最糟的状态。
值得留意的是命名规则里隐含的一条原则:名字要能读出用途,而不是读出类型之外什么也没有。iData1、iData2这种命名虽然合乎前缀,但信息量等于零;iRetryCount才是合格的名字。
4.2 变量、常量与类型:能小不大,能 const 不变
变量、常量与类型这一章,处理的是一类隐蔽的问题:变量的作用域和生命周期,比它的类型更容易出事。
第一条思路是“就近声明、就近使用”。变量在离它第一次被使用最近的地方定义,读者就不用在上百行之前去找它的来历,也顺带消除了“这个变量在这个函数里到底有没有被改过”的猜疑。
第二条思路是“初始化即定义”。定义变量时不给初值,是 C/C++ 里最常见的错误来源之一:栈上的局部变量内容不确定,一旦某条分支忘了赋值就直接使用,读到的是随机值,症状表现为时灵时不灵的玄学 bug。
第三条思路是“别用魔法数字”。代码里突然出现一个256,读者无法判断它是缓冲区大小、超时毫秒数还是某个协议的字段上限。给它一个具名常量,等于给这行代码加了一句解释。
第四条思路是“能窄不宽、能无符号别乱转”。类型选择的优先级是:够用就好,别为了省事全用int;同时尽量避免不必要的隐式类型转换,因为转换发生的那一刻,编译器可能一声不吭地截断了一个值。
这些条款在各家规范里大同小异,核心都是同一件事:让变量的“有效范围”尽可能小,让它的“取值”尽可能确定。
4.3 表达式与语句:运算符优先级不靠背,靠括号
运算符优先级表没人背得全,包括写了很多年 C 的人。规范对此的态度很务实:与其指望大家记住<<和+谁先算,不如用括号把意图写出来。加括号的成本是一个字符,不加括号的成本是一次线上事故。
同样务实的还有几条:避免在表达式里塞副作用(比如在判断条件里做自增);switch的每个分支以break结束,最后必须有default,哪怕它是空的;循环的边界条件写成一眼能看懂的形式,别把<=和<混着用却指望读者自己换算。
/* 反例:优先级靠记忆,副作用混在判断里 */ if (iFlag << 2 + iOffset) { DoSomething(); } /* 正例:括号写清意图,自增单独成句 */ iValue = iValue + 1; if ((iFlag << 2) + iOffset) { DoSomething(); }这两段的行为在多数编译器下一致,但第一段要求读代码的人在脑内跑一遍优先级表,第二段不需要。规范想省下的就是这一秒钟。
4.4 函数与过程:一件事、一个出口、参数别太多
函数这一章的核心是“职责单一”。一个函数只做一件事,判断标准很朴素:你能不能用一句不含“并且”的话说清它做了什么。说不清,就该拆。
参数也需要克制。参数越多,调用点的可读性越差,因为你会看到DoSomething(a, b, 1, 0, NULL)这种没人能看懂的调用——那个1和0分别代表什么?规范里对应的做法是控制参数个数,并把布尔型参数换成有名字的枚举或拆成两个函数。
函数声明还有一条很具体的格式规则:类型与名称不允许分行书写。函数调用和函数声明的续行,要在第一个参数处对齐。
/* 正例:声明写在一行内,续行参数与第一个参数对齐 */ BYTE ReportStatusCheckPara(HWND hWnd, BYTE ucCallNo, BYTE ucStatusReportNo);这里面hWnd是窗口句柄,ucCallNo是呼叫编号,ucStatusReportNo是状态上报编号——名字里的uc前缀表示无符号字符型。三行参数竖直对齐,一眼就能数出有几个参数、分别是什么类型。
5. 几个最容易搞混的地方:规则、风格、可靠性与可测试性的区别
这份文档里的概念密度不低,初学者最容易在几个词之间打转。下面五组,是我见过被混淆次数最多的。
5.1 混淆点一:编码规范 vs 代码风格
易混点:很多人把这两个词当同义词用,“咱们定个编码规范”说出口,实际上指的是“缩进用几个空格”。
为什么混:因为规范里最显眼的部分确实是缩进、空格、大括号位置,这些看上去纯属审美。
怎么区分:风格是规范的一个子集,只管“长什么样”;规范还管“能不能被改”“出错了怎么办”“这段代码测不测得动”。打个比方,风格是字体和字号,规范是整本排版手册,还包括章节编号、图表标注、引用格式。判断方法很简单:把一段代码的缩进全部重排,如果它仍然是好代码,说明风格只是表层;如果重排之后你就读不懂了,那问题可能出在命名和结构上,而不只是缩进。
5.2 混淆点二:规则 vs 建议
易混点:文档里明明分了“规则”和“建议”两档,但执行时常常一视同仁——要么全都得改,要么全都可以不改。
为什么混:两档在文档里的排版几乎一样,都带编号、都配正例反例,扫一眼看不出差别,只有仔细读那个方括号里的字才发现不同。
怎么区分:看违反之后的动作。违反规则,代码必须改;违反建议,需要留下一个理由,可以是注释、可以是评审记录,但不能什么都不说。这条区分线一旦模糊,规范就失去了弹性,而失去弹性的规范通常活不过两个版本。
5.3 混淆点三:断言 vs 错误处理
易混点:两者都表现为“如果某个条件不成立就做点什么”,于是有人用断言代替错误处理,或者反过来。
为什么混:代码形态太像了,都是一句判断加一个分支。
怎么区分:看这条分支是给谁看的。断言是给开发自己看的——它表达的是“按设计,这种情况绝不可能发生”,一旦触发,说明代码本身有 bug,程序应该立刻停下来,让问题在最接近源头的地方暴露,而不是带着错误状态继续跑到几百行之后才崩。错误处理是给使用者看的——文件打不开、网络超时、参数越界,这些是运行环境的常态,程序要能优雅地降级、返回错误码、给出提示,然后活下去。用一句话概括:断言管“我不该错”,错误处理管“世界会出错”。
5.4 混淆点四:可靠性 vs 可测试性
易混点:这两章在目录里挨着,听起来都是“让代码更靠谱”,很多人以为是一回事。
为什么混:目标确实相关——都是为了让软件少出问题。
怎么区分:可靠性是“少出错”,可测试性是“容易发现错”。可靠性关注的是边界检查、资源释放、异常路径是否被覆盖;可测试性关注的是这段代码能不能被单独拎出来验证,比如它是否依赖全局状态、是否把 I/O 和计算搅在一起、是否能构造出确定的输入输出。打比方,可靠性是把车造结实,可测试性是给车留一个能接诊断仪的接口。一辆车可能很结实但完全没法检测,也可能很好检测但本身毛病不少,这是两个维度。
一个具体的差别:一段直接读全局配置、算完再写回全局变量的函数,可靠性上可能没毛病,但可测试性很差——你没法在不改动全局状态的前提下测它。把它改成“参数传入、返回结果”,可靠性不变,可测试性立刻提升。这也是为什么“避免或少用全局变量”这条原则,同时也是一条第可测试性条款。
5.5 混淆点五:注释 vs 文档
易混点:文档里有一整章讲注释,于是有人以为把注释写足就等于文档齐全。
为什么混:两者都是用文字解释代码。
怎么区分:注释解释“为什么”,文档解释“怎么用”。注释贴在代码旁边,读者是维护者,关心的是“这行为什么要判空”“这个常量的 256 是哪来的”;文档独立于代码,读者是使用者,关心的是“这个接口怎么调、参数什么范围、失败了返回什么”。注释写多了会随代码一起腐烂,因为改代码的人未必改注释;接口文档则该跟着接口一起维护。一个好的判据是:把函数体全部删掉只留注释,如果还能拼出一份接口说明,那说明注释写得太“文档化”了;反之,如果接口文档里塞满了实现细节,那说明文档写成了注释。
6. 什么时候该照搬这套规范,什么时候别硬套
这套文档写于 2003 年,面向的是 C/C++ 语言的产品代码,正文覆盖基本原则、布局、注释、命名、变量常量与类型、表达式与语句、函数与过程、可靠性、可测试性、断言与错误处理,最后还附了编程模板和规范检查表。它的形态是“标准 + 附录工具”,也就是说,它不只告诉你该怎么做,还给了你一张能照着检查的表。
先说该用的时候。团队超过五个人、代码寿命超过三年、或者需要多人交替维护同一批文件,这三条里占两条,就值得把规范认真过一遍。人多意味着风格必然分裂,寿命长意味着今天省下的一分钟会在三年后变成一小时,交替维护意味着“作者本人还记得”这个前提不成立。更进一步,如果代码要通过外部评审或交付给第三方,一份成文规范还能省掉大量解释工作——它本身就是一种质量证据。此外,接手历史遗留代码时,这套东西的价值会突然变得极高:你不需要喜欢它的每一条,你只需要一个统一的尺子去丈量“哪里最该先改”。
再说别硬套的时候。两三个人的短期项目,把上百条全部执行,时间成本会吞掉收益。这种情况下我的建议是只抓三件事:命名能读懂、关键处有注释、大括号不省。教学或个人练手,也不必逐条背诵,挑布局和命名练手就够了,因为这两块带来的手感最直接。跨语言项目要特别注意,这套规范明确是针对 C/C++ 的,其它语言可以参照,但不能照抄——比如命名里的类型前缀,在带类型推导的语言里基本是负担。
| 场景 | 建议的力度 |
|---|---|
| 老代码库改造,满屏 TAB 和历史命名 | 新代码全执行,改动的行随手指带对齐,不做全量重排 |
| 三到五人的新项目,半年周期 | 规则类执行,建议类留档说明;评审只看规则 |
| 三十人以上、代码要活十年 | 全量执行,配合附录里的检查表做定期抽查 |
| 跨语言项目 | 只借用布局、注释、函数职责这些语言无关的部分 |
| 个人学习 | 练布局和命名,别背条款 |
还有一条常被忽视的边界:规范解决不了设计问题。一段架构上就该拆成三个模块却挤在一个文件里的代码,缩进得再标准也救不回来;反过来,设计清楚但风格混乱的代码,至少还能靠格式化工具抢救。所以推行规范的顺序应该是先有设计评审,再有编码规范,反过来做等于给一栋歪楼刷墙。
我自己的习惯是,每次开新文件前先把文件头注释模板贴上去,把模块名、创建日期、功能说明三行填完再动手写第一行代码;每次合并前跑一遍格式检查,把 TAB 和超过 80 列的行挑出来。这两件事花不了几分钟,但它们把“以后再说”变成了“现在就顺手”的默认动作。规范真正的价值不在文档里那些条文,而在于它能不能变成这种默认动作——文档只是把默认动作写清楚,剩下的靠每一次提交去落实。这份规范连同附录里的编程模板和检查表放在下面,需要的时候拿它当尺子量一量就好,希望帮到你。
本文还有配套的精品资源,点击获取