**软件公司软件开发规范
(试行版)
在公司团队协作开发的情况下,编程时应该强调的一个重要方面是程序的易读性,在保证软件的速度等性能指标能满足用户需求的情况下,能让其他程序员容易读懂你的程序。一套鲜明的编程风格,可以让协作者、后继者和自己一目了然,在很短的时间内看清程序的结构,理解设计的思路。大大的提高代码的可读性、可重用性、程序健壮性、可移植性和可维护性。
制定本编程规范的目的是为了提高公司的软件开发效率及所开发的软件的可维护性,提高软件的质量。本规范由程序风格、命名规则、注释规范、程序健壮性、可移植性、错误处理以及软件的模块化规范等部分组成。
一、程序风格:
1、严格采用阶梯层次组织程序代码:
各层次缩进的分格采用VC的缺省风格,即每层次缩进为4格,括号位于下一行。要求相匹配的大括号在同一列,对继行则要求再缩进4格。例如:
void main()
{
2、提示信息字符串的位置
在程序中需要给出的提示字符串,为了支持多种语言的开发,除了一些给调试用的临时信息外,其他所有的提示信息必须定义在资源中。
3、对变量的定义,尽量位于函数的开始位置。
二、命名规则:
1、变量名的命名规则
①、变量的命名规则要求用“匈牙利法则”。即开头字母用变量的类型,其余部分用变量的英文意思或其英文意思的缩写,尽量避免用中文的拼音,要求单词的第一个字母应大写。
即: 变量名=变量类型+变量的英文意思(或缩写)
对非通用的变量,在定义时加入注释说明,变量定义尽量可能放在函数的开始处。
见下表:
bool(BOOL) 用b开头 bIsParent
byte(BYTE) 用by开头 byFlag
short(int) 用n开头 nStepCount
long(LONG) 用l开头 lSum
char(CHAR) 用c开头 cCount
float(FLOAT) 用f开头 fAvg
double(DOUBLE) 用d开头 dDeta
void(VOID) 用v开头 vVariant
unsigned short(WORD) 用w开头 wCount
unsigned long(DWORD) 用dw开头 dwBroad
HANDLE(HINSTANCE) 用h开头 hHandle
DWORD 用dw开头 dwWord
LPCSTR(LPCTSTR) 用str开头 strString
用0结尾的字符串 用sz开头 szFileName
对未给出的变量类型要求提出并给出命名建议给技术委员会。
②、指针变量命名的基本原则为:
对一重指针变量的基本原则为:
“p”+变量类型前缀+命名
如一个float*型应该表示为pfStat
对多重指针变量的基本规则为:
二重指针: “pp”+变量类型前缀+命名
三重指针: “ppp”+变量类型前缀+命名
......
③、全局变量用g_开头,如一个全局的长型变量定义为g_lFailCount,即:变量名=g_+变量类型+变量的英文意思(或缩写)
④、静态变量用s_开头,如一个静态的指针变量定义为s_plPerv_Inst,即: 变量名=s_+变量类型+变量的英文意思(或缩写)
⑤、成员变量用m_开头,如一个长型成员变量定义为m_lCount;即:变量名=m_+变量类型+变量的英文意思(或缩写)
⑥、对枚举类型(enum)中的变量,要求用枚举变量或其缩写做前缀。并且要求用大写。
如:enum cmEMDAYS
{
EMDAYS_MONDAY;
EMDAYS_TUESDAY;
……
};
⑦、对struct、union、class变量的命名要求定义的类型用大写。并要加上前缀,其内部变量的命名规则与变量命名规则一致。
结构一般用S开头
如:struct ScmNPoint
{
int nX;//点的X位置
int nY; //点的Y位置
};
联合体一般用U开头
如: union UcmLPoint
{
long lX;
long lY;
}
类一般用C开头
如:
class CcmFPoint
{
public:
float fPoint;
};
对一般的结构应该定义为类模板,为以后的扩展性考虑
如:
template <class TYPE>
class CcmTVector3d
{
public:
TYPE x,y,z;
};
⑧、对常量(包括错误的编码)命名,要求常量名用大写,常量名用英文表达其意思。
如:#define CM_FILE_NOT_FOUND CMMAKEHR(0X20B) 其中CM表示类别。
⑨、对const 的变量要求在变量的命名规则前加入c_,即:c_+变量命名规则;例如:
const char* c_szFileName;
2、 函数的命名规范:
函数的命名应该尽量用英文表达出函数完成的功能。遵循动宾结构的命名法则,函数名中动词在前,并在命名前加入函数的前缀,函数名的长度不得少于8个字母。
例如:
long cmGetDeviceCount(……);
3、函数参数规范:
①、 参数名称的命名参照变量命名规范。
②、 为了提高程序的运行效率,减少参数占用的堆栈,传递大结构的参数,一律采用指针或引用方式传递。
③、 为了便于其他程序员识别某个指针参数是入口参数还是出口参数,同时便于编译器检查错误,应该在入口参数前加入const标志。如:
……cmCopyString(const char * c_szSource, char * szDest)
4、引出函数规范:
对于从动态库引出作为二次开发函数公开的函数,为了能与其他函数以及Windows的函数区分,采用类别前缀+基本命名规则的方法命名。例如:在对动态库中引出的一个图象编辑的函数定义为 imgFunctionname(其中img为image缩写)。
现给出三种库的命名前缀:
①、 对通用函数库,采用cm为前缀。
②、 对三维函数库,采用vr为前缀。
③、 对图象函数库,采用img为前缀。
对宏定义,结果代码用同样的前缀。
5、文件名(包括动态库、组件、控件、工程文件等)的命名规范:
文件名的命名要求表达出文件的内容,要求文件名的长度不得少于5个字母,严禁使用象file1,myfile之类的文件名。
三、注释规范:
1、函数头的注释
对于函数,应该从“功能”,“参数”,“返回值”、“主要思路”、“调用方法”、“日期”六个方面用如下格式注释:
//程序说明开始
//================================================================//
// 功能: 从一个String 中删除另一个String。
// 参数: strByDelete,strToDelete
// (入口) strByDelete: 被删除的字符串(原来的字符串)
// (出口) strToDelete: 要从上个字符串中删除的字符串。
// 返回: 找到并删除返回1,否则返回0。(对返回值有错误编码的要// 求列出错误编码)。
// 主要思路:本算法主要采用循环比较的方法来从strByDelete中找到
// 与strToDelete相匹配的字符串,对多匹配strByDelete
// 中有多个strToDelete子串)的情况没有处理。请参阅:
// 书名......
// 调用方法:......
// 日期:起始日期,如:2000/8/21.9:40--2000/8/23.21:45
//================================================================//
函数名(……)
//程序说明结束
①、 对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说明该角度参数是以弧度(PI),还是以度为单位),对既是入口又是出口的变量应该在入口和出口处同时标明。等等。
②、 函数的注释应该放置在函数的头文件中,在实现文件中的该函数的实现部分应该同时放置该注释。
③、 在注释中应该详细说明函数的主要实现思路、特别要注明自己的一些想法,如果有必要则应该写明对想法产生的来由。对一些模仿的函数应该注释上函数的出处。
④、 在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险方面,可能出错的地方。
⑤、 对日期的注释要求记录从开始写函数到结束函数的测试之间的日期。
⑥、 对函数注释开始到函数命名之间应该有一组用来标识的特殊字符串。
如果算法比较复杂,或算法中的变量定义与位置有关,则要求对变量的定义进行图解。对难以理解的算法能图解尽量图解。
2、变量的注释:
对于变量的注释紧跟在变量的后面说明变量的作用。原则上对于每个变量应该注释,但对于意义非常明显的变量,如:i,j等循环变量可以不注释。
例如: long lLineCount //线的根数。
3、文件的注释:
文件应该在文件开头加入以下注释:
/
// 工程: 文件所在的项目名。
// 作者:**,修改者:**
// 描述:说明文件的功能。
// 主要函数:…………
// 版本: 说明文件的版本,完成日期。
// 修改: 说明对文件的修改内容、修改原因以及修改日期。
// 参考文献: ......
/
为了头文件被重复包含要求对头文件进行定义如下:
#ifndef __FILENAME_H__
#define __FILENAME_H__
其中FILENAME为头文件的名字。
4、其他注释:
在函数内我们不需要注释每一行语句。但必须在各功能模块的每一主要部分之前添加块注释,注释每一组语句,在循环、流程的各分支等,尽可能多加以注释。
其中的循环、条件、选择等位置必须注释。
对于前后顺序不能颠倒的情况,建议在注释中增加序号。
例如:
......
//1、......注释
for (......)
{
}
if(......)
{//......注释
}
else
{//......注释
}
//......注释
switch(......)
{
case: ......// ......注释
......
case: ......// ......注释
......
default: //......注释
......
}
在其他顺序执行的程序中,每隔3-5行语句,必须加一个注释,注明这一段语句所组成的小模块的作用。对于自己的一些比较独特的思想要求在注释中标明。
四、程序健壮性:
1、函数的返回值规范:
对于函数的返回位置,尽量保持单一性,即一个函数尽量做到只有一个返回位置。(单入口单出口)。
要求大家统一函数的返回值,所有的函数的返回值都将以编码的方式返回。
例如编码定义如下:
#define CM_POINT_IS_NULL CMMAKEHR(0X200)
:
:
建议函数实现如下:
long 函数名(参数,……)
{
long lResult; //保持错误号
lResult=CM_OK;
//如果参数有错误则返回错误号
if(参数==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
……
END:
return lResult;
}
2、关于goto的应用:
对goto语句的应用,我们要求尽量少用goto语句。对一定要用的地方要求只能向后转移。
3、资源变量的处理(资源变量是指消耗系统资源的变量):
对资源变量一定赋初值。分配的资源在用完后必须马上释放,并重新赋值。
例:
long * plAllocMem;//定义一个分配内存的变量。
plAllocMem=(long*)calloc(40, sizeof( long ));//分配一段内存。
//处理分配内存错误
if(plAllocMem==NULL)
{
lResult=CM_MEM_ALLOC_FAILED;
goto END;
}
……
使用内存
……
//释放资源变量,并重新赋值。
if(pAllocMem!=NULL)
{
free(plAllocMem);
pAllocMem=NULL;
}
4、对复杂的条件判断,为了程序的可读性,应该尽量使用括号。
例:if(((szFileName!=NULL)&&(lCount>=0)))||(bIsReaded==TRUE))
五、可移植性:
1、高质量的代码要求能够跨平台,所以我们的代码应该考虑到对不同的平台的支持,特别是对windows98和windowsnt的支持。
2、由于C语言的移植性比较好,所以对算法函数要求用C代码,不能用C++代码。
3、对不同的硬件与软件的函数要做不同的处理。
五、错误处理:
1、错误报告处理。
编程中要求考虑函数的各种执行情况,尽可能处理所有的流程情况。将函数分为两类:
一类为与屏幕的显示无关,(不与用户交换信息的函数)
一类为与屏幕的显示相关。(与用户交换信息的函数)
对于与屏幕显示无关的函数,函数通过返回值来报告错误。
对于与屏幕显示有关的函数,函数要负责向用户发出警告,并进行错误处理。
错误处理代码一般单独建立通用处理函数。如下:
void cmDeal_With_Error(long ErrCode)
{
switch(ErrCode)
{
case 1://注释
......
case 2://注释
......
default://注释
......
}
}
2、 尽早发现程序中的错误:
①、 重视编译器中的警告信息。
对于编译器产生的警告信息,我们应该引起足够的重视,实际上许多警告信息指示了程序中潜在的错误危险。所以我们要认真检查每一个警告信息,查看是否有某种隐患。尽量消除警告信息。
②、 利用断言来检查错误
对于程序中的某种假设,或防止某些参数的非法值,利用断言来帮助查错是一种好的办法。
例如下面的函数:
long cmMemCpy(void * pvToMem, void* pvFromMem, size_t wSize)
{
……
if(pvToMem==NULL||pvFromMem==NULL)
{
lResult=CM _POINT_IS_NULL;
goto: END;
}
while(wSize-- >0)
{
*pvToMem++=pvFromMem++;
}
END:
return lResult;
}
采用判断可以检查传入的指针错误,但是这样的判断是程序最终的编译代码变大,同时降低了最终发布的程序的执行效率。由于传入空指针明显是调用这函数的程序的错误,而不是这个函数的错误,我们可以考虑采用断言来代替指针检查,即用
ASSERT( pvToMem!=NULL&&pvFromMem!=NULL)
代替
if(pvToMem==NULL||pvFromMem==NULL)
{
lResult=CM_POINT_IS_NULL;
goto: END;
}
这样只会在debug版中才会产生检查代码,而在正式发布版中不会带有这些代码。并且可以方便我们在程序调试中和测试时发现错误,同时又不影响程序的效率。
在下面的一些情况中必须加断言:
a、 数的参数,特别是指针参数必须利用断言来进行确认。
b、 利用断言检查程序中的各种假设的正确性。
c、 在程序设计中不要轻易认为某种情况不可能发生,对你认为不可能发生的情况必须用断言来证实。
为了使程序中的断言发挥作用,所有用于在开发内部进行测试或调试的动态库、执行程序、组件必须采用debug版。
说明:
在程序效率要求较高、或者调用比较频繁的函数,对入口参数的错误检查,使用断言方式,其优点如上所叙,但其健壮性不强,所以在其他情况下,仍要求使用传统的检查方式,以增强程序的健壮性,当然,为了调试方便,可同时使用断言方式。
③、 严格的测试:
对每一段代码都要求进行严格的测试,特别对一些功能函数要对其各种临界点(比如零值、无穷大的值等)进行测试。尽量做到每一段代码零错误。
六、模块化规范:
为了提高软件的重用性,减少重复开发的工作量。同时也为了提高程序的可读性,方便程序的维护,必须加强软件的模块化工作。模块化应该遵循以下几个基本规范:
1、 个函数应该作到精而小,函数的代码应该控制在一个适度的规模,每个函数的代码一般不能超过150行,如果超过这个规模,应该进行模块化的工作。对于一些特殊的函数确实要超过150行,应该提交出来讨论,通过后,要求编写者更加详细的对函数注释,并写明函数超行的原因,以及设计思想等。
2、 某一功能,如果重复实现三遍以上,既应该考虑模块化,将其写成通用函数。并向开发人员发布。并要求将接口文档和实现的功能备案。
3、 每一个开发人员要尽可能的利用其他人的现成的模块,减少重复开发。
4、 对函数进行模块化时,要考虑函数的层次关系,特别是在增加新的功能模块时,对原来的函数代码要进行认真的调整,做到相同功能的不同函数没有重复代码,此要求的目的在于便于代码维护。举例如下:
现有如下函数:
//从szFileName文件中取 ......
long ...... cmGetSomething(const char * c_szFileName,......)
{
CFile * pFile;//用来保存打开文件的地址
pFile=new CFile(c_szFileName,CFile::modeRead);//用创建一个只读文件
if(pFile==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
//从文件中读取......
......
//关闭文件
delete pFile;
END:
return lResult;
}
若现在需要增加如下接口的新函数:
long ...... cmReadSomething(CFile * pFile)
{
if(pFile==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
//从文件中读取......
......
END:
return lResult;
}
则要求如下:
将 long ......cmGetSomething(const char * c_szFileName,......)改为
long ...... cmGetSomething (const char * c_szFileName,......)
{
CFile * pFile; //用来保存打开文件的地址
long lResult=CM_OK;//错误返回码
//打开文件
pFile=new CFile(c_szFileName,CFile::modeRead);
if(pFile==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
//从文件中读取......
lResult=cmReadSomething(pFile,......);
IF_ERROR_GOTO_END
//关闭文件
delete pFile;
END:
return lResult;
}
模块化的一些注意事项:
① 、设计好模块接口,用面向对象的观点看,包括:函数接口和变量接口。
② 、定义好接口以后不要轻易改动,并在模块开头(文件的开头或函数的开头)加以说明,所以在定义接口时,一定要反复琢磨,保持风格一致。
③ 、注意全局变量也是一种接口,如果不是确实必要,应该尽量少用全局变量。
④ 、在函数接口中,尽量使函数的接口容易理解和使用,其中每个输入输出参数都只代表一种类型数据,不要把错误值和其他专用值混在函数的其他输入输出参数中。
⑤ 、争取编写出永远成功的函数,使调用者不必进行相应的错误处理。
此规范为试行版,解释权属于**软件公司技术委员会!员工在编写程序时请参阅FuncTemplate.h和FuncTemplate.cpp
FuncTemplate.h
//
//工程: FuncTemplate.h //
//描述: 用来处理对二叉树的一些算法,以及矩阵内存分配。 //
//版本: FuncTemplate 1.0版。 //
//
typedef struct _NODE //定义一个节点
{
struct _NODE * pLeftChild; //节点的左孩子
struct _NODE * pRightChild; //节点的右孩子
} NODE;
//==================================================================//
// 功能: 用循环来实现二叉树的左序遍历 //
// 参数: pNode //
//(入口) pNode: 二叉树的入口地址,即根节点的地址。 //
//(出口) 无。 //
// 返回: long 的函数返回码,如果返回值为CM_OK,表成功遍历,返回 //
// CM_POINT_IS_NULL表二叉树指针为空。 //
//==================================================================//
long cmWalkTreeUseCycle(const NODE * pNode);
//==================================================================//
// 功能: 对矩阵进行分配内存 //
// 参数: wRowSize,wColSize,ppplMatrix //
//(入口) wRowSize:矩阵的行数; //
//(入口) wColSize:矩阵的列数; //
//(入口) ppplMatrix:要分配的矩阵的地址; //
//(出口) ppplMatrix:分配的矩阵的地址; //
// 返回: long 的错误号,如果返回值为CM_OK,表分配成功,返回 //
// CM_POINTER_IS_NOT_NULL表矩阵的入口地址值不为空。 //
// CM_MEM_ALLOC_FAILED 系统的内存不足 //
//==================================================================//
long cmInitMatrix(const size_t wRowSize,const size_t wColSize,long *** ppplMatrix);
FuncTemplate.cpp
//
//工程: FuncTemplate.cpp //
//作者: ** //
//修改者: **,*** //
//描述: 用来处理对二叉树的一些算法,以及矩阵内存分配。
//主要函数:cmWalkTreeUseCycle,cmInitMatrix
//版本: FuncTemplate 1.0版。
//完成日期:2000-8-26 //
//修改日期: 2000-8-27,2001-12-21 //
//参考文献: 图形程序开发人员指南(机械工业出版社)
//
#define STRICT
#include "stdio.h"
#include "stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include "makehresult.h"
#include "memory.h"
#include "functemplate.h"
#define CM_MEM_POINTER_IS_NULL CMEMAKEHR(0X100) //表示指针不为空的错误
#define CM_MEM_POINTER_IS_NOT_NULL CMEMAKEHR(0X101) //表示指针不为空的错误
#define MAX_PUSHED_NODES 100 //定义最大压栈数量
程序函数说明开始
//==================================================================
// 功能: 用循环来实现二叉树的左序遍历
// 参数: cpNode //
//(入口) cpNode: 二叉树的入口地址,即根节点的地址。
//(出口) 无。 //
// 返回: long 的函数返回码,如果返回值为CM_OK,表成功遍历,返回
// MS_POINT_IS_NULL表二叉树指针为空。
// 调用方法:在调用此函数前必须先初始化二叉树
// 思路: 如果正在访问某节点,如果该节点有左分枝,就先访问左分枝。
// 接着访问该节点,如果该节点还有右分支。就再访问右分支。
// 访问左分枝时,在该处栈中做标记,当处理完左分枝就访问此
// 处。访问完每一个节点后,如果该节点没有右孩子,并且栈已经
// 为空,那么对该节点的访问就完成,代码对每一个节点都重复以
// 上*想*作。 //
// 参阅: 图形程序开发人员指南(机械工业出版社)Page:927
// 日期: 2000/8/26.9:40--2000/8/26.21:45
//==================================================================
//图解:
// 根节点
// @
// / /
// / /
// 左孩子@ @右孩子
// / / / /
// / / / /
// @ @ @ @
// 左孩子 右孩子左孩子 右孩子
HRESULT cmWalkTreeUseCycle(const NODE * cpNode)
程序函数说明结束
{
HRESULT lResult;//用来保存返回的错误号。
NODE * pNodeStack[MAX_PUSHED_NODES];//用来作为节点的堆栈。
NODE ** ppNodeStack;//用来指示节点堆栈的指针。
lResult=CM_OK;
//判断树是否为空。
if(cpNode !=NULL)
{
pNodeStack[0]=NULL; //设置堆栈为空。
ppNodeStack=pNodeStack+1;
for(;
{
//如果当前的节点有左孩子,对当前点压栈。
//并把当前点移到左孩子,开始遍历左子树,
//如此,直到找到没有左孩子的节点。
while (cpNode->pLeftChild!=NULL)
{
*ppNodeStack++=(NODE*)cpNode;
cpNode=cpNode->pLeftChild;
}
//我们现在处于没有左孩子的节点,所以访问
//节点,如果有右子树,然后访问右子树。或
//后入的节点。重复节点的出栈直到我们找到
//有右子树的节点,或所有节点出栈
for(;
{
//访问节点(调用别的函数,没有实现)
cmVisitNode(cpNode);
//如果节点有右孩子,使该孩子成为当前节
//点并开始遍历其子树,否则回朔访问节点
//直到我们发现一个有右子树的节点,或所
//有的入栈点已经被访问。
if(cpNode->pRightChild!=NULL)
{
cpNode=cpNode->pRightChild;
break;
}
//出栈下一个节点,我们可以访问它,并判
//断是否有右子树。
if((cpNode=*(--ppNodeStack))==NULL)
{
//栈为空并且当前节点没有右子树,完
//成遍历。
lResult =CM_OK;
goto END;
}
}
}
}
//如果指针为空则设置返回值信息为MS_POINT_IS_NULL。
lResult=CM_POINTER_IS_NULL;
END:
return lResult;
}
程序函数说明开始
//==================================================================
// 功能: 对矩阵进行分配内存
// 参数: cwRowSize,cwColSize,ppplMatrix
//(入口) cwRowSize:矩阵的行数;
// cwColSize:矩阵的列数;
// ppplMatrix:要分配的矩阵的地址;
//(出口) ppplMatrix:分配的矩阵的地址;
// 返回: long 的错误号,如果返回值为CM_OK,表分配成功,返回
// CM_POINTER_IS_NOT_NULL表矩阵的入口地址值不为空。
// CM_MEM_ALLOC_FAILED 系统的内存不足
// 调用方法:在调用此函数前必须先对传入值赋空。对传入值作引用。
// 如:定义: long ** pplMatrix;对pplMatrix赋空,即:
// pplMatrix=NULL; 调用为:cmInitMatrix(10,10,&pplMatrix);
// 思路: 先对每一行分配内存,再对每一行对应的列分配内存。
// 参阅: 无
// 修改人:
// 日期: 2000/8/29.9:40--2000/8/29.16:45
//==================================================================
HRESULT cmInitMatrix(const size_t cwRowSize,const size_t cwColSize,long *** ppplMatrix)
程序函数说明结束
{
HRESULT lResult; //存储返回值。
lResult=CM_OK;
WORD wI; //循环变量
//要求对传入值进行初始化NULL.
if(**ppplMatrix!=NULL)
{
lResult=CM_POINTER_IS_NOT_NULL;
goto END;
}
//对矩阵的行进行分配内存
**ppplMatrix=(long*)malloc(cwRowSize*sizeof(long*));
//如果分配失败则返回。
if((**ppplMatrix)==NULL)
{
lResult=CM_MEM_ALLOC_FAILED;
goto END;
}
//对每一行所拥有的列数进行分配内存。
for(wI=0;wI<cwRowSize;wI++)
{
*ppplMatrix=(long**)malloc(cwColSize* sizeof(long));
//如果分配失败则返回错误。
if(*ppplMatrix==NULL)
{
lResult=CM_MEM_ALLOC_FAILED;
goto END;
}
//对内存置为零
memset(*ppplMatrix,0,cwColSize*sizeof(long));
}
END:
//对错误进行处理。
if(FAILED(lResult))
//如果分配不成功则释放内存。
if(**ppplMatrix!=NULL)
{
//对每一行内存进行释放。
for(wI=0;wI<cwRowSize;wI++)
{
if(*ppplMatrix!=NULL)
{
free(*ppplMatrix);
*ppplMatrix=NULL;
}
}
//对列的内存进行释放。
free(**ppplMatrix);
**ppplMatrix=NULL;
}
return lResult;
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
错误处理扩展:
makehresult.h
//
//作者: **
//描述: 用来对返回代码的统一规定
//主要函数:
//参考文献:COM技术内幕(微软组件对象模型);[美]Dale Rogerson著
//
#ifndef __ERRORDEFINE_H__
#define __ERRORDEFINE_H__
#include "winerror.h"
//图解
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-----------+-------------------+
// |Sev|C|R| 设备代码 | 类编码 | 返回代码 |
// +---+-+-+-----------------------+-----------+-------------------+
//
//==============================================================
对设备代码的定义
//对通用函数定义的设备代码
#define FACILITY_CM 0x80
//对虚拟现实函数定义的设备代码
#define FACILITY_VR 0x81
//对图象函数定义的设备代码
#define FACILITY_IMG 0x82
//===================================================================================
//定义类编码(后16位中的前6位)
//从0X00~0X3F//
//其中0X38~0X3F为给程序员保留的临时类编码。
//标准类编码从0X00~0X37
#define MEMORY_CLASS 0x01
#define FILE_CLASS 0x02
/*
#define TEMP_CLASS1 0X38
#define TEMP_CLASS2 0X39
#define TEMP_CLASS3 0X3A
#define TEMP_CLASS4 0X3B
#define TEMP_CLASS5 0X3C
#define TEMP_CLASS6 0X3D
#define TEMP_CLASS7 0X3E
#define TEMP_CLASS8 0X3F
*/
//=======================================================================
//制作临时资源时位的前3位屏蔽(即为保存8个临时类),临时资源从0X0000~0X1FFF(程序员输入值)
//实际为0XE000~0XFFFF(宏转换后)
#define ADDTEMPCLASS(lResult) ((0X38<10)|lResult)
//定义返回通用函数的返回代码类型
//对lResult的传入值在0X0000~0X1FFF之间
#ifndef CMEMAKEHR
#define CMSMAKEHR(lResult) MAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_CM,ADDTEMPCLASS(lResult))
#define CMEMAKEHR(lResult) MAKE_HRESULT(SEVERITY_ERROR,FACILITY_CM,ADDTEMPCLASS(lResult))
#endif //CMEMAKEHR
//定义返回虚拟现实函数的返回代码类型
//对lResult的传入值在0X0000~0X1FFF之间
#ifndef VREMAKEHR
#define VRSMAKEHR(lResult) MAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_VR,ADDTEMPCLASS(lResult))
#define VREMAKEHR(lResult) MAKE_HRESULT(SEVERITY_ERROR,FACILITY_VR,ADDTEMPCLASS(lResult))
#endif //VREMAKEHR
//定义返回图象函数的返回代码类型
//对lResult的传入值在0X0000~0X1FFF之间
#ifndef IMGEMAKEHR
#define IMGSMAKEHR(lResult) MAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_IMG,ADDTEMPCLASS(lResult))
#define IMGEMAKEHR(lResult) MAKE_HRESULT(SEVERITY_ERROR,FACILITY_IMG,ADDTEMPCLASS(lResult))
#endif //IMGEMAKEHR
//当发生返回代码时,转到END处
//要求返回代码号用lResult,标记号用END
#define IF_ERROR_GOTO_END if(FAILED(lResult)) goto END;
//显示返回代码的描述信息
#define DISPLAY_HRESULT_MESSAGE if(FAILED(lResult)) cmDispResultMessage(lResult);
typedef long HRESULT;
typedef long LRESULT;
//===================================================================
//保持和windows系统(com)一致
#define CM_OK S_OK
#define CM_FALSE E_FAIL
#define VR_OK S_OK
#define VR_FALSE E_FAIL
#define IMG_OK S_OK
#define IMG_FALSE E_FAIL
#define CM_UNKNOW_ERROR CMEMAKEHR(0X000) //未知的错误
//内存错误
#define CM_MEM_ALLOC_FAIL CMEMAKEHR(0X001) //内存分配失败
#define CM_MEM_FREE_FAIL CMEMAKEHR(0X002) //内存释放失败
#define CM_INVALID_POINTER CMEMAKEHR(0X003) //无效的指针
//文件*想*作
#define CM_CREATE_FILE_FAIL CMEMAKEHR(0X010) //文件创建失败
#define CM_OPEN_FILE_FAIL CMEMAKEHR(0X011) //文件打开失败
#define CM_CLOSE_FILE_FAIL CMEMAKEHR(0X012) //文件关闭失败
#define CM_DELETE_FILE_FAIL CMEMAKEHR(0X013) //文件删除失败
#define CM_FILE_HAS_EXISTED CMEMAKEHR(0X014) //文件已经存在
#define CM_COPY_FILE_FAIL CMEMAKEHR(0X015) //文件拷贝失败
#define CM_FILE_NOT_OPEN CMEMAKEHR(0X016) //文件没有打开
#define CM_READ_FILE_FAIL CMEMAKEHR(0X017) //文件读取失败
#define CM_FIND_FILE_FAIL CMEMAKEHR(0X018) //文件查找失败
#define CM_FILE_HAS_ERROR CMEMAKEHR(0X019) //文件本身有错
#define CM_WRITE_FILE_FAIL CMEMAKEHR(0X020) //文件写入失败
//数组*想*作
#define CM_ARRAY_BEYOND CMEMAKEHR(0X060) //数组越界
//函数*想*作
#define CM_PARAM_BEYOND CMEMAKEHR(0X030) //参数越界
//数据库*想*作
#define CM_OPEN_DATABASE_FAIL CMEMAKEHR(0X040) //数据库打开失败
#define CM_OPEN_TABLE_FAIL CMEMAKEHR(0X041) //数据表打开失败
#define CM_TABLE_BEYOND CMEMAKEHR(0X042) //数据表访问越界
#define CM_CREATE_DSN_FAIL CMEMAKEHR(0X043) //创建数据源失败
#define CM_TABLE_EXIST CMEMAKEHR(0X044) //表已经存在
#define CM_DATABASE_NOT_OPEN CMEMAKEHR(0X045) //数据库没有打开
//其他*想*作
#define CM_BEYOND_PARAM CMEMAKEHR(0x100) //参数越界
#define CM_POINT_IS_INVALIAD CMEMAKEHR(0x101) //点为非法的点
//图象*想*作
#define CM_CREATE_BMP_FAIL CMEMAKEHR(0x200) //创建位图失败
//线程*想*作
#define CM_THREAD_IS_LOCK CMEMAKEHR(0x300) //线程背锁住
//数学计算
#define CM_EDGENUM_NOTENOUGH CMEMAKEHR(0x500) //边数不够
//网络*想*作
#define CM_CONNECT_FAIL CMEMAKEHR(0x700) //连接失败
//三维设备*想*作
#define DD_CREATE_DEVICE_FAIL CMEMAKEHR(0x400) //创建设备失败
#define DD_DELETE_DEVICE_FAIL CMEMAKEHR(0x401) //删除设备失败
#endif //__ERRORDEFINE_H__
#ifndef __ERRORMATCH_H__
#define __ERRORMATCH_H__
#include "errordefine.h"
#include "windows.h"
//errormatch.h
//用来对错误的匹配
#define lResultCount 128
#define IF_ERROR_DISPLAY_MESSAGE if(FAILED(lResult)) {DisplayChineseMessage(lResult); goto END;}
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) {if(p) {delete (p);(p)=NULL;}}
#endif
#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) {if(p) {delete[] (p);(p)=NULL;}}
#endif
//===============================================================================
typedef struct _RESULTINFOS //显示返回代码信息
{
long lResultNo; //返回代码号
char szResultMessageChinese[40]; //返回代码的中文描述
char szResultMessageEnglish[40]; //返回代码的英文描述
}RESULTINFOS;
//================================================================================
static RESULTINFOS sResultInfo[lResultCount]=
{
{CM_MEM_ALLOC_FAIL,"内存分配失败","Memeroy"},
{CM_MEM_FREE_FAIL,"内存释放失败","Memeroy"},
{CM_INVALID_POINTER,"无效的指针","Memeroy"},
{CM_CREATE_FILE_FAIL,"内存分配失败","Memeroy"},
{CM_OPEN_FILE_FAIL,"文件打开失败","Memeroy"},
{CM_CLOSE_FILE_FAIL,"文件关闭失败","Memeroy"},
{CM_FILE_HAS_EXISTED,"文件已经存在","Memeroy"},
{CM_COPY_FILE_FAIL,"文件拷贝失败","Memeroy"},
{CM_FILE_NOT_OPEN,"文件没有打开","Memeroy"},
{CM_READ_FILE_FAIL,"文件读取失败","Memeroy"},
{CM_FIND_FILE_FAIL,"文件查找失败","Memeroy"},
{CM_FILE_HAS_ERROR,"文件本身有错","Memeroy"},
{CM_WRITE_FILE_FAIL,"文件写入失败","Memeroy"},
{CM_ARRAY_BEYOND,"数组越界","Memeroy"},
{CM_PARAM_BEYOND,"参数越界","Memeroy"},
{CM_OPEN_DATABASE_FAIL,"数据库打开失败","Memeroy"},
{CM_OPEN_TABLE_FAIL,"数据表打开失败","Memeroy"},
{CM_TABLE_BEYOND,"数据表访问越界","Memeroy"},
{CM_CREATE_DSN_FAIL,"创建数据源失败","Memeroy"},
{CM_TABLE_EXIST,"表已经存在","Memeroy"},
{CM_DATABASE_NOT_OPEN,"数据库没有打开","Memeroy"},
{CM_CREATE_BMP_FAIL,"创建位图失败","Memeroy"},
{CM_THREAD_IS_LOCK,"线程背锁住","Memeroy"},
{CM_POINT_IS_INVALIAD,"点为非法的点",""},
{DD_CREATE_DEVICE_FAIL,"创建设备失败",""},
{DD_DELETE_DEVICE_FAIL,"删除设备失败",""}
};
static char * GetChineseMessage(HRESULT lResult)
{
for(long lI=0;lI<lResultCount;lI++)
{
if(sResultInfo[lI].lResultNo==lResult)
{
return sResultInfo[lI].szResultMessageChinese;
}
}
return NULL;
}
static char * GetEnglishMessage(HRESULT lResult)
{
for(long lI=0;lI<lResultCount;lI++)
{
if(sResultInfo[lI].lResultNo==lResult)
{
return sResultInfo[lI].szResultMessageEnglish;
}
}
return NULL;
}
static void DisplayChineseMessage(HRESULT lResult)
{
MessageBox(NULL,GetChineseMessage(lResult),"错误!",MB_OK);
}
static void DisplayEnglishMessage(HRESULT lResult)
{
MessageBox(NULL,GetEnglishMessage(lResult),"error!",MB_OK);
}
#endif//__ERRORMATCH_H__
(试行版)
在公司团队协作开发的情况下,编程时应该强调的一个重要方面是程序的易读性,在保证软件的速度等性能指标能满足用户需求的情况下,能让其他程序员容易读懂你的程序。一套鲜明的编程风格,可以让协作者、后继者和自己一目了然,在很短的时间内看清程序的结构,理解设计的思路。大大的提高代码的可读性、可重用性、程序健壮性、可移植性和可维护性。
制定本编程规范的目的是为了提高公司的软件开发效率及所开发的软件的可维护性,提高软件的质量。本规范由程序风格、命名规则、注释规范、程序健壮性、可移植性、错误处理以及软件的模块化规范等部分组成。
一、程序风格:
1、严格采用阶梯层次组织程序代码:
各层次缩进的分格采用VC的缺省风格,即每层次缩进为4格,括号位于下一行。要求相匹配的大括号在同一列,对继行则要求再缩进4格。例如:
void main()
{
......
long lI; //循环变量
long lSum;//用来记录和
float fAvg;//用来求平均值
......
//对数进行累加。
for( lI=0;lI<10;lI++)
{
lSum=lSum+lI;
...... }}
//求平均值。
fAvg=lSum/10.0;
......
2、提示信息字符串的位置
在程序中需要给出的提示字符串,为了支持多种语言的开发,除了一些给调试用的临时信息外,其他所有的提示信息必须定义在资源中。
3、对变量的定义,尽量位于函数的开始位置。
二、命名规则:
1、变量名的命名规则
①、变量的命名规则要求用“匈牙利法则”。即开头字母用变量的类型,其余部分用变量的英文意思或其英文意思的缩写,尽量避免用中文的拼音,要求单词的第一个字母应大写。
即: 变量名=变量类型+变量的英文意思(或缩写)
对非通用的变量,在定义时加入注释说明,变量定义尽量可能放在函数的开始处。
见下表:
bool(BOOL) 用b开头 bIsParent
byte(BYTE) 用by开头 byFlag
short(int) 用n开头 nStepCount
long(LONG) 用l开头 lSum
char(CHAR) 用c开头 cCount
float(FLOAT) 用f开头 fAvg
double(DOUBLE) 用d开头 dDeta
void(VOID) 用v开头 vVariant
unsigned short(WORD) 用w开头 wCount
unsigned long(DWORD) 用dw开头 dwBroad
HANDLE(HINSTANCE) 用h开头 hHandle
DWORD 用dw开头 dwWord
LPCSTR(LPCTSTR) 用str开头 strString
用0结尾的字符串 用sz开头 szFileName
对未给出的变量类型要求提出并给出命名建议给技术委员会。
②、指针变量命名的基本原则为:
对一重指针变量的基本原则为:
“p”+变量类型前缀+命名
如一个float*型应该表示为pfStat
对多重指针变量的基本规则为:
二重指针: “pp”+变量类型前缀+命名
三重指针: “ppp”+变量类型前缀+命名
......
③、全局变量用g_开头,如一个全局的长型变量定义为g_lFailCount,即:变量名=g_+变量类型+变量的英文意思(或缩写)
④、静态变量用s_开头,如一个静态的指针变量定义为s_plPerv_Inst,即: 变量名=s_+变量类型+变量的英文意思(或缩写)
⑤、成员变量用m_开头,如一个长型成员变量定义为m_lCount;即:变量名=m_+变量类型+变量的英文意思(或缩写)
⑥、对枚举类型(enum)中的变量,要求用枚举变量或其缩写做前缀。并且要求用大写。
如:enum cmEMDAYS
{
EMDAYS_MONDAY;
EMDAYS_TUESDAY;
……
};
⑦、对struct、union、class变量的命名要求定义的类型用大写。并要加上前缀,其内部变量的命名规则与变量命名规则一致。
结构一般用S开头
如:struct ScmNPoint
{
int nX;//点的X位置
int nY; //点的Y位置
};
联合体一般用U开头
如: union UcmLPoint
{
long lX;
long lY;
}
类一般用C开头
如:
class CcmFPoint
{
public:
float fPoint;
};
对一般的结构应该定义为类模板,为以后的扩展性考虑
如:
template <class TYPE>
class CcmTVector3d
{
public:
TYPE x,y,z;
};
⑧、对常量(包括错误的编码)命名,要求常量名用大写,常量名用英文表达其意思。
如:#define CM_FILE_NOT_FOUND CMMAKEHR(0X20B) 其中CM表示类别。
⑨、对const 的变量要求在变量的命名规则前加入c_,即:c_+变量命名规则;例如:
const char* c_szFileName;
2、 函数的命名规范:
函数的命名应该尽量用英文表达出函数完成的功能。遵循动宾结构的命名法则,函数名中动词在前,并在命名前加入函数的前缀,函数名的长度不得少于8个字母。
例如:
long cmGetDeviceCount(……);
3、函数参数规范:
①、 参数名称的命名参照变量命名规范。
②、 为了提高程序的运行效率,减少参数占用的堆栈,传递大结构的参数,一律采用指针或引用方式传递。
③、 为了便于其他程序员识别某个指针参数是入口参数还是出口参数,同时便于编译器检查错误,应该在入口参数前加入const标志。如:
……cmCopyString(const char * c_szSource, char * szDest)
4、引出函数规范:
对于从动态库引出作为二次开发函数公开的函数,为了能与其他函数以及Windows的函数区分,采用类别前缀+基本命名规则的方法命名。例如:在对动态库中引出的一个图象编辑的函数定义为 imgFunctionname(其中img为image缩写)。
现给出三种库的命名前缀:
①、 对通用函数库,采用cm为前缀。
②、 对三维函数库,采用vr为前缀。
③、 对图象函数库,采用img为前缀。
对宏定义,结果代码用同样的前缀。
5、文件名(包括动态库、组件、控件、工程文件等)的命名规范:
文件名的命名要求表达出文件的内容,要求文件名的长度不得少于5个字母,严禁使用象file1,myfile之类的文件名。
三、注释规范:
1、函数头的注释
对于函数,应该从“功能”,“参数”,“返回值”、“主要思路”、“调用方法”、“日期”六个方面用如下格式注释:
//程序说明开始
//================================================================//
// 功能: 从一个String 中删除另一个String。
// 参数: strByDelete,strToDelete
// (入口) strByDelete: 被删除的字符串(原来的字符串)
// (出口) strToDelete: 要从上个字符串中删除的字符串。
// 返回: 找到并删除返回1,否则返回0。(对返回值有错误编码的要// 求列出错误编码)。
// 主要思路:本算法主要采用循环比较的方法来从strByDelete中找到
// 与strToDelete相匹配的字符串,对多匹配strByDelete
// 中有多个strToDelete子串)的情况没有处理。请参阅:
// 书名......
// 调用方法:......
// 日期:起始日期,如:2000/8/21.9:40--2000/8/23.21:45
//================================================================//
函数名(……)
//程序说明结束
①、 对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说明该角度参数是以弧度(PI),还是以度为单位),对既是入口又是出口的变量应该在入口和出口处同时标明。等等。
②、 函数的注释应该放置在函数的头文件中,在实现文件中的该函数的实现部分应该同时放置该注释。
③、 在注释中应该详细说明函数的主要实现思路、特别要注明自己的一些想法,如果有必要则应该写明对想法产生的来由。对一些模仿的函数应该注释上函数的出处。
④、 在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险方面,可能出错的地方。
⑤、 对日期的注释要求记录从开始写函数到结束函数的测试之间的日期。
⑥、 对函数注释开始到函数命名之间应该有一组用来标识的特殊字符串。
如果算法比较复杂,或算法中的变量定义与位置有关,则要求对变量的定义进行图解。对难以理解的算法能图解尽量图解。
2、变量的注释:
对于变量的注释紧跟在变量的后面说明变量的作用。原则上对于每个变量应该注释,但对于意义非常明显的变量,如:i,j等循环变量可以不注释。
例如: long lLineCount //线的根数。
3、文件的注释:
文件应该在文件开头加入以下注释:
/
// 工程: 文件所在的项目名。
// 作者:**,修改者:**
// 描述:说明文件的功能。
// 主要函数:…………
// 版本: 说明文件的版本,完成日期。
// 修改: 说明对文件的修改内容、修改原因以及修改日期。
// 参考文献: ......
/
为了头文件被重复包含要求对头文件进行定义如下:
#ifndef __FILENAME_H__
#define __FILENAME_H__
其中FILENAME为头文件的名字。
4、其他注释:
在函数内我们不需要注释每一行语句。但必须在各功能模块的每一主要部分之前添加块注释,注释每一组语句,在循环、流程的各分支等,尽可能多加以注释。
其中的循环、条件、选择等位置必须注释。
对于前后顺序不能颠倒的情况,建议在注释中增加序号。
例如:
......
//1、......注释
for (......)
{
}
if(......)
{//......注释
}
else
{//......注释
}
//......注释
switch(......)
{
case: ......// ......注释
......
case: ......// ......注释
......
default: //......注释
......
}
在其他顺序执行的程序中,每隔3-5行语句,必须加一个注释,注明这一段语句所组成的小模块的作用。对于自己的一些比较独特的思想要求在注释中标明。
四、程序健壮性:
1、函数的返回值规范:
对于函数的返回位置,尽量保持单一性,即一个函数尽量做到只有一个返回位置。(单入口单出口)。
要求大家统一函数的返回值,所有的函数的返回值都将以编码的方式返回。
例如编码定义如下:
#define CM_POINT_IS_NULL CMMAKEHR(0X200)
:
:
建议函数实现如下:
long 函数名(参数,……)
{
long lResult; //保持错误号
lResult=CM_OK;
//如果参数有错误则返回错误号
if(参数==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
……
END:
return lResult;
}
2、关于goto的应用:
对goto语句的应用,我们要求尽量少用goto语句。对一定要用的地方要求只能向后转移。
3、资源变量的处理(资源变量是指消耗系统资源的变量):
对资源变量一定赋初值。分配的资源在用完后必须马上释放,并重新赋值。
例:
long * plAllocMem;//定义一个分配内存的变量。
plAllocMem=(long*)calloc(40, sizeof( long ));//分配一段内存。
//处理分配内存错误
if(plAllocMem==NULL)
{
lResult=CM_MEM_ALLOC_FAILED;
goto END;
}
……
使用内存
……
//释放资源变量,并重新赋值。
if(pAllocMem!=NULL)
{
free(plAllocMem);
pAllocMem=NULL;
}
4、对复杂的条件判断,为了程序的可读性,应该尽量使用括号。
例:if(((szFileName!=NULL)&&(lCount>=0)))||(bIsReaded==TRUE))
五、可移植性:
1、高质量的代码要求能够跨平台,所以我们的代码应该考虑到对不同的平台的支持,特别是对windows98和windowsnt的支持。
2、由于C语言的移植性比较好,所以对算法函数要求用C代码,不能用C++代码。
3、对不同的硬件与软件的函数要做不同的处理。
五、错误处理:
1、错误报告处理。
编程中要求考虑函数的各种执行情况,尽可能处理所有的流程情况。将函数分为两类:
一类为与屏幕的显示无关,(不与用户交换信息的函数)
一类为与屏幕的显示相关。(与用户交换信息的函数)
对于与屏幕显示无关的函数,函数通过返回值来报告错误。
对于与屏幕显示有关的函数,函数要负责向用户发出警告,并进行错误处理。
错误处理代码一般单独建立通用处理函数。如下:
void cmDeal_With_Error(long ErrCode)
{
switch(ErrCode)
{
case 1://注释
......
case 2://注释
......
default://注释
......
}
}
2、 尽早发现程序中的错误:
①、 重视编译器中的警告信息。
对于编译器产生的警告信息,我们应该引起足够的重视,实际上许多警告信息指示了程序中潜在的错误危险。所以我们要认真检查每一个警告信息,查看是否有某种隐患。尽量消除警告信息。
②、 利用断言来检查错误
对于程序中的某种假设,或防止某些参数的非法值,利用断言来帮助查错是一种好的办法。
例如下面的函数:
long cmMemCpy(void * pvToMem, void* pvFromMem, size_t wSize)
{
……
if(pvToMem==NULL||pvFromMem==NULL)
{
lResult=CM _POINT_IS_NULL;
goto: END;
}
while(wSize-- >0)
{
*pvToMem++=pvFromMem++;
}
END:
return lResult;
}
采用判断可以检查传入的指针错误,但是这样的判断是程序最终的编译代码变大,同时降低了最终发布的程序的执行效率。由于传入空指针明显是调用这函数的程序的错误,而不是这个函数的错误,我们可以考虑采用断言来代替指针检查,即用
ASSERT( pvToMem!=NULL&&pvFromMem!=NULL)
代替
if(pvToMem==NULL||pvFromMem==NULL)
{
lResult=CM_POINT_IS_NULL;
goto: END;
}
这样只会在debug版中才会产生检查代码,而在正式发布版中不会带有这些代码。并且可以方便我们在程序调试中和测试时发现错误,同时又不影响程序的效率。
在下面的一些情况中必须加断言:
a、 数的参数,特别是指针参数必须利用断言来进行确认。
b、 利用断言检查程序中的各种假设的正确性。
c、 在程序设计中不要轻易认为某种情况不可能发生,对你认为不可能发生的情况必须用断言来证实。
为了使程序中的断言发挥作用,所有用于在开发内部进行测试或调试的动态库、执行程序、组件必须采用debug版。
说明:
在程序效率要求较高、或者调用比较频繁的函数,对入口参数的错误检查,使用断言方式,其优点如上所叙,但其健壮性不强,所以在其他情况下,仍要求使用传统的检查方式,以增强程序的健壮性,当然,为了调试方便,可同时使用断言方式。
③、 严格的测试:
对每一段代码都要求进行严格的测试,特别对一些功能函数要对其各种临界点(比如零值、无穷大的值等)进行测试。尽量做到每一段代码零错误。
六、模块化规范:
为了提高软件的重用性,减少重复开发的工作量。同时也为了提高程序的可读性,方便程序的维护,必须加强软件的模块化工作。模块化应该遵循以下几个基本规范:
1、 个函数应该作到精而小,函数的代码应该控制在一个适度的规模,每个函数的代码一般不能超过150行,如果超过这个规模,应该进行模块化的工作。对于一些特殊的函数确实要超过150行,应该提交出来讨论,通过后,要求编写者更加详细的对函数注释,并写明函数超行的原因,以及设计思想等。
2、 某一功能,如果重复实现三遍以上,既应该考虑模块化,将其写成通用函数。并向开发人员发布。并要求将接口文档和实现的功能备案。
3、 每一个开发人员要尽可能的利用其他人的现成的模块,减少重复开发。
4、 对函数进行模块化时,要考虑函数的层次关系,特别是在增加新的功能模块时,对原来的函数代码要进行认真的调整,做到相同功能的不同函数没有重复代码,此要求的目的在于便于代码维护。举例如下:
现有如下函数:
//从szFileName文件中取 ......
long ...... cmGetSomething(const char * c_szFileName,......)
{
CFile * pFile;//用来保存打开文件的地址
pFile=new CFile(c_szFileName,CFile::modeRead);//用创建一个只读文件
if(pFile==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
//从文件中读取......
......
//关闭文件
delete pFile;
END:
return lResult;
}
若现在需要增加如下接口的新函数:
long ...... cmReadSomething(CFile * pFile)
{
if(pFile==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
//从文件中读取......
......
END:
return lResult;
}
则要求如下:
将 long ......cmGetSomething(const char * c_szFileName,......)改为
long ...... cmGetSomething (const char * c_szFileName,......)
{
CFile * pFile; //用来保存打开文件的地址
long lResult=CM_OK;//错误返回码
//打开文件
pFile=new CFile(c_szFileName,CFile::modeRead);
if(pFile==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
//从文件中读取......
lResult=cmReadSomething(pFile,......);
IF_ERROR_GOTO_END
//关闭文件
delete pFile;
END:
return lResult;
}
模块化的一些注意事项:
① 、设计好模块接口,用面向对象的观点看,包括:函数接口和变量接口。
② 、定义好接口以后不要轻易改动,并在模块开头(文件的开头或函数的开头)加以说明,所以在定义接口时,一定要反复琢磨,保持风格一致。
③ 、注意全局变量也是一种接口,如果不是确实必要,应该尽量少用全局变量。
④ 、在函数接口中,尽量使函数的接口容易理解和使用,其中每个输入输出参数都只代表一种类型数据,不要把错误值和其他专用值混在函数的其他输入输出参数中。
⑤ 、争取编写出永远成功的函数,使调用者不必进行相应的错误处理。
此规范为试行版,解释权属于**软件公司技术委员会!员工在编写程序时请参阅FuncTemplate.h和FuncTemplate.cpp
FuncTemplate.h
//
//工程: FuncTemplate.h //
//描述: 用来处理对二叉树的一些算法,以及矩阵内存分配。 //
//版本: FuncTemplate 1.0版。 //
//
typedef struct _NODE //定义一个节点
{
struct _NODE * pLeftChild; //节点的左孩子
struct _NODE * pRightChild; //节点的右孩子
} NODE;
//==================================================================//
// 功能: 用循环来实现二叉树的左序遍历 //
// 参数: pNode //
//(入口) pNode: 二叉树的入口地址,即根节点的地址。 //
//(出口) 无。 //
// 返回: long 的函数返回码,如果返回值为CM_OK,表成功遍历,返回 //
// CM_POINT_IS_NULL表二叉树指针为空。 //
//==================================================================//
long cmWalkTreeUseCycle(const NODE * pNode);
//==================================================================//
// 功能: 对矩阵进行分配内存 //
// 参数: wRowSize,wColSize,ppplMatrix //
//(入口) wRowSize:矩阵的行数; //
//(入口) wColSize:矩阵的列数; //
//(入口) ppplMatrix:要分配的矩阵的地址; //
//(出口) ppplMatrix:分配的矩阵的地址; //
// 返回: long 的错误号,如果返回值为CM_OK,表分配成功,返回 //
// CM_POINTER_IS_NOT_NULL表矩阵的入口地址值不为空。 //
// CM_MEM_ALLOC_FAILED 系统的内存不足 //
//==================================================================//
long cmInitMatrix(const size_t wRowSize,const size_t wColSize,long *** ppplMatrix);
FuncTemplate.cpp
//
//工程: FuncTemplate.cpp //
//作者: ** //
//修改者: **,*** //
//描述: 用来处理对二叉树的一些算法,以及矩阵内存分配。
//主要函数:cmWalkTreeUseCycle,cmInitMatrix
//版本: FuncTemplate 1.0版。
//完成日期:2000-8-26 //
//修改日期: 2000-8-27,2001-12-21 //
//参考文献: 图形程序开发人员指南(机械工业出版社)
//
#define STRICT
#include "stdio.h"
#include "stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include "makehresult.h"
#include "memory.h"
#include "functemplate.h"
#define CM_MEM_POINTER_IS_NULL CMEMAKEHR(0X100) //表示指针不为空的错误
#define CM_MEM_POINTER_IS_NOT_NULL CMEMAKEHR(0X101) //表示指针不为空的错误
#define MAX_PUSHED_NODES 100 //定义最大压栈数量
程序函数说明开始
//==================================================================
// 功能: 用循环来实现二叉树的左序遍历
// 参数: cpNode //
//(入口) cpNode: 二叉树的入口地址,即根节点的地址。
//(出口) 无。 //
// 返回: long 的函数返回码,如果返回值为CM_OK,表成功遍历,返回
// MS_POINT_IS_NULL表二叉树指针为空。
// 调用方法:在调用此函数前必须先初始化二叉树
// 思路: 如果正在访问某节点,如果该节点有左分枝,就先访问左分枝。
// 接着访问该节点,如果该节点还有右分支。就再访问右分支。
// 访问左分枝时,在该处栈中做标记,当处理完左分枝就访问此
// 处。访问完每一个节点后,如果该节点没有右孩子,并且栈已经
// 为空,那么对该节点的访问就完成,代码对每一个节点都重复以
// 上*想*作。 //
// 参阅: 图形程序开发人员指南(机械工业出版社)Page:927
// 日期: 2000/8/26.9:40--2000/8/26.21:45
//==================================================================
//图解:
// 根节点
// @
// / /
// / /
// 左孩子@ @右孩子
// / / / /
// / / / /
// @ @ @ @
// 左孩子 右孩子左孩子 右孩子
HRESULT cmWalkTreeUseCycle(const NODE * cpNode)
程序函数说明结束
{
HRESULT lResult;//用来保存返回的错误号。
NODE * pNodeStack[MAX_PUSHED_NODES];//用来作为节点的堆栈。
NODE ** ppNodeStack;//用来指示节点堆栈的指针。
lResult=CM_OK;
//判断树是否为空。
if(cpNode !=NULL)
{
pNodeStack[0]=NULL; //设置堆栈为空。
ppNodeStack=pNodeStack+1;
for(;
{
//如果当前的节点有左孩子,对当前点压栈。
//并把当前点移到左孩子,开始遍历左子树,
//如此,直到找到没有左孩子的节点。
while (cpNode->pLeftChild!=NULL)
{
*ppNodeStack++=(NODE*)cpNode;
cpNode=cpNode->pLeftChild;
}
//我们现在处于没有左孩子的节点,所以访问
//节点,如果有右子树,然后访问右子树。或
//后入的节点。重复节点的出栈直到我们找到
//有右子树的节点,或所有节点出栈
for(;
{
//访问节点(调用别的函数,没有实现)
cmVisitNode(cpNode);
//如果节点有右孩子,使该孩子成为当前节
//点并开始遍历其子树,否则回朔访问节点
//直到我们发现一个有右子树的节点,或所
//有的入栈点已经被访问。
if(cpNode->pRightChild!=NULL)
{
cpNode=cpNode->pRightChild;
break;
}
//出栈下一个节点,我们可以访问它,并判
//断是否有右子树。
if((cpNode=*(--ppNodeStack))==NULL)
{
//栈为空并且当前节点没有右子树,完
//成遍历。
lResult =CM_OK;
goto END;
}
}
}
}
//如果指针为空则设置返回值信息为MS_POINT_IS_NULL。
lResult=CM_POINTER_IS_NULL;
END:
return lResult;
}
程序函数说明开始
//==================================================================
// 功能: 对矩阵进行分配内存
// 参数: cwRowSize,cwColSize,ppplMatrix
//(入口) cwRowSize:矩阵的行数;
// cwColSize:矩阵的列数;
// ppplMatrix:要分配的矩阵的地址;
//(出口) ppplMatrix:分配的矩阵的地址;
// 返回: long 的错误号,如果返回值为CM_OK,表分配成功,返回
// CM_POINTER_IS_NOT_NULL表矩阵的入口地址值不为空。
// CM_MEM_ALLOC_FAILED 系统的内存不足
// 调用方法:在调用此函数前必须先对传入值赋空。对传入值作引用。
// 如:定义: long ** pplMatrix;对pplMatrix赋空,即:
// pplMatrix=NULL; 调用为:cmInitMatrix(10,10,&pplMatrix);
// 思路: 先对每一行分配内存,再对每一行对应的列分配内存。
// 参阅: 无
// 修改人:
// 日期: 2000/8/29.9:40--2000/8/29.16:45
//==================================================================
HRESULT cmInitMatrix(const size_t cwRowSize,const size_t cwColSize,long *** ppplMatrix)
程序函数说明结束
{
HRESULT lResult; //存储返回值。
lResult=CM_OK;
WORD wI; //循环变量
//要求对传入值进行初始化NULL.
if(**ppplMatrix!=NULL)
{
lResult=CM_POINTER_IS_NOT_NULL;
goto END;
}
//对矩阵的行进行分配内存
**ppplMatrix=(long*)malloc(cwRowSize*sizeof(long*));
//如果分配失败则返回。
if((**ppplMatrix)==NULL)
{
lResult=CM_MEM_ALLOC_FAILED;
goto END;
}
//对每一行所拥有的列数进行分配内存。
for(wI=0;wI<cwRowSize;wI++)
{
*ppplMatrix=(long**)malloc(cwColSize* sizeof(long));
//如果分配失败则返回错误。
if(*ppplMatrix==NULL)
{
lResult=CM_MEM_ALLOC_FAILED;
goto END;
}
//对内存置为零
memset(*ppplMatrix,0,cwColSize*sizeof(long));
}
END:
//对错误进行处理。
if(FAILED(lResult))
//如果分配不成功则释放内存。
if(**ppplMatrix!=NULL)
{
//对每一行内存进行释放。
for(wI=0;wI<cwRowSize;wI++)
{
if(*ppplMatrix!=NULL)
{
free(*ppplMatrix);
*ppplMatrix=NULL;
}
}
//对列的内存进行释放。
free(**ppplMatrix);
**ppplMatrix=NULL;
}
return lResult;
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
错误处理扩展:
makehresult.h
//
//作者: **
//描述: 用来对返回代码的统一规定
//主要函数:
//参考文献:COM技术内幕(微软组件对象模型);[美]Dale Rogerson著
//
#ifndef __ERRORDEFINE_H__
#define __ERRORDEFINE_H__
#include "winerror.h"
//图解
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-----------+-------------------+
// |Sev|C|R| 设备代码 | 类编码 | 返回代码 |
// +---+-+-+-----------------------+-----------+-------------------+
//
//==============================================================
对设备代码的定义
//对通用函数定义的设备代码
#define FACILITY_CM 0x80
//对虚拟现实函数定义的设备代码
#define FACILITY_VR 0x81
//对图象函数定义的设备代码
#define FACILITY_IMG 0x82
//===================================================================================
//定义类编码(后16位中的前6位)
//从0X00~0X3F//
//其中0X38~0X3F为给程序员保留的临时类编码。
//标准类编码从0X00~0X37
#define MEMORY_CLASS 0x01
#define FILE_CLASS 0x02
/*
#define TEMP_CLASS1 0X38
#define TEMP_CLASS2 0X39
#define TEMP_CLASS3 0X3A
#define TEMP_CLASS4 0X3B
#define TEMP_CLASS5 0X3C
#define TEMP_CLASS6 0X3D
#define TEMP_CLASS7 0X3E
#define TEMP_CLASS8 0X3F
*/
//=======================================================================
//制作临时资源时位的前3位屏蔽(即为保存8个临时类),临时资源从0X0000~0X1FFF(程序员输入值)
//实际为0XE000~0XFFFF(宏转换后)
#define ADDTEMPCLASS(lResult) ((0X38<10)|lResult)
//定义返回通用函数的返回代码类型
//对lResult的传入值在0X0000~0X1FFF之间
#ifndef CMEMAKEHR
#define CMSMAKEHR(lResult) MAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_CM,ADDTEMPCLASS(lResult))
#define CMEMAKEHR(lResult) MAKE_HRESULT(SEVERITY_ERROR,FACILITY_CM,ADDTEMPCLASS(lResult))
#endif //CMEMAKEHR
//定义返回虚拟现实函数的返回代码类型
//对lResult的传入值在0X0000~0X1FFF之间
#ifndef VREMAKEHR
#define VRSMAKEHR(lResult) MAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_VR,ADDTEMPCLASS(lResult))
#define VREMAKEHR(lResult) MAKE_HRESULT(SEVERITY_ERROR,FACILITY_VR,ADDTEMPCLASS(lResult))
#endif //VREMAKEHR
//定义返回图象函数的返回代码类型
//对lResult的传入值在0X0000~0X1FFF之间
#ifndef IMGEMAKEHR
#define IMGSMAKEHR(lResult) MAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_IMG,ADDTEMPCLASS(lResult))
#define IMGEMAKEHR(lResult) MAKE_HRESULT(SEVERITY_ERROR,FACILITY_IMG,ADDTEMPCLASS(lResult))
#endif //IMGEMAKEHR
//当发生返回代码时,转到END处
//要求返回代码号用lResult,标记号用END
#define IF_ERROR_GOTO_END if(FAILED(lResult)) goto END;
//显示返回代码的描述信息
#define DISPLAY_HRESULT_MESSAGE if(FAILED(lResult)) cmDispResultMessage(lResult);
typedef long HRESULT;
typedef long LRESULT;
//===================================================================
//保持和windows系统(com)一致
#define CM_OK S_OK
#define CM_FALSE E_FAIL
#define VR_OK S_OK
#define VR_FALSE E_FAIL
#define IMG_OK S_OK
#define IMG_FALSE E_FAIL
#define CM_UNKNOW_ERROR CMEMAKEHR(0X000) //未知的错误
//内存错误
#define CM_MEM_ALLOC_FAIL CMEMAKEHR(0X001) //内存分配失败
#define CM_MEM_FREE_FAIL CMEMAKEHR(0X002) //内存释放失败
#define CM_INVALID_POINTER CMEMAKEHR(0X003) //无效的指针
//文件*想*作
#define CM_CREATE_FILE_FAIL CMEMAKEHR(0X010) //文件创建失败
#define CM_OPEN_FILE_FAIL CMEMAKEHR(0X011) //文件打开失败
#define CM_CLOSE_FILE_FAIL CMEMAKEHR(0X012) //文件关闭失败
#define CM_DELETE_FILE_FAIL CMEMAKEHR(0X013) //文件删除失败
#define CM_FILE_HAS_EXISTED CMEMAKEHR(0X014) //文件已经存在
#define CM_COPY_FILE_FAIL CMEMAKEHR(0X015) //文件拷贝失败
#define CM_FILE_NOT_OPEN CMEMAKEHR(0X016) //文件没有打开
#define CM_READ_FILE_FAIL CMEMAKEHR(0X017) //文件读取失败
#define CM_FIND_FILE_FAIL CMEMAKEHR(0X018) //文件查找失败
#define CM_FILE_HAS_ERROR CMEMAKEHR(0X019) //文件本身有错
#define CM_WRITE_FILE_FAIL CMEMAKEHR(0X020) //文件写入失败
//数组*想*作
#define CM_ARRAY_BEYOND CMEMAKEHR(0X060) //数组越界
//函数*想*作
#define CM_PARAM_BEYOND CMEMAKEHR(0X030) //参数越界
//数据库*想*作
#define CM_OPEN_DATABASE_FAIL CMEMAKEHR(0X040) //数据库打开失败
#define CM_OPEN_TABLE_FAIL CMEMAKEHR(0X041) //数据表打开失败
#define CM_TABLE_BEYOND CMEMAKEHR(0X042) //数据表访问越界
#define CM_CREATE_DSN_FAIL CMEMAKEHR(0X043) //创建数据源失败
#define CM_TABLE_EXIST CMEMAKEHR(0X044) //表已经存在
#define CM_DATABASE_NOT_OPEN CMEMAKEHR(0X045) //数据库没有打开
//其他*想*作
#define CM_BEYOND_PARAM CMEMAKEHR(0x100) //参数越界
#define CM_POINT_IS_INVALIAD CMEMAKEHR(0x101) //点为非法的点
//图象*想*作
#define CM_CREATE_BMP_FAIL CMEMAKEHR(0x200) //创建位图失败
//线程*想*作
#define CM_THREAD_IS_LOCK CMEMAKEHR(0x300) //线程背锁住
//数学计算
#define CM_EDGENUM_NOTENOUGH CMEMAKEHR(0x500) //边数不够
//网络*想*作
#define CM_CONNECT_FAIL CMEMAKEHR(0x700) //连接失败
//三维设备*想*作
#define DD_CREATE_DEVICE_FAIL CMEMAKEHR(0x400) //创建设备失败
#define DD_DELETE_DEVICE_FAIL CMEMAKEHR(0x401) //删除设备失败
#endif //__ERRORDEFINE_H__
#ifndef __ERRORMATCH_H__
#define __ERRORMATCH_H__
#include "errordefine.h"
#include "windows.h"
//errormatch.h
//用来对错误的匹配
#define lResultCount 128
#define IF_ERROR_DISPLAY_MESSAGE if(FAILED(lResult)) {DisplayChineseMessage(lResult); goto END;}
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) {if(p) {delete (p);(p)=NULL;}}
#endif
#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) {if(p) {delete[] (p);(p)=NULL;}}
#endif
//===============================================================================
typedef struct _RESULTINFOS //显示返回代码信息
{
long lResultNo; //返回代码号
char szResultMessageChinese[40]; //返回代码的中文描述
char szResultMessageEnglish[40]; //返回代码的英文描述
}RESULTINFOS;
//================================================================================
static RESULTINFOS sResultInfo[lResultCount]=
{
{CM_MEM_ALLOC_FAIL,"内存分配失败","Memeroy"},
{CM_MEM_FREE_FAIL,"内存释放失败","Memeroy"},
{CM_INVALID_POINTER,"无效的指针","Memeroy"},
{CM_CREATE_FILE_FAIL,"内存分配失败","Memeroy"},
{CM_OPEN_FILE_FAIL,"文件打开失败","Memeroy"},
{CM_CLOSE_FILE_FAIL,"文件关闭失败","Memeroy"},
{CM_FILE_HAS_EXISTED,"文件已经存在","Memeroy"},
{CM_COPY_FILE_FAIL,"文件拷贝失败","Memeroy"},
{CM_FILE_NOT_OPEN,"文件没有打开","Memeroy"},
{CM_READ_FILE_FAIL,"文件读取失败","Memeroy"},
{CM_FIND_FILE_FAIL,"文件查找失败","Memeroy"},
{CM_FILE_HAS_ERROR,"文件本身有错","Memeroy"},
{CM_WRITE_FILE_FAIL,"文件写入失败","Memeroy"},
{CM_ARRAY_BEYOND,"数组越界","Memeroy"},
{CM_PARAM_BEYOND,"参数越界","Memeroy"},
{CM_OPEN_DATABASE_FAIL,"数据库打开失败","Memeroy"},
{CM_OPEN_TABLE_FAIL,"数据表打开失败","Memeroy"},
{CM_TABLE_BEYOND,"数据表访问越界","Memeroy"},
{CM_CREATE_DSN_FAIL,"创建数据源失败","Memeroy"},
{CM_TABLE_EXIST,"表已经存在","Memeroy"},
{CM_DATABASE_NOT_OPEN,"数据库没有打开","Memeroy"},
{CM_CREATE_BMP_FAIL,"创建位图失败","Memeroy"},
{CM_THREAD_IS_LOCK,"线程背锁住","Memeroy"},
{CM_POINT_IS_INVALIAD,"点为非法的点",""},
{DD_CREATE_DEVICE_FAIL,"创建设备失败",""},
{DD_DELETE_DEVICE_FAIL,"删除设备失败",""}
};
static char * GetChineseMessage(HRESULT lResult)
{
for(long lI=0;lI<lResultCount;lI++)
{
if(sResultInfo[lI].lResultNo==lResult)
{
return sResultInfo[lI].szResultMessageChinese;
}
}
return NULL;
}
static char * GetEnglishMessage(HRESULT lResult)
{
for(long lI=0;lI<lResultCount;lI++)
{
if(sResultInfo[lI].lResultNo==lResult)
{
return sResultInfo[lI].szResultMessageEnglish;
}
}
return NULL;
}
static void DisplayChineseMessage(HRESULT lResult)
{
MessageBox(NULL,GetChineseMessage(lResult),"错误!",MB_OK);
}
static void DisplayEnglishMessage(HRESULT lResult)
{
MessageBox(NULL,GetEnglishMessage(lResult),"error!",MB_OK);
}
#endif//__ERRORMATCH_H__