文章分类

当前位置:首页>技术文章>编程规范

编程规范

今天人们越来越明白软件设计更多地是一种工程而不是一种个人艺术由于大型产品的开发通常由很多的人协同作战如果不统一编程规范最终合到一起的程序其可读性将较差这不仅给代码的理解带来障碍增加维护阶段的工作量同时不规范的代码隐含错误的可能性也比较大 BELL 实验室的研究资料表明软件错误中 18%左右产生于概要设计阶段 15%左右产生于详细设计阶段而编码阶段产生的错误占的比例则接近 50%分析表明编码阶段产生的错误当中语法错误大概占 20%左右而由于未严格检查软件逻辑导致的错误函数模块之间接口错误及由于代码可理解度低导致优化维护阶段对代码的错误修改引起的错误则占了一半以上可见提高软件质量必须降低编码阶段的错误率如何有效降低编码阶段的错误呢 BELL 实验室的研究人员制定了详细的软件编程规范并培训每一位程序员最终的结果把编码阶段的错误降至 10%左右同时也降低了程序的测试费用效果相当显著本文从代码的可维护性可读可理解性可修改性代码逻辑与效率函数模块接口可测试性四个方面阐述了软件编程规范规范分成规则和建议两种其中规则部分为强制执行项目而建议部分则不作强制可根据习惯取舍 2. 编码规范 2.1. 排版风格程序块采用缩进风格编写缩进为 4 个空格位排版不混合使用空格和 TAB 键在两个以上的关键字变量常量进行对等操作时它们之间的操作符之前之后或者前后要加空格进行非对等操作时如果是关系密切的立即操作符如> 后不应加空格采用这种松散方式编写代码的目的是使代码更加清晰例如(1) 逗号分号只在后面加空格 printf("%d %d %d" , a, b, c);(2)比较操作符, 赋值操作符"=" "+="算术操作符"+""%"逻辑操作符"&&""&"位域操作符"= MAX_TIME_VALUE)a = b + c;a *= 2;a = b ^ 2;(3)"!

""~""++""--""&"地址运算符等单目操作符前后不加空格*pApple = \'a\'; // 内容操作"*"与内容之间 flag = !bIsEmpty; // 非操作"!"与内容之间 p = &cMem; // 地址操作"&" 与内容之间 i++; // "++","--"与内容之间(4)"->""."前后不加空格 p->id = pId; // "->"指针前后不加空格由于留空格所产生的清晰性是相对的所以在已经非常清晰的语句中没有必要再留空格如最内层的括号内侧(即左括号后面和右括号前面)不要加空格因为在 C/C++语言中括号已经是最清晰的标志了另外在长语句中如果需要加的空格非常多那么应该保持整体清晰而在局部不加空格最后即使留空格也不要连续留两个以上空格(为了保证缩进和排比留空除外) 函数体的开始类的定义结构的定义 iffordowhileswitch 及 case 语句中的程序都应采用缩进方式憑捄蛻}捰禀独占一行并且位于同一列同时与引用它们的语句左对齐例如下例不符合规范 for ( ... ) {... // 程序代码}if ( ... ){... // 程序代码}void DoExam( void ){... // 程序代码}应如下书写 for ( ... ){... // 程序代码}if ( ... ){... // 程序代码}void DoExam( void ){... // 程序代码} 功能相对独立的程序块之间或 forifdowhileswitch 等语句前后应加一空行例如以下例子不符合规范例一 if ( !

