Excel VBA代码调试
VBA 一出错就慌,其实问题往往不是“不会写”,而是“不会查”
前面写循环、写判断、写字符串的时候,很多人会有一种感觉:VBA 也还行,规则不算多,照着写总能跑起来。
但一到真的开始做表、处理实际数据,事情就不一样了。
你会发现,最折磨人的,很多时候不是“这句代码我不会写”,而是:
代码为什么报错了代码为什么运行到一半停了代码为什么看起来正常,结果却是错的
这个阶段,我觉得才是很多人真正开始接触 VBA 的分水岭。
因为前面学的是“怎么写代码”,到了这里,学的是“怎么查代码”。
这两个事,不是一回事。
我自己刚开始学 VBA 的时候,也经常干一件很低效的事:一看报错,就马上改代码;一看结果不对,就凭感觉乱猜。改半天,越改越乱。
后来才慢慢发现,很多错误不是靠猜出来的,是靠调试一点一点盯出来的。
所以这篇,我就把 VBA 里最常用的调试思路,按更容易上手的方式,给你捋一遍。
真正常见的错误,其实就三类
先别急着记断点、监视这些按钮。
我觉得更重要的一步,是先把错误分清楚。因为不同类型的错,排查方式其实不太一样。
1)编译错误:代码还没跑,就先被拦下来了
这种错误最直接。
你代码一运行,VBA 就告诉你:不行,这段我看不懂,不能执行。
比如最常见的这种:
-
If写了,End If忘了 -
For写了,Next没对上 -
某些符号漏了,语法结构不完整
这类错误有个特点:程序根本跑不起来。
所以它虽然烦,但反而没那么危险。至少 VBA 会第一时间拦住你,不让你继续错下去。
这类问题我后来有个很实用的习惯,就是别站在“人”的角度看代码,要尽量站在“计算机”的角度看。
你觉得“我明明知道这里是想结束判断,再继续循环”,没用。
计算机不知道。
它只会按顺序,一行一行读。读到哪里没闭合,读到哪里结构对不上,它就卡在那里。
所以很多时候,编译错误提示未必百分百说中了真正的问题,但它一定是在提醒你:前后结构已经乱了。
2)运行时错误:语法没问题,但运行到一半翻车了
这种也很常见。
代码写得没毛病,VBA 也让你运行了,但跑到中间某一行的时候,程序突然停住,弹出一个报错框。
这类错,通常不是“语法不会”,而是数据本身触发了问题。
比如你要算人均费用,公式是:
总费用 ÷ 参与人数
平时都没问题。
但只要某一行的参与人数刚好是 0,这一行一跑,就直接报错了。因为 0 不能当除数。
这种错误的麻烦在于,它不是一开始就能看出来的。
你写代码的时候,根本不知道后面表格里会出现什么值。
所以这类问题,必须在运行中查。
3)逻辑错误:最隐蔽,也最容易坑人
我觉得最烦的,其实是这个。
因为它往往不报错。
程序能跑完,结果也出来了,看起来一切正常,但你认真一看——不对。
而且这种错,比前两种更容易让人怀疑人生。
因为 VBA 不会提醒你。
它只负责执行。你写成乘以 100,它就乘以 100;你不小心写成乘以 1000,它也照样执行。最后结果大了 10 倍,它不会觉得哪里不合理。
所以很多人以为“没报错 = 没问题”,这件事其实特别危险。
很多真正影响结果的错,恰恰都是这种错。
真正好用的调试,不是瞎猜,而是这三样
如果结果不对,我现在一般不会先改代码。
我会先做三件事:
-
先下断点 -
再看变量 -
然后一行一行执行
这套东西一旦用顺了,很多原来看着很玄的错,都会变得很具体。
第一步:先下断点,把程序停在“嫌疑现场”
断点这个东西,说白了就是:
让程序先别往下跑,停在我怀疑的那一行前面。
如果你觉得某一句最可能有问题,就在那一行左边灰色区域点一下。
点完之后,再运行程序,代码跑到这里会自动停住,当前这一行会变成黄色高亮。
有两个点,初学者最好马上记住:
第一,黄色高亮那一行,此时还没有执行。也就是说,程序只是停在这儿,等你看。
第二,不是什么地方都能下断点。
像空白行、纯注释行、Dim 声明变量那种地方,一般就没必要下,也下不了。
如果你断点加多了,后面想一次性清掉,也很简单,直接用:
-
1
Ctrl + Shift + F9
这个我自己用得挺多,不然有时候调着调着,满屏都是断点。
在代码左边这个竖状的条子(即断点栏)上,用鼠标左键单击一下,会出现一个红点,这个就是断点。

