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

本文选自电子工业出版社2002年即将出版的由微软公司华人专家编著的《软件开发的科学与艺术》一书。全书透彻解析了微软软件开发的思想与过程。 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

双手互搏, 无坚不摧

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

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

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

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

.<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><group id="_x0000_s1026" style="MARGIN-TOP: 0.4pt; Z-INDEX: 1; MARGIN-LEFT: 54.75pt; WIDTH: 60.05pt; POSITION: absolute; HEIGHT: 24.45pt; mso-position-horizontal-relative: text; mso-position-vertical-relative: text" o:allowincell="f" coordsize="1201,489" coordorigin="2116,2999"><font size="2"><font face="Times New Roman"> <shapetype id="_x0000_t75" coordsize="21600,21600" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype></font></font><shape id="_x0000_s1027" style="LEFT: 2116px; WIDTH: 1201px; POSITION: absolute; TOP: 2999px; HEIGHT: 489px" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/jiangtao/LOCALS~1/Temp/msoclip1/01/clip_image001.wmz"></imagedata><textbox style="mso-next-textbox: #_x0000_s1027"></textbox></shape><shapetype id="_x0000_t202" coordsize="21600,21600" path="m0,0l0,21600,21600,21600,21600,0xe" o:spt="202"><stroke joinstyle="miter"></stroke><path o:connecttype="rect" gradientshapeok="t"></path></shapetype><shape id="_x0000_s1028" style="LEFT: 2428px; WIDTH: 635px; POSITION: absolute; TOP: 3127px; HEIGHT: 275px" stroked="f" filled="f" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1028" inset="0,0,0,0"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr><td style="BORDER-RIGHT: #d4d0c8; BORDER-TOP: #d4d0c8; BORDER-LEFT: #d4d0c8; BORDER-BOTTOM: #d4d0c8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; mso-line-height-alt: 0pt"><span style="FONT-SIZE: 9pt; FONT-FAMILY: 幼圆; mso-bidi-font-size: 10.0pt; mso-hansi-font-family: Arial; mso-ascii-font-family: Arial"><font face="Times New Roman">例</font></span><span lang="EN-US" style="FONT-SIZE: 9pt; FONT-FAMILY: Arial; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'; mso-fareast-font-family: 幼圆">815<p></p></span></p> </div> </td></tr></tbody></table></textbox></shape></group>

//

// Get file size first

//

DWORD dwFileSize = GetFileSize hFile, NULL ;

if dwFileSize = -1 {

// what can we do ? keep silent

ErrorTrace0, "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

ErrorTrace0, "GetFileSize failed with %d", GetLastError());

return;

}

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

见招拆招, 滴水不漏

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

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

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

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

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

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

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

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

<group id="_x0000_s1026" style="MARGIN-TOP: -0.55pt; Z-INDEX: 1; MARGIN-LEFT: 56.3pt; WIDTH: 60.05pt; POSITION: absolute; HEIGHT: 24.45pt" o:allowincell="f" coordsize="1201,489" coordorigin="2147,11130"><shapetype id="_x0000_t75" coordsize="21600,21600" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_s1027" style="LEFT: 2147px; WIDTH: 1201px; POSITION: absolute; TOP: 11130px; HEIGHT: 489px" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/jiangtao/LOCALS~1/Temp/msoclip1/01/clip_image001.wmz"></imagedata><textbox style="mso-next-textbox: #_x0000_s1027"></textbox></shape><shapetype id="_x0000_t202" coordsize="21600,21600" path="m0,0l0,21600,21600,21600,21600,0xe" o:spt="202"><stroke joinstyle="miter"></stroke><path o:connecttype="rect" gradientshapeok="t"></path></shapetype><shape id="_x0000_s1028" style="LEFT: 2450px; WIDTH: 711px; POSITION: absolute; TOP: 11261px; HEIGHT: 275px" stroked="f" filled="f" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1028" inset="0,0,0,0"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr><td style="BORDER-RIGHT: #d4d0c8; BORDER-TOP: #d4d0c8; BORDER-LEFT: #d4d0c8; BORDER-BOTTOM: #d4d0c8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 12pt"><span style="FONT-SIZE: 9pt; FONT-FAMILY: 幼圆; mso-bidi-font-size: 10.0pt; mso-hansi-font-family: Arial; mso-ascii-font-family: Arial"><font face="Times New Roman">例</font></span><span lang="EN-US" style="FONT-SIZE: 9pt; FONT-FAMILY: Arial; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'; mso-fareast-font-family: 幼圆">8.10<p></p></span></p> </div> </td></tr></tbody></table></textbox></shape></group>


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;

}

}

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


