目录
1 概述 5
1.1 编写目的 5
1.2 文档约定 5
1.3 预期的读者和阅读建议 5
1.4 参考文献 5
2 排版要求 5
2.1 程序块缩进 5
2.2 程序块之间空行 5
2.3 长语句和长表达式 6
2.4 循环、判断等长表达式或语句 7
2.5 长参数 7
2.6 短语句 8
2.7 条件、循环语句 8
2.8 语句对齐 8
2.9 函数、过程和结构等语句块 9
2.10 程序块分界符 9
2.11 操做符先后空格 10
2.12 其余 11
3 注释 11
3.1 有效注释量 11
3.2 公司标识 11
3.3 说明性文件 12
3.4 源文件头 13
3.5 函数头部说明 13
3.6 注释与代码一致 14
3.7 注释内容 14
3.8 注释缩写 14
3.9 注释位置 14
3.10 变量、常量注释 15
3.11 数据结构的注释 15
3.12 全局变量 16
3.13 注释缩排 16
3.14 注释与代码之间空行 17
3.15 变量定义、分支语句 17
3.16 其余 19
4 标识符命名 20
4.1 命名清晰 20
4.2 特殊命名需注释 21
4.3 命名风格保持一致 21
4.4 变量命名 21
4.5 命名规范与系统风格一致 21
4.6 其余 22
5 可读性 23
5.1 运算符优先级 23
5.2 避免直接使用数字做为标识符 23
5.3 其余 24
6 变量、结构 25
6.1 公共变量 25
6.2 公共变量说明 25
6.3 公共变量访问说明 25
6.4 公共变量赋值 26
6.5 防止局部变量与公共变量同名。 26
6.6 严禁使用未经初始化的变量做为右值。 26
6.7 其余 26
7 函数、过程 34
7.1 对所调用函数的错误返回码要仔细、全面地处理。 34
7.2 明确函数功能,精确(而不是近似)地实现函数设计。 34
7.3 局部变量 34
7.4 全局变量 34
7.5 接口函数参数 35
7.6 其余 35
8 可测性 44
8.1 调测开关 44
8.2 打印信息 45
8.3 单元测试 45
8.4 集成测试 45
8.5 断言使用 45
8.6 设置与取消有关测试手段时,不能影响软件功能功能 48
8.7 版本维护 48
8.8 其余 48
9 程序效率 50
9.1 编程时要常常注意代码的效率。 50
9.2 提升代码效率 50
9.3 全局效率高于局部效率 51
9.4 提升代码空间效率 51
9.5 循环体内工做量最小化 52
9.6 其余 53
10 质量保证 56
10.1 在软件设计过程当中构筑软件质量。 56
10.2 代码质量保证优先原则 56
10.3 只引用属于本身的存贮空间。 56
10.4 防止引用已经释放的内存空间。 56
10.5 内存及时释放 57
10.6 文件句柄及时关闭 57
10.7 防止内存操做越界 58
10.8 认真处理程序所能遇到的各类出错状况 59
10.9 初始化变量 59
10.10 数据一致性检查 59
10.11 严禁随意更改其它模块或系统的有关设置和配置 59
10.12 不能随意改变与其它模块的接口 59
10.13 系统接口 59
10.14 编程时,要防止差1错误 61
10.15 操做符检查 61
10.16 分支语句写完整 62
10.17 使用return语句 62
10.18 不要滥用goto语句 62
10.19 其余 62
11 代码编辑、编译、审查 65
11.1 打开编译器的全部告警开关对程序进行编译 65
11.2 在产品软件(项目组)中,要统一编译开关选项 65
11.3 经过代码走读及审查方式对代码进行检查。 65
11.4 测试部测试产品以前,应对代码进行抽查及评审 65
11.5 其余 65
12 代码测试、维护 67
12.1 单元测试要求至少达到语句覆盖 67
12.2 单元测试开始要跟踪每一条语句,并观察数据流及变量的变化 67
12.3 清理、整理或优化后的代码要通过审查及测试。 67
12.4 代码版本升级要通过严格测试 67
12.5 使用工具软件对代码版本进行维护 67
12.6 正式版本上软件的任何修改都应有详细的文档记录 67
12.7 其余 67
13 宏 68
13.1 用宏定义表达式时,要使用完备的括号 68
13.2 将宏所定义的多条表达式放在大括号中 68
13.3 使用宏时,不容许参数发生变化 69算法
1 概述
1.1 编写目的
为规范软件开发人员的代码编写提供参考依据和统一标准。
1.2 文档约定
说明本文档中所用到的专用术语定义或解释,缩略词定义。
1.3 预期的读者和阅读建议
本文档适用于全部软件开发人员。
1.4 参考文献
列出有关的参考文件,如:
a.属于本项目的其余已发表文件;
b.本文件中各处引用的文档资料。
列出这些文件的标题、做者,说明可以获得这些文件资料的来源。
2 排版要求
2.1 程序块缩进
程序块要采用缩进风格编写,缩进的空格数为4个。
说明:对于由开发工具自动生成的代码能够有不一致。
2.2 程序块之间空行
相对独立的程序块之间、变量说明以后必须加空行。
示例:以下例子不符合规范。
if (!valid_ni(ni))
{
… // program code
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;数据库
应以下书写
if (!valid_ni(ni))
{
… // program code
}编程
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
2.3 长语句和长表达式
较长的语句(>80字符)要分红多行书写,长表达式要在低优先级操做符处划分新行,操做符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
示例:
perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN
+ STAT_SIZE_PER_FRAM * sizeof( _UL );数组
act_task_table[frame_id * STAT_TASK_CHECK_NUMBER + index].occupied
= stat_poi[index].occupied;安全
act_task_table[taskno].duration_true_or_false
= SYS_get_sccp_statistic_state( stat_item );markdown
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
&& (n7stat_stat_item_valid (stat_item))
&& (act_task_table[taskno].result_data != 0));
2.4 循环、判断等长表达式或语句
循环、判断等语句中如有较长的表达式或语句,则要进行适应的划分,长表达式要在低优先级操做符处划分新行,操做符放在新行之首。
示例:
if ((taskno < max_act_task_number)
&& (n7stat_stat_item_valid (stat_item)))
{
… // program code
}数据结构
for (i = 0, j = 0; (i < BufferKeyword[word_index].word_length)
&& (j < NewKeyword.word_length); i++, j++)
{
… // program code
}多线程
for (i = 0, j = 0;
(i < first_word_length) && (j < second_word_length);
i++, j++)
{
… // program code
}
2.5 长参数
若函数或过程当中的参数较长,则要进行适当的划分。
示例:
n7stat_str_compare((BYTE *) & stat_object,
(BYTE *) & (act_task_table[taskno].stat_object),
sizeof (_STAT_OBJECT));less
n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER
+ index, stat_object );
2.6 短语句
不容许把多个短语句写在一行中,即一行只写一条语句。
示例:以下例子不符合规范。
rect.length = 0; rect.width = 0;编程语言
应以下书写
rect.length = 0;
rect.width = 0;
2.7 条件、循环语句
if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分不管多少都要加括号{}。
示例:以下例子不符合规范。
if (pUserCR == NULL) return;
应以下书写:
if (pUserCR == NULL)
{
return;
}
2.8 语句对齐
对齐只使用空格键,不使用TAB键。
说明:以避免用不一样的编辑器阅读程序时,因TAB键所设置的空格数目不一样而形成程序布局不整齐,
不要使用BC做为编辑器合版本,由于BC会自动将8个空格变为一个TAB键,所以使用BC合入的版本大多会将缩进变乱。
2.9 函数、过程和结构等语句块
函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case语句下的状况处理语句也要听从语句缩进要求。
2.10 程序块分界符
程序块的分界符(如C/C++语言的大括号‘{’和‘}’)应各独占一行而且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。
示例:以下例子不符合规范。
for (…) {
… // program code
}
if (…)
{
… // program code
}
void example_fun( void )
{
… // program code
}
应以下书写。
for (…)
{
… // program code
}
if (…)
{
… // program code
}
void example_fun( void )
{
… // program code
}
2.11 操做符先后空格
在两个以上的关键字、变量、常量进行对等操做时,它们之间的操做符以前、以后或者先后要加空格;进行非对等操做时,若是是关系密切的当即操做符(如->),后不该加空格。
说明:采用这种松散方式编写代码的目的是使代码更加清晰。
因为留空格所产生的清晰性是相对的,因此,在已经很是清晰的语句中没有必要再留空格,若是语句已足够清晰则括号内侧(即左括号后面和右括号前面)不须要加空格,多重括号间没必要加空格,由于在C/C++语言中括号已是最清晰的标志了。
在长语句中,若是须要加的空格很是多,那么应该保持总体清晰,而在局部不加空格。给操做符留空格时不要连续留两个以上空格。
示例:
(1) 逗号、分号只在后面加空格。
int a, b, c;
(2)比较操做符, 赋值操做符”=”、 “+=”,算术操做符”+”、”%”,逻辑操做符”&&”、”&”,位域操做符”<<”、”^”等双目操做符的先后加空格。
if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
(3)”!”、”~”、”++”、”–”、”&”(地址运算符)等单目操做符先后不加空格。
p = ‘a’; // 内容操做”“与内容之间
flag = !isEmpty; // 非操做”!”与内容之间
p = &mem; // 地址操做”&” 与内容之间
i++; // “++”,”–”与内容之间
(4)”->”、”.”先后不加空格。
p->id = pid; // “->”指针先后不加空格
(5) if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。
if (a >= b && c > d)
2.12 其余
2.12.1 一行程序以小于80字符为宜,不要写得过长。
3 注释
3.1 有效注释量
通常状况下,源程序有效注释量必须在20%以上。
说明:注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不
能太少,注释语言必须准确、易懂、简洁。
3.2 公司标识
在头文件中加入公司标识。
示例以下:
****************************************************************/
/* */
/* Copyright (c) 1996-1998 XXXXXX Company */
/* xxxxxxxx 版权全部 1996-1998 */
/* */
/* PROPRIETARY RIGHTS of XXXXXX Company are involved in the */
/* subject matter of this material. All manufacturing, reproduction, use,*/
/* and sales rights pertaining to this subject matter are governed by the */
/* license agreement. The recipient of this software implicitly accepts */
/* the terms of the license. */
/* 本软件文档资料是xxx公司的资产,任何人士阅读和使用本资料必须得到 */
/* 相应的书面受权,承担保密责任和接受相应的法律约束. */
/* */
/****************************************************************/
3.3 说明性文件
说明性文件(如头文件.h文件、.inc文件、.def文件、编译说明文件.cfg等)头部应进行注释,注释必须列出:版权说明、版本号、生成日期、做者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
示例:下面这段头文件的头注释比较标准,固然,并不局限于此格式,但上述信息建议要包含在内。
/***************************************
Copyright (C), 1996-1998, xxxxx. Co., Ltd.
File name: // 文件名
Author: Version: Date: // 做者、版本及完成日期
Description: // 用于详细说明此程序文件完成的主要功能,与其余模块
// 或函数的接口,输出值、取值范围、含义及参数间的控
// 制、顺序、独立或依赖等关系
Others: // 其它内容的说明
Function List: // 主要函数列表,每条记录应包括函数名及功能简要说明
1. ….
History: // 修改历史记录列表,每条修改记录应包括修改日期、修改
// 者及修改内容简述
1. Date:
Author:
Modification:
2. …
***************************************/
3.4 源文件头
源文件头部应进行注释,列出:版权说明、版本号、生成日期、做者、模块目的/功能、主要函数及其功能、修改日志等。
示例:下面这段源文件的头注释比较标准,固然,并不局限于此格式,但上述信息建议要包含在内。
/**************************************************
Copyright (C), 1988-1999, Xxxxxx Tech. Co., Ltd.
FileName: test.cpp
Author: Version : Date:
Description: // 模块描述
Version: // 版本信息
Function List: // 主要函数及其功能
1. ——-
History: // 历史修改记录
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
例2:
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
/* get replicate sub system index and net indicator */
应以下书写
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
3.10 变量、常量注释
对于全部有物理含义的变量、常量,若是其命名不是充分自注释的,在声明时都必须加以注释,说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。
示例:
/* active statistic task number */
3.11 数据结构的注释
数据结构声明(包括数组、结构、类、枚举等),若是其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每一个域的注释放在此域的右方。
示例:可按以下形式说明枚举/数据/联合结构。
/* sccp interface with sccp user primitive message name */
enum SCCP_USER_PRIMITIVE
{
N_UNITDATA_IND, /* sccp notify sccp user unit data come */
N_NOTICE_IND, /* sccp notify user the No.7 network can not */
/* transmission this message */
N_UNITDATA_REQ, /* sccp user’s unit data transmission request*/
};
3.12 全局变量
全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。
示例:
/* The ErrorCode when SCCP translate */
/* Global Title failure, as follows */ // 变量做用、含义
/* 0 - SUCCESS 1 - GT Table error */
/* 2 - GT error Others - no use */ // 变量取值范围
/* only function SCCPTranslate() in */
/* this modual can modify it, and other */
/* module can visit it through call */
/* the function GetGTTransErrorCode() */ // 使用方法
BYTE g_GTTranErrorCode;
3.13 注释缩排
注释与所描述内容进行一样的缩排。
说明:可以使程序排版整齐,并方便注释的阅读与理解。
示例:以下例子,排版不整齐,阅读稍感不方便。
void example_fun( void )
{
/* code one comments */
CodeBlock One
/* code two comments */ CodeBlock Two
}
应改成以下布局。
void example_fun( void )
{
/* code one comments */
CodeBlock One
/* code two comments */ CodeBlock Two
}
3.14 注释与代码之间空行
将注释与其上面的代码用空行隔开。
示例:以下例子,显得代码过于紧凑。
/* code one comments */
program code one
/* code two comments */
program code two
应以下书写
/* code one comments */
program code one
/* code two comments */
program code two
3.15 变量定义、分支语句
对变量的定义和分支语句(条件分支、循环语句等)必须编写注释。
说明:这些语句每每是程序实现某一特定功能的关键,对于维护人员来讲,良好的注释帮助更好的理解程序,有时甚至优于看设计文档。
示例(注意斜体加粗部分):
case CMD_UP:
ProcessUp();
break;
case CMD_DOWN:
ProcessDown();
break;
case CMD_FWD:
ProcessFwd();
if (…)
{
…
break;
}
else
{
ProcessCFW_B(); // now jump into case CMD_A
}
case CMD_A:
ProcessA();
break;
case CMD_B:
ProcessB();
break;
case CMD_C:
ProcessC();
break;
case CMD_D:
ProcessD();
break;
…
3.16 其余
3.16.1 避免在一行代码或表达式的中间插入注释。
说明:除非必要,不该在代码或表达中间插入注释,不然容易使代码可理解性变差。
3.16.2 经过对函数或过程、变量、结构等正确的命名以及合理地组织代码的结构,使代码成为自注释的。
说明:清晰准确的函数、变量等的命名,可增长代码可读性,并减小没必要要的注释。
3.16.3 在代码的功能、意图层次上进行注释,提供有用、额外的信息。
说明:注释的目的是解释代码的目的、功能和采用的方法,提供代码之外的信息,帮助读者理解代码,防止不必的重复注释信息。
示例:以下注释意义不大。
/* if receive_flag is TRUE */
if (receive_flag)
而以下的注释则给出了额外有用的信息。
/* if mtp receive a message from links */
if (receive_flag)
3.16.4 在程序块的结束行右方加注释标记,以代表某程序块的结束。
说明:当代码段较长,特别是多重嵌套时,这样作可使代码更清晰,更便于阅读。
示例:参见以下例子。
if (…)
{
// program code
while (index < MAX_INDEX) { // program code } /* end of while (index < MAX_INDEX) */ // 指明该条while语句结束
} /* end of if (…)*/ // 指明是哪条if语句结束
3.16.5 注释格式尽可能统一,建议使用“/* …… */”。
3.16.6 注释应考虑程序易读及外观排版的因素,使用的语言如果中、英兼有的,建议多使用中文,除非能用很是流利准确的英文表达。
说明:注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用
中文。
4 标识符命名
4.1 命名清晰
标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或你们基本能够理解的缩写,避免令人产生误解。
说明:较短的单词可经过去掉“元音”造成缩写;较长的单词可取单词的头几个字母造成
缩写;一些单词有你们公认的缩写。
示例:以下单词的缩写可以被你们基本承认。
temp 可缩写为 tmp ;
flag 可缩写为 flg ;
statistic 可缩写为 stat ;
increment 可缩写为 inc ;
message 可缩写为 msg ;
4.2 特殊命名需注释
命名中若使用特殊约定或缩写,则要有注释说明。
说明:应该在源文件的开始之处,对文件中所使用的缩写或约定,特别是特殊的缩写,进
行必要的注释说明。
4.3 命名风格保持一致
本身特有的命名风格,要自始至终保持一致,不可来回变化。
说明:我的的命名风格,在符合所在项目组或产品组的命名规则的前提下,才可以使用。(即
命名规则中没有规定到的地方才可有我的命名风格)。
4.4 变量命名
对于变量命名,禁止取单个字符(如i、j、k…),建议除了要有具体含义外,还能代表其变量类型、数据类型等,但i、j、k做局部循环变量是容许的。
说明:变量,尤为是局部变量,若是用单个字符表示,很容易敲错(如i写成j),而编译时又检查不出来,有可能为了这个小小的错误而花费大量的查错时间。
示例:下面所示的局部变量名的定义方法能够借鉴。
int liv_Width
其变量名解释以下:
l 局部变量(Local) (其它:g 全局变量(Global)…)
i 数据类型(Interger)
v 变量(Variable) (其它:c 常量(Const)…)
Width 变量含义
这样能够防止局部变量与全局变量重名。
4.5 命名规范与系统风格一致
命名规范必须与所使用的系统风格保持一致,并在同一项目中统一,好比采用UNIX的全小写加下划线的风格或大小写混排的方式,不要使用大小写与下划线混排的方式,用做特殊标识如标识成员变量或全局变量的m_和g_,其后加上大小写混排的方式是容许的。
示例: Add_User不容许,add_user、AddUser、m_AddUser容许。
4.6 其余
4.6.1 除非必要,不要用数字或较奇怪的字符来定义标识符。
示例:以下命名,令人产生疑惑。
void set_sls00( BYTE sls );
应改成有意义的单词命名
void set_udt_msg_sls( BYTE sls );
4.6.2 在同一软件产品内,应规划好接口部分标识符(变量、结构、函数及常量)的命名,防止编译、连接时产生冲突。
说明:对接口部分的标识符应该有更严格限制,防止冲突。如可规定接口部分的变量与常量以前加上“模块”标识等。
4.6.3 用正确的反义词组命名具备互斥意义的变量或相反动做的函数等。
说明:下面是一些在软件中经常使用的反义词组。
add / remove begin / end create / destroy
insert / delete first / last get / release
increment / decrement put / get
add / delete lock / unlock open / close
min / max old / new start / stop
next / previous source / target show / hide
send / receive source / destination
cut / paste up / down
示例:
int min_sum;
int max_sum;
int add_user( BYTE *user_name );
int delete_user( BYTE *user_name );
3.6.4 除了编译开关/头文件等特殊应用,应避免使用EXAMPLE_TEST之类如下划线开始和结尾的定义。
5 可读性
5.1 运算符优先级
注意运算符的优先级,并用括号明确表达式的操做顺序,避免使用默认优先级。
说明:防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而致使程序出错。
示例:下列语句中的表达式
word = (high << 8) | low (1)
if ((a | b) && (a & c)) (2)
if ((a | b) < (c & d)) (3)
若是书写为
high << 8 | low
a | b && a & c
a | b < c & d
因为
high << 8 | low = ( high << 8) | low,
a | b && a & c = (a | b) && (a & c),
(1)(2)不会出错,但语句不易理解;
a | b < c & d = a | (b < c) & d,(3)形成了判断条件出错。
5.2 避免直接使用数字做为标识符
避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不该直接使用数字,必须用有意义的枚举或宏来代替。
示例:以下的程序可读性差。
if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
… // program code
}
应改成以下形式。
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
… // program code
}
5.3 其余
5.3.1 源程序中关系较为紧密的代码应尽量相邻。
说明:便于程序阅读和查找。
示例:如下代码布局不太合理。
rect.length = 10;
char_poi = str;
rect.width = 5;
若按以下形式书写,可能更清晰一些。
rect.length = 10;
rect.width = 5; // 矩形的长与宽关系较密切,放在一块儿。
char_poi = str;
5.3.2 不要使用难懂的技巧性很高的语句,除非颇有必要时。
说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。
示例:以下表达式,考虑不周就可能出问题,也较难理解。
* stat_poi ++ += 1;
应分别改成以下。
*stat_poi += 1;
stat_poi++; // 此二语句功能至关于“ * stat_poi ++ += 1; ”
++ stat_poi;
stat_poi += 1; // 此二语句功能至关于“ ++ stat_poi += 1;
6 变量、结构
6.1 公共变量
去掉不必的公共变量。
说明:公共变量是增大模块间耦合的缘由之一,故应减小不必的公共变量以下降模块间
的耦合度。
6.2 公共变量说明
仔细定义并明确公共变量的含义、做用、取值范围及公共变量间的关系。
说明:在对变量声明的同时,应对其含义、做用及取值范围进行注释说明,同时如有必要
还应说明与其它变量的关系。
6.3 公共变量访问说明
明确公共变量与操做此公共变量的函数或过程的关系,如访问、修改及建立等。
说明:明确过程操做变量的关系后,将有利于程序的进一步优化、单元测试、系统联调以
及代码维护等。这种关系的说明可在注释或文档中描述。
示例:在源文件中,可按以下注释形式说明。
RELATION System_Init Input_Rec Print_Rec Stat_Score
Student Create Modify Access Access
Score Create Modify Access Access, Modify
注:RELATION为操做关系;System_Init、Input_Rec、Print_Rec、Stat_Score为四个不一样的函数;Student、Score为两个全局变量;Create表示建立,Modify表示修改,Access表示访问。
其中,函数Input_Rec、Stat_Score均可修改变量Score,故此变量将引发函数间较大的耦合,并可能增长代码测试、维护的难度。
6.4 公共变量赋值
当向公共变量传递数据时,要十分当心,防止赋与不合理的值或越界等现象发生。
说明:对公共变量赋值时,如有必要应进行合法性检查,以提升代码的可靠性、稳定性。
6.5 防止局部变量与公共变量同名。
说明:若使用了较好的命名规则,那么此问题可自动消除。
6.6 严禁使用未经初始化的变量做为右值。
说明:特别是在C/C++中引用未经赋值的指针,常常会引发系统崩溃。
6.7 其余
6.7.1 防止多个模块共用公共变量
构造仅有一个模块或函数能够修改、建立,而其他有关模块或函数只访问的公共变量,防止多个不一样模块或函数均可以修改、建立同一公共变量的现象。
说明:下降公共变量耦合度。
6.7.2 数据类型
使用严格形式定义的、可移植的数据类型,尽可能不要使用与具体硬件或软件环境关系密切的变量。
说明:使用标准的数据类型,有利于程序的移植。
示例:以下例子(在DOS下BC3.1环境中),在移植时可能产生问题。
void main()
{
register int index; // 寄存器变量
_AX = 0x4000; // _AX是BC3.1提供的寄存器“伪变量” ... // program code
}
6.7.3 结构
结构的功能要单一,是针对一种事务的抽象。
说明:设计结构时应力争使结构表明一种现实事务的抽象,而不是同时表明多种。结构中
的各元素应表明同一事务的不一样侧面,而不该把描述没有关系或关系很弱的不一样事务的元素放到同一结构中。
示例:以下结构不太清晰、合理。
typedef struct STUDENT_STRU
{
unsigned char name[8]; /* student’s name */
unsigned char age; /* student’s age */
unsigned char sex; /* student’s sex, as follows */
/* 0 - FEMALE; 1 - MALE */
unsigned char
teacher_name[8]; /* the student teacher’s name */
unisgned char
teacher_sex; /* his teacher sex */
} STUDENT;
若改成以下,可能更合理些。
typedef struct TEACHER_STRU
{
unsigned char name[8]; /* teacher name */
unisgned char sex; /* teacher sex, as follows */
/* 0 - FEMALE; 1 - MALE */
} TEACHER;
typedef struct STUDENT_STRU
{
unsigned char name[8]; /* student’s name */
unsigned char age; /* student’s age */
unsigned char sex; /* student’s sex, as follows */
/* 0 - FEMALE; 1 - MALE */
unsigned int teacher_ind; /* his teacher index */
} STUDENT;
6.7.4 不要设计面面俱到、很是灵活的数据结构
说明:面面俱到、灵活的数据结构反而容易引发误解和操做困难。
6.7.5 不一样结构间的关系不要过于复杂
说明:若两个结构间关系较复杂、密切,那么应合为一个结构。
示例:以下两个结构的构造不合理。
typedef struct PERSON_ONE_STRU
{
unsigned char name[8];
unsigned char addr[40];
unsigned char sex;
unsigned char city[15];
} PERSON_ONE;
typedef struct PERSON_TWO_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char tel;
} PERSON_TWO;
因为两个结构都是描述同一事物的,那么不如合成一个结构。
typedef struct PERSON_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char sex;
unsigned char addr[40];
unsigned char city[15];
unsigned char tel;
} PERSON;
6.7.6 结构中元素的个数应适中
若结构中元素个数过多可考虑依据某种原则把元素组成不一样的子结构,以减小原结构中元素的个数。
说明:增长结构的可理解性、可操做性和可维护性。
示例:假如认为如上的_PERSON结构元素过多,那么可以下对之划分。
typedef struct PERSON_BASE_INFO_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char sex;
} PERSON_BASE_INFO;
typedef struct PERSON_ADDRESS_STRU
{
unsigned char addr[40];
unsigned char city[15];
unsigned char tel;
} PERSON_ADDRESS;
typedef struct PERSON_STRU
{
PERSON_BASE_INFO person_base;
PERSON_ADDRESS person_addr;
} PERSON;
6.7.7 结构中元素布局和排列
仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间,并减小引发误用现象。
说明:合理排列结构中元素顺序,可节省空间并增长可理解性。
示例:以下结构中的位域排列,将占较大空间,可读性也稍差。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
PERSON person;
unsigned int set_flg: 1;
} EXAMPLE;
若改为以下形式,不只可节省1字节空间,可读性也变好了。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
unsigned int set_flg: 1;
PERSON person ;
} EXAMPLE;
6.7.8 结构中的保留项
结构的设计要尽可能考虑向前兼容和之后的版本升级,并为某些将来可能的应用保留余地(如预留一些空间等)。
说明:软件向前兼容的特性,是软件产品是否成功的重要标志之一。若是要想使产品具备
较好的前向兼容,那么在产品设计之初就应为之后版本升级保留必定余地,而且在产品升级时必须考虑前一版本的各类特性。
6.7.9 留心具体语言及编译器处理不一样数据类型的原则及有关细节。
说明:如在C语言中,static局部变量将在内存“数据区”中生成,而非static局部
变量将在“堆栈”中生成。这些细节对程序质量的保证很是重要。
6.7.10 编程时,要注意数据类型的强制转换。
说明:当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,
而这些细节若考虑不周,就颇有可能留下隐患。
6.7.11 对编译系统默认的数据类型转换,也要有充分的认识。
示例:以下赋值,多数编译器不产生告警,但值的含义仍是稍有变化。
char chr;
unsigned short int exam;
chr = -1;
exam = chr; // 编译器不产生告警,此时exam为0xFFFF。
6.7.12 尽可能减小没有必要的数据类型默认转换与强制转换。
6.7.13 合理地设计数据并使用自定义数据类型,避免数据间进行没必要要的类型转换。
6.7.14 对自定义数据类型进行恰当命名,使它成为自描述性的,以提升代码可读性。注意其命名方式在同一产品中的统一。
说明:使用自定义类型,能够弥补编程语言提供类型少、信息量不足的缺点,并能使程序
清晰、简洁。
示例:可参考以下方式声明自定义数据类型。
下面的声明可以使数据类型的使用简洁、明了。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
下面的声明可以使数据类型具备更丰富的含义。
typedef float DISTANCE;
typedef float SCORE;
6.7.15 当声明用于分布式环境或不一样CPU间通讯环境的数据结构时,必须考虑机器的字节顺序、使用的位域及字节对齐等问题 。
说明:好比Intel CPU与68360 CPU,在处理位域及整数时,其在内存存放的“顺序”
正好相反。
示例:假若有以下短整数及结构。
unsigned short int exam;
typedef struct EXAM_BIT_STRU
{ /* Intel 68360 */
unsigned int A1: 1; /* bit 0 7 */
unsigned int A2: 1; /* bit 1 6 */
unsigned int A3: 1; /* bit 2 5 */
} EXAM_BIT;
以下是Intel CPU生成短整数及位域的方式。
内存: 0 1 2 … (从低到高,以字节为单位)
exam exam低字节 exam高字节
内存: 0 bit 1 bit 2 bit … (字节的各“位”)
EXAM_BIT A1 A2 A3
以下是68360 CPU生成短整数及位域的方式。
内存: 0 1 2 … (从低到高,以字节为单位)
exam exam高字节 exam低字节
内存: 7 bit 6 bit 5 bit … (字节的各“位”)
EXAM_BIT A1 A2 A3
说明:在对齐方式下,CPU的运行效率要快得多。
示例:以下图,当一个long型数(如图中long1)在内存中的位置正好与内存的字边界对齐时,CPU存取这个数只需访问一次内存,而当一个long型数(如图中的long2)在内存中的位置跨越了字边界时,CPU存取这个数就须要屡次访问内存,如i960cx访问这样的数需读内存三次(一个BYTE、一个SHORT、一个BYTE,由CPU的微代码执行,对软件透明),全部对齐方式下CPU的运行效率明显快多了。
1 8 16 24 32
——- ——- ——- ——-
| long1 | long1 | long1 | long1 |
——- ——- ——- ——-
| | | | long2 |
——- ——- ——- ——–
| long2 | long2 | long2 | |
——- ——- ——- ——–
| ….
7 函数、过程
7.1 对所调用函数的错误返回码要仔细、全面地处理。
7.2 明确函数功能,精确(而不是近似)地实现函数设计。
7.3 局部变量
编写可重入函数时,应注意局部变量的使用(如编写C/C++语言的可重入函数时,应使用auto即缺省态局部变量或寄存器变量)。
说明:编写C/C++语言的可重入函数时,不该使用static局部变量,不然必须通过特殊
处理,才能使函数具备可重入性。
7.4 全局变量
编写可重入函数时,若使用全局变量,则应经过关中断、信号量(即P、V操做)等手段对其加以保护。
说明:若对所使用的全局变量不加以保护,则此函数就不具备可重入性,即当多个进程调
用此函数时,颇有可能使有关全局变量变为不可知状态。
示例:假设Exam是int型全局变量,函数Squre_Exam返回Exam平方值。那么以下
函数不具备可重入性。
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**) temp = Square_Exam( ); return temp;
}
此函数若被多个进程调用的话,其结果多是未知的,由于当(**)语句刚执行完后,另
外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam赋与另外一个不一样的para值,因此当控制从新回到“temp = Square_Exam( )”后,计算出的temp极可能不是预想中的结果。此函数应以下改进。
unsigned int example( int para )
{
unsigned int temp;
[申请信号量操做] // 若申请不到“信号量”,说明另外的进程正处于 Exam = para; // 给Exam赋值并计算其平方过程当中(即正在使用此 temp = Square_Exam( ); // 信号),本进程必须等待其释放信号后,才可继 [释放信号量操做] // 续执行。若申请到信号,则可继续执行,但其 // 它进程必须等待本进程释放信号量后,才能再使 // 用本信号。 return temp;
}
7.5 接口函数参数
在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责仍是由接口函数自己负责,缺省是由函数调用者负责。
说明:对于模块间接口函数的参数的合法性检查这一问题,每每有两个极端现象,即:要
么是调用者和被调用者对参数均不做合法性检查,结果就遗漏了合法性检查这一必要的处理过程,形成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种状况虽不会形成问题,但产生了冗余代码,下降了效率。
7.6 其余
7.6.1 防止将函数的参数做为工做变量。
说明:将函数的参数做为工做变量,有可能错误地改变参数内容,因此很危险。对必须改
变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。
示例:下函数的实现不太好。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count;
*sum = 0; for (count = 0; count < num; count++) { *sum += data[count]; // sum成了工做变量,不太好。 }
}
若改成以下,则更好些。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count ;
int sum_temp;
sum_temp = 0; for (count = 0; count < num; count ++) { sum_temp += data[count]; } *sum = sum_temp;
}
7.6.2 函数的规模尽可能限制在200行之内。
说明:不包括注释和空格行。
7.6.3 一个函数仅完成一件功能。
7.6.4 为简单功能编写函数。
说明:虽然为仅用一两行就可完成的功能去编函数好象没有必要,但用函数可以使功能明确
化,增长程序可读性,亦可方便维护、测试。
示例:以下语句的功能不很明显。
value = ( a > b ) ? a : b ;
改成以下就很清晰了。
int max (int a, int b)
{
return ((a > b) ? a : b);
}
value = max (a, b);
或改成以下。
value = MAX (a, b);
7.6.5 不要设计多用途面面俱到的函数。
说明:多功能集于一身的函数,极可能使函数的理解、测试、维护等变得困难。
7.6.6 函数的功能应该是能够预测的,也就是只要输入数据相同就应产生一样的输出。
说明:带有内部“存储器”的函数的功能多是不可预测的,由于它的输出可能取决于内
部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在C/C++语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值为指针类型时,则必须是STATIC的局部变量的地址做为返回值,若为AUTO类,则返回为错针。
示例:以下函数,其返回值(即功能)是不可预测的。
unsigned int integer_sum( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是static类型的。
// 若改成auto类型,则函数即变为可预测。
for (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
7.6.7 尽可能不要编写依赖于其余函数内部实现的函数。
说明:此条为函数独立性的基本要求。因为目前大部分高级语言都是结构化的,因此经过
具体语言的语法要求与编译器功能,基本就能够防止这种状况发生。但在汇编语言中,因为其灵活性,极可能使函数出现这种状况。
示例:以下是在DOS下TASM的汇编程序例子。过程Print_Msg的实现依赖于Input_Msg的具体实现,这种程序是非结构化的,难以维护、修改。
… // 程序代码
proc Print_Msg // 过程(函数)Print_Msg
… // 程序代码
jmp LABEL
… // 程序代码
endp
proc Input_Msg // 过程(函数)Input_Msg
… // 程序代码
LABEL:
… // 程序代码
endp
7.6.8 避免设计多参数函数,不使用的参数从接口中去掉。
说明:目的减小函数间接口的复杂度。
7.6.9 非调度函数应减小或防止控制参数,尽可能只使用数据参数。
说明:本建议目的是防止函数间的控制耦合。调度函数是指根据输入的消息类型或控制命
令,来启动相应的功能实体(即函数或过程),而自己并不完成具体功能。控制参数是指改变函数功能行为的参数,即函数要根据此参数来决定具体怎样工做。非调度函数的控制参数增长了函数间的控制耦合,极可能使函数间的耦合度增大,并使函数的功能不惟一。
示例:以下函数构造不太合理。
int add_sub( int a, int b, unsigned char add_sub_flg )
{
if (add_sub_flg == INTEGER_ADD)
{
return (a + b);
}
else
{
return (a b);
}
}
不如分为以下两个函数清晰。
int add( int a, int b )
{
return (a + b);
}
int sub( int a, int b )
{
return (a b);
}
7.6.10 检查函数全部参数输入的有效性。
7.6.11 检查函数全部非参数输入的有效性,如数据文件、公共变量等。
说明:函数的输入主要有两种:一种是参数输入;另外一种是全局变量、数据文件的输入,
即非参数输入。函数在使用输入以前,应进行必要的检查。
7.6.12 函数名应准确描述函数的功能。
7.6.13 使用动宾词组为执行某操做的函数命名。若是是OOP方法,能够只有动词(名词是对象自己)。
示例:参照以下方式命名函数。
void print_record( unsigned int rec_ind ) ;
int input_record( void ) ;
unsigned char get_current_color( void ) ;
7.6.14 避免使用无心义或含义不清的动词为函数命名。
说明:避免用含义不清的动词如process、handle等为函数命名,由于这些动词并无
说明要具体作什么。
7.6.15 函数的返回值要清楚、明了,让使用者不容易忽视错误状况。
说明:函数的每种出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误或
忽视错误返回码。
7.6.16 除非必要,最好不要把与函数返回值类型不一样的变量,以编译系统默认的转换方式或强制的转换方式做为返回值返回。
7.6.17 让函数在调用点显得易懂、容易理解。
7.6.18 在调用函数填写参数时,应尽可能减小没有必要的默认数据类型转换或强制数据类型转换。
说明:由于数据类型转换或多或少存在危险。
7.6.19 避免函数中没必要要语句,防止程序中的垃圾代码。
说明:程序中的垃圾代码不只占用额外的空间,并且还经常影响程序的功能与性能,很可
能给程序的测试、维护等形成没必要要的麻烦。
7.6.20 防止把没有关联的语句放到一个函数中。
说明:防止函数或过程内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到
同一个函数或过程当中。随机内聚给函数或过程的维护、测试及之后的升级等形成了不便,同时也使函数或过程的功能不明确。使用随机内聚函数,经常容易出如今一种应用场合须要改进此函数,而另外一种应用场合又不容许这种改进,从而陷入困境。
在编程时,常常遇到在不一样函数中使用相同的代码,许多开发人员都愿把这些代码提出来,
并构成一个新函数。若这些代码关联较大而且是完成一个功能的,那么这种构造是合理的,不然这种构造将产生随机内聚的函数。
示例:以下函数就是一种随机内聚。
void Init_Var( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的长与宽 */
Point.x = 10; Point.y = 10; /* 初始化“点”的坐标 */
}
矩形的长、宽与点的坐标基本没有任何关系,故以上函数是随机内聚。
应以下分为两个函数:
void Init_Rect( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的长与宽 */
}
void Init_Point( void )
{
Point.x = 10;
Point.y = 10; /* 初始化“点”的坐标 */
}
7.6.21 若是多段代码重复作同一件事情,那么在函数的划分上可能存在问题。
说明:若此段代码各语句之间有实质性关联而且是完成同一件功能的,那么可考虑把此段
代码构形成一个新的函数。
7.6.22 功能不明确较小的函数,特别是仅有一个上级函数调用它时,应考虑把它合并到上级函数中,而没必要单独存在。
说明:模块中函数划分的过多,通常会使函数间的接口变得复杂。因此太小的函数,特别
是扇入很低的或功能不明确的函数,不值得单独存在。
7.6.23 设计高扇入、合理扇出(小于7)的函数。
说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数
调用它。
扇出过大,代表函数过度复杂,须要控制和协调过多的下级函数;而扇出太小,如老是1,
代表函数的调用层次可能过多,这样不利程序阅读和函数结构的分析,而且程序运行时会对系统资源如堆栈空间等形成压力。函数较合理的扇出(调度函数除外)一般是3-5。扇出太大,通常是因为缺少中间层次,可适当增长中间层次的函数。扇出过小,可把下级函数进一步分解多个函数,或合并到上级函数中。固然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。
扇入越大,代表使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间
的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。
较良好的软件结构一般是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入
到公共模块中。
7.6.24 减小函数自己或函数间的递归调用。
说明:递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递
归调用通常都占用较多的系统资源(如栈空间);递归调用对程序的测试有必定影响。故除非为某些算法或功能的实现方便,应减小不必的递归调用。
7.6.25 仔细分析模块的功能及性能需求,并进一步细分,同时如有必要画出有关数据流图,据此来进行模块的函数划分与组织。
说明:函数的划分与组织是模块的实现过程当中很关键的步骤,如何划分出合理的函数结构,
关系到模块的最终效率和可维护性、可测性等。根据模块的功能图或/及数据流图映射出函数结构是经常使用方法之一。
7.6.26 改进模块中函数的结构,下降函数间的耦合度,并提升函数的独立性以及代码可读性、效率和可维护性。优化函数结构时,要遵照如下原则:
(1)不能影响模块功能的实现。
(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。
(3)经过分解或合并函数来改进软件结构。
(4)考查函数的规模,过大的要进行分解。
(5)下降函数间接口的复杂度。
(6)不一样层次的函数调用要有较合理的扇入、扇出。
(7)函数功能应可预测。
(8)提升函数内聚。(单一功能的函数内聚最高)
说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。
7.6.27 在多任务操做系统的环境下编程,要注意函数可重入性的构造。
说明:可重入性是指函数能够被多个任务进程调用。在多任务操做系统中,函数是否具备
可重入性是很是重要的,由于这是多个进程能够共用此函数的必要条件。另外,编译器是否提供可重入函数库,与它所服务的操做系统有关,只有操做系统是多任务时,编译器才有可能提供可重入函数库。如DOS下BC和MSC等就不具有可重入函数库,由于DOS是单用户单任务操做系统。
7.6.28 避免使用BOOL参数。
说明:缘由有二,其一是BOOL参数值无心义,TURE/FALSE的含义是很是模糊的,在调
用时很难知道该参数到底传达的是什么意思;其二是BOOL参数值不利于扩充。还有NULL也是一个无心义的单词。
7.6.29 对于提供了返回值的函数,在引用时最好使用其返回值。
7.6.30 当一个过程(函数)中对较长变量(通常是结构的成员)有较多引用时,能够用一个意义至关的宏代替。
说明:这样能够增长编程效率和程序的可读性。
示例:在某过程当中较多引用TheReceiveBuffer[FirstSocket].byDataPtr,
则能够经过如下宏定义来代替:
8 可测性
8.1 调测开关
在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,而且要有详细的说明。
说明:本规则是针对项目组或产品组的。
8.2 打印信息
在同一项目组或产品组内,调测打印出的信息串的格式要有统一的形式。信息串中至少要有所在模块名(或源文件名)及行号。
说明:统一的调测信息格式便于集成测试。
8.3 单元测试
编程的同时要为单元测试选择恰当的测试点,并仔细构造测试代码、测试用例,同时给出明确的注释说明。测试代码部分应做为(模块中的)一个子模块,以方便测试代码在模块中的安装与拆卸(经过调测开关)。
说明:为单元测试而准备。
8.4 集成测试
在进行集成测试/系统联调以前,要构造好测试环境、测试项目及测试用例,同时仔细分析并优化测试用例,以提升测试效率。
说明:好的测试用例应尽量模拟出程序所遇到的边界值、各类复杂环境及一些极端状况等。
8.5 断言使用
8.5.1 使用断言来发现软件问题,提升代码可测性。
说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动做,不然应报告),
它能够快速发现并定位软件问题,同时对系统错误进行自动报警。断言能够对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提升系统的可测性。实际应用时,可根据具体状况灵活地设计断言。
示例:下面是C语言中的一个断言,用宏来设计的。(其中NULL为0L)
void exam_assert( char * file_name, unsigned int line_no )
{
printf( “\n[EXAM]Assert failed: %s, line %u\n”,
file_name, line_no );
abort( );
}
if (condition) // 若条件成立,则无动做 NULL; else // 不然报告 exam_assert( __FILE__, __LINE__ )
8.5.2 用断言来检查程序正常运行时不该发生但在调测时有可能发生的非法状况。
8.5.3 不能用断言来检查最终产品确定会出现且必须处理的错误状况。
说明:断言是用来处理不该该发生的错误状况的,对于可能会发生的且必须处理的状况要
写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。
8.5.4 对较复杂的断言加上明确的注释。
说明:为复杂的断言加注释,可澄清断言含义并减小没必要要的误用。
8.5.5 用断言确认函数的参数。
示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,以下。
int exam_fun( unsigned char *str )
{
EXAM_ASSERT( str != NULL ); // 用断言检查“假设指针不为空”这个条件
... //other program code
}
8.5.6 用断言保证没有定义的特性或功能不被使用。
示例:假设某通讯模块在设计时,准备提供“无链接”和“链接” 这两种业务。但当前
的版本中仅实现了“无链接”业务,且在此版本的正式发行版中,用户(上层模块)不该产生“链接”业务的请求,那么在测试时可用断言检查用户是否使用“链接”业务。以下。
int msg_process( EXAM_MESSAGE *msg )
{
unsigned char service; /* message service class */
EXAM_ASSERT( msg != NULL );
service = get_msg_service_class( msg );
EXAM_ASSERT( service != EXAM_CONNECTION ); // 假设不使用链接业务 ... //other program code
}
8.5.7 用断言对程序开发环境(OS/Compiler/Hardware)的假设进行检查。
说明:程序运行时所需的软硬件环境及配置要求,不能用断言来检查,而必须由一段专门
代码处理。用断言仅可对程序开发环境中的假设及所配置的某版本软硬件是否具备某种功能的假设进行检查。如某网卡是否在系统运行环境中配置了,应由程序中正式代码来检查;而此网卡是否具备某设想的功能,则可由断言来检查。
对编译器提供的功能及特性假设可用断言检查,缘由是软件最终产品(即运行代码或机器
码)与编译器已没有任何直接关系,即软件运行过程当中(注意不是编译过程当中)不会也不该该对编译器的功能提出任何需求。
示例:用断言检查编译器的int型数据占用的内存空间是否为2,以下。
EXAM_ASSERT( sizeof( int ) == 2 );
8.5.8 正式软件产品中应把断言及其它调测代码去掉(即把有关的调测开关关掉)。
说明:加快软件运行速度。
8.6 设置与取消有关测试手段时,不能影响软件功能功能
说明:即有测试代码的软件和关掉测试代码的软件,在功能行为上应一致。
8.7 版本维护
8.7.1 用调测开关来切换软件的DEBUG版和正式版,而不要同时存在正式版本和DEBUG版本的不一样源文件,以减小维护的难度。
8.7.2 软件的DEBUG版本和发行版本应该统一维护,不容许分家,而且要时刻注意保证两个版本在实现功能上的一致性。
8.8 其余
8.8.1 在编写代码以前,应预先设计好程序调试与测试的方法和手段,并设计好各类调测开关及相应测试代码如打印函数等。
说明:程序的调试与测试是软件生存周期中很重要的一个阶段,如何对软件进行较全面、高率的测试并尽量地找出软件中的错误就成为很关键的问题。所以在编写源代码以前,除了要有一套比较完善的测试计划外,还应设计出一系列代码测试手段,为单元测试、集成测试及系统联调提供方便。
8.8.2 调测开关应分为不一样级别和类型。
说明:调测开关的设置及分类应从如下几方面考虑:针对模块或系统某部分代码的调测;
针对模块或系统某功能的调测;出于某种其它目的,如对性能、容量等的测试。这样作便于软件功能的调测,而且便于模块的单元测试、系统联调等。
8.8.3 编写防错程序,而后在处理错误以后可用断言宣布发生错误。
示例:假如某模块收到通讯链路上的消息,则应对消息的合法性进行检查,若消息类别不
是通讯协议中规定的,则应进行出错处理,以后可用断言报告,以下例。
/* Notice: this function does not call ‘abort’ to exit program */
void assert_report( char * file_name, unsigned int line_no )
{
printf( “\n[EXAM]Error Report: %s, line %u\n”,
file_name, line_no );
}
if ( condition ) // 若条件成立,则无动做 NULL; else // 不然报告 assert_report ( __FILE__, __LINE__ )
int msg_handle( unsigned char msg_name, unsigned char * msg )
{
switch( msg_name )
{
case MSG_ONE:
… // 消息MSG_ONE处理
return MSG_HANDLE_SUCCESS;
... // 其它合法消息处理 default: ... // 消息出错处理 ASSERT_REPORT( FALSE ); // “合法”消息不成立,报告 return MSG_HANDLE_ERROR; }
}
9 程序效率
9.1 编程时要常常注意代码的效率。
说明:代码效率分为全局效率、局部效率、时间效率及空间效率。全局效率是站在整个系
统的角度上的系统效率;局部效率是站在模块或函数角度上的效率;时间效率是程序处理输入任务所需的时间长短;空间效率是程序所需内存空间,如机器代码空间大小、数据空间大小、栈空间大小等。
9.2 提升代码效率
在保证软件系统的正确性、稳定性、可读性及可测性的前提下,提升代码效率。
说明:不能一味地追求代码效率,而对软件的正确性、稳定性、可读性及可测性形成影响。
9.3 全局效率高于局部效率
局部效率应为全局效率服务,不能由于提升局部效率而对全局效率形成影响。
9.4 提升代码空间效率
经过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提升空间效率。
说明:这种方式是解决软件空间效率的根本办法。
示例:以下记录学生学习成绩的结构不合理。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef struct STUDENT_SCORE_STRU
BYTE name[8]; BYTE age; BYTE sex; BYTE class; BYTE subject; float score;
} STUDENT_SCORE;
由于每位学生都有多科学习成绩,故如上结构将占用较大空间。应以下改进(分为两个结构),总的存贮空间将变小,操做也变得更方便。
typedef struct STUDENT_STRU
{
BYTE name[8];
BYTE age;
BYTE sex;
BYTE class;
} STUDENT;
typedef struct STUDENT_SCORE_STRU
{
WORD student_index;
BYTE subject;
float score;
} STUDENT_SCORE;
9.5 循环体内工做量最小化
说明:应仔细考虑循环体内的语句是否能够放在循环体以外,使循环体内工做量最小,从
而提升程序的时间效率。
示例:以下代码效率不高。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
back_sum = sum; /* backup sum */
}
语句“back_sum = sum;”彻底能够放在for语句以后,以下。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
}
back_sum = sum; /* backup sum */
9.6 其余
9.6.1 仔细分析有关算法,并进行优化。
9.6.2 仔细考查、分析系统及模块处理输入(如事务、消息等)的方式,并加以改进。
9.6.3 对模块中函数的划分及组织方式进行分析、优化,改进模块中函数的组织结构,提升程序效率。
说明:软件系统的效率主要与算法、处理任务方式、系统功能及函数结构有很大关系,仅
在代码上下功夫通常不能解决根本问题。
9.6.4 编程时,要随时留心代码效率;优化代码时,要考虑周全。
9.6.5 不该花过多的时间拼命地提升调用不很频繁的函数代码效率。
说明:对代码优化可提升效率,但若考虑不周颇有可能引发严重后果。
9.6.6 要仔细地构造或直接用汇编编写调用频繁或性能要求极高的函数。
说明:只有对编译系统产生机器码的方式以及硬件系统较为熟悉时,才可以使用汇编嵌入方
式。嵌入汇编可提升时间及空间效率,但也存在必定风险。
9.6.7 在保证程序质量的前提下,经过压缩代码量、去掉没必要要代码以及减小没必要要的局部和全局变量,来提升空间效率。
说明:这种方式对提升空间效率可起到必定做用,但每每不能解决根本问题。
9.6.8 在多重循环中,应将最忙的循环放在最内层。
说明:减小CPU切入循环层的次数。
示例:以下代码效率不高。
for (row = 0; row < 100; row++)
{
for (col = 0; col < 5; col++)
{
sum += a[row][col];
}
}
能够改成以下方式,以提升效率。
for (col = 0; col < 5; col++)
{
for (row = 0; row < 100; row++)
{
sum += a[row][col];
}
}
9.6.9 尽可能减小循环嵌套层次。
9.6.10 避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中。
说明:目的是减小判断次数。循环体中的判断语句是否能够移到循环体外,要视程序的具
体状况而言,通常状况,与循环变量无关的判断语句能够移到循环体外,而有关的则不能够。
示例:以下代码效率稍低。
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
if (data_type == RECT_AREA)
{
area_sum += rect_area[ind];
}
else
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
由于判断语句与循环变量无关,故可以下改进,以减小判断次数。
if (data_type == RECT_AREA)
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
area_sum += rect_area[ind];
}
}
else
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
9.6.11 尽可能用乘法或其它方法代替除法,特别是浮点运算中的除法。
说明:浮点运算除法要占用较多CPU资源。
示例:以下表达式运算可能要占较多CPU资源。
radius = circle_length / (2 * PAI);
应以下把浮点除法改成浮点乘法。
radius = circle_length * PAI_RECIPROCAL / 2;
9.6.12 不要一味追求紧凑的代码。
说明:由于紧凑的代码并不表明高效的机器码。
10 质量保证
10.1 在软件设计过程当中构筑软件质量。
10.2 代码质量保证优先原则
(1)正确性,指程序要实现设计要求的功能。
(2)稳定性、安全性,指程序稳定、可靠、安全。
(3)可测试性,指程序要具备良好的可测试性。
(4)规范/可读性,指程序书写风格、命名规则等要符合规范。
(5)全局效率,指软件系统的总体效率。
(6)局部效率,指某个模块/子模块/函数的自己效率。
(7)我的表达方式/我的方便性,指我的编程习惯。
10.3 只引用属于本身的存贮空间。
说明:若模块封装的较好,那么通常不会发生非法引用他人的空间。
10.4 防止引用已经释放的内存空间。
说明:在实际编程过程当中,稍不留心就会出如今一个模块中释放了某个内存块(如C语言
指针),而另外一模块在随后的某个时刻又使用了它。要防止这种状况发生。
10.5 内存及时释放
过程/函数中分配的内存,在过程/函数退出以前要释放。
10.6 文件句柄及时关闭
过程/函数中申请的(为打开文件而使用的)文件句柄,在过程/函数退出以前要关闭。
说明:分配的内存不释放以及文件句柄不关闭,是较常见的错误,并且稍不注意就有可能
发生。这类错误每每会引发很严重后果,且难以定位。
示例:下函数在退出以前,没有把分配的内存释放。
typedef unsigned char BYTE;
int example_fun( BYTE gt_len, BYTE *gt_code )
{
BYTE *gt_buf;
gt_buf = (BYTE *) malloc (MAX_GT_LENGTH); ... //program code, include check gt_buf if or not NULL. /* global title length error */ if (gt_len > MAX_GT_LENGTH) { return GT_LENGTH_ERROR; // 忘了释放gt_buf } ... // other program code
}
应改成以下。
int example_fun( BYTE gt_len, BYTE *gt_code )
{
BYTE *gt_buf;
gt_buf = (BYTE * ) malloc ( MAX_GT_LENGTH ); ... // program code, include check gt_buf if or not NULL. /* global title length error */ if (gt_len > MAX_GT_LENGTH) { free( gt_buf ); // 退出以前释放gt_buf return GT_LENGTH_ERROR; } ... // other program code
}
10.7 防止内存操做越界
说明:内存操做主要是指对数组、指针、内存地址等的操做。内存操做越界是软件系统主
要错误之一,后果每每很是严重,因此当咱们进行这些操做时必定要仔细当心。
示例:假设某软件系统最多可由10个用户同时使用,用户号为1-10,那么以下程序存在
问题。
unsigned char usr_login_flg[MAX_USR_NUM]= “”;
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no])
{
usr_login_flg[usr_no]= TRUE;
}
}
当usr_no为10时,将使用usr_login_flg越界。可采用以下方式解决。
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no - 1])
{
usr_login_flg[usr_no - 1]= TRUE;
}
}
10.8 认真处理程序所能遇到的各类出错状况
10.9 初始化变量
系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。
10.10 数据一致性检查
系统运行之初,要对加载到系统中的数据进行一致性检查。
说明:使用不一致的数据,容易使系统进入混乱状态和不可知状态。
10.11 严禁随意更改其它模块或系统的有关设置和配置
说明:编程时,不能为所欲为地更改不属于本身模块的有关设置如常量、数组的大小等。
10.12 不能随意改变与其它模块的接口
10.13 系统接口
充分了解系统的接口以后,再使用系统提供的功能。
示例:在B型机的各模块与操做系统的接口函数中,有一个要由各模块负责编写的初始化过程,此过
程在软件系统加载完成后,由操做系统发送的初始化消息来调度。所以就涉及到初始化消息的类型与消息发送的顺序问题,特别是消息顺序,若没搞清楚就开始编程,很容易引发严重后果。如下示例引自B型曾出现过的实际代码,其中使用了FID_FETCH_DATA与FID_INITIAL初始化消息类型,注意B型机的系统是在FID_FETCH_DATA以前发送FID_INITIAL的。
MID alarm_module_list[MAX_ALARM_MID];
int FAR SYS_ALARM_proc( FID function_id, int handle )
{
_UI i, j;
switch ( function_id ) { ... // program code case FID_INITAIL: for (i = 0; i < MAX_ALARM_MID; i++) { if (alarm_module_list[i]== BAM_MODULE // **) || (alarm_module_list[i]== LOCAL_MODULE) { for (j = 0; j < ALARM_CLASS_SUM; j++) { FAR_MALLOC( ... ); } } } ... // program code break; case FID_FETCH_DATA: ... // program code Get_Alarm_Module( ); // 初始化alarm_module_list break; ... // program code }
}
因为FID_INITIAL是在FID_FETCH_DATA以前执行的,而初始化
alarm_module_list是在FID_FETCH_DATA中进行的,故在FID_INITIAL中(**)处引用alarm_module_list变量时,它尚未被初始化。这是个严重错误。
应以下改正:要么把Get_Alarm_Module函数放在FID_INITIAL中(**)以前;要
么就必须考虑(**)处的判断语句是否能够用(不使用alarm_module_list变量的)其它方式替代,或者是否能够取消此判断语句。
10.14 编程时,要防止差1错误
说明:此类错误通常是因为把“<=”误写成“<”或“>=”误写成“>”等形成的,由此
引发的后果,不少状况下是很严重的,因此编程时,必定要在这些地方当心。当编完程序后,应对这些操做符进行完全检查。
10.15 操做符检查
要时刻注意易混淆的操做符。当编完程序后,应从头到尾检查一遍这些操做符,以防止拼写错误。
说明:形式相近的操做符最容易引发误用,如C/C++中的“=”与“==”、“|”与“||”、
“&”与“&&”等,若拼写错了,编译器不必定可以检查出来。
示例:如把“&”写成“&&”,或反之。
ret_flg = (pmsg->ret_flg & RETURN_MASK);
被写为:
ret_flg = (pmsg->ret_flg && RETURN_MASK);
rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));
被写为:
rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));
10.16 分支语句写完整
有可能的话,if语句尽可能加上else分支,对没有else分支的语句要当心对待;switch语句必须有default分支。
10.17 使用return语句
Unix下,多线程的中的子线程退出必需采用主动退出方式,即子线程应return出口。
10.18 不要滥用goto语句
说明:goto语句会破坏程序的结构性,因此除非确实须要,最好不使用goto语句。
10.19 其余
10.19.1 不使用与硬件或操做系统关系很大的语句,而使用建议的标准语句,以提升软件的可移植性和可重用性。
10.19.2 除非为了知足特殊需求,避免使用嵌入式汇编。
说明:程序中嵌入式汇编,通常都对可移植性有较大的影响。
10.19.3 精心地构造、划分子模块,并按“接口”部分及“内核”部分合理地组织子模块,以提升“内核”部分的可移植性和可重用性。
说明:对不一样产品中的某个功能相同的模块,若能作到其内核部分彻底或基本一致,那么
不管对产品的测试、维护,仍是对之后产品的升级都会有很大帮助。
10.19.4 精心构造算法,并对其性能、效率进行测试。
10.19.5 对较关键的算法最好使用其它算法来确认。
10.19.6 时刻注意表达式是否会上溢、下溢。
示例:以下程序将形成变量下溢。
unsigned char size ;
while (size– >= 0) // 将出现下溢
{
… // program code
}
当size等于0时,再减1不会小于0,而是0xFF,故程序是一个死循环。应以下修改。
char size; // 从unsigned char 改成char
while (size– >= 0)
{
… // program code
}
10.19.7 使用变量时要注意其边界值的状况。
示例:如C语言中字符型变量,有效值范围为-128到127。故如下表达式的计算存在一
定风险。
char chr = 127;
int sum = 200;
chr += 1; // 127为chr的边界值,再加1将使chr上溢到-128,而不是128。
sum += chr; // 故sum的结果不是328,而是72。
若chr与sum为同一种类型,或表达式按以下方式书写,可能会好些。
sum = sum + chr + 1;
10.19.8 留心程序机器码大小(如指令空间大小、数据空间大小、堆栈空间大小等)是否超出系统有关限制。
10.19.9 为用户提供良好的接口界面,使用户能较充分地了解系统内部运行状态及有关系统出错状况。
10.19.10 系统应具备必定的容错能力,对一些错误事件(如用户误操做等)能进行自动补救。
10.19.11 对一些具备危险性的操做代码(如写硬盘、删数据等)要仔细考虑,防止对数据、硬件等的安全构成危害,以提升系统的安全性。
10.19.12 第三方软件开发工具包
使用第三方提供的软件开发工具包或控件时,要注意如下几点:
(1)充分了解应用接口、使用环境及使用时注意事项。
(2)不能过度相信其正确性。
(3)除非必要,不要使用不熟悉的第三方工具包与控件。
说明:使用工具包与控件,可加快程序开发速度,节省时间,但使用以前必定对它有较充
分的了解,同时第三方工具包与控件也有可能存在问题。
10.19.13 资源文件
资源文件(多语言版本支持),若是资源是对语言敏感的,应让该资源与源代码文件脱离,具体方法有下面几种:使用单独的资源文件、DLL文件或其它单独的描述文件(如数据库格式)。
11 代码编辑、编译、审查
11.1 打开编译器的全部告警开关对程序进行编译
11.2 在产品软件(项目组)中,要统一编译开关选项
11.3 经过代码走读及审查方式对代码进行检查。
说明:代码走读主要是对程序的编程风格如注释、命名等以及编程时易出错的内容进行检
查,可由开发人员本身或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及程序的稳定性、安全性、可靠性等进行检查及评审,可经过自审、交叉审核或指定部门抽查等方式进行。
11.4 测试部测试产品以前,应对代码进行抽查及评审
11.5 其余
11.5.1 编写代码时要注意随时保存,并按期备份,防止因为断电、硬盘损坏等缘由形成代码丢失。
11.5.2 同产品软件(项目组)内,最好使用相同的编辑器,并使用相同的设置选项。
说明:同一项目组最好采用相同的智能语言编辑器,如Muiti Editor,Visual Editor
等,并设计、使用一套缩进宏及注释宏等,将缩进等问题交由编辑器处理。
11.5.3 要当心地使用编辑器提供的块拷贝功能编程。
说明:当某段代码与另外一段代码的处理功能类似时,许多开发人员都用编辑器提供的块拷
贝功能来完成这段代码的编写。因为程序功能相近,故所使用的变量、采用的表达式等在功能及命名上可能都很相近,因此使用块拷贝时要注意,除了修改相应的程序外,必定要把使用的每一个变量仔细查看一遍,以改为正确的。不该期望编译器能查出全部这种错误,好比当使用的是全局变量时,就有可能使某种错误隐藏下来。
11.5.4 合理地设计软件系统目录,方便开发人员使用。
说明:方便、合理的软件系统目录,可提升工做效率。目录构造的原则是方便有关源程序
的存储、查询、编译、连接等工做,同时目录中还应具备工做目录—-全部的编译、连接等工做应在此目录中进行,工具目录—-有关文件编辑器、文件查找等工具可存放在此目录中。
11.5.5 某些语句经编译后产生告警,但若是你认为它是正确的,那么应经过某种手段去掉告警信息。
说明:在Borland C/C++中,可用“#pragma warn”来关掉或打开某些告警。
示例:
int examples_fun( void )
{
// 程序,但无return语句。
}
编译函数examples_fun时本应产生“函数应有返回值”告警,但因为关掉了此告警信
息显示,因此编译时将不会产生此告警提示。
11.5.6 使用代码检查工具(如C语言用PC-Lint)对源程序检查。
11.5.7 使用软件工具(如 LogiSCOPE)进行代码审查。
12 代码测试、维护
12.1 单元测试要求至少达到语句覆盖
12.2 单元测试开始要跟踪每一条语句,并观察数据流及变量的变化
12.3 清理、整理或优化后的代码要通过审查及测试。
12.4 代码版本升级要通过严格测试
12.5 使用工具软件对代码版本进行维护
12.6 正式版本上软件的任何修改都应有详细的文档记录
12.7 其余
12.7.1 发现错误当即修改,而且要记录下来。
12.7.2 关键的代码在汇编级跟踪。
12.7.3 仔细设计并分析测试用例,使测试用例覆盖尽量多的状况,以提升测试用例的效率。
12.7.4 尽量模拟出程序的各类出错状况,对出错处理代码进行充分的测试。
12.7.5 仔细测试代码处理数据、变量的边界状况。
12.7.6 保留测试信息,以便分析、总结经验及进行更充分的测试。
12.7.7 不该经过“试”来解决问题,应寻找问题的根本缘由。
12.7.8 对自动消失的错误进行分析,搞清楚错误是如何消失的。
12.7.9 修改错误不只要治表,更要治本。
12.7.10 测试时应设法使不多发生的事件常常发生。
12.7.11 明确模块或函数处理哪些事件,并使它们常常发生。
12.7.12 坚持在编码阶段就对代码进行完全的单元测试,不要等之后的测试工做来发现问题。
12.7.13 去除代码运行的随机性(如去掉无用的数据、代码及尽量防止并注意函数中的“内部寄存器”等),让函数运行的结果可预测,并使出现的错误可再现。
13 宏
13.1 用宏定义表达式时,要使用完备的括号
示例:以下定义的宏都存在必定的风险。
正确的定义应为:
13.2 将宏所定义的多条表达式放在大括号中
示例:下面的语句只有宏的第一条表达式被执行。为了说明问题,for语句的书写稍不符规范。
a = 0;\ b = 0;
for (index = 0; index < RECT_TOTAL_NUM; index++)
INTI_RECT_VALUE( rect.a, rect.b );
正确的用法应为:
{\
a = 0;\
b = 0;\
}
for (index = 0; index < RECT_TOTAL_NUM; index++)
{
INTI_RECT_VALUE( rect[index].a, rect[index].b );
}
13.3 使用宏时,不容许参数发生变化
示例:以下用法可能致使错误。
int a = 5;
int b;
b = SQUARE( a++ ); // 结果:a = 7,即执行了两次增1。
正确的用法是: b = SQUARE( a ); a++; // 结果:a = 6,即只执行了一次增1。