联众公司开发部文档《联众公司C++/C代码编写规范》
);
? 函数体不要过长,尽量控制在50行以内(两个屏幕),
? 尽量避免设计带有“记忆”功能的函数,相同的输入应当产生相同的输出。
带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在C/C++语言中,函数的static局部变量是函数的“记忆”存储器。建议尽量少用static局部变量,除非必需。
? 不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,
例如全局变量、文件句柄等。
? 用于出错处理的返回值一定要清楚,让使用者和读者不容易忽视或误解错误情况。 ? 在变量初始声明时要赋初始值
5.2 参数的规则
? ? ? ?
参数的书写要完整,并且参数的名字要有意义,参数名必须写 参数的命名要有意义,顺序要合理
如果是指针或引用并且在函数体内不会修改它,则加const修饰,防止意外的修改 如果输入参数以值传递的方式传递对象,则宜改用“const &”方式传递,可以省去临时对象的构造和析构过程从而提高效率。
? 尽量避免设计太多参数的函数,参数太多难以使用并容易出错,建议控制在5个以内。 ? 尽量不用不定参数的函数,这种函数在编译时没有严格的类型检查,程序死掉后无从查
找。
例如:int printf(const char *format[,argument]...) ;
5.3 返回值规则
? 函数名字与返回值不要在语义上有冲突。
反面的例子:int getchar(void);
? 如果函数的返回值是对象,注意“值传递”和“引用传递”的区别。
引用传递可以提高效率,但是注意不要返回函数体内的局部对象的引用。 ? 不要将错误标志和正常的返回值混在一起返回。
错误标志用return返回,正常值用输出参数获得。
? 不要省略返回值的类型,如果没有返回值则应声明为void。因为C语言缺省的返回类
型是整形。
5.4 函数体编写规则
? 函数入口,对于函数的输入参数必须作合理性检查。用ASSERT来防止此类错误。 ? 函数出口,要对return语句的正确性和效率进行检查。
return语句不可返回指向“栈内存”的“指针”或“引用”; 要清楚返回的究竟是是“值”、“指针”还是“引用”; 如果返回的是对象,要考虑return语句的效率;
— 16 —
联众公司开发部文档《联众公司C++/C代码编写规范》
例如:
return CSting(str1+str2) ;
要优于下面的语句
CString strTemp = str1 + str2; return strTemp;
5.5 使用ASSERT
? 必须从代码上或用注释清楚的现实ASSERT判断的是什么东西,不要ASSERT发现了
错误而又不知道是什么错误。
? 使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者
是必然存在的并且是一定要作出处理的。
? 在函数的入口处,使用断言检查参数的有效性(合法性)。 ? 在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的
假定,就要使用断言对假定进行检查。 ? 一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当
进行防错设计时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。 ? ASSERT包含的语句在release版是不被编译的,如果一定要包含一条语句用VERIFY
5.6 使用const增强程序的健壮性
? 修饰函数的输入参数:当函数的输入参数是指针或引用且在函数内部不会被改变时加
const修饰。 例如:
BOOL Func(const char *pszName1, const CSting &strName2) ? 修饰函数的返回值:如果函数的返回值是指针,并且不希望这个指针被修改,加const
修饰。 例如:
const char *GetString(void); ? 修饰类的成员函数:如果一个成员函数不会修改类里的成员变量应声明为const,这
样如果函数体内试图修改成员变量或调用非const成员函数则编译器会报错。 例如:
int GetCount(void) const;
5.7 禁用goto语句
? 由于goto语句公认的副作用,所以禁用用goto语句,千万不要看到代码里有你也去
用
— 17 —
联众公司开发部文档《联众公司C++/C代码编写规范》
6. 指针的使用
6.1 指针声明后必须先置为NULL
? 类中的指针成员变量要在构在构造函数里置NULL ? 函数中在指针声明时就直接置NULL
例外:在声明后马上赋使用值的情况除外
6.2 类里的成员指针在删除后必须置为NULL
? 类里的成员指针或其他有可能还在别处用到的指针,在删除后必须置为NULL
例外:类里的成员指针在析构函数中可以不置NULL
6.3 指针在使用前判断是否有效
? 指针在使用前应该判断是否为NULL
? 如果是new或malloc得到的指针要判断是否内存分配失败 ? 使用窗口指针前某些时候还应判断 ::IsWindow()
例如:
CWnd *pWnd ;
pWnd = GetDlgItem(ID_***) ; if( pWnd == NULL ) {
...// 错误处理 } if( !::IsWindow( pWnd->m_hWnd) ) {
...// 错误处理 }
? 对在结构体中用类做成员的情况,使用sizeof得到结构长度时要高度警惕,尽量避免这
种情况
typedef struct tagA {
CString str; ... }A, *PA; A a;
a.str = “string”;
ASSERT(sizeof(a) == 4);
— 18 —
联众公司开发部文档《联众公司C++/C代码编写规范》
如果在发送消息的时候用sizeof(a)计算长度就错了。
6.4 给指针分配的内存要释放
? 函数中分配的内存在函数返回前要释放
例外:函数中分配的内存要在函数外使用的情况除外 ? 类的成员变量指针要在析构时释放
6.5 避免指针所指的对象已经不在了还操作指针
? 避免指针所指的对象已经不在了还操作指针,此类情况很容易被忽略。 例如: class A
{
public:
void Func(void){ cout << “Func of class A” << endl; } };
void Test(void)
{
A *p; {
A a;
p = &a; }
// 注意 a 的生命期
p->Func(); }
// p所指向的对象已经不在了
7. 避免数组访问的越界
? 数组越界引起的bug是很难查出来的,也没有什么好的方法防止数组越界,所以使用数
组时要小心了,尤其是用变量做数组的下标时
8. 代码修改规范
这断规范适用于对已经发布的程序或长期稳定运行的程序的修改,新的开发可以不遵守本规范中有关代码修改的部分。
? 第一原则不删除任何已经有的代码,用注释的办法使原有的代码失效
? 注释掉代码时要在注释的上面一行注明注释的人的姓名、日期、注释的原因,注释结束
也要表示出来,例如
// comment by somebody 2002/4/25 原因:下面的代码不在需要
— 19 —
联众公司开发部文档《联众公司C++/C代码编写规范》
// ...被注释的代码 // comment end
或写在注释行的后面,例如
// ... // comment by somebody 2002/4/25
其中: somebode是修改的人的名字
? 修改或添加的代码应紧跟在注释掉的代码的后边,并注明修改的人的姓名、日期,用下
面的格式将代码包围起来:
// add by somebody 2002/4/25 代码的功能或修改的原因 代码段
// add by somebody end ? ? ? ?
如果修改的是公用的文件或基类应该在文件的最顶部添加修改记录,参见3.8 代码的修改、添加和删除必须在日志文件中有记录,日志文件名readme.txt 对原有的不易读懂的没有注释的代码读懂后要顺便加上注释 添加新的函数是要考虑通用性,尽量可以通用
9. 其他
? 用warning level 4编译程序尽可能排除所有的warning,warning leve4能发现一些潜在
的bug隐患
? 服务器代码的编写必须要注意要有高运行的效率和消耗尽量少的资源,资源是指cpu、
memory、handle等,比如说在CGame的派生类中声明一个巨大的数组成员
? 写基类或服务器的程序时,不要checkin中途版本到SourceSafe,checkin的必须是编译
后运行调试联调无误的版本,这样就不会导致他人编译不通过或出错的问题,尤其是服务器要经常重新编译升级。
— 20 —