把握CB的调试艺术
副标题#e#
措施的bugs越少,最终用户对这个措施的评价越高。而开拓人员事先对bugs的处理惩罚越多,最终用户能提供的关于bugs的信息就越多,也越精确,这样,开拓人员在接到最终用户反应之后,就可以或许快速找到呈现bugs的那部门代码,并以最快速度宣布措施的进级包。
在这份教程中,我们从最根基的部门开始,慢慢先容很多在调试措施时“应该做”或“不该该做”的原则。正如你将看到的,这份教程中所指的“调试”这个词所包括的意思许多,而不可是如大部门人所想到的–操作IDE集成的调试器的“调试”。我但愿读过这份教程之后,读者可以在思路上有所收获。
写易读的代码
第一点,或许也是最重要的一点,就是写清洁易读的代码。易读的代码是很有代价的。请想象一下,假如随便扫视一眼代码或注释,就能立即知道这段代码的的浸染,以及在写代码的时候为什么要这样写,其时的思路是什么,那么就可以节省大量时间。这样的代码,在写的时候大概会稍稍慢一些,不外,当你调试措施时,就不会花上几个小时来寻找bugs,相反,你可以快速,简朴的完成除错事情。这时,你就会以为多花一些时间使措施易读是很值得的。
所以,我推荐你在写措施的时候,应该养本钱身的气势气魄,或是读一读Scott的关于代码气势气魄的文章。
利用Exceptions和Exception的处理惩罚要领
我们教程的下一步,仍然是以代码为基本的。因为撤除一些少数的环境,开拓人员不行能老是依靠于集成的调试东西。所以,学会用其它的要领来找到烦人的bugs是很重要的。一些重要的、处理惩罚的错误大概会在窗体之外产生。在C++尺度拟定出来之前的暗中日子里,在措施内里发出产生错误的信号,凡是是通过返回错误代码完成的(此刻这种要领仍然应用于OLE技能和一些Winapi函数),这样的处理惩罚要领很容易就会被忽略。(好比说,你常常查抄winapi函数的返回值吗?)所以,呈现问题的大概性并不小。由于以上的原因,我们需要一个这样的机制,它能让我们不能忽略这些错误,并且,这个机制应该能被我们节制和自界说的。在这样的需求下,异常处理惩罚机制呈现了。需要一个非凡的错误范例吗?简朴,界说一个新的异常范例就行了(和界说一个类的要领差不多),然后抛出(throw)它。下面这个例子说明白这一进程。
例1:
//----------------------------------------------------------------
class MyException
{
public:
AnsiString iMessage;
MyException(AnsiString Message) { iMessage=Message;}
};
throw new MyException(“Test Exception Message”);
//---------------------------------------------------------------
就是它!(不是十分好,下面我们会继承完善它)。简朴高效,并且便于自界说。也许你此刻会问:“我可以使抛出异常了,可是,怎么节制它们呢?我的意思是,我想在代码的最前面解除异常。”C++Builder为我们中界说了try {} catch (…) {}机制。这和我们方才界说的异常机制的布局很相似。这个机制完全可以凭据需要自界说。要利用异常处理惩罚了,只要把要执行的代码放到try块内里,为了让措施知道呈现异常后应该做什么,还需要界说一个catch()或是__finally块。Catch()语句内里可以指定一个要捕获的范例或是变量(好比例1,就是catch(MyException &E){ /* 异常处理惩罚代码/}这个机制很强大,甚至可以用它来捕获树布局或是担任类的异常,假如捕获了基类的异常,它就能捕获到担任这个基类的所有的类的异常。好比,在VCL中,所有的异常都是担任于Exception类。所以,catch(Exception& E)可以捕获到除了EsocketError的所有VCL异常。(这点请出格留意,今后还将继承接头。)为了让这个机制更强大,C++Builder中还界说了catch(…)语句。(没错,就是三个点)利用这条语句可以捕获到所有的异常。尚有更多的成果吗?虽然,你可以添加更多的catch()语句,可以向利用if…else if…语句那样利用它。留意,在一系列的catch()语句中,错误不会被反复的捕获,也就是说,假如前面的catch()语句捕获到了错误,后头的catch()语句将不会捕获这条错误。
例2:
//----------------------
try
{
// 正常代码
}
catch(EDBEngineError &E)
{
// 处理惩罚数据库引擎错误
}
catch(EExternalError &E)
{
// 处理惩罚窗口类的错误
}
catch(Exception &E)
{
// 处理惩罚所有的VCL错误
}
//----------------------
#p#分页标题#e#
请看例2,它的代码运行流程是这样的:“错误是EDBEngineError吗?是->处理惩罚它。不是->运行下一个catch语句”“错误是EExternalError吗?是-〉处理惩罚它。不是-〉运行下一个catch语句”等等。
这个机制尚有更多的成果。假如你想处理惩罚异常,可是不想在处理惩罚的位置遏制,那么可以从头抛出异常。这时,措施将继承寻找下一个catch()语句来处理惩罚这个异常。这个要领和“throw”差不多。这样,你处理惩罚过的异常会再次被抛出,继承寻找下一个catch语句来处理惩罚它。
最后一个要说的是__finally(这不是尺度的用法,是Borland添加的一个好要领),在__finally{}措施块中代码,无论是否产生异常城市被执行。这是一个清理措施中利用new分派的当地变量,配置用作旗标的变量值为正常的好位置。(好比,把一个期待状态的光标图标配置为正常光标。)
就是这些了。有时间的话,请看看C++Builder辅佐文件中的Exception类以及担任Exception的类。这些将对付领略本节所说的内容有很大辅佐。
利用记录机制
你不行能老是用调试器来调试代码,在某些环境下,大概无法利用内部集成的调试器,这时候,你就不得不依靠其他手段调试措施了。(好比:Windows NT处事措施,ISAPI/CGI措施,及时应用措施等等)。这时候,有履历的措施员大概会借助陈腐的调试要领,譬喻,利用一些分类的记录机制来确定措施实际运行的进程。我们很幸运,此刻有一系列的要领可以简朴的完成这样的事情。下面将先容3种我最喜欢的要领。
第一个:OutputDebugString。(WinAPI: VOID OutputDebugString(LPCTSTR lpOutputString);)很幸运,微软彻底的实现了调试子系统。它包罗的一些特点大概让你想把本身的记录系统扔掉。应用措施在调试器历程中运行时OutputDebugString将用C字符串把调试器输出的信息打印出来。假如措施没有在调试器历程中运行,它将忽略这些挪用。它会很好的在客户的呆板上运行,不会弹出信息窗口。假如在宣布给客户的时候,健忘去掉这些代码措施仅仅会变慢一点,不会有此外不良效果。
第二个要领:利用了Gexperts,通过 dbugint.pas接口举办调试。它是个可以称之为伟大的措施,你可以把它分发给客户。和OutputDebugString一样,假如客户没有这个措施,它就基础什么也不作。(它会自动检测呆板上是否安装了客户端)。要利用dbugintf,它很容易被插手到你的工程中,插手#include "dbugintf.HPp"(要把它插手工程,然后会编译它的pascal文件)。然后,你就可以直接利用SendDebug(要送到记录文件的字符串); 可能,你需要它更机敏一些,可以利用SendDebugEx(它给TMsgDlgType增加了一个新的动静范例)SendMethodEnter, SendMethodExit, SendSeparator等等(用法都差不多)。假如你规划给最终用户分发客户端 (Gdebug.exe),不要健忘include所需要的措施包。Gexperts可以在http://www.gexperts.org 获得,它是免费的。
第三个,或许是最费力的要领,就是利用你本身的记录节制。这个要领大概不是你想象的这么简朴。你大概首先会想到“在窗体上扔一个RichEdit,把它配置为只读的,然后往内里写记录”是这样吧?理论上不错,可是,实施起来…首先,利用RichEdit控件来做记录,会大大低落应用措施的速度,还会在内存中造成碎片,甚至丢失内存。凡是,在运行10分钟阁下之后,会使整个计较机的速度变慢!(这样做的确是在犯法!)所以,假如你但愿在本身的记录中可以或许利用彩色和图标,那么最好本身建设一个组件。假如没有这么高的要求,那么有一个简朴有效的要领,就是利用ListBox控件作记录,把ListBox的Style属性配置为lbOwnerDrawFixed,这样句柄将会自绘。(Gexperts的节制台就是用这样的要领建造的)。
#p#副标题#e#
将记录和异常处理惩罚团结利用
你不消老是担忧大概会产生什么偶尔的异常。一般来说,通过许多的bugs测试后(只管熬煎措施,看看它会不会瓦解),应用措施在运行是应该不会呈现什么错误。下面的这个技能,发起组件开拓者,在第一次把组件放在IDE情况测试的时候,很应该遵守。一个在IDE中发生的异常会导致许多问题,甚至大概无法从头启动IDE也不能规复。这个技能很简朴。在代码中每一个函数或是主要的函数中插手:
try
{
//函数的代码
}
catch(Exception &E)
{
SendDebugMessage(“Exception caught in classname::functionname of type:” +E.ClassName()
+” with the message:”+E.Message);
};
(把字符串中classname 和 functionname 替换成相应的类名和函数名。在呈现错误时,你会立即知道错误产生的位置。这样也就不至于强制重起IDE的了。
#p#分页标题#e#
此刻,让我们看看前面的内容, ClassName()给了我们什么样的辅佐呢克只是用于返回字符?ldquo;Exception”吗?每一次,E都被声明为异常范例?这是VCL另一个优秀的处所,所有的类都从Tobject担任,所以,这些类都能自动得到正确的范例和基类的范例,所有的更多的信息都可以在这里找到。(请拜见Tobject的辅佐)所以,尽量我们利用了Exception &E,个中的E.ClassName()将返回捕捉到的发生异常的实际类名。获得这些长处需要支付的价钱是编译出来的可执行文件变大了一些。所有的Delphi/Cbuilder用户都留意到了这一点,可是他们说,没有支付就没有收获。在http://www.bytamin-c.com/的howto栏目可以看到Xiphias的一系列文章。个中,他提到了利用TStringList作记录的要领:先将错误通过TStringList的Add要领插手到StringList内里,然后利用SaveToFile生存到硬盘上。不外要留意在措施竣事的时候不要健忘利用SaveToFile()要领把TStringList生存起来。可能,也可以每次捕获到异常之后都生存一次。
在代码外部举办异常处理惩罚
这是利用代码处理惩罚异常的最后一节,今后,我们将利用IDE的调试东西。可是,这节中讲到的要领在处理惩罚致命错误时是很重要的。好比说:可以显示一个包括了错误信息的对话框,这样,用户陈诉bugs的时候会排除的多。你必定不想听到用户陈诉说:“啊,这有个对话框,上面写着什么地点产生了错误”。有个好步伐能改进这种状况,请往下面看。
第一步:在主窗体(工程配置内里,自动建设的第一个窗体)中建设这样一个函数:
void __fastcall AppLevelExceptionHandler(Tobject *Sender, Exception *E)
{
}
然后,在内里加上显是错误(E->Message)的代码,错误范例(记着前面提到过的E.ClassName())以及其他需要的细节信息。
第二步:把它与系统挂钩。很简朴,只需要在窗体的OnCreate事件中插手这行:
Application->OnException=AppLevelExceptionHandler;
在这行代码上,不要吝啬,因为加上它,根基上就可以说所有的错误都不会遗漏了。无论在任那里所产生的错误都可以被捕获到。
此刻,所有以代码为基本的调试要领你都学会了,顿时把他们插手到你的工程里去吧。最好能把它们变为你的习惯。这将对你的措施有很大辅佐。