写在前面
本文论述了软件开发中关于
Release
模式和
Debug
下如何调试跟踪程序的原则和方法,以及才有作者所叙述的方法对软件开发流程的影响,随后作者给出了在
C#
中的具体实现。
入门简介
VS.Net
提供了两种机制来帮助开发人员诊断和纠正程序中的错误
.
一个是
debug
类
,
另一个是
trace
类
.
这两个类都有了一个
assert
函数
. Assert
在很多情况下可以用来检验变量
,
比如检验一个指针在调用了某一个系统的
API
之后是不是为空。即使是使用
try-catch
块,我们也要面对下面两种情况,其一是我们要抓取系统的
API
抛出的异常,但是我们无法简单的得到
API
失败时的返回值,我们只好来处理“异常
”
,,这会使编程变得复杂
.
其二,我们往往要生成两个版本-测试版和发布版,这也使开发变得复杂。
如果你是一个从来没有使用过assert和trace的程序员,你大可以读到这儿为止了,因为下面我要讲的东西可能没有一点会减轻你编程工作的强度,也不会减少开发费用,也不能给你正在测试的程序一些好的建议.
这是我在实际开发中亲身经历的一个例子
好几年以前
,
我还是一个多线路视频监视系统的开发组长
,
我们的视频系统可以通过
RS-232
线接收多达
64
个摄像机
.
这些摄像机直接或者通过专线或者通过拨号连接到视频系统上来
.
这是一个复杂的系统,我在这儿就不多说了。
实现跟踪的手段之一
:
跟踪日志
(Trace logs)
在每一个项目的开始
,
我们通常要记录下面的事情
:
1. 所有与 GUI 有关的事件 : 选择菜单 , 选择按钮等等 .
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
我们用
assert
的
WriteLine
来测试一下在编译过的中间代码中,测试版和发布版中的
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 语句被完全拿掉了。
1. Deubg 语句被完全拿掉了。
2. Trace.WriteLine
实际上是调用了
Debug.WriteLine.
3. Trace.Asser
是调用了
Debug.Assert
总结:
所有的
debug
函数都被拿掉了,所有的
Trace
函数都转化为调用
debug
的函数。这是因为
c#
中条件属性控制的原因,我们会用一个相同的机制来建立自己的一个更聪明的
debug
类。
原帖:http://tech.xgxy.com.cn/N/200607/14/N20060714_19402.SHTML