登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

秒大刀 博客

好好学习 天天向上

 
 
 

日志

 
 
 
 

[转]C#中Release和debug模式下调试跟踪程序的原则和方法  

2007-11-30 06:45:47|  分类: C# |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

[转]C#中Release和debug模式下调试跟踪程序的原则和方法

 

写在前面

本文论述了软件开发中关于Release模式和Debug下如何调试跟踪程序的原则和方法,以及才有作者所叙述的方法对软件开发流程的影响,随后作者给出了在C#中的具体实现。

 入门简介

VS.Net 提供了两种机制来帮助开发人员诊断和纠正程序中的错误.一个是debug, 另一个是trace.这两个类都有了一个assert函数. Assert在很多情况下可以用来检验变量, 比如检验一个指针在调用了某一个系统的API之后是不是为空。即使是使用try-catch块,我们也要面对下面两种情况,其一是我们要抓取系统的API抛出的异常,但是我们无法简单的得到API失败时的返回值,我们只好来处理“异常,,这会使编程变得复杂.其二,我们往往要生成两个版本-测试版和发布版,这也使开发变得复杂。


如果你是一个从来没有使用过asserttrace的程序员,你大可以读到这儿为止了,因为下面我要讲的东西可能没有一点会减轻你编程工作的强度,也不会减少开发费用,也不能给你正在测试的程序一些好的建议.

这是我在实际开发中亲身经历的一个例子

 

好几年以前,我还是一个多线路视频监视系统的开发组长,我们的视频系统可以通过RS-232线接收多达64个摄像机.这些摄像机直接或者通过专线或者通过拨号连接到视频系统上来.这是一个复杂的系统,我在这儿就不多说了。

 

实现跟踪的手段之一: 跟踪日志(Trace logs)

在每一个项目的开始,我们通常要记录下面的事情:
1.
所有与GUI有关的事件: 选择菜单,选择按钮等等.

2. 所有内部的消息(我们使用消息体系来调用没有个功能的实现)

3. 所有状态的变化.


对于上面的实现方法,我们输出一个调试日志来报告与消息相关的事件,消息和当前的状态.

 

 

实现跟踪的手段之二: 断言(assert)

 

我们也才有用assert来检验所有函数的参数和他们的返回值.当触发一个assert的时候,我们会把与这个相关的信息写的调试日志中.这时候,就会关闭调试日志,显示一个错误信息,并且中止程序的运行.

 

 

不配合的质量评估人员

 

在开发过程中,质量评估部门会测试不同的模块.最初,我们与质量评估部门的关系是敌对的,我们使用了先前的调试手段,而我们却发现, 质量评估部门正好测试到我们不让他们那么实现的情况(他们只想引起管理层的注意).当我们纠正了各种不同的观念之后, 质量评估部门的工作开始作的非常好.有一个问题,对于任何测试人员,当你问他具体作了些什么,他们会非常安心的对你说,我点击了X,然后是Y,接下去是Z. 而事实上,他们点击的是A,B,C. 有了我们上面对于GUI的实现手段,我们没有必要去问测试人员他们作了些什么,我们自己完全可以从日志文件中看的出来.

 

 

