购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

6.2 使用断点与监视

在程序设计中,“调试”的意思就是排查代码中存在的错误(主要是逻辑错误)。由于程序错误常被称为“Bug”,所以“调试”对应的英文单词就是“Debug”。与所有现代程序开发工具一样,VBA也提供了比较全面的调试工具,也就是本节将要介绍的断点、监视和单步执行。

关于Bug和Debug这两个术语的来历,计算机界流传着不同版本的故事。其中被公众普遍认可的说法是,在1946年(或1947年),哈佛大学科学家Grace Hopper工作时发现一台Mark II计算机出现错误,经过她与同事的排查,最终发现故障原因是一只飞蛾卡在了中继器上。由于 Bug(虫子)一词早在1870年代就已经被爱迪生等人用于指代“技术错误”,所以Hopper等人也从这只飞蛾开始,将计算机错误称为 Bug。Debugging 一词也是在此之前就被航空业用于指代“错误排查”,所以在Hopper提出“Bug”的称谓后,计算机界很快也开始使用Debug一词代表“错误调试”(引自维基百科)。图6.5所示为Hopper当时记录的错误日志。

图6.5 Hopper等人发现的史上第一个计算机

6.2.1 “望闻”之术
——设置断点

假如我们觉得 VBA 程序的输出结果不正常,进而怀疑其中某行代码存在逻辑错误,那么最直接的诊断办法就是让程序在“嫌疑代码”前暂停运行,从而让我们能够仔细观察它是否存在问题。这就像中医看诊时首先要“望气听声”,通过观察初步判断病源部位,以便后面聚焦于此并深入检查。

这种让程序在某行代码前暂停运行的技术,就称为在该行代码前添加一个“断点(Break Point)”。具体操作十分简单:在VBE中用鼠标单击该行代码前面的灰色边框即可。图6.6演示了这个过程。

