2007年8月9日星期四

Visual C++ 编码规范


1.说明

执行本规范的目的在于保证编码的规范性、和确保代码质量,提高软件代码的可读性、可修改性,避免在开发和调试过程中出现不必要的麻烦。此外,还可以统一代码风格,便于研发人员之间的技术交流。

2.适用范围

本标准适用于使用Visual C++进行软件程序开发的人员。

3.编码的基本要求

n 程序结构清晰,逐步求精,单个函数的程序行数一般不超过100行。

n 代码精简,避免编码拖沓冗长。

n 尽量使用标准库函数和公共函数。

n 不要随意定义全局变量,尽量使用局部变量、类的成员变量。

n 表达式复杂时,多使用括号以避免二义性。

3.1可读性

n 可读性第一,效率第二。

n 保持注释与代码完全一致。

n 每个源程序文件,都有文件头说明,说明规格见规范。

n 每个函数,都有函数头说明,说明规格见规范。

n 主要变量(结构、联合、类或对象)定义或引用时,注释能反映其含义。

n 常量定义(DEFINE)有相应说明。

n 处理过程的每个阶段都有相关注释说明。

n 在典型算法前都有注释。

n 利用缩进来显示程序的逻辑结构,缩进量一致并以空格键为单位,定义为 4个
字节,不建议用Tab键。

n 循环、分支层次不要超过五层。必要时,使用Goto 语句,减少层次。

n 注释可以与语句在同一行,也可以在上行。

n 空行和空白字符也是一种特殊注释。

n 一目了然的语句不加注释。

n 注释的作用范围可以为:定义、引用、条件分支以及一段代码。

n 注释行数(不包括程序头和函数头说明部份)应占总行数的 1/5 到 1/3 。

3.2结构化要求

n 代码严格缩进,缩进使用TAB键,设置TAB键等价于4个空格字符。

n 禁止出现两条等价的支路。

n 减少使用GOTO语句。为减少IF语句层次,也可以适当使用。

n CASE 实现多路分支。减少使用 ELSE IF句型。

n 避免从循环引出多个出口。

n 函数一般只有一个出口。为减少判断语句层次,可适当使用GOTO 语句。

n 不使用条件赋值语句。 如:if (a=b) …

n 不要轻易用条件分支去替换逻辑表达式。

n 考虑到代码量和可阅读性,有如下编码建议(非强制性规范):

1)减少括号的使用

if (!m_bIsOpen ) return FALSE;

如果写成,将大大增代码量,函数体代码语句多的时候不利于阅读:

if (!m_bIsOpen )

{

return FALSE;

}

else

{

// ......

}

2)使用GOTO语句减少语句层次

下面是函数体,使用GOTO语句减少代码层次的例子

该例子如使用IF语句做整体判断则明显增加代码缩进的层次。

{

if ( 条件不满足1 )

{

iret = -1;

goto label_end;

}

if ( 条件不满足2 )

{

iret = -2;

goto label_end;

}

label_end:

// ... ...

return iret;

}

3.3正确性与容错性要求

n 程序首先是正确,其次是才是优美。

n 无法证明你的程序没有错误,因此在编写完一段程序后,应先回头检查。

n 改一个错误时可能产生新的错误,因此在修改前首先考虑对其它程序的影响。

n 所有变量在调用前必须被初始化。数组、结构体要ZeroMemory 初始化。

n 对所有的用户输入,必须进行合法性检查。

n 不要比较浮点数的相等,如: 10.0 * 0.1 == 1.0 , 不可靠

n 程序与环境或状态发生关系时,必须主动去处理发生的意外事件,如文件能否逻辑锁定、打印机是否联机等。

n 单元测试也是编程的一部份,提交联调测试的程序必须先由程序员经过单元测试。

3.4可重用性要求

n 重复使用的、相对独立功能的算法或代码应抽象为公共控件或类。

n 公共控件或类应考虑使用OO编程思想,减少外界联系,考虑独立性和封装性。

