软件编程规范

更新时间:2024-01-12 21:38:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

软件编程规范

软 件 编 程 规 范

拟制: willim

日期: 审核:

日期:

1

2004-05-10 yyyy/mm/dd

软件编程规范

文档历史

日期 2004-5-10 作者 willim 版本号 0.1 备注 初稿

2

软件编程规范

目 录

第零章 前言 ..........................................................................................................................5 第一章 程序的版式 ................................................................................................................6 第二章 注释 ........................................................................................................................ 12 第三章 标识符命名 .............................................................................................................. 16 第四章 可读性 ..................................................................................................................... 20 第五章 变量、结构 .............................................................................................................. 22 第六章 函数、过程 .............................................................................................................. 25 第七章 程序效率 ................................................................................................................. 29 第八章 质量保证 ................................................................................................................. 33

3

软件编程规范

关键词:软件编程规范

摘 要:本规范适用于公司使用C/C++作为开发语言的项目。

缩略语清单:

规则:强制必须遵守的原则。 建议:可以加以考虑的原则。

示例:对此规则或建议从正、反两个方面给出例子。

说明:对本规则、建议的补充说明。?

参考文献:

《Rational Unified Process 2000》 Rational Software Corporation,2000

《移动开发部软件编程规范》 《QQ 组编程精粹培训》

《写代码的十个秘决》

john 腾讯计算机有限公司,2003-2-19

qingming 腾讯计算机有限公司,2003-9-8 sjh

腾讯计算机有限公司,2003-2-19

《零错误代码开发规范》 基础产品部 腾讯计算机有限公司,2003-2-19

《Thinking in C++》 刘宗田 等译,机械工业出版社,2000

4

软件编程规范

第零章 前言

对于软件开发遵循统一的规范,有利于增强代码的可读性,同时防范错误,更好的发挥所使用的语言的优点,提高工作效率。我们在参考已有规范的基础上,制定了本编程规范,用于指导公司的编码活动。

需要注意的是,在本规范中,代码的清晰易懂远比其它方面重要。因为程序的时间、空间效率再高,算法再好,如果可读性差,无法重用、维护和扩展,那么最终是一堆无用的代码,所做的只能是无用功。

要使自己写出的代码做到正确、可靠、简洁、高效(如速度快、内存占用率低等),不是一蹴而就的,这需要不断地总结自己在编写程序中出现的错误,不断地练习才能不断地提高。

“态度决定一切”,写程序时要使自己专注、认真,做到快乐编程。

5

软件编程规范

第一章 程序的版式

规则1-1:程序块要采用缩进风格编写,缩进的空格数为4个。

说明:由开发工具自动生成的代码可以不一致,但如果开发工具可以配置,则应该统一配

置缩进为4 个空格。

规则1-2:缩进或者对齐只使用空格键, 不能使用TAB 键

原因 :避免使用不同的编辑器阅读程序时, 因TAB 键所设置的空格数目不同而造成程序布局不整齐

规则1-3:相对独立的程序块之间、变量说明之后必须加空行。

说明 :以下情况应该是用空行分开:

1. 函数之间应该用空行分开

2. 一组局部变量声明和代码之间用空行分开 3. 用空行将代码按照逻辑片断划分

4. 除非函数非常简单( 如只有一两条语句),否则函数返回语句和其他语句用空行分开

5. 每个类声明之后应该加入空格同其他代码分开

示例:如下例子不符合规范。

if (!valid_ni(ni)) {

... // program code }

repssn_ind = ssn_data[index].repssn_index; repssn_ni = ssn_data[index].ni;

6

软件编程规范

应如下书写:

if (!valid_ni(ni)) {

... // program code }

repssn_ind = ssn_data[index].repssn_index; repssn_ni = ssn_data[index].ni;

规则1-4:较长的语句(>80字符)要分成多行书写 说明:以下情况应分多行书写

1. 长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。

2. 若函数或过程中的参数较长,则要进行适当的划分

3. 循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分,长表达式要在

低优先级操作符处划分新行,操作符放在新行之首 示例:

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 );

report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER) && (n7stat_stat_item_valid (stat_item))

