在尝试发现程序中错误的时候,我们是如何去调试的呢?我们常用的错误方式就是:添加许多中跟踪和调试语句,然后,一旦调试完毕,我们必须马上删除这些添加的语句.事实上,.net框架中早已经为我们提供了三个可以用于调试的类:
System.Diagnostics.Debug,System.Diagnostics.Trace,System.Diagnostics.EventLog。
前面两个类功能是基本上是一样的。不同的地方是,Debug只编译到Debug版本中,而Trace语句编译到Debug和Release版本中。EventLog类提供了一个入口,通过这个入口,你的程序可以写一些系统日志。EventLog类不支持运行时配置,但你可以把它封装到一个统一的简单接口中。
1.定义输出开关(TraceSwitch)
第一个参数是开关显示的名字,第二个参数是描述。
2.定义配置文件
.Net框架使用一个应用程序配置文件来控制变化多样的运行时设置。这是一个XML文件,在主应用程序运行时的目录中。该文件的名字与程序的名字相同,扩展名为“.config”。例如,如果程序是myApp.exe,就建一个myApp.exe.config。所有的配置信息都包含在一个configuration节点中:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>
.Net框架使用预定义的关键字来控制框架中一些类的行为。也可以自定义配置关键字和值。
也可以组合输出开关和Trace.WriteLineIf()方法来控制应用程序的输出。还可以在应用程序外以默认的方式关闭这个输出,以便使应用程序得到最好的性能。当发现bug时,你可以打开这个输出用于诊断和修正bug。
--1.配置输出开关
输出开关的值可以是五种状态之一:关闭(Off),错误(Error),警告(Warning),信息(Info)和详细(Verbose)。这些状态是环境的一部份,而且它们的值可以是从0到4。这样如果你编辑了这个配置文件中开关的值,那么就修改了所有由那个开关控制的输出语句。
在配置文件中写入以下语句就配置了一个名为MySwitch,值为info的输出开关.:
<system.diagnostics>
<switches>
<add name="MySwitch" value="3" />
</switches>
</system.diagnostics>
--2.添加监听者
默认是一个链接到Trace类上的监听者:一个DefaultTraceListener对象。DefaultTraceListener发送信息到调试器,而且在它的失败方法(断言失败时调用)会打印一些诊断信息然后终止程序。在产品发布环境中,你不可能看到这样的信息。但你可以配置不同的监听对象到产品发布环境中:那就是在应用程序的配置文件中添加监听者。下面就添加了一个TextWriterTraceListener 到应用程序中:
<system.diagnostics>
<trace autoflush="true" indentsize="1">
<listeners>
<add name="MyListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="MyListener.log"/>
</listeners>
</trace>
</system.diagnostics>
TextWriterTraceListener把所有的诊断信息到打印到一个MyListener.log文件中。name属性指定了监听者的名字,type指定了作者监听对象的类型,它必须是从System.Diagnostics.TraceListener派生下来的。initializeData的值是一个字符串,用于传给对象的构造函数。而TextWriterTraceListeners把它用于文件名。
3.为了调试,我们常常需要编写一些只在调试时用到的函数,对于调试函数,在发布(Release)版本中,编译器会自动取消对它的调用。
private void ImOK ()
{
Debug.Assert( this != null , " Testing Object State " , " this cannot be null " );
// More here.
}
// 调用方法:
public bool ProcessIterations ( int numIters)
{
ImOK ();
Debug.Assert(numIters > 0 , " ProcessIterations. " , " Iterations must be more than 0 " );
}
4.调试应用示例
--1.首先,写一个通用的类来跟踪程序:
{
static private TraceSwitch myAssemblySwitch =
new TraceSwitch( " MySwitch " , " The switch for this assembly " );
internal static void Msg( TraceLevel l, object o )
{
Trace.WriteLineIf( myAssemblySwitch.Level >= l, o, " MySwitch " );
}
internal static void Msg( TraceLevel l, string s )
{
Trace.WriteLineIf( myAssemblySwitch.Level >= l, s, " MySwitch " );
}
// Add additional output methods to suit.
}
--2.调用方法:
{
MyAssemblyDiagnostics.Msg( TraceLevel.Info, " Entering Method1. " );
bool rVal = DoMoreWork( );
if ( rVal == false )
{
MyAssemblyDiagnostics.Msg( TraceLevel.Warning, " DoMoreWork Failed in Method1 " );
}
MyAssemblyDiagnostics.Msg( TraceLevel.Info, " Exiting Method1. " );
}
--3.进一步扩展类:
利用一个全局的开关,还可以组建特殊的程序集开关,来控制整个及个别库文件的输出:
{
Trace.WriteLineIf ( librarySwitch.Level >= l || globalSwitch.Level >= l, o, " MyLibrary " );
}
internal static void Msg( TraceLevel l, string s )
{
Trace.WriteLineIf( librarySwitch.Level >= l || globalSwitch.Level >= l, s, " MyLibrary " );
}
5.几点建议
在写一个函数时,你应该考虑并确定你对那个函数做了什么样的假设。你应该问自己以下这些问题,
并把答案放到代码中:
--1. 当这个函数被调用时,这个对象必须是怎样的(对象初试化,某个内在变量值)?
--2. 当这个函数存在时,这个对象将会怎样(仍是#1,但包括该函数的副作用)?
--3. 该函数的任何参数必须是怎样的(允许空值吗,输入值的范围是什么)?
--4. 返回值必须是怎样的?
为了发现程序中的bug,以下是一些较好的做法:
--1. 当你创建类时,通常为每个类建一个跟踪开关。
--2. 通常为每个类建一个验证函数。
--3. 当你要诊断错误的行为时,添加其它的跟踪和调试语句。确信把这些变化保留在代码中。
參考文獻: http://tech.goodspeed.com.cn/120393.aspx 及 http://support.microsoft.com/kb/815788/zh-cn