n 公共控件或类应建立使用模板。

n 一些涉及到商业机密或者公司核心技术的部件,可使用DLL或者COM组件的形式对公用模块进行封装。

4. C++代码书写规范

4.1文件头的编写规范

每个文件开头部分至少应当包括下面几项内容:文件标题(Title )、版权说明(Copyright)、修改记录(Modification History)。

n Title 中包括文件的名称和文件的简单说明。Title 应该在一行内完成。

n Copyright 包括一个版权说明。

例如:

// Copyright (c) 2005 EASTCOM LingTong.

// All rights reserved.

n Modification History

记录文件修改的过程,只记录最主要的修改。当书写新的函数模块时,只需要使用

形如“add func1()”这样的说明;如果后面又对函数中的算法进行了修改,需要指出修改

的具体位置和修改方法。

Modification History 的具体格式为:

<版本号> <修改日期> <修改人和修改动作>

如:

// V0.9.0 20050823 创建初始版本

// V0.9.1 20050830 修改结构体项目对齐方式为默认

一个文件头的具体范例如下:

//////////////////////////////////////////////////////////////////////

//

// Copyright (c) 2005 EASTCOM LingTong.

// All rights reserved.

//

// 文件名 CCFrmw.h

// 类型 HPP

// 版本号 0.9.1

// 编制 Trueway Lee

// 描述 用于实现 ABWOA 模块DLL

// 该模块内部实现了 EASTCOM-ABWOA 软件总线机制

//

//--------------------------------------------------------------------

// 版本历史

//--------------------------------------------------------------------

// V0.9.0 20050823 创建初始版本

// V0.9.1 20050830 修改结构体项目对齐方式为默认

//

//////////////////////////////////////////////////////////////////////

4.2变量命名

为了便于程序的阅读,变量命名必须具有一定的实际意义。

变量命名按照匈牙利命令风格:形式为xAbcFghx由变量类型确定,AbcFgh表示连续意义字符串,如果连续意义字符串仅两个,可都大写。如OK。

下面是常规数据类型的变量命名方法:

类型名

C/C++ 等价类型

定义实例

BOOL

int

BOOL fFlag

LPSTR

char *

LPSTR szText

HANDLE

void *

HANDLE hEvent1

HWND

void *

HWND hWnd

INT

int

INT iIndex

UINT

unsigned int

UINT nIndex

LPVOID

void *

LPVOID pData

WORD

unsigned short

WORD wData

DWORD

unsigned long

DWORD dwData

BYTE

unsigned char

BYTE bChar

CHAR

char

CHAR cChar

LONG

long

LONG lIndex

ULONG

unsigned long

ULONG ulData

WPARAM

unsigned int

WPARAM wParam

LPARAM

long

LPARAM lParam

这里只是列举了一部分,更详细的内容请查看 MSDN 文档。

4.3各作用域变量命名规则

n 全局变量(g_类型、)

“g_” + “类型标识”+“变量名”

例如:g_nMsg、g_bFlag

n 类成员变量(m_xxxx)

“m_” + “类型标识”+“变量名”

例如:m_szText、m_hEvent

n 局部变量

“类型标识”+“变量名”

局部变量中可采用如下几个通用变量:nTemp,iRet,I,J(一般用于循环变量)。

n 函数参数变量,参数变量是局部变量的特例

a_” +“类型标识”+“变量名”

例如:int test_fun(long a_lMode, LPSTR a_szData);

4.4常量命名和宏定义

下面是常量和宏定义方面的一般规则:

n 常量和宏定义必须具有一定的实际意义;

n 常量和宏定义在 #include 和函数定义之间;

n 常量和宏定义一般全部以大写字母来撰写,中间可根据意义的连续性用下划线连接,每一条定义的右侧必须有一简单的注释,说明其作用。

#define _POSIX_ARG_MAX 4096

#define _POSIX_CHILD_MAX 6

#define _POSIX_LINK_MAX 8

#define _POSIX_MAX_CANON 255

