Board logo

标题: 良好的编程习惯. [打印本页]

作者: Webmaster    时间: 2005-7-15 10:13     标题: 良好的编程习惯.

1.禁止使用全局性的变量和函数的(当然CWinApp派生类的那个全局对象除外)
在面向对象的系统中,应该是所有的一切都是基于对象的。你原来习惯的全局函数完全可以放在一个类中,大不了作为一个静态成员函数。

     2.局部变量一定要初始化
如果你声明一个变量,千万不要以为编译器会自动将之赋零值!你随手给它赋个初值并不麻烦,却会使程序更可靠,何乐而不为呢?
int iTemp = 0;
CRect rectTemp = CRect(0, 0, 0, 0);

     3.成员函数的功能一定要单一;实现其功能时不要过分追求技巧,函数体也不能过长
功能单一才有利于公用。计算机是个笨蛋,老老实实的写法可能更适合它,也让别人易懂。你愿意看PageDown半天都找不着结尾的函数吗?把函数体的长度限制在200行之内吧!
交换两个整数i和j的值,方法二是不是比方法一(少用了一个变量)更简单易懂:
方法一:
i = i+j;
j = i-j;
i = i-j;
方法二:
int k = i;
i = j;
j = k;

     4.自己定义的类(和界面有关或存储有关,或较为复杂的)尽可能从MFC的最基本类CObject派生出来,但一些基本的类应尽量简单,例如类似于CRect, CPoint之类的类。
利用MFC的CObject作为基类的好处是可以方便的利用它提供的成员函数,比如判断运行时刻类IsKindOf(...)之类的函数。当然如果你定义的类非常简单,就没有这个必要了。

     5.尽可能的将自己定义的类的声明和定义分别放在同一个模块的头文件和实现文件中,象对话框由这种VC++自己生成的框架更是如此
假定你定义了一个用于寻找匹配文件的类CFindMatchedFile,那么在该类的声明和inline函数的定义放在头文件FindMatchedFile.h中,在FindMatchedFile.CPP中存放CFindMatchedFile的除inline函数之外的成员函数定义。这样可以增加各个类之间的独立性

     6.除了循环变量i,j,k...之外,文件、类、(成员)变量、(成员)函数的命名要有意义,大小写相间,一目了然;宏定义和常量全要用大写
操作系统和编译器既然允许长的名字,我们干嘛还客气?

     7.设计你的类时要尽量考虑到它的通用性
日积月累,你就会拥有自己的类库!

     8.对自己定义的复杂类一定提供构造和析构函数,在构造函数中初始化所有的成员变量,在析构函数中删除可能申请的内存空间
不要让你的构造和析构函数空闲着,也不要让它们再干其余的额外工作

     9.析构函数前一律加上virtual;所有虚函数前一律加上virtual
尽管virtual属性具有继承性,但显式的写上更明了。析构函数为virtual可以防止”memory leak”。下面的CSonClass是从CParentClass派生出来的:
CParentClass* pParentClass = new CSonClass;
...
delete pParentClass;
如果CSonClass的析构函数是virtual的,那么一切正常;否则最后一句话仅仅释放了ParentClass占用的空间,并没有释放CSonClass占用的空间

     10.不需要修改类的成员变量的成员函数,将之声明为const
如此可以保证不出现不必要的修改。比如:
DWORD CBirthDay::GetBirthYear() const
{
return m_dwYear;
}

     11.输入参数声明为const,如果成员函数不会修改该输入参数的内容的话;当需要输入参数是一个类的实例时,请使用引用,或者指针,但严禁直接传送实例,那样不仅效率低,而且易出错,不会带来任何好处
用类的实例作为输入参数将大大降低系统的效率,用引用是最好的方法:
CBirthDay::CBirthDay(cosnt CBirthDay& BirthDay)
{
m_dwYear = BirthDay.m_dwYear;
m_dwMonth = BirthDay.m_dwMonth;
m_dwDay = BirthDay.m_dwDay;
}

     12.自己申请的内存Buffer释放后,将其指针置为NULL
