目录
1. 文件结构.. 1
1.1 版权和版本的声明格式.. 1
1.2 头文件的书写格式.. 1
2. 程序的版式.. 4
2.1 空行的使用.. 4
2.2 代码行.. 4
2.3 代码行内的空格.. 5
2.4 对齐.. 6
2.5 长行拆分.. 7
2.6 修饰符的位置.. 7
2.7 注释.. 7
3. 命名规则.. 10
3.1 共性规则.. 10
3.2 Windows 程序命名规则.. 11
4. 表达式和基本语句.. 14
4.1 运算符的优先级.. 14
4.2 表达式与复合表达式.. 14
4.3 if语句.. 15
4.4 循环语句的效率.. 17
4.5 for语句的循环控制变量.. 17
4.6 switch 语句.. 18
4.7 goto 语句.. 18
5. 数据结构.. 19
5.1 基本规则.. 19
6. 常量.. 21
6.1 常量的命名规则.. 21
6.2 常量的使用.. 21
6.3 常量定义规则.. 21
6.4 C++类中的常量.. 22
7. 函数(操作)设计.. 23
7.1 函数外部特性的注释规则.. 23
7.2 参数的规则.. 23
7.3 返回值的规则.. 25
7.4 函数内部的实现规则.. 26
7.5 其它规范.. 28
8. 内存管理.. 29
8.1 基本原理.. 29
8.2 基本规则.. 30
9. 代码可测性.. 31
9.1 基本规则.. 31
9.2 编辑、编译、审查.. 32
9.3 代码测试.. 33
10.源代码升级.. 35
10.1 基本规则.. 35
11.其它规则和建议.. 37
11.1 其它规则.. 37
11.2 其它建议.. 38
每个C++/C 程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。C++/C 程序的头文件以“.h”为后缀,C 程序的定义文件以“.c”为后缀,C++程序的定义文件通常以 “.cpp”为后缀(也有一些系统以“.cc”或“.cxx”为后缀)。对于 DLL 文件,通常还包括“.def”文件。
头文件和 C 程序文件中都必须包含版权和版本的声明。
版权和版本的声明位于头文件和定义文件的开头(参见示例 1-1),主要内容有:
1. 版权信息。
2. 文件名称、标识符、摘要。
3. 当前版本号、作者/修改者、完成日期。
4. 版本历史信息。
示例1‑1版权和版本的声明范例
头文件的作用:通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
1. 头文件必须包含下列内容:
1.1. 头文件开头处的版权和版本声明。
1.2. 预处理块。
1.3. 函数和类结构声明等。
2. 正确使用预处理块:为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块。
3. 正确引用头文件的格式:用 #include
4. 头文件中只存放“声明”而不存放“定义”
5. 在 C++ 语法中,类的成员函数可以在声明的同时被定义,并且自动成为内联函数。这虽然会带来书写上的方便,但却造成了风格不一致,弊大于利。建议将成员函数的定义与声明分开,不论该函数体有多么小。
6. 不提倡使用全局变量,尽量不要在头文件中出现像 extern int value 这类声明。
示例1‑2 C++/C 头文件的结构范例
7. 定义文件的书写格式:必须包含三部分内容:
7.1. 定义文件开头处的版权和版本声明;
7.2. 对一些头文件的引用;
7.3. 程序的实现体(包括数据和代码)。
假设定义文件的名称为 graphics.cpp,定义文件的典型结构为:
示例1‑3 C++/C 定义文件的结构范例
版式虽然不会影响程序的功能,但会影响可读性。程序的版式追求清晰、美观,是程序风格的重要构成因素。
空行起着分隔程序段落的作用。空行得体(不过多也不过少)将使程序的布局更加清晰。空行不会浪费内存,虽然打印含有空行的程序是会多消耗一些纸张,但是值得。所以不要舍不得用空行。
1. 在每个类声明之后、每个函数定义结束之后都要加两个空行。
2. 在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
示例2‑1 函数之间的空行 示例 2‑2函数内部的空行
1. 一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。
2. if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。
示例 2‑3 风格良好的代码行 示例2‑4 风格不良的代码行
1. 关键字之后要留空格。像 const、virtual、inline、case 等关键字之后至少要留一个空格,否则无法辨析关键字。
2. if、for、while 等关键字之后应留一个空格再跟左括号 ‘(’,以突出关键字。
3. 函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。
4. ‘(’向后紧跟‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。
5. ‘,’之后要留空格,如 Function(x, y, z)。如果‘;’不是一行的结束符号,其后要留空格,如 for (initialization; condition; update)。
6. 赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=”“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。
7. 一元操作符如“!”、“~”、“++”、“=”、“&”(地址运算符)等前后不加空格。
8. 像“[]”、“.”、“->”这类操作符前后不加空格。
9. 对于表达式比较长的 for 语句和 if 语句,为了紧凑起见可以适当地去掉一些空格,如 for (i=0; i<10; i++)和 if ((a<=b) && (c<=d))
示例2‑5代码行内的空格范例
Void Func1(int x, int y, int z); |
// 良好的风格 |
void Func1 (int x,int y,int z); |
// 不良的风格 |
if (year >= 2000) |
// 良好的风格 |
if(year>=2000) |
// 不良的风格 |
if ((a>=b) && (c<=d)) |
// 良好的风格 |
if(a>=b&&c<=d) |
// 不良的风格 |
for (i=0; i<10; i++) |
// 良好的风格 |
for(i=0;i<10;i++) |
// 不良的风格 |
for (i= 0; i< 10; i ++) |
// 过多的空格 |
x = a < b ? a : b; |
// 良好的风格 |
x=a |
// 不好的风格 |
int *x = &y; |
// 良好的风格 |
int * x = & y; |
// 不良的风格 |
array[5] = 0; |
// 不要写成 array [ 5 ] = 0; |
a.Function(); |
// 不要写成 a . Function(); |
b->Function(); |
// 不要写成 b -> Function(); |
1. 程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。
2. { }之内的代码块在‘{’右TAB键处左对齐,每个TAB键占位4个空格字符。
示例 2‑6 风格良好的对齐 示例 2‑7 风格不良的对齐
示例 2‑8 风格良好的对齐(续)
1. 代码行最大长度宜控制在 70 至 80 个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。
2. 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
示例 2‑9 长行的拆分
应当将修饰符*和&紧靠变量名。例如:
1. C 语言的注释符为 “/*…*/”。C++语言中,程序块的注释常采用 “/*…*/”,行注释一般采用“//…”。尽量使用“//…”方式进行注释,为防止错误地注释掉有效代码,建议采用 C++风格的注释。
2. 注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少。一般情况下,源程序有效注释量必须在20%左右。
3. 注释主要用于解释代码复杂度,如果代码本来就是清楚的,则不必加注释(即代码能够充分自注释)。否则多此一举,令人厌烦。例如:
4. 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
5. 注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。注释可以采用英文,也可以采用中文。如果对英文注释没有把握时必须使用中文注释。
6. 尽量避免在注释中使用缩写,特别是不常用缩写。
7. 注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
8. 在一个注释块中,使用空注释行而非空行来分割注释段。
9. 当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。例如:
10. (指南)类与函数的实现和声明中,除非代码(特别是命名)能够充分地自注释,否则应给与注释(见 2.7.11)。此外,通常还需要提供一些超出解释代码复杂部分以外的信息。至少需要记录以下信息:
10.1. 每个类的目的;
10.2. 函数名不能清楚说明它的目的时,则记录函数相应的目的;
10.3. 返回值的含义;如不可预测函数布尔返回值的含义 :如,ture值是否表示函数执行成功 ;
10.4. 出现异常的条件;
10.5. 如果有的话,参数的前置或后置条件;
10.6. 其他数据访问,特别是当数据被修改时:对有副作用的函数特别重要;
10.7. 合理使用类或函数所需的限制或其他信息;
10.8. 对类型和对象来说,语言无法表达的任何不变量或其他限制。
11. (指南)对于所有有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释;数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须加以注释;全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。
1. 标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。命名中若使用特殊约定或缩写,则要有注释说明。
[提示1]较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写;一些单词有大家公认的缩写。
示例3‑1 被认可的单词缩写
Temp 可缩写为 tmp; Flag 可缩写为 flg; Statistic 可缩写为 stat; Increment 可缩写为 inc; Message 可缩写为 msg; |
[提示2]应该在源文件的开始之处,对文件中所使用的缩写或约定,特别是特殊的缩写,进行必要的注释说明。
2. 标识符最好采用英文单词或其组合,便于记忆和阅读,可望文知意,不必进行“解码”。不能使用汉语拼音来命名 。程序中的英文单词一般不会太复杂 ,用词应当准确。例如不要把CurrentValue 写成 NowValue。
3. 标识符的长度应当符合“min-length && max-information”原则,在保证准确表达其意义的情况下越短越好。一般禁止使用单字符命名变量,但 i、j、k 等单字符变量作局部循环变量是允许的。永远不要声明以一个或多个下划线 ('_') 开头的名称。
4. 命名规则尽量与所采用的操作系统或开发工具的风格保持一致。Windows下源代码通常采用“大小写”混排的方式,如 AddChild。而Unix/Linux应用程序的标识符通常采用“小写加下划线”的方式,如add_child。别把这两类风格混在一起用。但是,当Unix/Linux的源代码是从WINDOWS下移植而来时,可以允许移植部分保留WINDOWS风格。
5. 程序中不要出现仅靠大小写区分的相似的标识符。例如:
6. 程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
7. 变量的名字应当使用“名词”或者“形容词+名词”。例如:
8. 全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。例如:
9. 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。例如:
表格3‑1软件中常用的反义词组
add / remove |
begin / end |
create / destroy |
insert / delete |
first / last |
get / release |
increment / decrement |
lock / unlock |
put / get |
add / delete |
old / new |
open / close |
min / max |
start / stop |
show / hide |
next / previous |
source / target |
|
send / receive |
source / destination |
|
cut / paste |
up / down |
|
【建议】尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)。
10. 命名规则中没有规定到的地方可使用个人命名风格,个人命名风格必须自始至终保持一致,不可来回变化。
1. 采用合理的简化的“匈牙利”命名规则。
2. 类名和数据结构定义用大写字母开头的单词组合而成;类名前加前缀‘C’(大写);数据结构前不加前缀,直接以大写字母开始;类的名称使用名词或名词短语。例如:
3. 变量和参数用小写字母开头的单词组合而成。即第一个单词全部小写,后续单词首字母大写。
4. 函数名用大写字母开头的单词组合而成(例外规则见 3.2.11)。
5. 常量全用大写的字母,用下划线分割单词。
6. 变量和参数以小写字母前缀来表示变量的数据类型,变量或参数名的其余部分则描述变量或参数的功能,可以由一个或多个英语单词的缩写或全称组成,单词首字母必须大写。数据结构变量可以没有小写字母前缀。只有函数体中用到的局部循环变量可以采用简单的如i、uc 等简单形式外,其余类型变量都必须遵循变量命名规则。
表格3‑2 常见的前缀
b |
BOOL |
c |
char:8 比特字符 |
uc |
UHCAR 或 unsigned char:8 比特无符号字符 |
sz |
char 型数组 |
w |
WORD:16 比特的无符号字 |
dw |
DWORD:32 比特的无符号双字 |
n |
int:整数。WINDOWS 系统下为 32 比特 |
l |
long:32 比特的有符号长整数 |
f |
float 型单精度浮点数 |
d |
double 型双精度浮点数 |
s |
short:16 比特的有符号短整数 |
h |
HANDLE:句柄 |
p |
数据结构指针变量:WINDOWS 系统下为长指针 |
pc |
PCHAR:指向 unsigned char 的指针 |
puc |
PUCHAR:指向 UCHAR 的指针 |
pw |
PWORD:指向 WORD 的指针 |
pdw |
PDWORD:指向 DWORD 的指针 |
pn |
指向 int 的指针 |
pl |
指向 long 的指针 |
pf |
指向 float 型单精度浮点数的指针 |
pd |
指向 double 型双精度浮点数的指针 |
如果需要的变量前缀没有在上面指定,必须向上级主管反映,不能自行定义新的类型前缀。
7. 常量的命名必须使用英语准确表达相关的物理意义,开头必须使用大写的英文字母,单词之间必须用下划线分隔。例如要表示坐席卡上的最大通道数,应当定义为
#define MAX_USER_CH 8
而不是
#define MAXUSERCH 8
已经定义的常量前缀为:
“S_”前缀: |
表示状态的常量 |
“E_”前缀: |
表示事件的常量 |
“C_”前缀: |
表示程序中用到的常数 |
“MAX_”前缀: |
表示上限的常量 |
“MIN_”前缀: |
表示下限的常量 |
8. 静态(static)变量加前缀“s_”。例如:
9. 全局变量名称前加前缀“g_”。例如:
10. 类的数据成员加前缀 m_(表示member),这样可以避免数据成员与成员函数的参数同名。例如:
11. 为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为各种标识符加上能反映软件性质的前缀。例如三维图形标准 OpenGL 的所有库函数均以gl 开头,所有常量(或宏定义)均以 GL 开头。项目经理可以根据需要规定具体项目或函数库的前缀。
C++/C 语言的运算符有数十个,运算符的优先级与结合律如下表所示。注意一元运算符+-*的优先级高于对应的二元运算符。
表格4‑1运算符的优先级与结合律
优先级 |
运算符 |
结合律 |
从高到低排列 |
( ) [ ] ->. |
从左至右 |
! ~ + +-- (类型)sizeof + - * & |
从右至左 |
|
* / % |
从左至右 |
|
+ - |
从左至右 |
|
<< >> |
从左至右 |
|
< <= > >= |
从左至右 |
|
== != |
从左至右 |
|
& |
从左至右 |
|
^ |
从左至右 |
|
| |
从左至右 |
|
&& |
从左至右 |
|
|| |
从右至左 |
|
?: |
从右至左 |
|
= += -= *= /= %= &= ^= |= <<= >>= |
从左至右 |
1. 如果代码行中的运算符比较多,为了防止产生歧义并提高可读性,用括号确定表达式的操作顺序,尽量避免使用默认的优先级。例如:
2. 避免表达式的过深嵌套。表达式的嵌套层数定义为:忽略操作符优先级原则时,从左到右求表达式的值所需圆括号对的嵌套数。嵌套层数过多会使表达式难以理解。
如 a = b = c = 0 这样的表达式称为复合表达式。允许复合表达式存在的理由是:(1)书写简洁;(2)可以提高编译效率。但要防止滥用复合表达式。
1. 不要编写太复杂的复合表达式,例如:
2. 不要有多用途的复合表达式,例如:
该表达式既求 a 值又求 d 值。应该拆分为两个独立的语句:
3. 不要把程序中的复合表达式与“真正的数学表达式”混淆。例如:
并不表示
而是成了令人费解的
4. (C++代码)不要使用旧类型转换,而使用新类型转换符(dynamic_cast、static_cast、reinterpret_cast、const_cast)而不要使用旧类型转换符。同时,避免同时转换,特别是向下转换(将基类对象转换成派生类对象)。
5. (C++代码)BOOL(布尔)表达式使用新的bool类型。
6. 已删除的对象指针要赋予空指针值,设置已删除对象的指针为空指针可避免灾难的发生:重复删除非空指针是有害的,但重复删除空指针是无害的。即使在函数返回前,删除操作后也总要赋一个空指针,因为以后可能添加新的代码。
7. 不要使用难懂的技巧性很高的语句,除非很有必要时。高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。
[示例]如下表达式,考虑不周就可能出问题,也较难理解。
应分别改为如下。
1. 不可将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较。根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE 的值究竟是什么并没有统一的标准。
例如Visual C++将TRUE定义为1,而Visual Basic 则将TRUE定义为-1。
假设布尔变量名字为flag,它与零值比较的标准if语句如下:
其它的用法都属于不良风格,例如:
2. 应当将整型变量用“==”或“!=”直接与 0 比较。
假设整型变量的名字为value,它与零值比较的标准if语句如下:
不可模仿布尔变量的风格而写成
3. 不可将浮点变量用“==”或“!=”与任何数字比较。
千万要留意,无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避免将浮点变量用 “==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
假设浮点变量的名字为 x,E 是允许的误差(即精度),应当将
转化为
4. 应当将指针变量用“==”或“!=”与 NULL 比较。
指针变量的零值是“空”(记为 NULL)。尽管NULL的值与0相同,但是两者意义不同。假设指针变量的名字为p,它与零值比较的标准if语句如下:
不要写成
5. 当判断条件比较简单时,可以将 if/else/return 的组合,适当简练。例如:
改写成更加简练的
C++/C 循环语句中,for 语句使用频率最高,while 语句其次,do 语句很少用。本节重点论述循环体的效率。提高循环体效率的基本办法是降低循环体的复杂性。
【建议】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
示例 4‑1 低效率:长循环在最外层 示例4‑2 高效率:长循环在最内层
6. 【建议】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。例如,下面代码:
示例 4‑3 效率低但程序简洁 示例 4‑4 效率高但程序不简洁
前者比后者多执行了 N-1 次逻辑判断。并且由于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。如果 N 非常大,最好采用示后者的写法,可以提高效率。如果N 非常小,两者效率差别并不明显,采用前者的写法比较好,因为程序更加简洁。
禁止在 for 循环体内修改循环变量,防止 for 循环失去控制。
【建议】for 语句的循环控制变量的取值采用“半开半闭区间”写法。例如
示例 4‑5 循环变量属于半开半闭区间 示例4‑6 循环变量属于闭区间
前者的写法更加直观,尽管两者的功能是相同的。
1. 每个 case 语句的结尾不要忘了加 break,否则将导致多个分支重叠(除非有意使多个分支重叠)。
2. 不要忘记最后那个default 分支。即使程序真的不需要default 处理,也应该保留语句 default :break;这样做并非多此一举,而是为了防止别人误以为你忘了default处理。
3. 由于switch 语句最终以if/else 的方式实现,如果case的数目较多且程序对运行效率要求较高,必须考虑尽量将最常用到的case放到靠前位置,或者使用函数指针以提高代码速度。
【建议】少用、慎用 goto 语句。
1. 声明规则:数据结构的定义必须严格按照下列格式进行:
如果数据结构中的成员不能通过变量名称准确的表达其物理意义,则必须对该变量加上中文注释。
2. 当声明多个具有相同维数的变量数组时,必须先将所有的变量定义到一个数据结构中,再用该数据结构来声明一个结构变量数组或指针。
例如,下列数据结构
必须改写为
3. 在函数体中,当一个包含某数据结构变量或指针的逻辑表达式被引用 2 次以上时,必须先定义并初始化一个指向该数据结构的指针,然后在代码中用指针进行操作。
4. (C++代码)尽量使用类定义数据结构,而不是使用结构。
5. 结构的功能要单一,是针对一种事务的抽象(可以参考数据库定义的范式理论)。
6. 不同结构间的关系不要过于复杂。如果某个结构与其它结构关系较复杂,一般可以将两个结构合并。
7. 结构中元素的个数应适中。若结构中元素个数过多可考虑依据某种原则把元素组成不同的子结构,以减少原结构中元素的个数。
8. 仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间,并减少引起误用现象。
9. 结构的设计要尽量考虑向前兼容和以后的版本升级,并为某些未来可能的应用保留余地(如预留一些空间等)。软件向前兼容的特性,是软件产品是否成功的重要标志之一。如果要想使产品具有较好的前向兼容,那么在产品设计之初就应为以后版本升级保留一定余地,并且在产品升级时必须考虑前一版本的各种特性。
10. 对自定义数据类型进行恰当命名,使它成为自描述性的,以提高代码可读性。注意其命名方式在同一产品中的统一。
常量是一种标识符,它的值在运行期间恒定不变。C语言用#defin或enum来定义常量(称为宏常量)。C++语言除了#define外还可以用const来定义常量(称为 const 常量)。C++语言可以用const来定义常量,也可以用#define来定义常量。但是前者比后者有更多的优点:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。如果不使用常量,直接在程序中填写数字或字符串,程序的可读性(可理解性)变差。程序员自己会忘记那些数字或字符串是什么意思,用户则更加不知它们从何处来、表示什么。在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错。
1. 常量的命名必须使用英语准确表达相关的物理意义,开头必须使用大写的英文字母,单词之间必须用下划线分隔。例如要表示坐席卡上的最大通道数,应当定义为
#define MAX_USER_CH 8
而不是
#define MAXUSERCH 8
1. 常用的常量前缀为:
“S_”前缀: 表示状态的常量 “E_”前缀: 表示事件的常量 “C_”前缀: 表示程序中用到的常数 “MAX_”前缀: 表示上限的常量 “MIN_”前缀: 表示下限的常量 |
1. 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。例如:
2. 在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
3. 尽可能使用“可伸缩”常量。可伸缩常量避免了字长变化的问题。示例
1. 需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
2. 如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。例如:
有时我们希望某些常量只在类中有效。由于 #define 定义的宏常量是全局的,不能达到目的,于是想当然地觉得应该用 const 修饰数据成员来实现。 const 数据成员的确是存在的,但其含义却不是我们所期望的。const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同。不能在类声明中初始化 const 数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道 SIZE 的值是什么。
const 数据成员的初始化只能在类构造函数的初始化表中进行,例如
怎样才能建立在整个类中都恒定的常量呢?别指望const数据成员了,应该用类中的枚举常量来实现。例如
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159)。
函数是 C++/C 程序的基本功能单元,其重要性不言而喻。函数设计的细微缺点很容易导致该函数被错用,所以光使函数的功能正确是不够的。本章重点规定函数的接口设计和内部实现的一些规则。
函数接口的两个要素是参数和返回值。C语言中,函数的参数和返回值的传递方式有两种:值传递(pass by value)和指针传递(pass bypointer)。C++语言中多了引用传递(pass by reference)。
1. 函数外部特性注释必须在函数体上部采用中文说明,标准格式如下:
2. 函数中逻辑比较复杂的代码段必须用中文对每一个子功能代码段进行详细注释;
3. 输入参数的英文名称无法准确反映该参数物理特性,必须用中文详细说明该参数的含义;
4. 输入参数中各比特具有特定含义的,必须对每个比特(或比特组合)进行说明;
5. 输入参数必须满足一定范围,否则可能导致本函数产生非法操作的,必须在“注意事项”中表明;
6. 调用本函数时需要满足某些特定条件的(前置条件),必须将所有条件必须在“注意事项”中详细说明;
7. 函数实现功能较复杂的函数;
8. 公用代码库中的函数;
9. 使用的全局变量,特别是修改全局变量时;
1. 参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用 void 填充。例如:
2. 参数命名要恰当,顺序要合理。
例如,编写字符串拷贝函数 StringCopy,它有两个参数,如果把参数名字起为 str1 和 str2,那么很难搞清楚究竟是把 str1 拷贝到str2 中,还是刚好倒过来。可以把参数名字起得更有意义,如叫strSource 和strDestination。这样从名字上就可以看出应该把 strSource 拷贝到strDestination。
还有一个问题,这两个参数那一个该在前那一个该在后?参数的顺序要遵循程序员的习惯。一般地,应将目的参数放在前面,源参数放在后面。如果将函数声明为:
别人在使用时可能会不假思索地写成如下形式:
3. 如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改。例如:
[建议]对仅作为输入的参数,尽量使用const修饰符。
4. 如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
5. 参数缺省值只能出现在函数的声明中,而不能出现在定义体中。例如:
6. 如果函数有多个参数,参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样。例如:
7. 函数调用规则:当输入参数与函数声明的参数类型不一致时,必须显式地进行强制转换。例如,一个函数声明为
当使用2个int型参数nCurrentBId和nCurrentBCh去调用上述函数 时,必须写成
[注意](C++代码)简单类型转换可以采用该方法,建议采用 4.2.4 规则。
8. 【建议】避免函数有太多的参数,参数个数尽量控制在 5个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。
9. 【建议】尽量不要使用类型和数目不确定的参数。C标准库函数printf是采用不确定参数的典型代表,其原型为:
这种风格的函数在编译时丧失了严格的类型安全检查。
10. 【建议】非调度函数应减少或防止控制参数,尽量只使用数据参数。该规则可降低代码的控制耦合。
11. 避免重载以指针和整形数为实参的函数。
[说明] 例如以下两个重载操作 void f(char* p),void f(int i),如果以下调用可能会导致混淆:
1. 不要省略返回值的类型。
C语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为void类型。C++语言有很严格的类型安全检查,不允许上述情况发生。由于C++程序可以调用C函数,为了避免混乱,规定任何C++/C函数都必须有类型。如果函数没有返回值,那么应声明为 void 类型。
2. 函数名字与返回值类型在语义上不可冲突。
违反这条规则的典型代表是C标准库函数getchar。按照getchar名字的意思,将变量c声明为char类型是很自然的事情。但不幸的是getchar的确不是char类型,而是int类型,其原型如下:
由于c是char类型,取值范围是[-128, 127],如果宏EOF的值在char的取值范围之外,那么if语句将总是失败,这种“危险”人们一般哪里料得到!导致本例错误的责任并不在用户,是函数getchar误导了使用者。
3. 不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回。
4. 【建议】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。例如字符串拷贝函数 strcpy 的原型:
strcpy 函数将strSrc拷贝至输出参数strDest中,同时函数的返回值又是strDest。这样做并非多此一举,可以获得如下灵活性:
5. 【建议】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。
6. 在函数体的“出口处”,对 return 语句的正确性和效率进行检查。如果函数有返回值,那么函数的“出口处”是 return 语句。注意事项包括:
6.1. return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。例如
6.2. 要搞清楚返回的究竟是“值”、“指针”还是“引用”。如果函数返回值是一个对象,要考虑 return 语句的效率。
7. 【建议】函数要尽量只有一个返回点。
8. 不能返回引用和指针到局部对象。
[说明] 离开函数作用域时会销毁局部对象;使用销毁了的对象会造成灾难。
9. 不可返回由new初始化,之后又已解除引用的指针。
[说明]由于支持链式表达式,造成返回对象不能删除,导致内存泄漏。
1. 如果设计函数对输入参数有范围限制,并且被其它模块或程序调用,需要与调用函数者约定是否进行输入参数检查。如果没有途径与调用者进行沟通(例如语音部语音卡驱动程序的 API 函数),必须检查输入参数的合法性。
[提示] 在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责。说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。
2. (C++代码)使operator=检查自赋值。
[说明] 执行检查有两点重要的理由:首先,派生类对象的赋值涉及到调用每一个基类(在继承层次结构中位于此类的上方)的赋值操作符,跳过这些操作符就可以节省很多运行时间。其次,在复制“rvalue”对象前,赋值涉及到解构“lvalue”对象。在自赋值时,rvalue 对象在赋值前就已销毁了,因此赋值的结果是不确定的。
3. 函数体内的局部变量的声明与应用应遵循标准C的语法,即需要用到的所有局部变量在函数定义的开始部分统一进行声明。不能在首次引用的代码行中同时进行声明和定义。例如,下列代码是不允许的:
必须改写为
4. 推荐集中声明变量的原则处理简单变量。对象声明靠近其第一次使用点,同时在定义时初始化对象。
[说明] 如果对象不是自初始化的,在对象定义中指定初始值。如果不可能指定一个有意义的初始值,则赋值“NULL”或考虑后面再作定义。对大型对象,通常不建议先构造对象,此后再使用赋值语句初始化的作法。因为这样做的代价高昂。如果构造时不可能合适地初始化对象,则使用传统的“NULL”值进行对象初始化,它意味着“未初始化”。只在初始化过程中声明“不可用但值已知”的对象时才使用NULL值。但算法在受控模式下可以拒绝接受:在对象合适的初始化之前使用它时可以指出这是未经初始化的变量错误。
5. 赋值表达式的显式强制转换规则:在一个赋值表达式中,如果参与运算的各变量类型不一致,必须显示使用强制转换语法。例如:
正确写法为
6. 在程序内部检测到错误时,必须通过下列方式之一将该错误输出:
6.1. 使用OutputDebugString函数(用户态代码)或DbgPrint函数(内核态)将错误信息打印到DEBUGVIEW。
6.2. 记录到日志文件,错误信息的标准格式为:“fata error @函数名称(重要参数值):错误原因”,其中(重要参数值)为可选。
7. 编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
8. 防止将函数的参数作为工作变量。
9. 尽可能减少复杂性:
9.1. 不要书写过长的函数,例如其代码超过 100行(见规则7.5.2)。
9.2. 尽可能减少返回语句的数量,1 是理想值(见规则7.5.7)。
9.3. 尽量使循环复杂性降到10以下(对单一退出语句函数为判定语句总数加1)。
9.4. 尽量使扩展循环复杂性降到15 以下(对单一退出语句函数为判定语句+逻辑操作符+1的和)。
9.5. 尽量减小引用的平均最大作用范围(局部对象声明与对象第一次使用间的行距)。
1. 【建议】函数的功能要单一,不要设计多用途的函数。
2. 【建议】函数体的规模要小,尽量控制在100行代码之内(禁止超过 200 行的大型函数出现)。对运行效率要求很高的代码(如SYS 驱动程序)可以不接受本规范。
3. 【建议】确保函数是可测试的,相同的输入应当产生相同的输出。
说明:带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在 C/C++语言中,函数中的static局部变量具有“记忆”功能。建议尽量少用static局部变量,除非必需。
4. 【建议】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。
5. 【建议】用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。
6. 避免创建对全局有副作用的函数。
说明:应尽量避免创建对全局有副作用的函数,因为它可能改变并不知情的数据而非它们内部对象的状态,如全局和名字空间数据,但如果这是不可避免的,则应明确记录下所有的副作用以作为函数规约的一部分。只在所需的对象中传递参数增加了代码的上下文无关性,并提高了它的强壮性和易理解性。
7. 【建议】减少函数本身或函数间的递归调用。
说明:递归调用特别是函数间的递归调用(如 A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。
8. 【建议】设计高扇入、合理扇出(小于 7)的函数。
说明:扇出是指一个函数直接调用 (控制)其它函数的数目,而扇入是指有多少上级函数调用它。
9. 【建议】避免使用无意义或含义不清的动词为函数命名。
说明:避免用含义不清的动词如 process、handle 等为函数命名,因为这些动词并没有说明要具体做什么。
按照内存分配的位置不同,内存分配方式有三种:
1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
3. 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
程序员们经常编写内存管理程序,往往提心吊胆。同时,由于个人编程的习惯性缺陷,导致同类问题重复出现,造成程序代码质量低下。常见错误包括:
4. 内存分配未成功,却使用了它。
5. 内存分配虽然成功,但是尚未初始化就引用它。
6. 内存分配成功并且已经初始化,但操作越过了内存的边界。
7. 忘记了释放内存,造成内存泄露。
8. 释放了内存却继续使用它。包括以下三种情况:
8.1. 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
8.2. 函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
8.3. 使用 free 或 delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
C库函数malloc、calloc和realloc不应用于分配对象空间:此时应使用C++操作符new。只有在内存传递给C库函数处理时,才使用C函数分配内存。不要使用delete 来释放由C函数分配的内存,或释放由new 创建的对象。
动态内存分配的常见方法包括:
ü GlobalAlloc/GlobalFree:直接与每个进程的默认堆通讯的Microsoft Win32堆调用。 ü LocalAlloc/LocalFree:直接与每个进程的默认堆通讯的Win32堆调用(用于与Microsoft Windows NT的兼容性)。 ü COM 的 IMalloc分配器(或CoTaskMemAlloc/CoTaskMemFree):函数使用默认的每个进程堆。自动化使用组件对象模型 (COM)的分配器,而请求使用每进程堆。 ü C/C++运行时(CRT)分配器:提供malloc()和free()以及 new 和 delete 运算符。 |
COM组件编程时的智能指针和自动垃圾回收机制超出本文的返回,将来会在《组件编程规范》中定义。
1. 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为 NULL 的内存。
2. 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
3. 避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1”操作。
4. 动态内存的申请与释放必须配对,防止内存泄漏。
5. 用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
6. malloc返回值的类型是void*,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
7. 在malloc的“( )”中尽量使用sizeof运算符。
8. 用delete释放对象数组时,留意不要丢了符号‘[]’。
9. 在程序内部检测到错误时,必须通过下列方式之一将该错误输出:
9.1. 使用OutputDebugString函数(用户态代码)或DbgPrint函数(内核态)将错误信息打印到DEBUGVIEW;
9.2. 记录到日志文件
错误信息的标准格式为:
“fata error @函数名称(重要参数值):错误原因 ”,
其中(重要参数值)为可选。
10. 【建议】检查程序中内存申请/释放操作的成对性,防止内存泄漏。
1. 在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,并且要有详细的说明。
说明:本规则是针对项目组或产品组的。
2. 在同一项目组或产品组内,调测打印出的信息串的格式要有统一的形式。信息串中至少要有所在模块名(或源文件名)及行号。
说明:统一的调测信息格式便于集成测试。
3. 编程的同时要为单元测试选择恰当的测试点,并仔细构造测试代码、测试用例,同时给出明确的注释说明。测试代码部分应作为(模块中的)一个子模块,以方便测试代码在模块中的安装与拆卸(通过调测开关)。
说明:为单元测试而准备。
4. 在进行集成测试/系统联调之前,要构造好测试环境、测试项目及测试用例,同时仔细分析并优化测试用例,以提高测试效率。
说明:好的测试用例应尽可能模拟出程序所遇到的边界值、各种复杂环境及一些极端情况等。
5. 使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要做出处理的。
6. 在函数的入口处,使用断言检查参数的有效性(合法性)。
[说明]在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
[注意]断言是用来处理不应该发生的错误情况的,对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。
7. 【建议】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。
8. 正式发布的版本,必须使用RELEASE模式。不能用断言来检查最终产品肯定会出现且必须处理的错误情况。
9. 在软件系统中设置与取消有关测试手段,不能对软件实现的功能等产生影响。
说明:即有测试代码的软件和关掉测试代码的软件,在功能行为上应一致。
10. 用调测开关来切换软件的 DEBUG版和正式版,而不要同时存在正式版本和DEBUG 版本的不同源文件,以减少维护的难度。
11. 【建议】调测开关应分为不同级别和类型,通过开放不同的调试开关,进行不同级别的调试。
12. 【建议】编写防错程序,然后在处理错误之后可用断言宣布发生错误。
1. 打开编译器的所有告警开关对程序进行编译。
2. 在产品软件(项目组)中,要统一编译开关选项。
3. 【建议】通过代码走读及审查方式对代码进行检查。
说明:代码走读主要是对程序的编程风格如注释、命名等以及编程时易出错的内容进行检查,可由开发人员自己或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及程序的稳定性、安全性、可靠性等进行检查及评审,可通过自审、交叉审核或指定部门抽查等方式进行。
4. 【建议】测试部测试产品之前,应对代码进行抽查及评审。
5. 【建议】编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码丢失。
6. 【建议】同产品软件(项目组)内,最好使用相同的编辑器,并使用相同的设置选项。
说明:同一项目组最好采用相同的智能语言编辑器,并设计、使用一套缩进宏及注释宏等,将缩进等问题交由编辑器处理。
7. 【建议】要小心地使用编辑器提供的块拷贝功能编程。
说明:当某段代码与另一段代码的处理功能相似时,许多开发人员都用编辑器提供的块拷贝功能来完成这段代码的编写。由于程序功能相近,故所使用的变量、采用的表达式等在功能及命名上可能都很相近,所以使用块拷贝时要注意,除了修改相应的程序外,一定要把使用的每个变量仔细查看一遍,以改成正确的。不应指望编译器能查出所有这种错误,比如当使用的是全局变量时,就有可能使某种错误隐藏下来。
8. 【建议】合理地设计软件系统目录,方便开发人员使用。
说明:方便、合理的软件系统目录,可提高工作效率。目录构造的原则是方便有关源程序的存储、查询、编译、链接等工作,同时目录中还应具有工作目录----所有的编译、链接等工作应在此目录中进行,工具目录----有关文件编辑器、文件查找等工具可存放在此目录中。
9. 【建议】某些语句经编译后产生告警,但如果你认为它是正确的,那么应通过某种手段去掉告警信息。
说明:在 Borland C/C++中,可用“#pragma warn”来关掉或打开某些告警。
10. 【建议】使用代码检查工具对源程序检查。
11. 【建议】使用软件工具进行代码审查。
12. 【建议】工作目录结构的规定: project name(项目名称)
——bin 执行文件 ——log 日志文件 ——lib 库文件 ——include 头文件 ——src 源程序 ——dat 数据文件 |
在实际项目中,项目经理可以按照该建议作具体规定。
13. 【建议】工程中不起作用的文件或类应删除,工程目录下的非工程文件也应该移走,保持工程的清洁,避免混淆难于管理。
14. 【建议】在VC环境下,建议将常用的头文件全部放入stdafx.h中,而在每个cpp开始处嵌入stdafx.h。避免头文件的交叉引用,如果有严重的交叉引用,适当使用类的声明。
15. 【建议】将独立性比较强的模块抽出来,做成DLL,控件或COM组件,该模块可单独编写和测试,也增强了其可重用性。
16. 【建议】一个比较大的工程应留有一定的消息接口或插件接口等。
17. 【建议】工程的版本控制要严格,版本格式为xx.xx.xx,必要时使用Build次数或日期。高版本尽量兼容低版本的用法、数据或协议。
18. 【建议】工程的编译宏定义和工程参数设置应正确,每作一个新工程时应检查工程参数是否正确。建议字节对齐方式为1字节对齐。
1. 单元测试要求至少达到语句覆盖。
2. 单元测试开始要跟踪每一条语句,并观察数据流及变量的变化。
3. 【建议】清理、整理或优化后的代码要经过审查及测试。
4. 代码版本升级要经过严格测试。
5. 使用工具软件对代码版本进行维护。
6. 正式版本上软件的任何修改都应有详细的文档记录。
7. 建议】仔细设计并分析测试用例,使测试用例覆盖尽可能多的情况,以提高测试用例的效率。
8. 【建议】尽可能模拟出程序的各种出错情况,对出错处理代码进行充分的测试。
9. 【建议】仔细测试代码处理数据、变量的边界情况。
10. 【建议】保留测试信息,以便分析、总结经验及进行更充分的测试。
11. 【建议】不应通过“试”来解决问题,应寻找问题的根本原因。
12. 【建议】对自动消失的错误进行分析,搞清楚错误是如何消失的。
13. 【建议】修改错误不仅要治表,更要治本。
14. 【建议】测试时应设法使很少发生的事件经常发生。
15. 【建议】明确模块或函数处理哪些事件,并使它们经常发生。
16. 【建议】 坚持在编码阶段就对代码进行彻底的单元测试,不要等以后的测试工作来发现问题。
17. 【建议】去除代码运行的随机性(如去掉无用的数据、代码及尽可能防止并注意函数中的“内部寄存器”等),让函数运行的结果可预测,并使出现的错误可再现。
1. 源代码升级标记的格式:源代码升级标记包含了对相关代码进行修改的方式、作者、时间、修改原因等信息。设置源代码升级标记是为了可以追溯代码的更新历史。源代码升级标记必须按照下列标准格式书写:
方式 by 姓名简写 for 修改原因, 年.月.日
其中,
方式: masked=屏蔽,appended(或 added)=追加
姓名简写: 可以是修改者姓名的汉语拼音首字母缩写
修改原因: 可以用英语或中文简单说明本次代码修改的大致原因
年.月.日: 本次修改开始的时间
2. 被屏蔽代码行(或段)的标记规则:被屏蔽代码必须标记。标记方法为在被屏蔽代码行(或段)的上方一行加上源代码升级标记。
3. 新增加代码行(或段)的标记规则:新增加代码必须标记。
3.1. 对于长度较短的单行新增代码,可以直接将修改标记放置在该行的后面,例如
3.2. 对于长度较长的新增单行代码,也可以将修改标记放置在该行的上方后面,例如
3.3. 对于新增代码段,按照下列方式设置修改标记:
4. 【语音部】版本检查函数的升级:驱动初始化时,shp_a3.dll会通过函数HwGetDllVersion 检查硬件层各个DLL的版本号,硬件层DLL中又通过 IOCTL检查相应SYS文件的版本号。
当出现以下情况之一时,必须增加相应DLL或SYS的版本号,以防版本不对应引起的各种奇怪问题。
4.1. 硬件层为shp_a3.dll增加新的输出函数时,需提高硬件DLL本身的版本号和shp_a3.dll中的相应检查。
4.2. SYS与DLL的IOCTL接口改变时,需提高SYS的版本号和硬件DLL中的相应检查。
5. 文件升级更改记录:
源代码文件的顶部区域为文件升级备忘录区,详细记录对该文件的每一次改动。由于前述的修改代码升级所涉及的源代码升级标记对修改原因记录较为简单。因此,必须在文件升级备忘录区将本次修改的来龙去脉说清楚。具体方式为:
1. 宏编译指令的书写也必须尽量使用TAB键区分嵌套层次。当一段由#ifdef、#else 和#endif描述的编译指令中没有其它数据结构、变量或函数声明代码时,必须使用TAB键来区分被包括部分的嵌套层次。例如下列编译控制:
必须改写为
2. 不要一味地追求程序的效率,应当在满足正确性、可靠性、健壮性、可读性等质量因素的前提下,设法提高程序的效率。
3. 以提高程序的全局效率为主,提高局部效率为辅。
4. 在优化程序的效率时,应当先找出限制效率的“瓶颈”,不要在无关紧要之处优化。
5. 先优化数据结构和算法,再优化执行代码。
6. 有时候时间效率和空间效率可能对立,此时应当分析那个更重要,作出适当的折衷。例如多花费一些内存来提高性能。
7. 不要追求紧凑的代码,因为紧凑的代码并不能产生高效的机器码。
8. 使用第三方提供的软件开发工具包或控件时,要注意以下几点:
8.1. 充分了解应用接口、使用环境及使用时注意事项。
8.2. 不能过分相信其正确性。
8.3. 除非必要,不要使用不熟悉的第三方工具包与控件。
说明:使用工具包与控件,可加快程序开发速度,节省时间,但使用之前一定对它有较充分的了解,同时第三方工具包与控件也有可能存在问题。
9. 系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。
10. 系统运行之初,要对加载到系统中的数据进行一致性检查。
说明:使用不一致的数据,容易使系统进入混乱状态和不可知状态。
1. 【建议】当心那些视觉上不易分辨的操作符发生书写错误。我们经常会把“==”误写成“=”,象“||”、“&&”、“<=”、“>=”这类符号也很容易发生“丢 1”失误。然而编译器却不一定能自动指出这类错误。
2. 【建议】变量(指针、数组)被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。
3. 【建议】当心变量的初值、缺省值错误,或者精度不够。
4. 【建议】当心数据类型转换发生错误。尽量使用显式的数据类型转换(让人们知道发生了什么事),避免让编译器轻悄悄地进行隐式的数据类型转换。
5. 【建议】当心变量发生上溢或下溢,数组的下标越界。
6. 【建议】当心忘记编写错误处理程序,当心错误处理程序本身有误。
7. 【建议】当心文件 I/O 有错误。
8. 【建议】避免编写技巧性很高代码。
9. 【建议】不要设计面面俱到、非常灵活的数据结构。
10. 【建议】如果原有的代码质量比较好,尽量复用它。但是不要修补很差劲的代码,应当重新编写。
11. 【建议】尽量使用标准库函数,不要“发明”已经存在的库函数。
12. 【建议】尽量不要使用与具体硬件或软件环境关系密切的变量。
13. 【建议】把编译器的选择项设置为最严格状态。
14. 【建议】资源文件(多语言版本支持),如果资源是对语言敏感的,应让该资源与源代码文件脱离,具体方法有下面几种:使用单独的资源文件、DLL文件或其它单独的描述文件(如数据库格式)。