第二步:别光看代码,要看变量现在到底变成了什么
程序停下来之后,接下来最重要的,不是继续猜,而是看当前状态。
最简单的方法,就是把鼠标停在变量上。
你停在 i 上,它会告诉你 i 现在是多少。你停在 Cells(i, 2) 这种表达式上,它也会直接显示当前拿到的是哪个单元格、什么值。
这个方法很方便,临时看一眼特别快。
但问题也很明显:你一次只能看一个。
所以如果你想持续盯几个关键变量,更好用的是 监视窗口。
比如你现在怀疑的是:
-
循环变量 i -
累加变量 total -
当前读到的单元格值
那就把这几个都加到监视里。程序每走一步,它们的值都会跟着变。
这时候你看问题会很直观,不再是“我猜它应该变了”,而是“我看到它真的变了”。
鼠标移动到变量这个地方,就可以直接看到现在这个变量的值是什么样子,值是多少。

还可以通过“本地窗口”直接看变量的值,这个看得更清楚。

第三步:按 F8,一行一行走
这个是我觉得最有用的。
如果断点是“先停下”,那 F8 就是“慢慢看”。
按一次 F8,程序执行一行。再按一次,再执行一行。
你就能看到:
这一行执行前是什么状态这一行执行后变量变成了什么到底是从哪一行开始跑偏的
很多逻辑错误,真的不是看出来的,是 F8 一步一步踩出来的。
我用的比较多的还有这个“调试”工具栏。这个工具栏可以单步运行,是使用频率最高的功能。此外,它还可以实现一些高级功能(这些我们以后再说):
我们可以今天先学着使用这个“单步执行”功能,它和 F8 键的功能是一样的。由于系统默认没有开启这个工具栏,我们今天来看一下怎么把它调出来。

就在这个视图,工具栏,调试,把它勾选上就可以了。

调试工具是可以随意拖动的。如果你不喜欢现在这个位置,可以选中左边那几个小点,然后按住左键,就可以随意拖动了。
拿一个最常见的汇总场景说一下
比如现在有一张季度报名人数表,我们想把四个季度的人数加总,写到最后一行。
表格先长这样:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
按理说,这种题很简单,就是循环一遍,把 B2:B5 累加起来,最后写到 B6。
你可能一时不察,写成了下面这样:
-
1 -
2 -
3 -
4 -
5 -
6 -
7 -
8 -
9 -
10 -
11 -
12 -
13
Sub SumEnrollData()Dim i AsIntegerDim total AsLongFor i = 2To5total=0'这里其实写错了,每次循环都把累计结果清零total=total+Cells(i,2).Value '把当前这一行的报名人数加进去Next iCells(6, 2).Value = total '把结果写入 B6EndSub
这段代码能不能运行?
能。
会不会报错?
不会。
但结果一定不对。
因为 total 在每一轮循环里都被重新归零了。最后留下来的,只会是最后一行的值,不会是真正的总和。
这就是特别典型的逻辑错误。
这种错,怎么调?
我一般会这么查。
先在这一行下断点
-
1
total = total + Cells(i, 2).Value
因为这句最直接影响结果。
程序停下来后,先看几个东西:
-
i现在是不是 2 -
Cells(i, 2)现在是不是B2 -
total此时是不是 0
第一次停住的时候,这些一般都没问题。
然后按一次 F8
这时候程序执行完加法,total 变成了 728。
看起来也没问题。
再按一次 F8
程序走到下一轮循环,这时候如果你盯着监视窗口看,就会发现一个很关键的变化:
它又跑回了这句:
-
1
total = 0
也就是说,刚刚累加出来的 728,还没来得及继续往后加,就先被清空了。
问题就出来了。
这种时候你会发现,调试最大的价值不是“帮你自动修复”,而是它能让你真的看到错误发生的那个瞬间。
这个感觉和你肉眼看代码,完全不是一回事。
改对之后,代码应该长这样
-
1 -
2 -
3 -
4 -
5 -
6 -
7 -
8 -
9 -
10 -
11 -
12 -
13 -
14
Sub SumEnrollData_Correct()Dim i AsIntegerDim total AsLongtotal=0'初始化只做一次,放在循环外面For i = 2To5total=total+Cells(i,2).Value '逐行累加 B2:B5 的人数Next iCells(6, 2).Value = total '把最终结果写入 B6EndSub
这个改动其实就一行位置变化。
但如果你没调试过,很多人会盯着整段代码来回看半天,还是不知道问题在哪。
如果你不想老是打断程序,可以用 Debug.Print
前面的断点、监视、F8,都很好用。
但它们有个共同点:会打断程序运行。
有时候我不想让程序停下来,只是想顺手看看某个变量在运行过程中到底怎么变化,这时候我更喜欢用:
-
1
Debug.Print
这个东西特别像在代码里偷偷埋一个“汇报员”。
程序每跑到这里,就往“立即窗口”里打一条信息。
比如还用刚才那个累加的例子,你可以这么写:
-
1 -
2 -
3 -
4 -
5 -
6 -
7 -
8 -
9 -
10 -
11 -
12 -
13 -
14 -
15
Sub SumEnrollData_DebugPrint()Dim i AsIntegerDim total AsLongtotal = 0For i = 2To5total = total + Cells(i, 2).ValueDebug.Print "当前行是"; i; ",累计人数是"; total '把中间结果输出到立即窗口Next iCells(6, 2).Value = totalEndSub
这样程序照常运行,不会中断。
但你打开“立即窗口”之后,会看到一行一行的输出,大概像这样:
-
当前行是 2,累计人数是 728 -
当前行是 3,累计人数是 1426 -
当前行是 4,累计人数是 2171 -
当前行是 5,累计人数是 2862
这个方法特别适合查:
-
循环到底跑了几次 -
某个变量每轮变成了什么 -
程序有没有走进你以为会走的分支
还有个小细节也挺实用。
Debug.Print 里如果你用分号,前后内容会直接接着输出;如果用逗号,两个内容之间会拉开一点距离,效果有点像补了个 Tab。
平时简单输出我一般直接分号就够了。真要排查复杂点的东西,再去细分格式。
还有两个细节,顺手记一下就够用
一个是 F8 和 Shift + F8 的区别。
F8 是逐语句执行,真的一行一行进去看。Shift + F8 是逐过程执行。
什么意思?
就是如果这一行代码里调用了别的过程,按 F8 会直接钻进去,一行一行看那个子过程;按 Shift + F8 则是把那个子过程整体跑完,停在下一句。
当你的代码开始拆成多个 Sub、多个函数之后,这个会明显省时间。
另一个是,调试出错之后,别忘了先“重新设置”。
很多初学者会在暂停状态下直接改代码,然后又觉得怎么结果怪怪的。其实程序还停在调试状态里,现场没清干净。
先重置,再改,再跑,会稳很多。
最后说个我自己的判断
做程序员这么久,我很大的一个感触就是,我们在写代码的时候,其实写逻辑大家可能都差不多,这种简单的业务逻辑相差都不大。
但是真正的高手,是能定位出来问题并快速解决的。
这个能力,本质上就是调试能力。
所以如果你现在刚学到这里,别把调试当成一个附带知识点。
它不是边角料。
很多时候,它才是你开始真正理解 VBA 的那一步。
你后面写循环、写判断、写字符串、写过程,甚至以后写得更复杂一点,都会越来越频繁地用到它。
代码会写,当然重要。
但代码出问题之后,自己能把它找出来,这件事更重要。
夜雨聆风