使用指针之前先判断其有效性,用完就释放之:
DWORD* pdwMyBuffer = new DWORD[100];
if (pdwMyBuffer != NULL)
{
// 干你的工作
...
delete []pdwMyBuffer;
pdwMyBuffer = NULL;
}

     13.对浮点数比较大小时不要使用==
本来应该相等的两个浮点数由于计算机内部表示的原因可能略有微小的误差,这时用==就会认为它们不等。应该使用两个浮点数之间的差异的绝对值小于某个可以接受的值来判断判断它们是否相等,比如用
if (fabs(fOldWidth-fNewWidth) < 0.000001)
来代替
if (fOldWidth == fNewWith)

     14.对数组和缓冲区进行检查,防止越界,尤其是变长的情况下
你申请多少空间,就只能用多少。越界使用往往是造成程序无故突然退出的祸首。

     15.可以使用ASSERT来提前发现你程序中潜在的错误
if语句是说可能存在不同的情况;而ASSERT语句是说肯定存在的情况,如果ASSERT失败,就说明在此之前已经发生了错误。ASSERT用在自己负责的模块内部很有用,可以提前发现一些不应发生的错误,提高程序的执行效率。

     16.特别谨慎使用goto语句,最好别用它
尽管goto语句在某些特殊的情况下(比如编译器中)还很管用, 但它破坏了整个程序的结构,尤其使用goto嵌套时,更让人一头雾水(很久以前就有人提出取消它)。所以不到万不得已时刻不要用它,可以用break,continue之类的语句替代之。

     17.所有成员函数尽量是单入口,单出口
也许多出口的程序写起来更简洁,意义也更明了。但出了问题调试时会很难定位,所以宁可多用一些BOOL变量,多加些判断,保证单出口:
BOOL CMyClass::DoSomething(PVOID pvBuffer, ...)
{
if (pvBuffer == NULL)
return FALSE;
...
return TRUE;
}
可以这样写:
BOOL CMyClass::DoSomething(PVOID pvBuffer, ...)
{
BOOL bRet = FALSE;
if (pvBuffer != NULL)
{
...
bRet = TRUE;
}
...
return bRet;
}

     18.调用函数时要严格按照接口规范
严格按照函数的输入要求给它合适的参数
     19.实现(成员)函数时务必要求输入参数是在要求范围之内
尤其你定义的(成员)函数给别人调用时,用if语句要比ASSERT更合适

     20.调用函数后务必要判断其返回值的合法性,如果它的返回值不是void的话
不要假定函数调用永远是成功的,异常情况总会发生的,尽管可能性不大

     21.在你劳神的地方请加上详细的注释说明。除了最简单的存取成员变量的Set_/Get_成员函数之外,其余大部分的函数写上注释是良好的习惯。尽量使你的程序让别人很容易看懂
太多的注释会使程序很难看,但一些复杂的算法和数据结构处还是要加上注释的,这样别人就容易看懂。否则时间长了,你自己都未必看明白了

     22.自己做代码内部(单元)测试时,必须做到语句覆盖,并且特别要注意边界值的覆盖
要让每个语句都被执行过,并且边界值(最大和最小)也被测试过。你在程序中写的各种情况都可能在用户那里出现

     23.代码写完后要尽可能多的做一些静态检查(Debug调试可是很费神费时的)。尤其是对算法和数据管理(比如对文件存取)部分
比如:
BOOL CMyClass::CheckValidParameters();
...
// 下面少加了函数调用的()
CMyClass MyClass;
if (MyClass.CheckValidParameters)
{
...
}

     24.在涉及永久存储和必须确保数据结构的长度不变的情况下,一定要使用固定长度的数据类型,比如(我们自定义的)UINT8、UINT16、UINT32等等;在不关心数据长度的时候,提倡使用int和UINT,这样在操作系统升级的时候,你的程序的速度会随之提高
以后使用int和UINT时你得提醒自己它们的长度是不小于32位的!现在UINT和DWORD一样都是32位,保不定在Win64出来时UINT就成了64位而DWORD不变,这时使用UINT就会提高软件的速度。当然存储时必须使用固定长度的类型。

     25.类型之间的转换一律使用显式转换