&& (act_task_table[taskno].result_data != 0));

n7stat_str_compare((BYTE *) & stat_object,

(BYTE *) & (act_task_table[taskno].stat_object), sizeof (_STAT_OBJECT));

n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER + index, stat_object );

7

软件编程规范

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 }

规则1-5:不允许把多个短语句写在一行中,即一行只写一条语句。

说明:一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释

示例:如下例子不符合规范

rect.length = 0; rect.width = 0; 应如下书写

rect.length = 0; rect.width = 0;

规则1-6:if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加括号{}。

8

软件编程规范

示例:如下例子不符合规范。

if (pUserCR == NULL) return; 应如下书写:

if (pUserCR == NULL) {

return; }

建议1-1:对齐只使用空格键,不使用TAB键。

说明:编辑器对TAB键所设置的空格数目不同,因此会造成程序布局不整齐。

建议1-2:函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case语句下的情况处理语句也要遵从语句缩进要求。

建议1-3:程序块的分界符(如C/C++语言的大括号‘{’和‘}’)应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。

示例:如下例子不符合规范。

for (...) {

... // program code }

if (...) {

... // program code }

void example_fun( void ) {

... // program code } 应如下书写。

for (...)

9

软件编程规范

{

... // program code }

if (...) {

... // program code }

void example_fun( void ) {

... // program code }

规则1-7:代码行之内应该留有适当的空格

说明:采用这种松散方式编写代码的目的是使代码更加清晰。代码行内应该适当的使用空格, 具体地说来:

1. 关键字之后要留空格。象const、virtual、inline、case 等关键字之后至少要留一个空格, 否则无法辨析关键字。象if、for、while 等关键字之后应留一个空格再跟左括号‘( ’, 以突出关键字。

2. 函数名之后不要留空格, 紧跟左括号’(’ , 以与关键字区别。

3. ‘( ’ 向后紧跟,‘ )’、‘ ,’、‘ ;’ 向前紧跟, 紧跟处不留空格。 4. ‘ ,’ 之后要留空格, 如Function(x, y, z)。如果‘ ;’ 不是一行的结束符号, 其后也要留空格, 如for (initialization; condition; update)。

5. 值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“ =”、“ +=” “ >=”、“ <=”、“ +”、“ *”、“ %”、“ &&”、“ ||”、“ <<” ,“ ^” 等二元操作符的前后应当加空格。

6. 一元操作符如“ !”、“ ~”、“ ++”、“ --”、“ &”( 地址运算符) 等 前后不加空格。

7. 象“[ ]”、“ .”、“ ->” 这类操作符前后不加空格。

示例:应该如下书写

10

软件编程规范

应该按照以下的格式书写: void foo() {

?// program code

}

if (i == 0) {

?// program code

}

foo->bar, foo.bar, foo[bar]

i++, !i, &i

i += 9, a *b

11

第二章 注释

规则2-1:源文件头部应进行注释,列出:生成日期、作者、模块目的/功能等。

示例:下面这段源文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

/************************************************************ FileName: test.cpp

Author: Version : Date: Description: // 模块描述 Version: // 版本信息

Function List: // 主要函数及其功能 1. -------

History: // 历史修改记录

David 96/10/12 1.0 build this moudle

***********************************************************/ 说明:Description一项描述本文件的内容、功能、内部各部分之间的关系及本文件与其它文件关系等。History是修改历史记录列表,每条修改记录应包括修改日期、修改者及修改内容简述。

也可以采用javadoc 风格的文档注释,这里不再举例,下同。

规则2-2:函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值等。 示例:下面这段函数的注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

/************************************************* Function: // 函数名称

Description: // 函数功能、性能等的描述 Calls: // 被本函数调用的函数清单 Called By: // 调用本函数的函数清单

Table Accessed: // 被访问的表(此项仅对于牵扯到数据库操作的程序)

12

Table Updated: // 被修改的表(此项仅对于牵扯到数据库操作的程序) Input: // 输入参数说明,包括每个参数的作 // 用、取值说明及参数间关系。 Output: // 对输出参数的说明。 Return: // 函数返回值的说明 Others: // 其它说明

*************************************************/

规则2-3:注释应该和代码同时更新,不再有用的注释要删除。

规则2-4:注释的内容要清楚、明了,不能有二义性。 说明:错误的注释不但无益反而有害。

建议2-1:避免在注释中使用非常用的缩写或者术语。

规则2-5:注释的版式

说明:注释也需要与代码一样整齐排版

1. 注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面,如放于上方则需与其上面的代码用空行隔开。 2. 注释与所描述内容进行同样的缩排 3. 将注释与其上面的代码用空行隔开

4. 变量、常量、宏的注释应放在其上方相邻位置或右方

示例:如下例子不符合规范。

例1:注释在代码行之下

repssn_ind = ssn_data[index].repssn_index; repssn_ni = ssn_data[index].ni;

/* get replicate sub system index and net indicator */

13

例2:缩进不统一

void example_fun( void ) {

/* code one comments */ CodeBlock One

/* code two comments */ CodeBlock Two }

例3:代码过于紧凑

/* code one comments */ program code one

/* code two comments */ program code two

规则2-6:对于所有有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释,说明其物理含义。 示例:以下都是允许的注释方式

/* active statistic task number */ #define MAX_ACT_TASK_NUMBER 1000

#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */

规则2-7:数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释可放在此域的右方。

示例:可按如下形式说明枚举/数据/联合结构。

/* 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 */

14

/* transmission this message */

N_UNITDATA_REQ, /* sccp user's unit data transmission request*/ };

建议2-2:对重要变量的定义需编写注释,特别是全局变量,更应有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。

示例:

/* 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;

建议2-3:分支语句(条件分支、循环语句等)需编写注释。

说明:这些语句往往是程序实现某一特定功能的关键,对于维护人员来说,良好的注释帮助更好的理解程序,有时甚至优于看设计文档。

规则2-8:注释不宜过多,也不能太少,源程序中有效注释量控制在20%~30%之间。 说明:注释是对代码的“提示”,而不是文档,不可喧宾夺主,注释太多会让人眼花缭乱。

15

第三章 标识符命名

规则3-1:命名尽量使用英文单词,力求简单清楚,避免使用引起误解的词汇和模糊的缩写,使人产生误解

说明:较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写;一些单词有大家公认的缩写。

示例:如下单词的缩写能够被大家基本认可。

temp 可缩写为 tmp ; flag 可缩写为 flg ; statistic 可缩写为 stat ; increment 可缩写为 inc ; message 可缩写为 msg ;

规则3-2:命名规范必须与所使用的系统风格保持一致,并在同一项目中统一 说明:

1. 如在UNIX系统,可采用全小写加下划线的风格或大小写混排的方式,但不能使用大

小写与下划线混排的方式。

2. 用作特殊标识如标识成员变量或全局变量的m_和g_,其后加上大小写混排的方式是允许的 示例:

Add_User不允许,add_user、AddUser、m_AddUser允许。

建议3-1:变量的命名可参考“匈牙利”标记法(Hungarian Notation):TypePrefix+ Name

说明:以下的基本变量前缀可供参考: 前缀 含义 示例 说明

p s str sz psz h c by y n f d b u w Pointer String CString zero-terminated null-terminated string Handle Character (char) Byte or Unsigned Char Integer (int) Float Double Bool Unsigned... WORD or Unsigned Integer char* pszName String sName; CString strName; 很多情况下, p总是和它所指向的变量的类型前缀一起使用。 char szName[16]; char* pszName; HWND hWindow char cLetter; Sometimes c is used to denote a counter object. byte byMouthFull; byte yMouthFull; int nSizeOfArray; float fRootBeer; double dDecker; bool bIsTrue; BOOL bIsTrue; int bIsTrue; An integer can store a boolean value as long as you remember not to assign it a value other than 0 or 1 unsigned wValue; int Sometimes l is appended to p to denote that the pointer is a long. For example: lpszName is a long pointer to a zero-terminated string. l Long long lIdentifier; dw DWORD or Unsigned Long Integer Class CObject; Class Object; struct SPlayer; C or just a capital Class first letteri S Struct C is used heavily in Microsoft's Foundation Classes but using just a capital first letter is emphasized by Microsoft's J++. I class IMotion { Interface (ususally a struct public: or class with only pure virtual void virtual methods and no Fly() = 0; member variables) }; class CRocket { public: class XMotion:public IMotion { Used extensively in COM. X Nested Class Used extensively in COM.

public: void Fly(); } m_xUnknown; } class CAirplane { public: class XMotion:public Instantiation of a nested IMotion { Used extensively in COM. class. public: void Fly(); } m_xUnknown; } class CThing { private: int m_nSize; }; x m_ Class Member Identifiers g_ Global Constant globals are usually in all caps. The g_ would denote String* g_psBuffer that a particular global is not a constant. void* pvObject stPlayer In most cases, v will be included with p because it is a common trick to typecast pointers to void pointers. v st

Void (no type) Struct variable 规则3-3:常量、宏和模板名采用全大写的方式, 每个单词间用下划线分隔

建议3-2: 枚举类型enum 常量应以大写字母开头或全部大写 建议3-3:命名中若使用了特殊约定或缩写,则要有注释说明

说明:应该在源文件的开始之处,对文件中所使用的缩写或约定,特别是特殊的缩写,进行必要的注释说明。

规则3-4:自己特有的命名风格,要自始至终保持一致,不可来回变化

说明:个人的命名风格,在符合所在项目组或产品组的命名规则的前提下,才可使用。(即命名规则中没有规定到的地方才可有个人命名风格)。

规则3-5:对于变量命名,禁止取单个字符(如i、j、k...),建议除了要有具体含义外,还能表明其变量类型、数据类型等,但i、j、k作局部循环变量是允许的

说明:变量,尤其是局部变量,如果用单个字符表示,很容易敲错(如i写成j),而编译时又检查不出来,有可能为了这个小小的错误而花费大量的查错时间。

建议3-4:除非必要,不要用数字或较奇怪的字符来定义标识符

示例:

1. 如下命名,使人产生疑惑: #define _EXAMPLE_0_TEST_ #define _EXAMPLE_1_TEST_ void set_sls00( BYTE sls );

应改为有意义的单词命名

#define _EXAMPLE_UNIT_TEST_ #define _EXAMPLE_ASSERT_TEST_ void set_udt_msg_sls( BYTE sls );

2. 避免使用看上去相似的名称,如“l”、“1”和“I”看上去非常相似

建议3-5:函数名以大写字母开头,采用谓-宾结构(动-名),且应反映函数执行什么操作以及返回什么内容

说明:函数在表达式中使用,通常用于 if 子句,因此它们的意图应一目了然。 示例:

不好的命名:if (CheckSize(x))

没有帮助作用,因为它没有告诉我们 CheckSize是在出错时返回 true 还是在不出错时返回 true。 好的命名:if (ValidSize(x))

则使函数的意图很明确。

建议3-6:类、结构、联合、枚举的命名须分别以C、S、U、E开头,其他部分遵从一般变量命名规范

第四章 可读性

规则4-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)造成了判断条件出错。

建议4-1:不要编写太复杂 、多用途的复合表达式

规则4-2:避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的枚举或宏来代替。 示例:如下的程序可读性差。

if (Trunk[index].trunk_state == 0) {

Trunk[index].trunk_state = 1; ... // program code }

应改为如下形式。

#define TRUNK_IDLE 0 #define TRUNK_BUSY 1

if (Trunk[index].trunk_state == TRUNK_IDLE) {

Trunk[index].trunk_state = TRUNK_BUSY; ... // program code }

规则4-3:不要使用难懂的技巧性很高的语句,除非很有必要时。

说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。 示例:如下表达式,考虑不周就可能出问题,也较难理解。

* stat_poi ++ += 1;

* ++ stat_poi += 1;

应分别改为如下。 *stat_poi += 1;

stat_poi++; // 此二语句功能相当于“ * stat_poi ++ += 1; ”

++ stat_poi;

*stat_poi += 1; // 此二语句功能相当于“ * ++ stat_poi += 1; ”

第五章 变量、结构

建议5-1:尽量少使用全局变量,尽量去掉没必要的公共变量。

说明:公共变量是增大模块间耦合的原因之一,故应减少没必要的公共变量以降低模块间的耦合度。

规则5-1:变量,特别是指针变量,被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。

说明:在C/C++中引用未经赋值的指针,经常会引起系统崩溃。

建议5-2:仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间,并减少引起误用现象。

说明:合理排列结构中元素顺序,可节省空间并增加可理解性。 示例:如下结构中的位域排列,将占较大空间,可读性也稍差。

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;

建议5-3:留心具体语言及编译器处理不同数据类型的原则及有关细节。

说明:如在C语言中,static局部变量将在内存“数据区”中生成,而非static局部

变量将在“堆栈”中生成。这些细节对程序质量的保证非常重要。

建议5-4:尽量减少没有必要的数据类型默认转换与强制转换。

说明:当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节若考虑不周,就很有可能留下隐患。

规则5-2:当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的字节顺序、使用的位域及字节对齐等问题 。 说明:

1. 在Intel CPU与SPARC CPU,在处理位域及整数时,其在内存存放的“顺序”正好

相反。

2. 在对齐方式下,CPU的运行效率要快一些 示例:

1. 字节顺序

假如有如下短整数及结构。 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

如下是SPARC CPU生成短整数及位域的方式。

内存: 0 1 2 ... (从低到高,以字节为单位)

exam exam高字节 exam低字节

内存: 7 bit 6 bit 5 bit ... (字节的各“位”) EXAM_BIT A1 A2 A3 2. 对齐

如下图,当一个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 | | ------- ------- ------- -------- | ....

软件编程规范

第六章 函数、过程

规则6-1:调用函数要检查所有可能的返回情况, 不应该的返回情况要用ASSERT来确认。

建议6-1:编写可重入函数时,应注意局部变量的使用(如编写C/C++语言的可重入函数时,应使用auto即缺省态局部变量或寄存器变量)。

说明:编写C/C++语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

建议6-2:在同一项目组应明确规定,对接口函数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责。

说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

建议6-3:函数的规模尽量限制在200行以内。

说明:不包括注释和空格行。

建议6-4:一个函数仅完成一件功能。

说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。

规则6-2:尽量写类的构造、拷贝构造、析构和赋值函数 ,而不使用系统缺省的。

说明:编译器以“位拷贝”的方式自动生成缺省的拷贝构造函数和赋值函数函数 ,倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误

示例:假设有以下的类定义:

class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &other); // 拷贝构造函数

软件编程规范

};

如果将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成以下的错误:

1) b.m_data原有的内存没被释放,造成内存泄露;

2) b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方; 3) 在对象被析构时,m_data被释放了两次。

~ String(void); // 析构函数 String & operate =(const String &other); // 赋值函数 private: char *m_data; // 用于保存字符串

String a , b ;

建议6-5:尽量不使用与程序运行的环境相关的系统函数

示例:如strxfrm () 和strcoll () ,这两个函数依赖于 LC_COLLATE 的设置。如果进程所运行的环境变量没有与开发环境一样设置,可能会产生错误的结果

建议6-6:尽量不要编写依赖于其他函数内部实现的函数。

说明:此条为函数独立性的基本要求。由于目前大部分高级语言都是结构化的,所以通过具体语言的语法要求与编译器功能,基本就可以防止这种情况发生。但在汇编语言中,由于其灵活性,很可能使函数出现这种情况。

示例:如下是在DOS下TASM的汇编程序例子。过程Print_Msg的实现依赖于Input_Msg的具体实现,这种程序是非结构化的,难以维护、修改。

... // 程序代码

proc Print_Msg // 过程(函数)Print_Msg ... // 程序代码 jmp LABEL ... // 程序代码 endp

proc Input_Msg // 过程(函数)Input_Msg ... // 程序代码

软件编程规范

LABEL:

... // 程序代码 endp

规则6-3:检查函数所有参数与非参数的有效性。 说明:

1. 函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即

非参数输入。函数在使用输入之前,应进行必要的检查。 2. 不应该的入口情况要用ASSERT来确认

3. 有时候不处理也是一种处理,但要明确哪些情况不处理。try...catch 是一种常用的不处

理的处理手段

建议6-7:函数实现中不改变内容的参数要定义成const

示例: int GetStrLen(const char*);

int GetNumberCount(const CString&);

规则6-4:函数的返回值要清楚、明了,让使用者不容易忽视错误情况。

说明:函数的每种出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误或忽视错误返回码。

建议6-8:设计高扇入、合理扇出(小于7)的函数。

说明:

1. 扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调

用它。

2. 扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总

是1,表明函数的调用层次可能过多,这样不利程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。

3. 扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函

数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。 4. 较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则

扇入到公共模块中。

软件编程规范

规则6-5:减少函数本身或函数间的递归调用。

说明:递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。

软件编程规范

第七章 程序效率

规则7-1:在保证软件系统的正确性、稳定性、可读性及可测性的前提下,提高代码效率。 说明:

1. 代码效率分为全局效率、局部效率、时间效率及空间效率。全局效率是站在整个系统

的角度上的系统效率;局部效率是站在模块或函数角度上的效率;时间效率是程序处理输入任务所需的时间长短;空间效率是程序所需内存空间,如机器代码空间大小、数据空间大小、栈空间大小等。

2. 不能一味地追求代码效率,而对软件的正确性、稳定性、可读性及可测性造成影响。 规则7-2:局部效率应为全局效率服务,不能因为提高局部效率而对全局效率造成影响。

建议7-1:通过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。

说明:这种方式是解决软件空间效率的根本办法。

示例:如下记录学生学习成绩的结构不合理。

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; 规则7-3:循环体内工作量最小化。 说明:

1. 应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而

提高程序的时间效率。

2. 在多重循环中,应将最忙的循环放在最内层,这样可以减少CPU切入循环层的次数,

从而提高效率

示例:如下代码效率不高。

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 */

建议7-2:对模块中函数的划分及组织方式进行分析、优化,改进模块中函数的组织结构,提高程序效率。

说明:软件系统的效率主要与算法、处理任务方式、系统功能及函数结构有很大关系,仅在代码上下功夫一般不能解决根本问题。

建议7-3:避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中。

说明:目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以。

示例:如下代码效率稍低。

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; } }

建议7-4:在逻辑清楚且不影响可读性的情况下,代码越少越好

说明:写程序不是以行数的多少来判断一个人的工作效率,代码不是越多越好。

规则7-4:尽量使用标准库函数,不要“发明”已经存在的库函数“

建议7-5:要尽量重用已有的代码,直接调用已有的API

说明:如果原有的代码质量比较好,尽量复用它。但是不要修补很差劲的代码,应当重新编写。

软件编程规范

第八章质量保证

规则8-1:只引用属于自己的存贮空间。

说明:若模块封装的较好,那么一般不会发生非法引用他人的空间。 规则8-2:防止引用已经释放的内存空间。

说明:在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块(如C语言指针),而另一模块在随后的某个时刻又使用了它。

规则8-3:过程/函数中动态分配的资源(包括内存、文件等),在过程/函数退出之前要释放。 示例:以下例子将造成内存泄露 :

void Func(void) {

char *p = (char *) malloc(128); ?? //do something return ; }

建议8-1:充分理解new/delete,malloc/free 等指针相关的函数的意义,对指针操作时需小心翼翼

示例:

1. 内存被释放了,并不表示指针会消亡或者成了NULL指针

char *p = (char *) malloc(100); strcpy(p, “hello”); free(p); …… //do something

if (p != NULL) {

软件编程规范

?? //These code will be executed ? }

p 没有成为NULL 指针,if 中间的代码被错误执行了。

2. new 与delete 要“步调一致”

void Func() {

Object *pObjects=new Object[10] ; ?? // do something delete pObjects ; }

申请了10 个Object空间,但只释放了1 个,造成内存泄露。正确的写法是: delete []pObjects ;

规则8-4:防止内存操作越界。

说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。

示例:假设某软件系统最多可由10个用户同时使用,用户号为1-10,那么如下程序存在问题。

#define MAX_USR_NUM 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; } }

建议8-1:要时刻注意易混淆的操作符。当编完程序后,应从头至尾检查一遍这些操作符,以防止拼写错误。

说明:形式相近的操作符最容易引起误用,如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 ));

建议8-2:条件表达式要把常量写在前面

说明:习惯写 if (MAX_COUNT == nIndex) 就不会发生 if (nIndex = MAX_COUNT)的错误。

建议8-3:有可能的话,if语句尽量加上else分支,对没有else分支的语句要小心对待;switch语句必须有default分支。 规则8-2:尽量少用goto语句。 说明:

1. goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。 2. 使用goto 语句时,不能往回跳 3. 尽量不要用多于一个的goto语句标记

规则8-3:不使用与硬件或操作系统关系很大的语句,而使用建议的标准语句,以提高软件的

软件编程规范

可移植性和可重用性。 示例:

for( idx=0 ; idx<40; dest[idx]=src[idx++] ) ;

这段代码中,在不同的操作系统 ,有不同的执行顺序。是先执行idx++ 呢?还是先执行dest[idx]=src[idx++] ?

正确的写法是:

for (idx=0 ; idx<40; idx ++) {

dest[idx]=src[idx] ; }

建议8-4:时刻注意表达式是否会上溢、下溢。

示例:如下程序将造成变量下溢。

unsigned char size ;

while (size-- >= 0) // 将出现下溢 {

... // program code }

当size等于0时,再减1不会小于0,而是0xFF,故程序是一个死循环。应如下修改。 char size; // 从unsigned char 改为char while (size-- >= 0) {

... // program code }

规则8-4:使用第三方提供的软件开发工具包或控件时,要注意以下几点:

(1)充分了解应用接口、使用环境及使用时注意事项。 (2)不能过分相信其正确性。

(3)除非必要,不要使用不熟悉的第三方工具包与控件。

说明:使用工具包与控件,可加快程序开发速度,节省时间,但使用之前一定对它有较充分的了解,同时第三方工具包与控件也有可能存在问题。

软件编程规范

规则8-5:资源文件(多语言版本支持),如果资源是对语言敏感的,应让该资源与源代码文件脱离,具体方法有下面几种:使用单独的资源文件、DLL文件或其它单独的描述文件(如数据库格式)。

规则8-6:打开编译器的所有告警开关对程序进行编译,并且要确认、处理所有的编译告警

建议8-5:通过代码走读及审查方式对代码进行检查。

说明:代码走读主要是对程序的编程风格如注释、命名等以及编程时易出错的内容进行检查,可由开发人员自己或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及程序的稳定性、安全性、可靠性等进行检查及评审,可通过自审、交叉审核或指定部门抽查等方式进行

建议8-6:如果可能,单元测试要覆盖98%以上的代码,尽可能早地发现和解决问题。

说明:

1. 尽早发现问题可以避免问题的扩大化,减少运维成本。

2. 开发人员要树立“不要依赖测试人员”的观念,且不要抱侥幸心理,会出问题的地方

总是会出问题的。

建议8-7:如果可能,尽量使用pc-lint,purify,LogiScope 等测试工具,以提高效率

本文来源:https://www.bwwdw.com/article/w5qo.html

Top