ValidNi( ni ) ){... // 程序代码}nRepssnInd = SsnData[ index ].nRepssnIndex ;nRepssnNi = SsnData[ index ].ni ;例二 char *pContext;int nIndex;long lCounter;pContext = new (CString);if(pContext == NULL){return FALSE;}应如下书写例一 if ( !

ValidNi( ni ) ){... // 程序代码}nRepssnInd = SsnData[ index ].nRepssnIndex ;nRepssnNi = SsnData[ index ].ni ;例二 char *pContext;int nIndex;long lCounter;pContext = new (CString);if(pContext == NULL){return FALSE;} ifwhileforcasedefaultdo 等语句自占一行示例如下例子不符合规范 if(pUserCR == NULL) return;应如下书写 if( pUserCR == NULL ){return;} 若语句较长多于 80 字符可分成多行写划分出的新行要进行适应的缩进使排版整齐语句可读 memset(pData->pData + pData->nCount, 0,(m_nMax - pData->nCount) * sizeof(LPVOID));CNoTrackObject* pValue =(CNoTrackObject*)_afxThreadData->GetThreadValue(m_nSlot);for ( i = 0, j = 0 ; ( i 一行最多写一条语句示例如下例子不符合规范 rect.length = 0 ; rect.width = 0 ;rect.length = width = 0;都应书写成 rect.length = 0 ;rect.width = 0 ; 对结构成员赋值等号对齐示例 rect.top = 0;rect.left = 0;rect.right = 300;rect.bottom = 200; #define 的各个字段对齐以下示例不符合规范#define MAX_TASK_NUMBER 100#define LEFT_X 10#define BOTTOM_Y 400 应书写成#define MAX_TASK_NUMBER 100#define LEFT_X 10#define BOTTOM_Y 400 不同类型的操作符混合使用时使用括号给出优先级如本来是正确的代码 if( year % 4 == 0 || year % 100 != 0 && year % 400 ==0 )如果加上括号则更清晰 if((year %

4) == 0 || ((year % 100) != 0 && (year % 400) ==

0))2.2. 可理解性 2.2.1.注释注释的原则是有助于对程序的阅读理解注释不宜太多也不能太少太少不利于代码理解太多则会对阅读产生干扰因此只在必要的地方才加注释而且注释要准确易懂尽可能简洁注释量一般控制在 30%到 50%之间程序在必要的地方必须有注释注释要准确易懂简洁例如如下注释意义不大/* 如果 bReceiveFlag 为 TRUE */if ( bReceiveFlag == TRUE)而如下的注释则给出了额外有用的信息/* 如果 mtp 从连接处获得一个消息*/if ( bReceiveFlag == TURE) 注释应与其描述的代码相近对代码的注释应放在其上方或右方对单条语句的注释相邻位置不可放在下面如放于上方则需与其上面的代码用空行隔开示例如下例子不符合规范例子 1/* 获得系统指针和网络指针的副本 */nRepssnInd = SsnData[ index ].nRepssnIndex ;nRepssnNi = SsnData[ index ].ni ;例子 2nRepssnInd = SsnData[ index ].nRepssnIndex ;nRepssnNi = SsnData[ index ].ni ;/*获得系统指针和网络指针的副本 */应如下书写/*获得系统指针和网络指针的副本 */nRepssnInd = SsnData[ index ].nRepssnIndex ;nRepssnNi = SsnData[ index ].ni ; 对于所有的常量变量数据结构声明(包括数组结构类枚举等)如果其命名不是充分自注释的在声明时都必须加以注释说明其含义示例/* 活动任务的数量 */#define MAX_ACT_TASK_NUMBER 1000#define MAX_ACT_TASK_NUMBER 1000 /*活动任务的数量 *//* 带原始用户信息的 SCCP 接口 */enum SCCP_USER_PRIMITIVE{N_UNITDATA_IND , /* 向 SCCP 用户报告单元数据已经到达 */N_UNITDATA_REQ , /* SCCP 用户的单元数据发送请求 */} 头文件源文件的头部应进行注释注释必须列出文件名作者目的功能修改日志等例如/*********************************************文件名编写者编写日期简要描述修改记录********************************************/说明摷蛞枋鰯一项描述本文件的目的和功能等撔薷募锹紨是修改日志列表每条修改记录应包括修改日期修改者及修改内容简述函数头部应进行注释列出函数的目的功能输入参数输出参数修改日志等形式如下/*************************************************函数名称简要描述 // 函数目的功能等的描述输入 // 输入参数说明包括每个参数的作用取值说明及参数间关系输出 // 输出参数的说明返回值的说明修改日志*************************************************/对一些复杂的函数在注释中最好提供典型用法仔细定义并明确公共变量的含义作用取值范围及使用方法在对变量声明的同时应对其含义作用取值范围及使用方法进行注释说明同时若有必要还应说明与其它变量的关系明确公共变量与操作此公共变量的函数或过程的关系如访问修改及创建等示例/* SCCP 转换时错误代码 *//* 全局错误代码含义如下 */ // 变量作用含义/* 0 成功 1 GT 表错误 2 GT 错误其它值未使用 */ // 变量取值范围对指针进行充分的注释说明对其作用含义使用范围注意事项等说明清楚在对指针变量特别是比较复杂的指针变量声明时应对其含义作用及使用范围进行注释说明如有必要还应说明其使用方法注意事项等示例/* 学生记录列表的头指针 *//* 当在此模块中创建该列表时该头指针必须初始化 *//* 这样可以利用 GetListHead()获得这一列表*/ //指针作用含义/* 该指针只在本模块使用其它模块通过调用 GetListHead()获取*//* 当使用时必须保证它非空 */ //使用范围方法 STUDENT_RECORD *pStudentRecHead; 对重要代码段的功能意图进行注释提供有用的额外的信息并在该代码段的结束处加一行注释表示该段代码结束示例/* 可选通道的组合 */if ((gsmBCIe31->radioChReq >= DUAL_HR_RCR)&& (gsmBCIe32->radioChReq >= DUAL_HR_RCR)){gsmBCIe31->radioChReq = FR_RCR;gsmBCIe32->radioChReq = FR_RCR;}else if ((gsmBCIe31->radioChReq >= DUAL_HR_RCR)&& (gsmBCIe32->radioChReq == FR_RCR) ){gsmBCIe31->radioChReq = FR_RCR;}else if ((gsmBCIe31->radioChReq == FR_RCR)&& (gsmBCIe32->radioChReq >= DUAL_HR_RCR)){gsmBCIe32->radioChReq = FR_RCR;}/* 本块结束 ( 可选通道组合 ) */ 在 switch 语句中对没有 break 语句的 case 分支加上注释说明示例 switch(SubT30State){case TA0:AT(CHANNEL, "AT+FCLASS=1\ ",0);if(T30Status !=