实地测试(Field Testing

 

因为内部测试与实际使用有很大的出入,我们会选择一些用户,提供一个beta版本给他们测试,这些版本中我们打开了我们前面所讲的调试手段.这可以显示一些问题,多数是因为硬件的不同,而这个我们在内部测试中并没有发现.

 

 

回归测试(Regression Testing

 

最后,用于我们前面说的调试手段已经做到GUI,所有我们可以记录和回放我们的测试教本.所以我们可以实现回归测试,这个测试是用来测试我们的新代码对以前的东西有没有影响.

 

 

开发测试(Development Testing

 

不必多说,上面的调试手段对我们自己的开发测试也是非常有用的(特别是有了版本控制以后,它可以允许我们退回到旧版本上,测试相同的事件序列.)

 

 

提前发布产品

 

管理层因为上一个项目的原因非常恼火,因为他们要应付客户的发火,所以这次分配了三个月的事件用来测试(以前从来没有听说这么长的时间,我们的编码仅用了六个月,而测试在编码过程中就已经开始了!),由于我们的调试手段,质量评估部门仅用一个月的时间就没有办法再发现新的问题了,所以我们的产品提前发布.

 

发布版本 VS 测试版本

 

由于我们得调试手段非常深入,出于性能的原因,我们在发布版本中关闭了跟踪的语句,但是我们在发布版本中保留了assert. 这是因为在实地工作中,我们的程序会连续不断的运行,会使日志文件非常大.此外,assert语句会报告一个非常有价值的消息,为什么会处罚这个assert,因为在质量评估部门必然会有东西会出错,特别是由于世界各地不同的硬件产品.


我认为这是一个非常好的体系,发布一个不带调试手段的版本给我们的客户. 如果客户那儿持续有问题,并且想和你一起来解决这个问题,你可以给他一个带有全部调试手段的版本. 除此之外,使用正确的assert控制,你可以显示一个非常漂亮的出错信息给你的客户.我比较喜欢IE6的出错信息,就象这样对此使用引起的不方便表示道歉,我们发现一个问题等等”. 你可以用这个机会自动重新启动你的程序.从一个用户的观点来看,这好像是被发生事故是被安全气囊划伤了一下子,却不是直接撞在挡风玻璃上,这就是进步.如果客户可以接入互联网,那么你的程序可以把错误信息发送给你,这就是为什么么在发布版本中保留assert的原因,你可以得到除了错误发生地址,栈的信息,寄存器和内存映象以外更多的信息.

 

 

调试(Debug)和跟踪(Trace)

 

C#, debug trace 类都提供了这些功能,但是它们都可以被关闭.缺省情况下,在调试(Debug)模式下它们是可用的.在发布(release)模式下,dubg的功能是关闭的, trace仍然可以使用.它们两个都支持 assert trace. 通常的使用方法这样. Debug.Assert 来使用assert功能, Trace.Write来使用trace功能.


如果你研究了我前面说得内容,就会觉得这样作不对. 出于对程序性能和生存周期的考虑,纯粹的trace应该在发布版本中拿去, Debug.Assert应该保留.


因此, 程序中应该用Debug.Write保存跟踪文件, Trace.Assert来实现assert. 当在生成发布版时, Debug.Write就会失去作用,assert仍然保留.

 

 

Try-Catch 块中处理异常

 

通常开发人员对于异常只有一个愿望:它能不能看起来更温和友好一些。不如,是不是可以让用户不必停止程序就可以结束对一个文件的度操作?如果答案是是的话,那么我们的代码就是正确的,如果答案是否,我们就要产生一个断言(assert).我们收集类似像这种情况的信息,他们在处理异常是就会成为非常有用的东西,我们就可以在类似“程序运行失败“消息中,显示更多的信息给用户。

 

 

Behind The Scenes

我们用assertWriteLine来测试一下在编译过的中间代码中,测试版和发布版中的debug类实现起来有什么不同


使用如下的C#代码

Debug.WriteLine("Debug.WriteLine");

System.Diagnostics.Trace.WriteLine("Test.WriteLine");

                         

Debug.Assert(false, "Debug.Assert");

System.Diagnostics.Trace.Assert(true, "Trace.Assert");

 

在测试模式下,编译器产生如下的代码

                          Debug.WriteLine("Debug.WriteLine");

0000000f  mov         ecx,dword ptr ds:[01BA03CCh]

00000015  call        dword ptr ds:[02EF870Ch]

                          System.Diagnostics.Trace.WriteLine("Test.WriteLine");

0000001b  mov         ecx,dword ptr ds:[01BA03D0h]

00000021  call        dword ptr ds:[02EF89F4h]

                          Debug.Assert(false, "Debug.Assert");

00000027  mov         edx,dword ptr ds:[01BA03D4h]

0000002d  xor         ecx,ecx

0000002f  call        dword ptr ds:[02EF86ECh]

                          System.Diagnostics.Trace.Assert(true, "Trace.Assert");

00000035  mov         edx,dword ptr ds:[01BA03D8h]

0000003b  mov         ecx,1

00000040  call        dword ptr ds:[02EF89D4h]

在发布模式下,编译器产生如下代码

0000000f  mov         ecx,dword ptr ds:[01BA03CCh]

00000015  call        dword ptr ds:[02EF870Ch]

0000001b  mov         edx,dword ptr ds:[01BA03D0h]

00000021  mov         ecx,1

00000026  call        dword ptr ds:[02EF86ECh]

 

你可以注意到如下有趣的几点:
1. Deubg
语句被完全拿掉了。

2. Trace.WriteLine实际上是调用了Debug.WriteLine.

3. Trace.Asser是调用了Debug.Assert

 

总结:

所有的debug函数都被拿掉了,所有的Trace函数都转化为调用debug的函数。这是因为c#中条件属性控制的原因,我们会用一个相同的机制来建立自己的一个更聪明的debug类。

(未完,待续)


实现一个定制的debug

 

不幸的是,debugtrace类都不能重写,所以我们只好实现自己的版本,在我们的类中:
trace
使用DEBUG属性

  1. assert使用Trace属性
  2. 添加了一个警告(warm)功能
  3. 添加了一个警告/错误词典。
  4. 实现了目前trace/debug的所有功能
  5. 可以简单把日志输出的一个文本文件中。
  6. 警告和assert,使用debug属性,可以产生一个用户友好的警告信息。
  7. 可以抓到未处理的异常,可以进一步提高用户友好特性。

 

测试版和发布版

在我们的debug类中,作了如下修改:
测试模式(debug mode)

包括所有的跟踪模式(trace mode)的功能,例如:

所有的写功能是打开的。

可以添加接受文件

所有的处理,清理,关闭接受文件的功能是打开的。


Trace mode:
跟踪模式(Trace mode):

可以初始化一个未知的异常

可以输出警告(Warn 方法)

可以打开assert.

 

未知异常处理

 

下面的代码是一个未知异常处理器。它提供了一个平和中断程序的方法,作测试模式下(debug mode),可以把异常输出到文件中。

[Conditional("TRACE")]

public static void InitializeUnhandledExceptionHandler()

{

         AppDomain.CurrentDomain.UnhandledException+=new UnhandledExceptionEventHandler(DbgExceptionHandler);

}

 

public static void DbgExceptionHandler(object sender, UnhandledExceptionEventArgs args)

{

         Exception e=(Exception) args.ExceptionObject;

         Trace.WriteLine("Exception: "+e.Message+"\n"+e.GetType()+"\nStack Trace:\n"+e.StackTrace);

         MessageBox.Show(

                 "A fatal problem has occurred.\n"+e.Message+"\nin: "+e.GetType(),

                 "Program Stopped",

                  MessageBoxButtons.OK,

                 MessageBoxIcon.Stop,

                 MessageBoxDefaultButton.Button1);

         Trace.Close();

         Process.GetCurrentProcess().Kill();

}

 

 

Warn方法

下面的代码是解释如何给用户输出一个警告信息,提示他们应该作一些什么正确的操作。

[Conditional("TRACE")]

public static void Warn(bool b, DbgKey key)

{

         if (!b)

         {

                 Trace.WriteLine("Warning: "+key.Name);

                 if (problems.Contains(key))

                 {

                          string explanation=GetExplanation(key);

                          MessageBox.Show(

                                   explanation,

                                   "Warning",

                                   MessageBoxButtons.OK,

                                   MessageBoxIcon.Warning,

                                   MessageBoxDefaultButton.Button1);

                 }

                 else

                 {

                          MessageBox.Show(

                                   "A problem has occurred that should be corrected.\n\nReference: "+key.Name,

                                   "Warning",

                                   MessageBoxButtons.OK,

                                   MessageBoxIcon.Warning,

                                   MessageBoxDefaultButton.Button1);

                 }

         }

}

 

 

 Assert 方法

 

当调用一个重写的Assert(bool b, DbgKey key)方法时,可以产生一个断言信息,并提示给用户一个友好的信息。这个方法和上面的warn方法,可以使用错误字典来给用户显示一些有用的信息。

[Conditional("TRACE")]

public static void Assert(bool b, DbgKey key)

{

         if (!b)

         {

                 Trace.WriteLine("Assert: "+key.Name);

                 if (problems.Contains(key))

                 {

                          string explanation=GetExplanation(key);

                          MessageBox.Show(

                                   explanation,

                                   "Program Stopped",

                                   MessageBoxButtons.OK,

                                   MessageBoxIcon.Stop,

                                   MessageBoxDefaultButton.Button1);

                 }

                 else

                 {

                          MessageBox.Show(

                                   "A fatal problem has occurred.\n\nReference: "+key.Name,

                                   "Program Stopped",

                                   MessageBoxButtons.OK,

                                   MessageBoxIcon.Stop,

                                   MessageBoxDefaultButton.Button1);

                 }

                 Trace.Close();

                 Process.GetCurrentProcess().Kill();

         }

}

 

Verify方法

public static void Verify(bool b)

{

         Assert(b, new DbgKey("_NEVER_FAIL_"));

}

 

 

添加到日志文件中

调用LogOutput可以添加到日志文件中。

[Conditional("DEBUG")]

public static void LogOutput(string fn)

{

         Debug.Listeners.Add(new TextWriterTraceListener(fn));

         DateTime dt=DateTime.Now;

         Debug.WriteLine("Debug Logging Initialized: "+dt.ToString());

}

 

 

使用方法

 

下面的例子显示如何使用我们的写好类

Dbg.LogOutput("out.txt");

Dbg.InitializeUnhandledExceptionHandler();

Dbg.Problems.Add(new DbgKey("IO"),

         "Cannot create file.",

         new String[] {

                 "The specified path is invalid.",

                 "The network connection is down.",

                 "The file already exists."});

Dbg.Warn(false, new DbgKey("IO"));

Dbg.Warn(false, new DbgKey("Foo"));

Dbg.WriteLine("A trace message");

int a=0;

int n=1/a;                                                            // generate an unhandled exception

Dbg.Assert(false, new DbgKey("IO"));                // comment out above exception to make this happen

 

 

结束语

在上面的文章中,我们知道了在测试模式下(debug mdoe),asserttrace都是应该打开的。在发布模式下(release mode),只有assert是打开的。接着,我们知道了如果不能在try-catch中处理的异常,就应该是assert,如果能处理的就要显示给用户一个警告信息。最后,我们看到assert应该提供一些问题如何解决的方案给我们的用户。同时,在测试状态中的版本,所有的traceassert都要写到日志文件中,而在发布版本中,就没有日志文件了。

当然,如果能扩展这个类使他能从XML文件中读取错误字典,就更好了。

 

 

参考资料

Writing Custom .NET Trace Listeners, Vagif Abilov (CodeProject)
General Guidelines for C# Class Implementation, Eddie Velasquez (CodeProject)

 

补充:

代码下载:http://www.codeproject.com/useritems/DebugTreatise/DebugTreatise_src.zip

  评论这张
 
阅读(1717)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018