软件编程规范(forC).pdf
Q/ZX 深圳市中兴通讯股份有限公司企业标准(标准类别)Q/ZX B XXX 20032003-07-21 发布2003-07-21实施深圳市中兴通讯股份有限公司网络事业部发布软件编程规范(for C)Q/ZX B XXX 2003 I目次前言.II1范围.12术语和定义.13基本原则.24命名规则.35常用数据及变量要求.56程序语句要求.77布局要求.118注释要求.159断言和错误处理.1810测试代码要求.1911动态内存分配.20Q/ZX B XXX 2003 第 II 页 共 36 页前言本标准根据中兴通讯网络事业部研究所(南京和上海)实际情况并参考有关规定编写,本文规范了研究所软件开发过程中编码的规范性要求。编码规范是指对软件代码的规划、编写、注释、检查、测试等步骤的提出的符合一定规范的要求,目的是为了提高研究所软件开发的质量和工程化水平。本标准由网络事业部质量部提出并归口。本标准由研究所(南京)测试部、研究所(上海)软件开发三部、研究所(上海)软件开发四部起草,主要起草人:樊晓兵、田小渝本标准于 2003 年 7 月首次发布。Q/ZX B XXX 2003 第 1 页 共 36 页软件编程规范(for C)1范围本标准适用于深圳市中兴通讯股份有限公司网络事业部研究所(南京、上海)进行新产品的软件设计、开发工作。2术语和定义本规范按照以下术语进行描述:规则:编程时必须遵守的原则;建议:编程时必须重点考虑的原则;说明:对上述规则或建议的解释;正例:对上述规则或建议给出的正面的例子;反例:对上述规则或建议给出的反面的例子。标准化:为保证产品的先进性、稳定性、可生产性,我们必须遵循一定的研发流程及设计规范,解决产品设计过程中的品种控制、可用性、兼容性和互换性等问题,减少成本、提高产品质量。模块化:是为了获得最佳效益,从系统观点出发,研究产品的构成形式,用分解和组合方法,建立模块体系,并运用模块组合产品的过程。模块化的表现形式是组合化,模块化是实现产品设计继承性和互换性、产品兼容性和多样性的必要条件。工程化:根据系统工程的思想,按顾客需求,在充分的市场调研、论证的基础上,以采用成熟、经济、实用的技术为主,结合具有一定把握的预研成果组织市场、设计、工艺、生产及服务等人员按并行工程的思想组织研发,将功能、可靠性、可生产性及成本设计到产品中去,并根据内外反馈的质量信息,持续改进,不断地超越竞争对手,在同业中达到与保持领先地位。鲁棒性:鲁棒性是与系统稳定性相联系的一个性质。它是指控制系统在其特性或参数发生摄动时仍可使品质指标保持不变的性能。可靠性:有关出错保障能力、健壮性、内部信息的一致性、错误识别能力、错误处理能力以及系统对噪声的敏感性等非功能性指标。结构化:结构化 程序是通过把程序的主要功能分开然后变成程序中函数的基本片段来建立的。通过孤立函数中的过程,结构化 程序使一个过程可以影响另一个的机会最少。这也使得容易隔离问题。分隔使你可以编写更加清楚的代码并维持对每一个函数的控制。全局变量消失,代之以具有较小的、更容易控制的范围的参数和局郡变量。可重用性:要求软件的模块或成份应是结构化和参数化的,并按某种适当方式存档,以便模块或成份可以达到重用的目的,从而相对减少了实际工作的复杂性和软件的规模。封装性:封装性的概念来自于面向对象的程序设计,封装意味着操作可见而将数据和操作的实现方法隐藏在所定义的对象中。内聚性:指的是在一个子程序中,各种操作之间互相联系的紧密程度。耦合度:是模块间联系强弱的度量。聚合度:是模块所执行任务的整体统一性的度量。可移植性:是软件在不同的操作环境下能够运行的程度。Q/ZX B XXX 2003 第 2 页 共 36 页3基本原则首先为人编写程序,其次才是计算机。说明:这是软件开发的基本要点,软件的生命周期贯穿产品的开发、测试、生产、用户使用、版本升级和后期维护等长期过程,只有易读、易维护的软件代码才是有生命力的。程序代码应该简明清晰和显式地表达意图。说明:不要玩弄技巧。过分完弄技巧,会使程序的可读性差。所有的代码尽可能遵循ANSI C 标准,尽可能不使用ANSI C 未定义的或编译器扩展的功能。编程时首先达到正确性,其次考虑效率。说明:编程首先考虑的是满足正确性、健壮性、可维护性,可移植性等质量因素,最后才考虑程序的效率和资源占用。保持一致性,尽可能多的使用相同的规则。坚守规范的总目标。说明:如果进行一个编码决定时,没有直接的规则可循。那么,所采取的决定必须符合规范的总目标。避免及少用全局变量。说明:不允许跨文件的全局变量。为了避免各种副作用。修正老的代码。说明:修正代码也是软件编程一个过程。按规范对老的代码进行修正,本身也是为了将来再维护老代码的方便。Q/ZX B XXX 2003 第 3 页 共 36 页4命名规则规则:规则 4.1:宏、常量,都要使用全大写字母.正例:DISP_BUF_SIZE,MIN_VALUE,MAX_VALUE等等规则 4.2:变量名字和函数名采用骆驼式命名,由前后缀加英语单词或单词的缩写组合而成,每个单词的第一个字母为大写,其余为小写。变量名和函数名只能由英文字母,数字,及下划线的一个子集来组成,并严格禁止使用连续的下划线,下划线也不能出现在标识符头或结尾(预编译开关除外)。说明:命名时遵循:a)名字以小写字母的前缀开始;b)在两个单词之间一般不再用下划线“_”来连接;c)单词必须是有意义的,拒绝毫无意义的单词,见反例;d)常用计算值的限定词,如 total,averages,max,count 等;常用反义词如:max/min,input/output,next/previous等。正例:chMapIndex;反例:xxx YYY2规则 4.3:使用一致的前缀来区分变量的活动范围。说明:变量活动范围前缀规范如下:g_ :全局变量s_ :模块内静态变量空 :局部变量不加范围前缀v_ :函数参数变量名中除去前缀的其他部分要符合规则4.2。规则 4.4:一致地使用缩写.说明:一律使用公司缩写字典中规定的缩写Q/ZX B XXX 2003 第 4 页 共 36 页表 1 常用缩略语表常用词缩写ArgumentArgBufferBufClearClrClockClkCompareCmpConfigurationCfgContextCtxDelayDlyDeviceDevDisableDisDisplayDispEnableEnErrorErrFunctionFnctHexadecimalHexHigh Priority TaskHPTI/O SystemIOSInitializeInitMailboxMboxManagerMgrManualManMaximumMaxMessageMsgMinimumMinMultiplexMuxOperating SystemOSOverflowOvfParameterParamPointerPtrPreviousPrevPriorityPrioReadRdReadyRdyRegisterRegScheduleSchedSemaphoreSemStackStkSynchronizeSyncTimerTmrQ/ZX B XXX 2003 第 5 页 共 36 页5常用数据及变量要求建立自己的数据类型,以增加程序的可变动性,并使其成为自说明的。数据初始化容易产生错误,因此应采用适当的技术来避免由意外初始值所产生的错误。尽量减小变量的作用域。把对变量引用集中到一起,应尽量使变量成为局部或模块的,避免使用全局变量。使每个变量有且仅有一个功能。并不是因为全局数据危险才避免使用它们,而是因为可以用更好的技术来代替它。如果全局数据确实不可避免的话,应通过存取子程序来对其进行存取操作。存取子程序不仅具备全局变量和全部功能,而且可以提供更多的功能。规则:规则 5.1:有名结构和联合必须被类型化。正例:typedef struct char nameNAME_SIZE;INT16U score;T_STUDENT;规则 5.2:不可将布尔值直接与TRUE或者 1 进行比较。说明:根据布尔类型的语义,零值为“假”(记为 FALSE),任何非零值都是“真”(记为 TRUE)。TRUE的值究竟是什么并没有统一的标准。规则 5.3:一个变量一个功能,不能把一个变量用作多种用途。说明:一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其代表的意义也不同。反例:变量 PageCount 代表是已经打印的页数,但当它等于-l 时则表示发生了错误;变量CustomerID 代表的是顾客号码,但当它的值超过500,000 时,CustomerId减500,000 表示一个过期未付款的帐号;规则 5.4:简单局部变量在定义时赋初值。复杂局部变量在变量统一定义完成后即刻赋初值。说明:对复杂变量要求memset清零后才对个别域赋初值。正例:WORD *pTemp=NULL;/*variable definition section*/NP55_msc_cmd_args_t struCmdArgs;,/*initialize local variable*/memset(&struCmdArgs,0,sizeof(struCmdArgs);,Q/ZX B XXX 2003 第 6 页 共 36 页规则 5.5:for循环语句中,初始化语句仅对循环变量赋初值,不允许对其它变量进行赋值。反例:for (wLoop=0,pTemp=NULL;wLoop MAX_SLOT_NUM;wLoop+)应为:pTemp=NULL;for (wLoop=0;wLoop ”不需要用括号表示有效级。正例:if(comData-dataUnion.Data.ReqType=CH_Init_Req_M)|(comData-dataUnion.Data.ReqType=CH_Subseq_Req_M)建议 5.2:尽量使用含义直观的常量来表示那些将在程序中的数字或字符串。正例:#define MAX_VALUE 200 /*macro constant */建议 5.3:不能在同一个系统中重复定义相同的宏或类型结构。建议 5.4:使用可以移植的数据类型。说明:禁止使用C数据类型。使用基于目标处理器和编译器声明的数据类型。正例:表 2 常用可移植数据类型表TypedefunsignedCharBOOLEAN;/*Logical data type(TRUE or FALSE)*/TypedefunsignedCharINT8U;/*Unsinged 8 bit value */TypedefsignedCharINT8S;/*Signed 8 bit value */TypedefunsignedShortINT16U;/*Unsigned 16 bit value */TypedefSignedShortINT16S;/*Signed 16 bit value */TypedefFloatFP32;/*32 bit,single prec.Floating-point */TypedefdoubleFP64;/*64bit,double prec.Floating-point */Q/ZX B XXX 2003 第 7 页 共 36 页6程序语句要求本部分主要规定了软件编码过程中各类语句的书写规范,力求使书写的代码整洁易懂。规则:规则 6.1:顺序语句按照语句完成的功能划分程序块,一个程序块可以有120 条语句。不同功能块之间必须有空行。规则 6.2:程序每一行的字符数不能超过132 个。说明:计算机显示器一行所能显示的字符有限,超过一屏的语句很难读。限制每行程序的字符数可以增强程序阅读的方便性。一般性原则,一条语句的字符总数不超过一屏。规则 6.3:在程序中较长的表达式分行书写,在优先级比较低的操作符后另起一行。说明:有的表达式比较复杂,如果采用适当的分行书写,可以使程序美观而且易于理解。具体如何格式化续行,需注意以下几点:使续行明显,指能清楚地表明该行不是一条语句的结尾,最好的方法是将该行独立出来后有明显的语法错误,譬如用算术运算符、逻辑运算符等作为行尾,如:if(msg-format=CH_A_Format_M&hData-officeType=CH_BSC_M)TotalBill=TotalBill+CustomerPurchases ID +SalesTax(CustomerPurchases ID );把紧密关联的元素放在一起,下面是个 反例:TotalBill=TotalBill+CustomerPurchases ID +SalesTax(CustomerPurchases ID );函数调用的续行可退后标准格数,如:DrawLine(Wnd.North,Wnd.South,Wnd.East,Wnd.West,CurrentWidth,CurrentAttribute);赋值语句的续行应写在赋值号以后,如:TotalBill=TotalBill+CustomerPurchases ID +SalesTax(CustomerPurchases ID );规则 6.4:条件语句中的条件表达式最多不多于3 个条件表达式。说明:保持代码简洁。如果需要判断的条件较多,建议用临时布尔变量先计算是否满足条件。Q/ZX B XXX 2003 第 8 页 共 36 页规则 6.5:在 switch语句中,每一个case 分支必须使用break 结尾,最后一个分支必须是default分支。每一个必须用 括起来。说明:允许多个连续的case 分支使用同一个break 结尾。避免漏掉break 语句造成程序错误。同时保持程序简洁。还需要注意:“”“”与 switch,default对齐,“必须另起一行书写。规则 6.6:if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加 。规则 6.7:不使用goto 语句。说明:使用 goto 语句只允许向后跳转。使用goto 语句需征得项目经理的允许。规则 6.8:每行只定义一个变量,有意识地安排变量定义顺序并使多个变量定义语句间对齐。正例:WORD index;WORD tempIndex;chHOCData_T*hocData;chHOAData_T*hoaData;规则 6.9:在条件判断语句中,当整型变量与0 比较时,不可模仿布尔变量的风格,应当将整型变量用“=”或“!=”直接与0 比较。正例:if(iValue=0).if(iValue!=0).反例:if(iValue)/会让人误解 iValue是布尔变量if(!iValue)规则 6.10:不可将浮点变量用“=”或“!=”与任何数字比较。说明:无论是 float还是 double 类型的变量,都有精度限制。所以一定要避免将浮点变量用“=”或“!=”与数字比较,应该转化成“=”或“=”形式。Q/ZX B XXX 2003 第 9 页 共 36 页反例:if(fResult=0.0)/隐含错误的比较其中 EPSINON 是允许的误差(即精度)。建议:建议 6.1:if、for、do while、while语句循环嵌套总次数不大于5 次。建议 6.2:if/else语句中:“”“”与“if”对齐,这样做的好处是通过寻找“”“”对就能把握住程序的逻辑结构,可读性很好,“必须另起一行书写。较长的条件判断语句按以上续行的原则分成若干行。尽量在最后添加else,以确保逻辑的完备性,可在else 中增加断言。建议 6.3:for语句中:一般用 i,j,k 及有意义的单词如index,day 等来作为循环的计数,必须注意计数值的范围以防止死循环。循环条件中,三条语句之间添加空格以增加可读性。“”“”与“for”对齐,“必须另起一行书写。在循环体中一般不使用break,continue。循环的长度不宜过长,做到一目了然。正例:f or(i=0;i CTMALFlag)|!(facdir2-CDMATMAHOList.Num)如果一行放不下,则可以分成两行,如:i f(comData-dataUnion.Data.ReqType=CH_Init_Req_M|comData-dataUnion.Data.ReqType=CH_Subseq_Req_M)用空格、断行使函数参数更好读,如:DISPATCH_MSG(&ccuSlaveMsgMap,paraIn);ASEND(cahHOCompEvent,(BYTE far*)msgOut,(WORD)sizeof(*msgOut),chBSSAPPID);建议 6.6:把相关的赋值语句对齐。说明:如果几个赋值语句是相关的,则应该把等号对齐。正例:cmPDBResbModule.Head =0;cmPDBResbModule.Tail=wMaxNumOfPDB-1;cmPDBResbModule.Free=wMaxNumOfPDB;cmPDBResbModule.Address=pPDBAddr;cmPDBResbModule.Size=wPDBSize;cmPDBResbModule.MaxNumber=wMaxNumOfPDB;建议 6.7:把相关的宏定义语句对齐。Q/ZX B XXX 2003 第 11 页 共 36 页7布局要求程序布局的目的是显示出程序良好的逻辑结构,提高程序的准确性、连续性、可读性、可维护性。更重要的是,统一的程序布局和编程风格,有助于提高整个项目的开发质量,提高开发效率,降低开发成本。同时,对于普通程序员来说,养成良好的编程习惯有助于提高自己的编程水平,提高编程效率。因此,统一的、良好的程序布局和编程风格不仅仅是个人主观美学上的或是形式上的问题,而且会涉及到产品质量,涉及到个人编程能力的提高,必须引起大家重视。程序布局应遵循的原则:正确表达出程序的逻辑结构自始至终地体现代码的逻辑结构提高可读性(使程序好看、易读)易于修改规则:规则 7.1:禁止使用TAB,必须使用空格进行缩进。缩进为4 个空格。规则 7.2:遵循统一的布局顺序来书写实现文件和头文件。说明:实现文件(.c)布局:文件头(包含一些公司、作者以及一些描述信息)修正记录#include#define 常量文件内部使用的宏文件内部使用的数据类型全局变量静态变量extern声明静态表格静态函数原型全局函数静态函数头文件(.h)布局文件头(包含一些公司、作者以及一些描述信息)修正记录#include#define 常量全局宏全局数据类型全局函数原型Q/ZX B XXX 2003 第 12 页 共 36 页头文件 用于定义结构及宏,不得用于定义全局变量,在头文件头部,结构及宏定义之前必须包含文件注释、条件编译、头文件包含等几个部分。源文件 用于定义全局变量及函数体等,源文件必须包含文件注释、头文件包含、全局变量定义及外部包含、函数原型说明、外部引用函数说明、函数注释、函数体等几个部分。全局变量定义及外部包含变量声明都放在源文件中。正例:/*MAP PID definition*/PID clhMapPid;PID clvMapPid;PID clcMapPid;/*extern global variable*/extern ccmData_T ccmDataMAX_INDEX_NUM;extern waDidToIndex_T wFindIndexByDid;函数原型说明、外部引用函数依据如下示例的布局格式。正例:/*session resource management*/VOID ccmInitDidIndexTbl();BOOL ccmGetIndexByDid(WORD wDid,PWORD pwIndex);/*extern function declaration*/extern BOOL cmGetDid(BYTE cModule,PWORD pwDid);/*apply session*/extern BOOL cmFreeDid(BYTE cModule,WORD wDid);/*release session*/外部定义的全局变量和外部函数声名引用,必须增加注释,声名来源,目的是提高代码的可读性,如:正例:extern ccmData_T ccmDataMAX_INDEX_NUM;/*XXXX.C*/*extern global function*/extern BOOL cmGetDid(BYTE cModule,PWORD pwDid);/*YYY.C*/规则 7.3:使用注释块分离上面定义的节。正例:Q/ZX B XXX 2003 第 13 页 共 36 页 /*DATA TYPES */typedef unsigned char BOOLEAN;/*PROTOTYPES */BOOLEAN OSIsTaskRdy(void);规则 7.4:头文件必须要能够避免重复包含。说明:头文件包含一般可采用以下两种方式:1 包含别的模块的头文件时,用不加路径的头文件,然后在编译时增加路径设置,如下:#include“hmCom.h”#include“cmDef.h”2 包含本模块的头文件时,可用相对路径,推荐使用,如HLR MAP:include“.includehmCom.h”include“.includehmDef.h”通过下面的宏定义来避免头文件的重复包含,并要求每个与#if匹配的#endif后加上注释以明确说明避免哪个头文件的重复包含;或者#if/#endif匹配对的语句行统一缩进4 个空格对齐以明确表示匹配的层次关系。正例:#if !defined(module_H)#define module_H /*file body*/#endif/*end */规则 7.5:一行代码执行一个动作。反例:DispDegTblIx =0;DispDigMsk =0 x80;应为:DispDegTblIx =0;DispDigMsk =0 x80;规则 7.6:一元运算符和它们的操作数之间不要使用空格。说明:一元操作符如“!”、“”、“+”、“-”、“*”、“&”(地址运算符)等前后不加空格。“”、“.”、“-”这类操作符前后不加空格。Q/ZX B XXX 2003 第 14 页 共 36 页正例:!value bits +i (INT32U)x *ptr&x sizeof(x)规则 7.7:二元(和三元)运算符和它们的操作数之间至少需要一个空格。正例:c1 =c2;x +y;i +=2;规则 7.8:关键字之后要留空格。说明:if、for、while 等关键字之后应留一个空格再跟左括号(,以突出关键字。规则 7.9:函数名之后不要留空格。说明:函数名后紧跟左括号(,以与关键字区别。规则7.10:(向后紧跟,)、,、;向前紧跟,紧跟处不留空格。,之后要留空格。;不是行结束符号时其后要留空格。建议:建议 7.1:把同属性的元素对齐。说明:例如把同一类的一组语句的等号排成一条直线下来,直观上使人一看就知道这些句子是属于同一类的。如果不是同一类的,不要这样对齐。正例:disReq.tag_res_addr=1;disReq.res_addr.tag_spc=1;disReq.res_addr.tag_ssn=1;disReq.res_addr.tag_gt=0;disReq.res_addr.tag_route=1;disReq.res_addr.ssn=BSC_SSN;disReq.reason=0;disReq.connect_id=conn_id;disReq.len_ud=0;Q/ZX B XXX 2003 第 15 页 共 36 页8注释要求注释可以分成五类:代码的重复重复的注释,用不同的词重申了代码的内容。它没有给读者提供代码的附加信息。代码的解释解释性注释,典型地用于解释复杂的,有效的和灵敏的代码段。这种情况下,他们是有用的,但常常是由于代码是易混淆的。假如代码复杂到需要解释,那么改进代码总比增加注释更好些。使代码本身清晰,然后使用总结或注释。代码中的标记标记注释并非是故意留在代码中的注释。它是给开发者的记录,表示工作还未做。一些开发者的标记注释为语法错误的标记(例如*),因而编译程序标记它并提醒他们要做更多的工作。其它开发者把一套特殊字符放人注释中,因而他们可以发现它们,但编译程序不能识别它们。代码的总结总结代码的注释做法是;它简化一些代码行成一或两句话。这样的注释比起仅重复代码而使读者比读代码更快的那种注释更有价值了。总结注释是相当有用的,特别是当其它人但不是代码的编者试图修改代码时。代码意图的描述意图这一层上的注释,解释了代码的目的。意图注释在问题一级上,而不是在答案一级操作。注释应遵循以下原则:是否注释就像是立法。注释得好,是非常值得的,注释得不好,则是浪费时间而且有害。源代码中应含有关于程序的绝大部分重要信息。只要程序还在运行,那么代码中的注释便不会丢失或被丢弃。把重要信息加入代码是非常重要的。好的注释是在意愿层次上进行的,它们解释的是“为什么”而不是“是什么”。注释应表达出代码本身表达不了的意思。好的代码应是自说明的。当你对代码进行注释时,应问一下自己“如何改进代码以使得对其注释是多余的?”,改进代码再加注释以使它更清楚。注释的基本要求是完整、简洁、有效,删除不必要的注释。注释统一用“/*”和“*/”,不用“/”,注释不得嵌套。规则:规则 8.1:文件头部必须有文件注释,且严格遵循公司格式。说明:文件的注释放在文件的头部,以注释本文件,其中必须包括的内容有:模块名、文件名、说明及版本记录。正例:Q/ZX B XXX 2003 第 16 页 共 36 页参考以下公司文件格式:/*ModuleName:ZXPCS CCM HLR FileName:msgmap.C DESCRIPTION:Init the global variable of HLR History:Date Version Modifier Activies=08/10/99 1.0 Ding Guigui create*/规则 8.2:接口函数必须有注释,函数的注释放在每一个函数的前面,以说明函数。内部函数的注释应该尽可能简单。说明:必须包括的内容:函数名、功能描述、入参、出参、返回、全局变量及历史记录。正例:格式如下:/*FUNCTION NAME:ccuCtlInit*DESCRIPTION:Init the global variable of CC*INPUT :None*OUTPUT:None*RETURN:None*GLOBAL:None*NOTE:*MODIFY DATE VERSION AUTHOR REASON*=99/08/01 1.0 Ding Guigui Create*/规则 8.3:使用注释块来为代码分段。说明:注释块中的语句要居中,并且使用大写字母书写。正例:/*VARIABLES */当需要对某一程序段如对if/else语句进行注释时,可以将注释放在段的前面,并且与代码对齐。正例:i f(chmMapPDB-ProfValid.MSStatus=cmADDelinqAcc_M|chmMapPDB-ProfValid.MSStatus=cmADStolUnit_M|chmMapPDB-ProfValid.MSStatus=cmADUnspec_M)/*program*/Q/ZX B XXX 2003 第 17 页 共 36 页/*inform cc release cic*/i f(ccInformFlag)/*program*/建议:建议 8.1:单条语句的注释可以放在语句的前面或者右边,放在前面时与代码对齐,放在右边时与代码保持一定距离。可用中英文注释,注释用“/*/”,不用“/”。正例:/*initialize data section of process*/cmInitPDB(cmHLR_M,chmMaxNumOfPDB_M,(PBYTE)chmMapPDBArray,(WORD)sizeof(chmMapPDB_T);case Timer1Event:/*Timer1 is set for LU or MO or MT*/建议 8.2:使用英文进行注释。建议 8.3:其他应该注释的地方。说明:其 他 应 该 注 释 的 地 方 包 括:a)所有的全局变量和静态变量应有详细的解释。b)函数内的除临时变量外的其余变量都要注释说明。c)结构中的每一个变量。d)头文件中的消息、事件、结构,常量说明必须详细注释。e)各主要功能块要有注释,说明此功能的实现过程及方式。Q/ZX B XXX 2003 第 18 页 共 36 页9断言和错误处理断言是对某种假设条件进行检查。它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。在实际应用时,可根据具体情况灵活地设计断言。消除程序错误的最好方法是尽早、尽可能容易地发现错误,以最小代价自动查错。努力减少程序员查错所需的技巧。保存两个版本,一个整洁快速用于程序的交付;另一个,用于调试。同时维护同一程序的两个版本,并利用C的预处理程序有条件地包含或不包含相应的检查部分。“不要让事情很少发生。”需要确定子系统中可能发生哪些事情,并且使它们一定发生和经常发生。如果发现子系统中有极罕见的行为,要干方百计地设法使其重现。规则:规则 9.1:整个软件系统应该采用统一的断言。如果系统不提供断言,则应该自己构造一个统一的断言供编程时使用。规则 9.2:使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。说明:断言是用来处理不应该发生的错误情况的,对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。规则 9.3:用断言保证没有定义的特性或功能不被使用。说明:假设某通信模块在设计时,在消息处理接口准备处理“同步消息”和“异步消息”。但当前的版本中的消息处理接口仅实现了处理“异步消息”,且在此版本的正式发行版中,用户层(上层模块)不应产生发送“同步消息”的请求,那么在测试时可用断言检查用户是否发送了“同步消息”。规则 9.4:用断言来检查程序正常运行时不应发生但在调测时有可能发生的非法情况。说明:对 RELEASE 版本不用的测试代码可以通过断言来检查测试代码中的非法情况。规则 9.5:对接口函数的有值域范围的输入参数进行断言检查。规则 9.6:指向指针的指针及更多级的指针必须逐级检查。规则 9.7:对所有接口函数的返回结果进行断言检查。规则 9.8:正式软件产品中应把断言及其它调测代码去掉(即把有关的调测开关关掉)。建议:建议 9.1:对复杂算法的结果进行断言检查(使用第二种算法来校验)。Q/ZX B XXX 2003 第 19 页 共 36 页10 测试代码要求编码始于对设计的分析,结束于对自己的代码进行了严格的自测。在设计阶段就必须考虑所编写代码的可测试性,只有提供足够的测试手段才能全面、高效地发现和解决代码中的各类问题。编写的代码是否可测试,是衡量代码质量的最基本的、最重要的尺度之一。测试是设计的一部分。规则:规则 10.1:所有测试代码和测试用例必须保存,作为软件配置项同步入库。说明:测试代码和测试用例是软件配置项的重要组成部分。没有测试代码和测试用例的软件系统是不完整的,有缺陷的。规则 10.2:测试代码是软件配置的一部分,必须同时遵守本规范的规定。规则10.3:在同一项目组或产品组内,为准备集成测试和系统联调,要有一套统一的调测开关及相应信息输出函数,并且要有详细的说明。统一的调试接口和输出函数由模块设计和测试人员根据项目特性统一制订,由项目系统人员统一纳入系统设计中。说明:本规则是针对项目组或产品组的。规则10.4:在同一个项目组或产品组内,调测打印出的信息串要有统一的格式。信息串中应当包含所在的模块名(或源文件名)及行号等信息。说明:统一的调测信息格式便于集成测试。建议:建议 10.1:测试要达到三个方面的覆盖:值域覆盖,语句覆盖,分支覆盖。说明:好的测试代码要达到一定的覆盖率要求。测试覆盖率是软件质量的一个重要指标。建议 10.2:测试一点代码,编写一点代码。说明:提交的每个“独立功能”都必须是经过了自测的。建议 10.3:如果可能的话,使用PC-Lint、LogiScope 等工具进行代码审查。建议10.4:如果原有的代码质量比较好,尽量复用它。但是不要修补很差劲的代码,应当重新编写。建议 10.5:把编译器的选择项设置为最严格状态。说明:可以通过编译器尽可能地发现错误。建议10.6:在编写代码之前,应预先设计好程序调试与测试的方法和手段,并设计好各种调测开关及相应测试代码(如打印函数等)。Q/ZX B XXX 2003 第 20 页 共 36 页11 动态内存分配在程序编制之前,必须了解编译系统的内存分配方式,特别是编译系统对不同类型的变量的内存分配规则,如局部变量在何处分配、静态变量在何处分配等。防止内存操作越界。必须对动态申请的内存做有效性检查,并进行初始化;动态内存的释放必须和分配成对以防止内存泄漏,释放后内存指针置为NULL。规则:规则 11.1:数组和动态内存必须赋初值。说明:不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。规则 11.2:移交了内存所有权的而又不立即消亡的指针必须明确清为NULL。规则 11.3:不允许使用recalloc()。说明:明确地进行新申请、拷贝然后释放旧内存三步操作。建议:建议 11.1:遵循谁申请谁释放的原则。建议 11.2:不允许使用memcpy(),用 memmove()代替。建议11.3:不要将动态申请的内存作参数传给被调用者释放,不允许将申请的内存作返回结果传给调用者释放。建议11.4:由于内存总量是有限的,软件系统各模块应约束自己的代码,尽量少占用系统内存。建议 11.5:在往一个内存区连续赋值之前(memset,memcpy),应确保内存区的大小能够容纳所赋的数据。Q/ZX B XXX 2003 第 21 页 共 36 页附录 A 代码完成过程中的检查单A.1 编程中的一般问题a)是否为程序建立了DEBUG 版本?调试版本应该包括使用断言对模块间相互调用的参数,传递的数据进进行严格检查,记录各种系统异常报告,日志等等。b)是否将发现的错误都及时改正了?c)是否坚持彻底测试代码?d)是否了解编码的优先顺序?在每个人的脑子里都应该有一个编码的优先表,它将决定你在编码的时候优先关注的问题。e)编译程序是否使用了可选的各种警告?A.2 将更改归并到主程序中,版本提交之前:a)是否通过了最高级别的编译选项的编译?(推荐使用BC 5.0,VC 5.0)对于 C 的代码,要求使用BC的编译器和VC的编译器,使用最高级别的编译选项进行编译,因为我们使用的iRmx 或 pSos 等编译器对于代码中不规范的地方的检查不是非常的严格,很多比较危险的用法都检查不出来。b)代码是否改正了所有的编译警告?要求对所有编译出的警告都要修改代码,直至消除警告为止。c)是否进行了单元测试?d)是否经历了每一条编码途径的测试,同时是否密切关注数据的变化?代码的遍历测试是保证代码质量的一个非常重要的步骤。遍历有很多种,最高级别的是逻辑覆盖,而最低级别的是分支覆盖,我们目前有可能做到的是分支覆盖,就是保证每一个代码分支都曾至少执行过一遍。也许大家觉得遍历代码太浪费时间,但其实只要大家去做了,就会发觉经过一段的时间之后,可能在无意识之中就完成了这一个过程。并不需要花费很多的时间。通过代码的遍历检查的时候,密切的注意数据的变化,你就可以找到如下的错误:上溢和下溢错误数据转