0){return(1);}InitFax(); /* 准备发送传真 */AT(CHANNEL, "ATD\ ",-1); /*发送 CNG 接收 CED 和 HDLC 标志*/T1_Flg = 1;iResCode = 0;/* 没有 break; */case TA1:iResCode = GetModemMsg(CHANNEL);break;default:break;} 维护代码时要更新相应的注释删除不再有用的注释保持代码注释的一致性避免产生误解 2.2 命名本文列出 Visual C++的标识符命名规范标识符缩写形成缩写的几种技术 1) 去掉所有的不在词头的元音字母如 screen 写成 scrn, primtive 写成 prmv2) 使用每个单词的头一个或几个字母如 Channel Activation 写成 ChanActivRelease Indication 写成 RelInd3) 使用变量名中每个有典型意义的单词如 Count of Failure 写成 FailCnt4) 去掉无用的单词后缀 ing, ed 等如 Paging Request 写成 PagReq5) 使用标准的或惯用的缩写形式包括协议文件中出现的缩写形式如 BSIC(BaseStation Identification Code)MAP(Mobile Application Part)关于缩写的准则 1) 缩写应该保持一致性如 Channel 不要有时缩写成 Chan 有时缩写成 ChLength 有时缩写成 Len 有时缩写成 len2) 在源代码头部加入注解来说明协议相关的非通用缩写 3) 标识符的长度不超过 32 个字符变量命名约定参照匈牙利记法即[作用范围域前缀] + [前缀] + 基本类型 + 变量名其中前缀是可选项以小写字母表示基本类型是必选项以小写字母表示变量名是必选项可多个单词(或缩写)合在一起每个单词首字母大写前缀列表如下前缀意义举例 g_ Global 全局变量 g_MyVarm_ 类成员变量或模块级变量 m_ListBox, m_Sizes_ static 静态变量 s_Counth Handle 句柄 hWndp Pointer 指针 pTheWordlp Long Point 长指针 lpCmda Array 数组 aErr 基本类型列表如下基本类型意义举例 b Boolean 布尔 bIsOKby Byte 字节 byNumc Char 字符 cMyChari 或 n Intger 整数 nTestNumberu Unsigned integer 无符号整数 uCountul Unsigned Long 无符号长整数 ulTimew Word 字 wParadw Double Word 双字 dwParal Long 长型 lParaf Float 浮点数 fTotals String 字符串 sTempsz NULL 结束的字符串 szTreesfn Funtion 函数 fnAddenm 枚举型 enmDaysx,y x,y 坐标宏和常量的命名宏和常量的命名规则单词的字母全部大写各单词之间用下划线隔开命名举例#define MAX_SLOT_NUM 8#define EI_ENCR_INFO 0x07const int MAX_ARRAY 结构和结构成员的命名结构名各单词的字母均为大写单词间用下划线连接可用或不用 typedef 但是要保持一致不能有的结构用 typedef 有的又不用如 typedef struct LOCAL_SPC_TABLE_STRU{char cValid;int nSpcCode[MAX_NET_NUM];} LOCAL_SPC_TABLE ;结构成员的命名同变量的命名规则枚举和枚举成员的命名枚举名各单词的字母均为大写单词间用下划线隔开枚举成员的命名规则单词的字母全部大写各单词之间用下划线隔开要求各成员的第一个单词相同命名举例 typdef enum{LAPD_ MDL_ASSIGN_REQ,LAPD_MDL_ASSIGN_IND,LAPD_DL_DATA_REQ,LAPD_DL_DATA_IND,LAPD_DL_UNIT_DATA_REQ,LAPD_DL_UNIT_DATA_IND,} LAPD_PRMV_TYPE; 类的命名前缀意义举例 C 类 CMyClassCO COM 类 COMMyObjectClassCF COM class factory CFMyClassFactoryI COM interface class IMyInterfaceCImpl COM implementation class CImplMyInterface 函数的命名单词首字母为大写其余均为小写单词之间不用下划线函数名应以一个动词开头即函数名应类似摱鼋峁箶命名举例 void PerformSelfTest(void) ;void ProcChanAct(MSG_CHAN_ACTIV *pMsg, UC MsgLen);2.3. 可维护性在逻辑表达式中使用明确的逻辑判断示例如下逻辑表达式不规范 1) if ( strlen(strName) )2) for ( index = MAX_SSN_NUMBER; index ; index -- )3) while ( p && *p ) // 假设 p 为字符指针应改为如下 1) if ( strlen(strName) !=

0 )2) for ( index = MAX_SSN_NUMBER; index != 0 ; index -- )3) while ((p != NULL) && (*p != \'\\0\' )) 预编译条件不应分离一完整的语句不正确 if (( cond == GLRUN)#ifdef DEBUG|| (cond == GLWAIT)#endif){}正确#ifdef DEBUGif( cond == GLRUN || cond == GLWAIT )#elseif( cond == GLRUN )#endif{} 在宏定义中合并预编译条件不正确#ifdef EXPORTfor ( i = 0; i 使用宏定义表达式时要使用完备的括号如下的宏定义表达式都存在一定的隐患#define REC_AREA(a, b) a * b#define REC_AREA(a, b) (a * b)#define REC_AREA(a, b) (a) * (b)正确的定义为#define REC_AREA(a, b) ((a) * (b)) 宏所定义的多条表达式应放在大括号内示例下面的语句只有宏中的第一条表达式被执行为了说明问题 for 语句的书写稍不符规范#define INIT_RECT_VALUE( a, b ) a = 0 ; b = 0 ;for ( index = 0 ; index 宏定义不能隐藏重要的细节避免有 returnbreak 等导致程序流程转向的语句如下例子是不规范的应用其中隐藏了程序的执行流程#define FOR_ALL for(i = 0; i 使用宏时不允许参数发生变化下面的例子隐藏了重要的细节隐含了错误#define SQUARE ((x) * (x))..w = SQUARE(++value);这个引用将被展开称 w = ((++value) * (++value));其中 value 累加了两次与设计思想不符正确的用法是 w = SQUARE(x);x++; 当 ifwhilefor 等语句的程序块为摽諗时使用搟}敺拧_while ( *s++ == *t++ ) ;以上代码不符合规范正确的书写方式为 while( *s++ == *t++ ){/* 无循环体 */}或 while( *s++ == *t++ ){} 结构中元素布局合理一行只定义一个元素如下例子不符合规范 typedef struct{_UI left, top, right, bottom;} RECT;应书写称 typedef struct{_UI left; /* 矩形左侧 x 坐标 */_UI top;_UI right;_UI bottom;} RECT; 枚举值从小到大顺序定义包含头文件时使用撓喽月肪稊不使用摼月肪稊-------------------------------------------------------------第 16 页如下引用#include "c:\\switch\\inc\\def.inc"应改为#include "inc\\def.inc"或#include "def.inc" 不允许使用复杂的操作符组合等下面用法不好 iMaxVal = ( (a > b ?

a : b) > c ? (a > b ? a : b) : c );应该为 iTemp = ( a > b ? a : b);iMaxVal = (iTemp > b ? iTemp : b);不要把"++""--"操作符与其他如"+=""-="等组合在一起形成复杂奇怪的表达式如下的表达式那以理解*pStatPoi++ += 1;*++pStatPoi += 1;应分别改为*pStatPoi += 1;pStatPoi++;和++pStatPoi;*pStatPoi += 1; 函数和过程中关系较为紧密的代码尽可能相邻如初始化代码应放在一起不应在中间插入实现其它功能的代码以下代码不符合规范,for (uiUserNo = 0; uiUserNo 每个函数的源程序行数原则上应该少于 200 行对于消息分流处理函数完成的功能统一但由于消息的种类多可能超过 200 行的限制不属于违反规定语句嵌套层次不得超过 5 层嵌套层次太多增加了代码的复杂度及测试的难度容易出错增加代码维护的难度用 sizeof 来确定结构联合或变量占用的空间这样可提高程序的可读性可维护性同时也增加了程序的可移植性避免相同的代码段在多个地方出现当某段代码需在不同的地方重复使用时应根据代码段的规模大小使用函数调用或宏调用的方式代替这样对该代码段的修改就可在一处完成增强代码的可维护性使用强制类型转换示例 USER_RECORD *pUser;pUser = (USER_RECORD *) malloc (MAX_USER * sizeof(USER_RECORD)); 避免使用 goto 语句避免产生摮绦蚪釘 program knots 在循环语句中尽量避免 breakgoto 的使用如下例子 for( i = 0; i 功能相近的一组常量最好使用枚举来定义不推荐定义方式/* 功能寄存器值 */#define ERR_DATE 1 /* 日期错误 */#define ERR_TIME 2 /* 时间错误 */#define ERR_TASK_NO 3 /* 任务号错误 */推荐按如下方式书写/*功能寄存器值 */enum ERR_TYPE{ERR_DATE = 1, /*日期错误 */ERR_TIME = 2, /*时间错误 */ERR_TASK_NO = 3 /* 任务号错误 */} 每个函数完成单一的功能不设计多用途面面俱到的函数多功能集于一身的函数很可能使函数的理解测试维护等变得困难使函数功能明确化增加程序可读性亦可方便维护测试循环判断语句的程序块部分用花括号括起来即使只有一条语句如-------------------------------------------------------------第 19 页 if( bCondition == TRUE )bFlag = YES;建议按以下方式书写 if( bCondition == TRUE ){bFlag = YES;}这样做的好处是便于代码的修改增删一行只声明一个变量不推荐的书写方式 void DoSomething(void){int Amicrtmrs, nRC;int nCode, nStatus;推荐做法 void DoSomething(void){int nAmicrtmrs; /* ICR 计时器 */int nRC; /* 返回码 */int nCode; /* 访问码 */int nStatus; /* 处理机状态 */ 使用专门的初始化函数对所有的公共变量进行初始化使用可移植的数据类型尽量不要使用与具体硬件或软件环境关系密切的变量用明确的函数实现不明确的语句功能示例如下语句的功能不很明显 value = ( a > b ) ?

a : b ;改为如下就很清晰了 int max( int a, int b ){return ( ( a > b ) ? a : b ) ;-------------------------------------------------------------第 20 页}value = max( a, b ) ;或改为如下#define MAX( a, b ) ( ( ( a ) > ( b ) ) ?

( a ) : ( b ) )value = MAX( a, b ) ;4. 程序正确性效率严禁使用未经初始化的变量引用未经初始化的变量可能会产生不可预知的后果特别是引用未经初始化的指针经常会导致系统崩溃需特别注意声明变量的同时初始化除了能防止引用未经初始化的变量外还可能生成更高效的机器代码定义公共指针的同时对其初始化这样便于指针的合法性检查防止应用未经初始化的指针建议对局部指针也在定义的同时初始化形成习惯较大的局部变量 2K 以上应声明成静态类型 static 避免占用太多的堆栈空间避免发生堆栈溢出出现不可预知的软件故障防止内存操作越界说明内存操作主要是指对数组指针内存地址等的操作内存操作越界是软件系统主要错误之一后果往往非常严重所以当我们进行这些操作时一定要仔细小心 A 数组越界 char aMyArray[10];for( i = 0; i 减少没必要的指针使用特别是较复杂的指针如指针的指针数组的指针指针的数组函数的指针等用指针虽然灵活但也对程序的稳定性造成一定威胁主要原因是当要操作一个指针时此指针可能正指向一个非法的地址安安全全地使用一个指针并不是一件容易的事情防止引用已经释放的内存空间在实际编程过程中稍不留心就会出现在一个模块中释放了某个内存块如指针而另一模块在随后的某个时刻又使用了它要防止这种情况发生程序中分配的内存申请的文件句柄在不用时应及时释放或关闭分配的内存不释放以及文件句柄不关闭是较常见的错误而且稍不注意就有可能发生这类错误往往会引起很严重后果且难以定位注意变量的有效取值范围防止表达式出现上溢或下溢示例 unsigned char cIndex = 10;while( cIndex-- >=

0 ){} //将出现下溢当 cIndex 等于 0 时再减 1 不会小于 0 而是 0xFF 故程序是一个死循环 char chr = 127;chr += 1; //127 为 chr 的边界值再加 1 将使 chr 上溢到-128 而不是 128 防止精度损失以下代码将产生精度丢失#define DELAY_MILLISECONDS 10000char time;time = DELAY_MILLISECONDS;WaitTime( time );代码的本意是想产生 10 秒钟的延时然而由于 time 为字符型变量只取 DELAY_MILLISECONDS 的低字节高位字节将丢失结果只产生了 16 毫秒的延时-------------------------------------------------------------第 22 页防止操易混淆的作符拼写错误形式相近的操作符最容易引起误用如 C/C++中的=斢霌==敗|斢霌||敗&斢霌&&數龋羝葱创砹耍嘁肫鞑灰欢芄患觳槌隼础_示例如把&斝闯蓳&&敚蚍粗_bRetFlag = ( pMsg -> bRetFlag & RETURN_MASK ) ;被写为 bRetFlag = ( pMsg -> bRetFlag && RETURN_MASK ) ; 使用无符号类型定义位域变量示例 typedef struct{int bit1 : 1;int bit2 : 1;int bit3 : 1;} bit;bit.bit1 = 1;bit.bit2 = 3;bit.bit3 = 6;printf("%d, %d, %d", bit.bit1, bit.bit2, bit.bit3 );输出结果为-1,-1, -2 不是: 1,3,6. switch 语句的程序块中必须有 default 语句对不期望的情况包括异常情况进行处理保证程序逻辑严谨当声明用于分布式环境或不同 CPU 间通信环境的数据结构时必须考虑机器的字节顺序使用的位域也要有充分的考虑比如 Intel CPU 与 68360 CPU 在处理位域及整数时其在内存存放的撍承驍正好相反示例假如有如下短整数及结构 unsigned short int exam ;typedef struct _EXAM_BIT_STRU{ /* Intel 68360 */unsigned int A1 : 1 ; /* bit 0 2 */unsigned int A2 : 1 ; /* bit 1 1 */unsigned int A3 : 1 ; /* bit 2 0 */} _EXAM_BIT ;-------------------------------------------------------------第 23 页如下是 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 低字节内存 0 bit 1 bit 2 bit ... 字节的各撐粩_EXAM_BIT A3 A2 A1 编写可重入函数时应注意局部变量的使用如编写 C/C++语言的可重入函数时应使用 auto 即缺省态局部变量或寄存器变量可重入性是指函数可以被多个任务进程调用在多任务操作系统中函数是否具有可重入性是非常重要的因为这是多个进程可以共用此函数的必要条件另外编译器是否提供可重入函数库与它所服务的操作系统有关只有操作系统是多任务时编译器才有可能提供可重入函数库如 DOS 下 BC 和 MSC 等就不具备可重入函数库因为 DOS 是单用户单任务操作系统编写 C/C++语言的可重入函数时不应使用 static 局部变量否则必须经过特殊处理才能使函数具有可重入性编写可重入函数时若使用全局变量则应通过关中断信号量即 P V 操作等手段对其加以保护结构中的位域应尽可能相邻结构中的位域在开始处应对齐撟纸跀或撟謹的边界这样可减少结构占用的内存空间减少 CPU 处理位域的时间提高程序效率示例如下结构中的位域布局不合理假设例子在 Intel CPU 环境下 typedef struct _EXAMPLE_STRU{unsigned int nExamOne : 6 ;unsigned int nExamTwo : 3 ; // 此位域跨越字节摻唤訑处 unsigned int nExamThree : 4 ;} _EXAMPLE ;-------------------------------------------------------------第 24 页应改为如下按字节对齐 typedef struct _EXAMPLE_STRU{unsigned int nExamOne : 6 ;unsigned int nFreeOne : 2 ; // 保留 bit 位使下个位域从字节开始 unsigned int nExamTwo : 3 ; // 此位域从新的字节处开始 unsigned int nExamThree : 4 ;} _EXAMPLE ; 避免函数中不必要语句防止程序中的垃圾代码预留代码应以注释的方式出现程序中的垃圾代码不仅占用额外的空间而且还常常影响程序的功能与性能很可能给程序的测试维护等造成不必要的麻烦通过对系统数据结构的划分与组织的改进以及对程序算法的优化来提高空间效率这种方式是解决软件空间效率的根本办法示例如下记录学生学习成绩的结构不合理 typedef unsigned char _UC ;typedef unsigned int _UI ;typedef struct _STUDENT_SCORE_STRU{_UC szName[ 8 ] ;_UC cAge ;_UC cSex ;_UC cClass ;_UC cSubject ;float fScore ;} _STUDENT_SCORE ;因为每位学生都有多科学习成绩故如上结构将占用较大空间应如下改进分为两个结构总的存贮空间将变小操作也变得更方便 typedef struct _STUDENT_STRU{_UC szName[ 8 ] ;_UC cAge ;-------------------------------------------------------------第 25 页_UC cSex ;_UC cClass ;} _STUDENT ;typedef struct _STUDENT_SCORE_STRU{_UI iStudentIndex ;_UC cSubject ;float fScore ;} _STUDENT_SCORE ; 循环体内工作量最小化应仔细考虑循环体内的语句是否可以放在循环体之外使循环体内工作量最小从而提高程序的时间效率示例如下代码效率不高 for ( i= 0 ; i 在多重循环中应将最忙的循环放在最内层避免循环体内含判断语句将与循环变量无关的判断语句移到循环体外目的是减少判断次数循环体中的判断语句是否可以移到循环体外要视程序的具体情况而言一般情况与循环变量无关的判断语句可以移到循环体外而有关的则不可以尽量用乘法或其它方法代替除法特别是浮点运算中的除法在时间效率要求不是特别严格时要优先保证程序的可读性说明浮点运算除法要占用较多 CPU 资源示例如下表达式运算可能要占较多 CPU 资源-------------------------------------------------------------第 26 页#define PAI

3.1416fRadius = fCircleLength / ( 2 * PAI ) ;应如下把浮点除法改为浮点乘法#define PAI_RECIPROCAL ( 1 /

3.1416 ) // 编译器编译时将生成具体浮点数 fRadius = fCircleLength * PAI_RECIPROCAL / 2 ; 用++敚--敳僮鞔鎿+=1 敚-=1 敚岣叱绦蛩俣取_ 系统输入如用户输入系统输出如信息包输出系统资源操作如内存分配文件及目录操作网络操作如通信调用等任务之间的操作如通信调用等时必须进行错误超时或者异常处理定义字符串变量的同时将其初始化为空即摂以避免无限长字符串在 switch 语句中将经常性的处理放在前面 2.5. 接口头文件应采用 #ifndef / #define / #endif 的方式来防止多次被嵌入示例如下假设头文件为揇 EF.INC"则其内容应为#ifndef __DEF_INC#define __DEF_INC...#endif 去掉没有必要的公共变量编程时应尽量少用公共变量公共变量是增大模块间耦合的原因之一故应减少没必要的公共变量以降低模块间的耦合度应该构造仅有一个模块或函数可以修改创建而其余有关模块或函数只访问的公共变量防止多个不同模块或函数都可以修改创建同一公共变量的现象当向公共变量传递数据时要防止越界现象发生对公共变量赋值时若有必要应进行合法性检查以提高代码的可靠性稳定性返回值为指针的函数不可将局部变量的地址作为返回值当函数退出时非 static 局部变量将消失所以引用返回的指针将可能引起严重后果下例将不能完成正确的功能 char *GetFilename(int nFileNo){-------------------------------------------------------------第 27 页 char szFileName[20];sprintf( szFileName, "COUNT%d", nFileNo);return szFileName;} 尽量不设计多参数函数将不使用的参数从接口中去掉降低接口复杂度减少函数间接口的复杂度对所调用函数的返回码要仔细全面地处理防止把错误传递到后面的处理流程如有意不检查其返回码应明确指明如(void)fclose(fp); 显示地给出函数的返回值类型无返回值函数定义为 voidC C++语言的编译系统默认无显示返回值函数的返回值类型为 int 声明函数原型时给出参数名称和类型并且与实现此函数时的参数名称类型保持一致无参数的函数用 void 声明示例下面声明不正确 int CheckData( ) ;int SetPoint( int, int ) ;int SetPoint( x, y )int x, y;应改为如下声明 int CheckData( void ) ;int SetPoint( int x, int y ) ; 检查接口函数所有输入参数的有效性可直接检查或使用断言进行检查尤其是指针参数只在本模块内使用的函数可不检查检查函数的所有非参数输入如数据文件公共变量等可直接检查或使用断言进行检查尤其是指针变量声明函数原型时对于数组型参数不要声明为指针维护函数接口的清晰性示例假设函数 SortInt()完成的功能是对一组整数排序接受的参数是一整数数-------------------------------------------------------------第 28 页组及数组中的元素个数以下声明不符合规范 void SortInt(int num, int *data);应声明为 void SortInt(int num, int data[]);2.6.代码可测性模块编写应该有完善的测试方面的考虑源代码中应该设计了代码测试的内容如打印宏开关变量值函数名称函数值等在编写代码之前应预先设计好程序调试与测试的方法和手段并设计好各种调测开关及相应测试代码如打印函数等程序的调试与测试是软件生存周期中很重要的一个阶段如何对软件进行较全面高率的测试并尽可能地找出软件中的错误就成为很关键的问题因此在编写源代码之前除了要有一套比较完善的测试计划外还应设计出一系列代码测试手段为单元测试集成测试及系统联调提供方便在同一项目组或产品组内要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数并且要有详细的说明本规则是针对项目组或产品组的示例.ext 文件示例文件名为 EXAMPLE.EXT/* 头文件开始 */#ifndef __EXAMPLE_EXT#define __EXAMPLE_EXT#define _EXAMPLE_DEBUG_ // 模块测试总开关打开开关的含义是模块可以// 进行单元测试或其它功能目的等的测试#ifdef _EXAMPLE_DEBUG_#define _EXAMPLE_UNIT_TEST_ // 单元测试宏开关#define _EXAMPLE_ASSERT_TEST_ // 断言测试开关... // 其它测试开关#endif#ifndef _EXAMPLE_UNIT_TEST_ // 若没有定义单元测试-------------------------------------------------------------第 29 页#include // 各模块共用的头文件#include // 系统接口头文件#ifndef _SYSTEM_DEBUG_VERSION_ // 如果是发行版本即非 DEBUG 版#undef _EXAMPLE_UNIT_TEST_#undef _EXAMPLE_ASSERT_TEST_... // 将所有与测试有关的开关都关掉即编译时不含任何测试代码#endif#include // 与另一模块的接口头文件... // 其它接口头文件#else // 若定义了单元测试则应构造单元测试所需的环境结构等 typdef unsigned char _UC ;typdef unsigned long _UL ;#define TRUE

1... // 所有为单元测试准备的环境如宏枚举结构联合等#endif#endif /* EXAMPLE.EXT 结束 *//* 头文件结束 */ 在同一项目组或产品组内调测打印出的信息串的格式要有统一的形式信息串中至少要有所在模块名或源文件名及行号统一的调测信息格式便于集成测试使用断言来发现软件问题提高代码可测性断言是对某种假设条件进行检查可理解为若条件成立则无动作否则应报告它可以快速发现并定位软件问题同时对系统错误进行自动报警断言可以对在系统中隐藏很深用其它手段极难发现的问题进行定位从而缩短软件问题定位时间提高系统的可测性实际应用时可根据具体情况灵活地设计断言示例下面是 C 语言中的一个断言用宏来设计的其中 NULL 为 0L#ifdef _EXAM_ASSERT_TEST_ // 若使用断言测试 void ExamAssert( char * szFileName, unsigned int nLineNo ){printf( "\ [EXAM] Assert failed: %s, line %u\ ",szFileName, nLineNo ) ;abort( ) ;-------------------------------------------------------------第 30 页}#define EXAM_ASSERT( condition ) if ( condition )\\ //若条件成立则无动作 NULL ; else \\ //否则报告 ExamAssert( __FILE__, __LINE__ )#else // 若不使用断言测试#define EXAM_ASSERT( condition ) NULL#endif /* ASSERT 结束 */ 用断言来检查程序正常运行时不应发生但在调测时有可能发生的非法情况不能用断言代替错误处理来检查最终产品肯定会出现且必须处理的错误情况如某模块收到其它模块或链路上的消息后要对消息的合理性进行检查此过程为正常的错误检查不能用断言来代替


© 星光电子技术工作室 2021 All Rights Reserved.
如有任何问题、建议、业务、合作,请联系手机/微信:13713820066