如:
WORD wTemp = 0;
LONG lTemp = 0;
...
wTemp = (WORD)lTemp;
...
lTemp = (LONG)wTemp;

     26.类中的成员变量尽可能地不定义为public变量,而通过相应的Get_、Set_函数得到或设置相应变量的值。为将来的派生考虑,也应尽量使用protected而非private成员

     27. BOOL变量一定要严格的赋TRUE或FALSE值;在if语句中,语义应写清楚
TRUE的定义是1,FALSE的定义是0。尽管任何一个非零值判断时被认为是“真”,但不是严格等于“TRUE”!你自己写程序时应该确认一个BOOL变量应该要么是TRUE,要么是FALSE。
int i = 10;

if (i)
{
}
应写为
int i = 10;

if (i != 0)
{
}

BOOL变量表达的是“逻辑值”,所以两个BOOL变量之间不要直接用==比较是否相等:
BOOL bTemp = TRUE;

if (bTemp == TRUE)
{
}
应写为
BOOL bTemp = TRUE;

if (bTemp)
{
}

     28.定义宏时,参数使用扩号,结果也应扩起来
#define SUB(a,b)   ((a)-(b))
以确保如下形式的调用不会出错:
3*SUB(3,4-5)

     29.变量的长度一定要用sizeof来求
例如int类型原来是2字节,而现在是4字节

     30. 同一指针new和delete时用的类型一定要完全一致
char* p = new char[10];
int* p1 = (int*)p;
错误的删除方法:
delete []p1;   // wrong
正确的删除方法:
delete [](char*)p1;   // right
或者:
delete []p;   // right

     31.new一个实例和多个实例时的delete方法不一样
new char[n] 和 delete []对应。
new char和delete对应。

     32.实现(成员)函数时,可能需要几个小功能模块。每个小模块之间最好用{}间隔起来,以保证每个小模块之间的局部变量互不干扰,尽量避免出现各个小模块之间公用的局部变量,因为上个模块可能会在局部变量中留下下个模块不希望的值
void CMyClass::DoSomething(…)
{
     int i = 0, j = 0, k = 0;
     // 小功能模块1使用i, j, k
     
// 小功能模块2使用i, j
     
// 小功能模块3使用k
}
可以改为这样写:
void CMyClass::DoSomething(…)
{
     {
int i = 0, j = 0, k = 0;
     // 小功能模块1使用i, j, k
}

{
int i = 0, j = 0;
     // 小功能模块2使用i, j
}

{
     int k = 0;
     // 小功能模块3使用k
}
}

     33.注意在C++中一个类的创建过程和删除过程的顺序刚好是相反的:
其创建过程是:
                       1.按虚基类的出现顺序创建各个虚基类
                       2.按非虚基类的出现顺序创建各个非虚基类
                       3.按本类中非静态成员变量的出现顺序创建它们
                       4.调用本类的构造函数
其删除顺序刚好相反:
                       1.调用本类的析构函数
                       2.按本类中非静态成员变量的出现逆顺序删除它们
                       3.按非虚基类的出现逆顺序删除各个非虚基类
                       4.按虚基类的出现逆顺序删除各个虚基类
看看下面的例子:
class A
{
public:
inline A();
inline virutal ~A();
inline virtual void DoSomething();
};

inline A::A()
{
}

inline A::~A()
{
     DoSomething();
}

inline A::DoSomething()
{
printf(_T(”A::DoSomething()\n”));
}

class B: public A
{
public:
inline virtual void DoSomething();
};

inline B::DoSomething()
{
printf(_T(”B::DoSomething()\n”));
}
void main()
{
B* pB = new B;
delete pB;
}
这个例子的输出结果是:
A::DoSomething()
而不是
B:: DoSomething()
因为进入A::~A()时,B本身已经“终结”了。故此时调用DoSomething()只能是A:: DoSomething()。得出结论:凡在析构函数中调用的函数只能是(纯)自己的和基类的,别指望调用子类的虚函数。

     34.写程序时要把编译选项的“警告级别(Warning Level)”置为最高级,并且最终编译结果不允许出现任何警告!因为任何警告都是错误可能的隐藏之地!

     35.类的实现文件(.CPP)头上在#include之后要加上下面的语句,可以发现程序中的“Memory Leak”:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif




欢迎光临 工程家园 (http://heubbs.com./) Powered by Discuz! 7.2