2007年8月10日星期五

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变量命名

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

变量命名按照匈牙利命令风格:形式为xAbcFgh,x由变量类型确定,Abc、Fgh表示连续意义字符串,如果连续意义字符串仅两个,可都大写。如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. 阶段成果分开保存

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

没有评论: