分享
 
 
 

《软件开发的科学与艺术》节选-写好代码的十个秘诀

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

本文选自电子工业出版社2002年即将出版的由微软公司华人专家编著的《软件开发的科学与艺术》一书。全书透彻解析了微软软件开发的思想与过程。

双手互搏, 无坚不摧

作为一个软件开发人员,必须测试自己的程序,使得代码做得更好,更加稳定。就我个人的经验来说,如果没有测试过代码,程序就不可能正确运行。

另外,在同一组的开发人员之间做得很多的一件事就是:别人来对你的代码进行检查,反过来你对别人的代码进行检查,这个过程不仅是希望检查的人来发现你的代码中的问题,或是你去发现别人代码中的问题,更重要的是在向别人讲解你的代码的过程中,可以发现自己遗漏的地方和问题,理顺自己思路。

下面这段程序是我在开发Exchange Server时写的一段代码,当时写完以后我没有测试它。因为这段代码实在是太简单了,只有几行代码:取文件的长度,如果出错就返回。于是我仅仅是编译通过后就将其提交(checkin)到实际产品中了。

结果第二天早上当我到办公室的时候,发现我的三位上司都已经铁青着脸在那里等我了。原来,整个Exchange Server都运行不起来了!因为我的这段代码被加在了Exchange Server启动代码序列中,当Server启动时,由于我这段代码的错误,一启动就失败,导致了DOA(dead on arrival)。

.

例815

//

// Get file size first

//

DWORD dwFileSize = GetFileSize( hFile, NULL );

if ( dwFileSize = -1 ) {

// what can we do ? keep silent

ErrorTrace(0, "GetFileSize failed with %d", GetLastError());

return;

}

注:GetFileSize调用失败时将返回–1。

这段代码的错误在于:if的判断条件写成了赋值,所以无论怎样都会出错,然后返回。

其实改进的方法很简单:就是将-1移到前面。这样,如果你遗漏了一个“=”,编译时编译器就会发现错误。所以在if语句中,要把常量放在前面。

//

// Get file size first

//

DWORD dwFileSize = GetFileSize( hFile, NULL );

if ( -1 == dwFileSize ) {

// what can we do ? keep silent

ErrorTrace(0, "GetFileSize failed with %d", GetLastError());

return;

}

这件事情给我的教训是很深刻的。

见招拆招, 滴水不漏

错误情况(Error Case)是指那些不易重现的错误。一定要对错误情况进行处理,免得程序崩溃。其中最常出现的情况如下。

× 内存耗尽。不要认为100 B内存很小,不会出错。因为在系统运行时,尽管我们自己的程序申请的内存很少,但是不能保证别人的程序申请的内存也很少。在开发过程中就经常出现这样的情况:别人的程序申请的内存太多,导致我的程序由于内存申请不到而崩溃了。

× 异常。在C++中经常会出现异常,要做好处理它的准备。用过MFC的用户都知道,如果出现异常,就必须处理它,否则程序随时会崩溃。

× 网络中断。不要以为发送一个socket都会成功。尤其是用异步socket做一些网络软件,客户端将数据包发送到服务器端时很容易出错。由于是异步,所以尽管发送方是正确的,但过一段时间接收方就可能出错,而且这种异步错误是很难检测的。有些错误可以在调试过程中借助一些手段产生出来,有些错误则是不能重现的。在错误不能重现的情况下,我们就要做一些工具,迫使错误情况出现。

另外,在编程的过程中还应该注意:

× 在C++的对象的构造函数中不要做一些可能会失败的操作,如内存的操作,因为在构造函数中出错后是没有方法知道的。

× 处理错误情况时,要释放分配到的资源;接口中应该清楚地定义程序的行为,如返回状态,异常的处理等,要让调用者清楚地知道接口的定义。

× 千万不要忽略错误,造成程序崩溃或退出。

例8.10

CWInfFile::CWInfFile() {

m_plLines = new TPtrList(); // ...

m_plSections = new TPtrList(); // ...

m_ReadContext.posLine = m_plLines->end();

. . .

}

这段程序中最大的问题是MFC的new操作,如果失败了会产生异常。如果前一个new操作分配正常,而后面的new分配出错,则前一个变量的分配会导致内存的泄漏。解决的办法是:用一个try…catch语句来处理,如果出现异常,则删除变量,释放内存。

CWInfFile::CWInfFile() {

try {

m_plLines = new TPtrList(); // ...

m_plSections = new TPtrList(); // ...

m_ReadContext.posLine = m_plLines->end();

. . .

} catch ( . . . ) {

if (m_plLines) delete m_plLines;

if (m_plSections) delete m_plSections;

}

}

但是,这里面还有一个问题:由于构造函数是最先执行的,如果new分配出错,就会执行delete语句,而此时变量m_plLines和m_plSections还没有初始化,若对它们进行delete操作就会出错。所以我们应该在构造函数中将其初始化。

CWInfFile::CWInfFile() : m_plLines(NULL), m_plSections(NULL) {

try {

m_plLines = new TPtrList(); // ...

m_plSections = new TPtrList(); // ...

m_ReadContext.posLine = m_plLines->end();

. . .

} catch ( . . . ) {

if (m_plLines) delete m_plLines;

if (m_plSections) delete m_plSections;

}

}

请注意,这里如果用的不是MFC,则new操作出错时就会返回NULL,而不会抛出异常,那么第三个变量的初始化就会出错。

CWInfFile::CWInfFile() : m_plLines(NULL), m_plSections(NULL) {

try {

m_plLines = new TPtrList(); // ...

m_plSections = new TPtrList(); // ...

m_ReadContext.posLine = m_plLines->end();

. . .

} catch ( . . . ) {

if (m_plLines) delete m_plLines;

if (m_plSections) delete m_plSections;

}

}

下面讲的是在构造函数中不要使用一些会失败的操作。

例8.11

Class foo {

private:

CHAR* m_pszName;

DWORD m_cbName;

public:

foo(CHAR* pszName);

CHAR* GetName()

{return m_pszName;}

};

foo::foo(CHAR* pszName)

{

m_pszName = (BYTE*) malloc(NAME_LEN);

if (m_pszName == NULL) {

return;

}

strcpy(m_pszName, pszName);

m_cbName = strlen(pszName);

}

……

foo* pfoo = new foo(“MyName”);

if (pfoo) {

CHAR c = *(pfoo->GetName());

}

由于在构造函数中无法返回错误码,当我们使用new操作符创建一个foo对象时,会调用其构造函数,如果malloc函数出错,构造函数就直接返回,则pfoo就没有分配值,这样在后面的程序中对pfoo的访问就会出错。因此,在构造函数中不要用一些会失败的操作。

在下面这个改动后的版本中,构造函数中不再包含失败的成分,而是增加了一个init函数来完成内存的分配操作。在使用中先调用new函数再调用init函数,检验返回值,判断是否执行正确,有错就将其返回。

Class foo {

private:

CHAR* m_pszName;

DWORD m_cbName;

public:

foo();

HRESULT

Init(CHAR* pszName);

};

foo::foo()

{

m_cbName = 0;

m_pszName = NULL;

}

HRESULT foo::Init(CHAR* pszName)

{

HRESULT hr = S_OK;

if (pszName) {

m_cbName = lstrlen(pszName);

m_pszName = (CHAR*)malloc(m_cbName+1);

if (m_pszName == NULL) {

hr = E_OUTOFMEMORY;

return hr;

}

strcpy(m_pszName, pszName);

} else {

hr = E_INVALIDARG;

}

return hr;

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有