1、第 1 页 (共 14 页)VC+ 编程规范1. 编写编程规范的目的 安全:增加代码的正确性和稳定性,使它稳定可遵守编程规范可以大幅度减少各种易犯的错误和隐患。 易读:保证代码的易读性和可维护性 美观: 尽量统一项目组内人员的编程风格 便于代码调试和错误定位在编码时就注意留下跟踪的线索会使错误定位和修改更加容易。2. 编程规范的作用范围只要能写出高质量的程序,并不强求所有程序都必须完全遵循本编码规范。原则上: 外来的代码,只要测试正确,代码复查没有问题,程序文档齐全,可以有自己的规范和风格 过去的程序,原则上不为了适合本规范而专门改写程序。但是如果方便,也可以顺手做一些修改,如加一些注释。 这
2、个规范主要针对本开发组新开发的,用 VC+编写的界面或业务逻辑模块的编写。某些模块如果有自己的编程风格,请制订相应的文档加以说明。3. 命名规则包括如下 C+ 要素的命名:类、结构、函数、变量、常量(包括资源、自定义消息) 、宏、自定义类型等命名的主要原则是: 使用有意义的单词或多个词组合,一目了然。不要使用人名、项目组名 命名时请考虑名字的唯一性,不要使用容易引起混淆的名称。 长短要合适。多个词组合较长时, 可以使用单词的缩写 主要采用 Windows 的命名风格1. 对于类、函数和变量,每个单词的首字符大写, 其余小写2. 常量和宏定义全部大写3. 自己定义的类型(使用了typedef)全
3、部大写,指针使用LP大写前缀不要用 MyFile.cpp,test1.h,bitmap001.bmp, nByte, CClass 等等没有意义的名字。 临时使用的文件的名字要用函数生成,避免使用“ 硬名字”,如 temp.123。不能使用程序 员的姓名或缩写来命名,如 yls.cpp。第 2 页 (共 14 页)3.1. 类的命名 以大写的 C 开头,采用如下格式:CXXXYYYYY其中 XXX 指的是模块名称的缩写,参见附录 2。 YYYYY 是类本身的名称例如: CAdmDlgSearchID3.2. 结构/自定义类型 结构/联合命名使用 typedef 后必须全部大写,单词中间可以使用
4、下划线连接。 如果不使用 typedef,则在全部大写的名字前加 st。 结构变量的命名应尽量使用该结构的缩写前缀。前缀使用两个以上的小写字母。 自己定义的类型(使用了 typedef)全部大写,指针使用 LP 大写前缀3.3. 函数 使用动宾词组表达函数实际所作的事 同名的函数(重载函数)在功能上应该完全相同, 在参数上的差别也应一目了然 不得出现名字非常相近但功能不同的函数. 如 CreatePage1(), CreatePage2()等3.4. 变量变量命名原则上使用匈牙利命名法,一般由“前缀类型修饰主体”等部分组成,如: g_strRegHome 前缀(以下划线分割)g_ :表示全局变
5、量(包含全局静态变量) 如: g_lpEnvt_ :表示线程的全局变量: 如: int _declspec(thread) t_nCountm_ :表示类成员变量(包含类静态变量) 如: m_dwFlagsg_str :表示全局的字符串常量 如: const TCHAR g_strRegHome = _T(“SoftwareFounderFounder Author Tool4.0”); 类型修饰(小写字母),参见附录 1。 主体使用一个或多个单词表示变量代表的确切含义, 参见下面的大小写规则窗口菜单句柄等命名: hWndxxxx, hMenuxxxx3.5. 常量(包括资源、自定义消息,枚举
6、,宏等) 宏定义和常量全要用大写 自定义的消息 WM_A_ 资源 ID 的命名:参见附录 2:资源 ID 的命名。 枚举类型:命名方式类似结构,相应的前缀为 enum。枚举值的命名一律大写(等同于宏) 。枚举变量的命名按整数命名。 内部的限制量,如列表的最大长度,文件名的最大长度等,统一使用MAX_xxxx_xxxx_LEN 来命名,并集中放置在一个头文件中。数据库相关的限制值放在另一个头文件中定义。第 3 页 (共 14 页)4. 编程规则4.1. 类 对自己定义的复杂类一定提供构造和析构函数, 析构函数前一律加virtual,防止因继承而产生的memory leak。(有两种特殊情况:1.
7、部支持位拷贝。2.某些特殊情况析构函数必须不是虚函数?) 在构造函数中初始化所有的成员变量,在析构函数中删除可能申请的内存空间。 推荐采用公开的Get/Set函数对成员变量进行存储,而把成员变量设成protected或者private,并加上注释。 声明成员函数和变量时要按功能分组,给外部用的要排在前面 类的继承深度不要超过4层 类的功能要单一,公共函数个数一般要小于20个(专门用于保存数据的类除外)内部protected函数以及private函数个数一般应小于20个4.2. 函数 长度一般禁止超过200行 必须检查输入值是否合法 实现(成员)函数功能之前必须使用ASSERT()对输入参数的合
8、法性进行检查,尤其你定义的(成员)函数给别人调用时,要判断其合法性。 函数返回值。函数有返回值的时候,一般需要对返回值进行判断,除非有充分的理由确认一定能执行成功。根据返回值的结果决定后续的操作。特别指出的是:new/CoCreateInstance 一个对象之后,一定要判断对象是否创建成功。 如果不成功,如何处理,如果成功如何处理。 调用函数时要严格按照接口规范调用,调用后要判断执行情况,并做适当的错误处理(稍后会给出错误和异常处理规范) 。 函数出口。为了程序跟踪调试方便,所有函数请尽量保持最少的出口,如果可能,请保持一个出口。4.3. 变量 初始化。当声明一个变量时,编译器是不会自动将之
9、清零的,所以务必要自己初始化一下变量。 布尔变量统一使用 BOOL(TRUE, FALSE) ,不要使用 bool(true, false)在 VC4.2 及之前版本, sizeof(bool) = 4.在 VC5 及更高的版本, sizeof(bool) = 1,而在 VC 中, BOOL 定义为 int 型 一律用显式类型转换 float 和 BOOL 禁止用“=“ 判断。BOOL 应该用逻辑运算关系符,而 float 应该用差值区间来判断“相等” 。 在涉及永久存储和必须确保数据结构的长度不变的情况下,一定要使用固定长度的数据类型,比如_int8, _int16, _int32, _in
10、t64 类型的长度一律用 sizeof()获得第 4 页 (共 14 页)比如 int 的长度原来是 2 bytes, 现在是 4 bytes, 等 IA-64 出来后就会变成 8 bytes.4.4. 常量 禁止直接引用常量,而应该用通过#define 和 enum 以及资源来引用。在程序中不要直接使用常量(如 0.00001、 “Error!”等) ,而要使用相应的枚举类型、常量定义和资源。具体来说,对于只有相对意义的常量,请使用枚举类型;对于有绝对数值意义的常量请使用常量定义(如:#define CP_EPSILON 0.00001) ;对于程序中要显示给最终用户看到的字符串常量,请使用
11、字符串资源(如:AfxMessageBox(“Error!”),请改为 AfxMessageBox (IDS_CP_ERROR_MSG )) 。枚举和常量定义一般直接放在头文件中。对于字符串常量,可以放在头文件中,也可以放到资源文件中。前者的好处是便于移植(因为它跟 Windows 的资源没有关系) ,后者的好处是便于本地化(因为可以直接修改可执行文件的资源) ,请根据实际情况处理。在编写简易测试程序或者记录内部调试信息时,可以直接使用字符串常量,而不必去引用资源。4.5. 指针( 包括数组) 指针变量声明的书写风格:类型后面紧跟*号.例如:DWORD* m_pdw. / 建议采用DWORD
12、*m_pdw. / 不推荐DWORD * m_pdw. / 不推荐 指针变量必须初始化。对于类的成员变量,可以在构造函数里初始化。对于函数内部局部变量,声明时就请初始化。如 DWORD* pdwFrame = NULL; 删除指针前请判定指针是否为 NULL。删除后把指针置为 NULL(即恢复初始状态) 严格防止越界对数组和缓冲区进行检查,防止越界,尤其是变长的情况下。你申请多少空间,就只能用多少。越界使用往往是造成程序无故突然退出的祸首,也是各种安全漏洞的根源。禁止使用 strcpy, sprintf 等函数,而要用 strncpy, snprintf 等函数替代。 同一指针 new 和 d
13、elete 时用的类型一定要完全一致如: char* p = new char10;int* p1 = (int*)p; 错误的删除方法:delete p1; / wrong 正确的删除方法:delete (char*)p1; / right 或者:delete p; / right删数组用 delete new 一个实例和多个实例时的 delete 方法不一样:第 5 页 (共 14 页) new charn 和 delete 对应。 new char 和 delete 对应。例如: char* p = new char10;delete p; / right4.6. 流程控制 除非特别必要
14、,一般不用goto4.7. 宏和表达式 定义宏时,参数使用扩号,结果也应扩起来,如: #define SUB(a,b) (a)-(b)这可确保如下形式的调用不会出错:3*SUB(3,4-5) 如果有 Get 函数应该用 const 关键字修饰。 输入参数要有适当的用 const 修饰当不需要修改输入参数时,应该将输入参数声明为 const;一种比较重要的情况是,需要输入的参数是一个类的实例时,请使用引用,或者指针。严禁直接传送实例,那样不仅效率低,而且易出错,不会带来任何好处。第 6 页 (共 14 页)5. 程序版式5.1. 注释 在你劳神的地方请加上详细的注释说明。除了最简单的存取成员变量
15、的 Set_/Get_成员函数之外,其余大部分的函数写上注释是良好的习惯。尽量使你的程序让别人很容易看懂 太多的注释会使程序很难看,但一些复杂的算法和数据结构处还是要加上注释的,这样别人就容易看懂。否则时间长了,你自己都未必看明白了 推荐使用/做注释,每行不要超过 80 字符 程序头要有说明(说明函数功能、参数定义、返回值和处理流程) ,鼓励尽量多的注释。 对于变量声明除了循环变量外,一般都在行尾加注释,重要变量要加详细注释。 头文件和 cpp 头部应有注释,简单描述文件的内容 对于程序中的比较关键的算法和函数,必须加注释5.2. 的使用 放到行首,需要匹配的上下对齐推荐使用这种风格if (.
16、).else if ( .).不推荐这种风格:if (.) else if ( .) 即使只有一句,也请使用括号。以便于将来扩充。 对于匹配的,如果中间的代码行数较多,或者嵌套层数较多,请在 后面加上注释,以便标识与哪个 匹配。5.3. 空格、空行的使用 关键字后面一般要留有空格,以便于阅读。 运算符前后一般留有空格,以便于阅读。第 7 页 (共 14 页) 相对独立的代码前后一般留有空行,以便于阅读。 但空行、空格不能滥用。如连续多个空格、连续多个空行。 代码的长度每行不应超过 80 字,否则不利于阅读6. VC+特有的问题6.1. Debug vs. ReleaseVC里最独有的问题就是D
17、ebug版本一切正常,Release版本却运行却不正常,甚至死机。除了VC的help里说的Common Problems Switching from Debug to Release Build外,其实最常见的病因如下:规范自定义消息响应函数的原型VC帮助中指出,几种自定义消息响应函数的原型如下:ON_MESSAGE( , )afx_msg LONG memberFxn( UINT, LONG );ON_REGISTERED_MESSAGE( , )afx_msg LONG memberFxn( UINT, LONG );ON_THREAD_MESSAGE( , )afx_msg void
18、memberFxn( UINT, LONG );ON_REGISTERED_THREAD_MESSAGE( , )afx_msg void memberFxn( UINT, LONG );程序中自定义消息的响应函数务必遵循上述规范,否则将为害Release版本,后果是严重的,道路是曲折的!6.2. ASSERT 当有多个退出时,则在每个退出口前加一句NFC_TRACE();第 8 页 (共 14 页)6.3. Warning Level写程序时要把编译选项的“警告级别(Warning Level)”置为最高级,4级最终编译结果不允许出现任何警告!任何警告都是错误可能的隐藏之地!6.4. 用 V
19、C 的 DEBUG_NEW 防止 memory leakVC提供自己的检测memory leak的方法,只要你把这段代码加到.cpp头部即可:#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE = _FILE_;#endif6.5. 关于字符串、数字的重点标识为了便于查找、发现、并尽量避免直接使用字符串常量、数字常量,请在各自的VC开发环境里,对字符串、数字进行重点标识。方法如下:选择ToolsOptionFormat, 在Colors况中选择 String、Number, 分别设定前景、背景色。建议:
20、由于数字常量一般较短,不必设置背景色。而对于字符串,一般较长,请设置背景色。6.6. 工程文件工程文件必需完备、紧致,不要在工程中包含没用的废弃文件,也不能有空缺。7. 调试和测试7.1. 关键算法处一定要仔细进行静态检查,并配上详细注释动态调试由于数据用例所限,并不能解决所有潜在问题;关键算法处必须进行仔细的静态代码检查,务必使代码实现忠实于设计。7.2. 单元测试的语句覆盖除去 catch 里的异常处理代码外要做到 100%的语句覆盖,测试时用 VC 带的 profile 来做语句覆盖检查。对于某些关键异常的处理要找特殊用例进行实际测试;对于普通的异常处理稍后会给出的专门的异常处理编码规范
21、。单元测试结束时必须提交相应的 profile第 9 页 (共 14 页)7.3. 利用 TRACE 发现和调试错误 随时注意VC给出的TRACE信息 每次从DEBUG状态结束运行时,查看TRACE信息 使用DEBUG_NEW帮助查找memory Leak TRACE各种错误信息。一般的调试信息在放入Sourcesafe前请去掉,以免影响别人。避免正常运行时的大量无意义的TRACE信息。7.4. 使用断言(ASSERT,VERIFY) 注意ASSERT与VERIFY的差别 区分不应该发生的情形(非法情形)与可能发生的情形(运行错误情形) 对意义不明确的断言加上注释 使用断言对函数参数进行确认,
22、查出对函数的非法调用 使用断言对函数的假定环境进行确认 使用断言检查不应该发生的情形 使用断言检查验证函数的正确性调试代码不要产生副作用第 10 页 (共 14 页)附录 1 数据类型和一些 MFC 对象的缩写Char (null term) szTCHAR sz,TCHAR* psz,BSTR bstrBSTR* pbstrCString class. strInteger nBoolean bByte bylong lShort sColorRef clrUnsigned integer/ UINT uHandle hWord wDWORD dwfloat fDouble dPointer
23、 pFar Pointer pf, lpCharacter cVoid vComboBox cmbListBox lstButton btnStatic Text txtGroup Box grpCheckBox chkRadio Button btnCPoint, Point ptRectangle RECTCRect or RECT rect(or x and y coordinate types),rcPosition Type posWindow Handle hWnd附录 2:程序模块的划分和缩写各模块缩写:Broadcast: Bro第 11 页 (共 14 页)Admin: Ad
24、m附录 3:资源 ID 的命名 一律用大写命名 资源对应文件的命名建议资源ID对应文件的命名 采用 和资源 ID 接近的命名方式。以便查找。如资源ID为: IDB_BMP_INFINITY, 对应的资源文件为:bmp_Founder_Infinity.bmp。前缀 资源类型 例-IDR_XX_ Accelerator or menu IDR_SYS_MAINFRAMEIDD_XX_ Dialog box IDD_SYS_ABOUTBOXIDC_XX_ Cursor IDC_SYS_BLUE_CURIDI_XX_ Icon IDI_SYS_MAINICONIDB_XX_ Bitmap IDB_S
25、YS_BEGINIDS_XX_ String IDS_TT_TITLEIDS_ERR_xxxx 错误信息字符串 IDS_ERR_OPENFAILEDIDS_MSG_xxxx 提示信息字符串 IDS_MSG_PLAYLISTLOADIDP_XX_ String used in message box IDP_TT_ERRORMSG1=“xxxx%1“IDOK, IDCANCEL Standard push buttonIDC_aaaa_bbbb Other dialog controls IDC_PRC_BUTTON_DELIDM_XX_ Menu item IDM_MM_NEWID_XX_ C
26、ommand ID_MM_NEW_PROJECTIDW_XX_ View or child windows IDW_IO_INPUT_VIEW其中:1. XX标识某个模块的名字。2. IDC_aaa_bbb_: 对话框中的Control, 其中aaa表示对话框的缩写, bbb表示资源类型参见附录1的缩写BTN: 按钮,CHK: checkBox,RAD: radioBox,EDIT: edit,CMB: Combo box,LIST: Listbox,STA: static(在需要时才命名, 否则应定义为IDC_STATIC)SCR: Scrollbar,SPIN: spin,SLID: sl
27、ide,PROG: progress,TREE: treeTAB: tab,REDIT: RichEdit,ANI: Animation,第 12 页 (共 14 页)附录 4:文件头和函数头的注释版式文件头/ Copyright (c)2002,北京北大方正电子有限公司/ All rights reserved./ 文件名称: filename.h/ 文件标识:?/ 摘 要: 简要描述本文件的内容/ 当前版本: 1.1/ 作 者: 输入作者(或修改者)名字/ 完成日期: 2001 年 7 月 20 日/ 取代版本:1.0/ 原作者 : 输入原作者(或修改者)名字/ 完成日期: 2001 年
28、5 月 10 日/函数头/Function: / 系统初始化时调用该函数,对内容模块进行初始化,在 CMainFrame 创建后/ 由 CBroadcastApp:InitInstance()调用;/Parameters: void/Return: / TRUE- 模块初始化成功;/ FALSE - 模块初始化失败;/附录 5:建议1 源文件应尽量使用8.3格式。这一点应该视情况而定文件名只能包含字母、数字和下划线,不得使用其他的字母。超长的文件名应使用缩写方式减少文件名的长度。建议使用如下的缩写的规则(部分情况可以有例外,视具体情况而定): 缩写一般可以去掉元音字母以及不发音字母第 13 页
29、 (共 14 页) 单词的首字母一般应该保留。 当一个单词必须缩成一个字母时,应该选用最有代表性的字母,或首字母。 多音节的单词可以去掉后面的音节而只保留前面的第一、二音节。较短的二音节单词一般不缩写,如果必须缩写,可以只保留一个字母 多个单词组成文件名时,应该保留较重要的有意义的单词(或多留几个字母) ,其他单词可以使用简写或只用首字母,去掉不必要的无意义的单词 可以使用一些谐音表示一个单词,如 2 表示to,4表示for等等,当单词数量少,字母少的情况下,不需要缩写 较短的单词一般不缩写,或缩写为一到二个字母2. 避免整块拷贝代码段尽量避免整块复制代码段,如果出现这样的情况要分析原因,如果
30、这段代码完成独立的功能,应考虑使用函数,否则,应考虑使用宏定义。否则因为修改引起的不一致往往是错误的根源。3 尽量不要使用动态数组。可以使用CObArray,但不要使用其动态增减特性。4文件命名4.1. cpp与h一一对应4.2. 全局的宏如果不限在一个cpp中使用, 可以单独定义头文件4.3. 源文件命名应包含“前缀+命名“,下面列出前缀的含义 d:对话框以及派生类 b:基本的函数库或类库 i:Interface定义 m:媒体管理部分程序 (除 d, i以外) 其他文件一律以字母a开头(以便以后扩充)4.4. 临时文件名必须调用系统函数生成, 禁止使用固定文件名。一般情况下,临时文件应在系统
31、临时目录下(通过系统函数取到临时目录名)生成。12.4 关于包含文件:1. 一般来说,在*.h文件里尽量不要 #include “*.h“。需要的话,在*.cpp文件里包含。可以通过声明类型(如:interface INle*;class CNLe*等)等方式尽量避免。这样尽量降低文件间的依赖性!2. 包含“*.h“,如果能明确路径的,请务必使用完整路径!这样便于在其他机器上顺利编译运行。如:FdmNle工程下面,FdmNle.cpp里,#include “AdminAdmin.h“#include “DBBrokerDBBroker.h“#include “DBBrokerDBBroker_
32、i.c“5. 头文件的书写规范5.1. 头文件中尽量不要包含别的头文件5.2. 头文件中应使用条件编译来保证该文件不被重复include 类尽量从CObject继承,主要好处是以便使用CObList等collection类可以方便的serialize,其次是可以使用RuntimeClass等高级特性。第 14 页 (共 14 页) 禁止使用公有的成员变量 局部变量声明要求相应集中,不要在函数内部随处声明变量。 功能要单一,函数复杂度应小于10。经验证明:当函数复杂度大于10时其可能导致的BUG数也急剧增加。11.2 TAB键TAB键统一定义为四个空格符,源文件中不允许保留TAB键,否则由于不同的开发环境以及编辑器在处理TAB键时不一致,会导致代码显示混乱。11.5 条件判断表达式的书写1. =, != . 如果表达式有一边为常量,如 NULL ,请把常量放到前面,以避免把“=“写成 “。