#define _POSIX_MAX_INPUT 255

#define _POSIX_NAME_MAX 14

考虑到阅读的方便性,也可以使用大小写混合的形式定义常量,但第一段必须是大写,标识该常量的使用访问或者类别。

#define ERROR_SSIPC_Name_Error (-1)

#define ERROR_SSIPC_Time_Out (-2)

#define ERROR_SSIPC_Call_Failture (-3)

#define ERROR_SSIPC_Pack_Data (-4)

n 资源名字定义格式:
菜单:IDM_XX或者CM_XX
位图:IDB_XX
对话框:IDD_XX
字符串:IDS_XX
DLGINIT:DIALOG_XX
ICON:IDR_XX

4.5函数命名

n 函数原型说明包括引用外来函数及内部函数,外部引用须在右侧注明函数来源:模
块名及文件名, 如是内部函数,不需要特殊说明。

n 函数名第一个字母大写,其他部分要求用大小写字母组合命名。

必要时可用下划线间隔。

示例如下:

extern void UpdateDB_Tfgd (TRACK_NAME) //Module Name r01/sdw.c
extern PrintTrackData (TRACK_NAME)
//Module Name
r04/tern.c
extern ImportantPoint (void)
//Module Name
r01/sdw.c
void ShowChar (int
int long)

void ScrollUp_V (int
int)

4.6结构体

n 结构体类型命名使用大写字母,原则上前面以下划线开始;

n 结构体变量用大小写字母组合,第一个字母大写,必要时可用下划线间隔;

n 对于私有数据区,须注明其所属的进程。全局数据定义只需注明其用途。


示例如下:
typedef struct
{
char szProductName[20]

char szAuthor[20]

char szReleaseDate[16]

char szVersion[10]

unsigned long MaxTables

unsigned long UsedTables

}DBS_DATABASE, * LPDBS_DATABASE

DBS_DATABASE g_dataBase

4.7控件的命名

用小写前缀表示类别
fm 窗口
cmd 按钮
cob combo,下拉式列表框
txt 文本输入框
lab label,标签
img image,图象
pic picture
grd Grid,网格
scr 滚动条
lst 列表框
frm fram

控件命名可以为:

n 小写类别+大小写混合的控件标识

n 小写类别+“__”+ 控件标识

比如: labText、lab_text 都可以。

4.8注释

n 原则上注释要求使用中文;

n 文件开始注释内容包括:公司名称、版权、作者名称、时间、模块用途、背景介绍等;

n 复杂的算法需要加上流程说明;

n 函数注释包括:输入、输出、函数描述、流程处理、全局变量、调用样例等;

n 复杂的函数需要加上变量用途说明;

n 程序中注释(阶段注释)包括:修改时间和作者、方便理解的注释等;

注释主要分为三类,文件头注释、函数体内阶段注释、函数注释

引用一: 文件开头的注释模板

//////////////////////////////////////////////////////////////////////

//

// Copyright (c) 2005 EASTCOM LingTong.

// All rights reserved.

//

// 文件名: cccdmfw_xpp.h

// 类型: XPP

// 版本号: 0.1

// 编制: jjt

// 描述: 用于实现 ABWOA 出钞机构模块定义

// 该模块内部实现了 EASTCOM-ABWOA 模块出钞机构的动作,

// 基于ABWOA模块的出钞机构CCCdmFW类定义

//

//--------------------------------------------------------------------

// 版本历史:

//--------------------------------------------------------------------

// V0.1 2005-9-22 10:30 创建初始版本

//

//

//////////////////////////////////////////////////////////////////////


引用二: 程序中的注释模板

if ( ( pBuff->sCallType == SWBUS_Asynchronization_Mode ) &&

( pBuff->pJobState ) )

pBuff->pJobState->sJobState = CCFRMW_JOB_RUNNING;// 设置任务状态

//---------------------------------------------------------------------

// 获得模块信息

//---------------------------------------------------------------------

LPMYMODULENODE lpInfo = NULL;