<group id="_x0000_s1029" style="MARGIN-TOP: 1.7pt; Z-INDEX: 2; MARGIN-LEFT: 57.65pt; WIDTH: 59.1pt; POSITION: absolute; HEIGHT: 24.2pt" o:allowincell="f" coordsize="1182,484" coordorigin="2174,9505"><group id="_x0000_s1030" style="LEFT: 2174px; WIDTH: 1182px; POSITION: absolute; TOP: 9505px; HEIGHT: 450px" coordsize="1182,450" coordorigin="2174,6217"><shape id="_x0000_s1031" style="LEFT: 2266px; WIDTH: 211px; POSITION: absolute; TOP: 6623px; HEIGHT: 44px" coordsize="421,219" path="m270,0l55,18,33,33,20,49,8,68,,99,1,133,8,152,20,172,43,190,69,204,96,213,119,217,150,219,148,217,315,202,421,,270,0xe" strokeweight=".5pt" fillcolor="gray"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape><shape id="_x0000_s1032" style="LEFT: 2174px; WIDTH: 1182px; POSITION: absolute; TOP: 6217px; HEIGHT: 449px" coordsize="2363,2245" path="m149,4l132,8,111,17,84,38,50,68,25,100,10,134,4,164,,204,,242,5,274,10,307,17,336,41,413,75,498,125,591,175,697,225,791,268,884,318,996,361,1138,390,1263,419,1418,433,1586,455,1735,455,1816,455,1922,455,1990,449,2054,426,2121,410,2160,390,2195,370,2218,355,2231,340,2245,550,2225,957,2177,1294,2139,1681,2102,1969,2090,2191,2096,2249,2096,2284,2090,2306,2059,2327,2024,2345,1974,2356,1913,2363,1854,2363,1768,2360,1700,2356,1592,2335,1474,2299,1322,2256,1182,2220,1058,2163,921,2105,797,2055,679,1976,510,1933,411,1907,344,1893,293,1887,244,1887,202,1919,50,1933,19,172,,149,4xe" strokeweight=".5pt"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape><shape id="_x0000_s1033" style="LEFT: 2236px; WIDTH: 97px; POSITION: absolute; TOP: 6238px; HEIGHT: 29px" coordsize="194,147" path="m194,11l144,135,94,147,69,145,44,137,25,122,12,108,3,89,,73,1,51,8,35,22,23,40,13,62,7,80,4,100,1,116,1,136,,194,11xe" strokeweight=".5pt" fillcolor="gray"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape><shape id="_x0000_s1034" style="LEFT: 2273px; WIDTH: 69px; POSITION: absolute; TOP: 6236px; HEIGHT: 24px" coordsize="136,120" path="m0,14l25,30,37,45,43,61,44,84,39,102,25,120,136,99,127,,,14xe" strokeweight=".5pt" fillcolor="black"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape><shape id="_x0000_s1035" style="LEFT: 2254px; WIDTH: 990px; POSITION: absolute; TOP: 6217px; HEIGHT: 50px" coordsize="1979,248" path="m1873,18l0,,52,7,71,12,92,19,105,31,120,47,128,67,132,88,135,108,136,126,132,152,128,177,114,198,94,220,69,235,49,248,172,236,308,217,524,205,703,192,918,192,1155,198,1449,205,1729,223,1844,242,1876,246,1912,247,1937,242,1959,223,1971,201,1977,181,1979,159,1974,120,1965,95,1950,69,1937,53,1922,39,1899,26,1873,18xe" strokeweight=".5pt"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape></group><group id="_x0000_s1036" style="LEFT: 3006px; WIDTH: 317px; POSITION: absolute; TOP: 9825px; HEIGHT: 164px" coordsize="317,164" coordorigin="3006,6537"><shape id="_x0000_s1037" style="LEFT: 3006px; WIDTH: 317px; POSITION: absolute; TOP: 6579px; HEIGHT: 122px" coordsize="635,608" path="m219,31l157,235,84,409,,608,71,568,152,524,197,503,233,490,269,483,303,479,349,471,320,405,356,401,399,409,442,428,476,445,504,466,534,487,581,524,635,560,623,431,594,327,564,207,539,119,522,50,515,,219,31xe" strokeweight=".25pt" fillcolor="red"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape><oval id="_x0000_s1038" style="LEFT: 3090px; WIDTH: 193px; POSITION: absolute; TOP: 6537px; HEIGHT: 61px" strokeweight=".25pt" fillcolor="yellow"><stroke color2="white"><font face="Times New Roman" size="2"></font></stroke></oval><shape id="_x0000_s1039" style="LEFT: 3093px; WIDTH: 184px; POSITION: absolute; TOP: 6537px; HEIGHT: 62px" coordsize="369,310" path="m181,0l168,57,130,7,138,62,92,21,110,76,59,43,88,94,26,75,70,117,6,111,67,139,,153,67,167,6,192,71,193,24,232,87,215,52,263,110,235,92,288,132,250,131,304,161,254,181,310,197,256,217,306,229,251,263,294,254,238,301,270,278,218,338,237,297,197,360,196,304,173,369,153,302,140,364,118,295,115,350,84,284,92,323,51,264,75,286,28,237,62,246,9,206,54,181,0xe" strokeweight=".25pt" fillcolor="olive"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape><oval id="_x0000_s1040" style="LEFT: 3127px; WIDTH: 120px; POSITION: absolute; TOP: 6549px; HEIGHT: 37px" strokeweight=".5pt" fillcolor="yellow" strokecolor="olive"><stroke color2="white"><font face="Times New Roman" size="2"></font></stroke></oval><shape id="_x0000_s1041" style="LEFT: 3138px; WIDTH: 28px; POSITION: absolute; TOP: 6598px; HEIGHT: 67px" coordsize="58,333" filled="f" path="m58,306l25,319,,333,55,,55,0e" strokeweight=".25pt"><path arrowok="t"><font face="Times New Roman" size="2"></font></path></shape></group><shape id="_x0000_s1042" style="LEFT: 2457px; WIDTH: 692px; POSITION: absolute; TOP: 9629px; HEIGHT: 275px" stroked="f" filled="f" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1042" inset="0,0,0,0"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr><td style="BORDER-RIGHT: #d4d0c8; BORDER-TOP: #d4d0c8; BORDER-LEFT: #d4d0c8; BORDER-BOTTOM: #d4d0c8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 12pt"><span style="FONT-SIZE: 9pt; FONT-FAMILY: 幼圆; mso-bidi-font-size: 10.0pt; mso-hansi-font-family: Arial; mso-ascii-font-family: Arial"><font face="Times New Roman">例</font></span><span lang="EN-US" style="FONT-SIZE: 9pt; FONT-FAMILY: Arial; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'; mso-fareast-font-family: 幼圆">8.11<p></p></span></p> </div> </td></tr></tbody></table></textbox></shape></group>


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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值