图6.6 单击边框添加断点示例(右图为单击鼠标左键之

需要提醒读者的是,那些在理论上不可能发生逻辑错误,或者对于调试程序没有明显意义的代码行,是不能添加断点的。比如代码中的空白行、没有任何代码的注释语句行、Option Explicit,以及使用Dim声明变量的语句前,都没有办法也没有必要添加断点。

一旦某行代码前被添加了断点,再次运行 VBA 程序时,程序就会在执行到这行语句时自动暂停,同时把这行代码使用黄色背景高亮显示,进入单步调试状态。注意,此时这行代码还没有被执行!而接下来要做的事情,就是使用“监视”功能查看程序当前状态了。

在一个程序中可以添加多个断点,从而让程序在所有“嫌疑语句”前停止运行。当程序在断点处暂停运行后,如果再次单击VBE的“运行”按钮,程序还会继续执行下去,直到遇见下一个断点或End Sub为止。所以,如果把For循环内部的某行代码标记为断点,那么在程序暂停运行后再单击“运行”按钮,程序会执行一遍循环体中的代码,并再次循环到这个语句,从而再次暂停。如此反复,直到把整个循环都执行完毕。

最后需要说明的是,我们可以随时清除断点,即使程序处于暂停运行状态。与添加断点的操作相同,只要在某行断点代码的灰色边框上再次单击鼠标左键,就可以清除断点标记。如果代码中有很多个断点,希望能够全部清除干净,则可以在VBE的“调试”菜单中选择“清除所有断点”命令,或者使用快捷键“Ctrl+Shift+F9”。

6.2.2 “问”的技巧
——添加监视

当程序因遇到断点而暂停运行时,我们可以深入观察程序此时的状态,特别是每个变量或表达式的当前取值。如果某个变量的数值与预期不符,那么从它入手,顺藤摸瓜,就可以找出错误产生的根源。

怎样在程序暂停运行时观察一个变量的取值呢?最简单的办法,就是在VBE中把鼠标光标移动到这个变量上并静止不动,这时很快就会看到一个黄色背景的文字框,里面显示的就是这个变量当前的取值。图6.7演示了这一技巧。

图6.7 在程序暂停时移动光标查看取值

在图6.7的第一个截图中,由于光标悬停在变量 i 的上方,所以可以看到“i=3”的提示信息,即在程序执行到当前黄色高亮的这一行(断点行)时,变量 i 的取值为数字3。而在第二个截图中,光标悬停在表达式“Cells(i ,3)”的上方,所以这时的提示信息就变成了第i行第3列单元格的内容,也就是“负数”。

使用光标悬停方法查看程序状态十分方便,但是必须把光标移动到某个变量或表达式上方才能观察其取值,无法同时、持续地观察多个内容。因此,VBA为我们提供了另一个更加正规的观察工具,即“监视(Watch)”。

只要在VBE代码窗口中单击鼠标右键,并在弹出的菜单中选中“添加监视”(在英文版Office中为“Add Watch”)命令,就可以弹出“添加监视”对话框。在“表达式”一栏中输入想查看的变量或表达式,并单击“确定”按钮,就会发现VBE下方出现一个“监视窗口”,我们要观察的内容也会详细地列示在这个窗口中。图6.8就是通过上述方式添加了变量 i 和单元格Cells(i,3)两个监视,同时正在准备添加对Cells(i,4)内容的监视。

一旦对某个表达式添加了监视,它的各种信息就会一直显示在监视窗口中,使我们不移动鼠标也能随时看到它的取值变化,除非在监视窗口中单击鼠标右键并选择“删除监视”命令。如果不小心关闭了监视窗口也没有关系,只要在VBA编辑器的菜单栏中找到“视图”—“监视窗口”命令,就可以让它重新出现在屏幕上。

读者可能还注意到,图6.8监视窗口中的第一行“Cells(i,3)”前面有一个加号。这是因为VBA中的Cells单元格其实是“对象类型”数据,内部包含了大量的属性信息,比如这个单元格的位置、行列号、大小等。所以只要单击加号,“Cells(i,3)”就会展开显示它内部的所有属性,供开发者深入分析。关于对象类型的知识本书后面章节很快就会介绍,届时读者就可以充分利用监视窗口的这个功能。

图6.8 在程序中添加监视

6.2.3 “切”脉秘籍
——让程序单步执行

1.单步执行的基本用法

若想快速分析出错误的原因,单靠观察断点处的表达式取值仍然不够。只有深入程序执行流程的内部,看到每行代码运行后的结果,才能逐行排查,最终找到错误根源,这就像中医诊脉时通过持续观察脉象的变化来判断病情一样。

想让程序每运行一行都暂停一次,当然可以通过把每行代码都标记为断点的方式实现,不过实际操作起来十分烦琐。更简单的方法就是使用VBA调试器中的“单步执行”工具。

假如代码中存在一个断点,那么当程序暂停在断点行之后,只要按键盘上的“F8”键,就可以让程序再执行一行并暂停。此时可以看到,断点的下一行代码被高亮显示为黄色背景,相应的,监视窗口中各个表达式的数值也可能因为这次执行而发生变化。连续按“F8”键,就可以持续观察程序状态的变化过程,一旦在单步执行某行时发现异常,就可以基本确定错误与这行有关。下面就结合案例6-2的需求和代码(包含错误),演示这种典型的调试过程。

案例6-2: 如图6.9所示,工作表中存有某学习机构四个季度的报名人数,并要求做一个VBA程序,能够自动将所有人数汇总至C7单元格中。但是由于程序中存在逻辑错误,实际输出结果(及存在错误的代码)并不正确。请利用VBA调试工具发现错误原因并纠正。

在这个案例中,我们发现总计人数计算出错,所以理所当然地想到最有可能出错的地方就是“s=s+Cells(i,3)”这行代码,因为它会直接影响最终汇总结果,所以我们可以在这行代码前面标记断点,如图6.10所示。

图6.9 案例6-2的预期效果、代码(含错)和实际运行结果

图6.10 为案例6-2的代码添加断点

单击“运行”按钮,程序暂停在断点行上。为了深入观察程序变化,我们为变量 s 和单元格Cells(i,3)添加两个监视,如图6.11所示。

图6.11 为案例6-2的代码添加监视

此时可以看到,程序第一次进入For循环就暂停在这里,因而循环变量i为3,相应的单元格Cells(i,3)也就是C3单元格内容为728,完全正常。变量 s 现在的数值为0,也符合我们的预期,因为s的作用就是“累加器”,用于累计所有数字之和。既然程序是第一次执行这句加法操作,而在此之前还没有累加过任何数字,所以s现在为0也是正确的。

因此现在还看不出问题所在,所以按“F8”键,让程序再执行一行试试,如图6.12所示。

图6.12 在断点处按“F8”键执行单步调试

从上图可以看到,按“F8”键后程序又执行了一行,也就是完成了“s=s+Cells(i,3)”的计算,然后暂停在“Next i”语句上。这时再观察监视窗口,发现变量s的数值已经从0变化为728。也就是说,在累加了第一个数字之后,s的结果仍然正确。

既然这一步也看不出异常,那么就再按“F8”键,看看会出现什么情况,如图6.13所示。

图6.13 第二次按“F8”键执行单步调试

第二次执行单步调试后,程序运行了“Next i”语句,让i增加1(也就是从3变为4)。由于i没有超出For语句中指定的范围(3到6),所以再次执行循环体,让光标停留在“s=0”这一句。注意此时的监视窗口,Cells(i,3)的数值变为了698。这是因为此时 i 的数值已经变化为4,所以Cells(i,3)指代的是C4单元格的内容,这也没有问题。

不过到这一步时,很多读者可能已经感觉到问题的所在:如果执行完黄色高亮的这条语句,s的数值就会变回0,也就是说之前累加的结果728将被完全清空,失去意义。为了验证这个猜想,第三次按“F8”键,让程序实际执行这行代码看看结果怎样,如图6.14所示。

图6.14 第三次按“F8”键执行单步调试

果然,执行完这行代码之后监视窗口中 s 的值变为了0,符合我们的分析。如果此时再次按“F8”键,错误原因就更加明显了,如图6.15所示。

图6.15 第四次按“F8”键执行单步调试

可以看到,由于s之前已被清零,所以再次执行加法操作后s的数值等于698,也就是C4单元格的内容,而上一次循环中记录在s中的C3单元格数值则被完全抛弃。可以想见,下次循环时s又会先被清零,再通过加法操作变成C5单元格的数值;最后一次循环时再变成C6单元格中的数值691,也就是图6.9中所示的最终错误结果。于是通过断点、监视和单步这三大调试工具,我们终于查清了这段代码中的逻辑错误。

最后需要说明的是,单步调试并非一定要与断点功能联合使用。如果程序中没有设置断点,按“F8”键后程序将从第一行开始逐行单步执行,此时同样可以使用监视窗口查看表达式数值。

2.逐语句与逐过程的区别

前面介绍的单步调试属于逐语句执行,也就是每按一次“F8”键就执行一行代码。除此之外,VBA中还支持另一种单步调试,称为“逐过程执行”,操作方法为同时按“Shift”键和“F8”键。这种单步调试是针对存在“过程调用”的 VBA 代码设计的,而有关过程调用的知识将在后面章节中讲解。

之所以称为“逐过程执行”,是因为当代码中存在过程调用时,使用这种方法可以把被调用的整个过程当作一行语句,一步执行完毕。而在普通的逐语句执行方式下,程序则会进入被调用的过程,在每行代码上均暂停一次。图6.16所示为两种方式的区别。

图6.16(a)在过程调用处按“F8”键,即逐语句执行

图6.16(b)在过程调用处按“Shift+F8”组

图6.16中的代码包括Demo_A和Demo_B两个子过程,并且Demo_A中的一行代码调用了Demo_B(Call Demo_B)。如果按“F8”键使用逐语句方式对Demo_A进行单步调试,那么在执行完调用语句后就会转入Demo_B中逐行执行。但是如果像图6.16(b)那样按“Shift+F8”组合键,程序则会一次性把Demo_B运行完毕,并直接停到调用语句的下一行。

如果觉得“Shift+F8”这种快捷键组合不便记忆,读者也可以直接在VBA编辑器的“调试”菜单中选择“逐过程”命令,程序运行效果与使用快捷键完全相同。此外,“调试”菜单中还有一个名为“跳出”的菜单。它的用处是:如果像图6.16(a)中那样因为按“F8”键而进入被调用子过程 Demo_B 中,那么可以随时使用这个功能把 Demo_B 的剩余代码一次性执行完毕,并跳回Demo_A 的下行代码上。当程序中存在大量子过程与函数的调用时,使用逐过程执行和跳出功能可以明显提高调试效率。 hS0Kgce4XYmnHQpEC1C1FKfl6to0AjHmQv4EmwqdJCoyb48NLyHD22fb3hx5AXhb

点击中间区域
呼出菜单
上一章
目录
下一章
×