int iret = g_load.GetModuleInfo(pBuff->szFWName, &lpInfo);

if ( iret != CCFRMW_RC_OK ) return iret;

//---------------------------------------------------------------------

// 获得模块相关的对象信息(找到最后的子类,作为服务对象)

//---------------------------------------------------------------------

LPMYMODULEDETAIL lpDetail = NULL;

iret = g_load.GetFirstObjectInfo(lpInfo, &lpDetail);

if ( iret != CCFRMW_RC_OK ) return iret;


引用三: 函数开头的注释模板,考虑到编码的方便性,函数注释可以简单一点

// 获得DD变量值

SHORT CCDataDicFW::GetDataItem( // get the data item - USHORT

CHAR * pszDataItemName, // (in) property name

USHORT & rusVar, // (out) buffer for retrieved data

ULONG ulIndex) // (in) index (used for indexing arrays)

{

SHORT sRc = CCDATADICTFW_OK;

char ownerFW[NODE_VALUE_LEN];

SHORT funcId;

TrcWritef(TRC_ENTRY,"> CCDataDicFW::GetDataItem(%s)",pszDataItemName);

sRc = GetDataInfo(GET,ownerFW,funcId,pszDataItemName);

if(sRc == CCDATADICTFW_OK)

{

sRc = FrmResolve(ownerFW,funcId, pszDataItemName, strlen(pszDataItemName)+1, (void *)&rusVar, sizeof(USHORT), NULL, 0, ulIndex);

}

TrcWritef(TRC_ENTRY,"<>

return sRc;

}

4.9 限制使用全局变量

考虑到代码移植方面和出现信息存贮冲突,在代码中,避免使用全局变量。

可以进行替代:

n 使用类的成员变量

n 使用类的静态变量

n 线程入口函数使用参数传递指针

n 一些不能避免的情况,如:WINPROC 中的变量访问,仍可以使用全局变量。

4.10 数据初始化

与很多语言不同,C/C++一般不提供对结构体变量、字符数组、整形等数据的初始化控制。比如 integer I; 此语句执行后,I 里面的值是一个随机数字。

char buff[100]; 执行后,buff 的每个单元的内容也是一个随机值。

为了避免出现问题,要求变量申明后,一定要初始化,这里还包括了类的成员变量。

比如:

char buff[100];

ZeroMemory(buff, sizeof(buff));

下面针对各类数据类型做数据初始化的规范说明:

n 简单类型数据,需要在变量标识后加“=初始值;”

integer i = 0;

char c = 0;

double d = 12.0;

n 指针变量、类似指针的变量,在申请内存前和使用完成后,都要使值保持NULL

HANDLE p = NULL;

// ......

if (p )

{

CloseHandle(p);

p = NULL;

}

n 字符数组,注意{0} 的妙用

char buff[100];

ZeroMemory(buff, sizeof(buff));

也可以写成一句话:

char buff[100] ={0};

n 结构体变量,注意{0} 的妙用

MYSTRUCTA bb;

ZeroMemory(&bb, sizeof(MYSTRUCTA ));

也可以写成一句话:

MYSTRUCTA bb = {0};

n C++类中,在类的构造子中完成对成员变量数据的初始化

5.调试规范

5.1 Trace跟踪

n 程序中应该包括足够的调试信息。调试信息一律使用Trace跟踪。

n 在函数入口处,推荐使用assert() 对于参数的有效性进行判断。

n 对于入口参数是指针的,必须使用Assert()进行判断。

n assert()的使用方法为:assert(<逻辑表达式>)assert()的具体使用方法请参考MSDN文档,这里不做详细说明。

n 在代码中易出现问题的地方,也应该使用assert()、或者其他的机制进行相应的检测。

5.2调试记录、测试报告

调试记录是软件产品的一个重要组成部分。在软件的调试过程中,应做好调试记录,以便于软件的后续开发和维护工作。

调试的文档包括两个内容:

n 调试记录

即发现软件中的Bug 并进行修改的记录。

在软件编码过程中的修改很频繁,对每个发现的问题都记录下来是不现实的。在实际记录过程中可选择以下信息的部分作为记录内容,调试记录一般需要包括如下内容:

(日期)

[如:2000 6 26 ]

(问题编号)

[如:1]

(问题征兆)

[如:发现缓冲中的报文被无端修改]

(问题根源及解决方法)

[如:由于在多任务环境中对共享资源没有使用互斥机制,对于缓冲增加一个信号量进行保护(修改位于buffer.c 中)]

n 测试报告

在软件项目实现阶段,除了要编写好代码以外,经常需要对实现的子模块进行测试,尤其是对提供给他人使用的模块,必须进行测试。

在测试前,需要做测试计划,测试中做好零散的记录,测试完成后,整理成报告。测试报告是一笔宝贵的财富,当软件做到一定程度的功能,以后对软件的更新,可以参照历史测试报告进行回归测试。减少重新编制测试报告的工作量,同时能保存测试指标,使软件再测试具有延续性。

5.3测试用例、测试代码

为使测试自动化和更具全面性,需花费时间和精力制作测试用例和编写测试代码,比如测试类的功能,就需要编写测试代码。

测试用例需要按照软件工程学的相关规范来制作。最简单的原则是,测试用例要能覆盖问题可能出现的范围。详细测试用例的制作方法参考软件工程学资料。

测试代码需要实现对单个模块的模拟调用,在整个项目相关的软件模块并为一一齐备的情况下,也需要模拟实现未真正实现的功能模块。此外,测试代码还可以用于构建软件模块的压力测试、内存泄漏测试。

5.4调试、测试要求

n 对于测试代码和测试用例,要注意保存,并适当使用文档加以说明。

文档中应该说明测试代码的测试目的。如果测试代码和文档分别存放,还需要在文档中对测试代码的保存位置加以说明。

n 写好测试报告。例如:对某个模块的若干接口加以测试,要记录测试的目的、具体的测试方法和测试的结果(如:通过测试,或测试失败)。

n 出现问题,及时通知相关模块具体负责人。

测试人员在测出问题后,除完成测试报告外,还需要和模块负责人进行必要的沟通。

6.源码管理办法

1. 修改及时提交

源码完成编制、修改后,须提交到配置库。使用指定的版本管理软件实施控制。

当某功能模块完成编程后,使用软件管理工具提交。在进行提交之前,必须对新加入的功能进行一定的测试,在尽量保证正确性后在进行提交。

避免出现长时间修改而不提交的情况,因为可能出现多人修改同一模块的情况。长时间不提交将导致并发的修改方每次修改前不能获得最新的版本。

2. 提交要完整

每次提交,要注意完成性,把本次涉及到的修改都做提交,否则容易出现别处下载更新代码后不能编译、调试的情况。

3. 先更新,再修改

每次进行修改前,从配置库“Check out”最新版本到本地,以免在老版本上做修改。

4. 写修改说明

每次提交,须在提交报告中注明新加入功能的内容、修改的文件范围等内容。

5. 定期更新本地版本

需要定期对本地版本的代码、文档做全面更新,特别是在软件项目庞大或者项目组人员较多的情况下,需要定期更新本地版本,特别是非自身负责的模块,以便能在最新版本的代码上进行联合编译、调试和测试。

6. 共用的.H、.CPP、.LIB、.DLL文件单独存放

类似VC++的安装目录安排,需设置INCLUDE、LIB、SRC等共有目录,将共有代码放置其内,避免出现每个模块将共有代码放在各自的项目内的情况。

单独存放共用代码,利于统一更新。

7. 配置库中除管理源代码外,还管理 BIN 内容

阶段编译结果的EXE、DLL、OCX等内容,也作为配置项,减少不必要的重新编译。

8. 阶段成果分开保存

针对相对稳定的阶段成果,需和主配置分开保存。以避免最新的修改影响阶段成果。

没有评论: