第6章 错误处理和代码调试
良好的代码要具有容错机制,这就需要设计者对可能发生的错误进行预先处理。代码设计完成后还需要对代码进行调试,确保最终交给用户的程序是能够正确运行。本章介绍了在代码实践过程中各种不同的错误处理方法、代码调试方法和工具。
6.1 错误处理机制
设计者要设计一个尽善尽美的程序是很难的。在设计程序的时候就要考虑到用户各种可能的操作以及由某些不正确的操作而产生的错误,并为自己编写的代码添加错误处理机制,使程序能够正常运行。
6.1.1 错误类型
在VBA中将错误类型分为了4种分别是:语法错误、编译错误、运行错误和逻辑错误等。
1. 语法错误
语法错误产生的原因通常是由于用户的输入不当造成的。例如用户将某些关键字书写错误,或者标点符号发生书写错误。语法错误在该行代码输入完毕后即可被发现。默认情况下语法错误会用红色字体显示并弹出一个消息框,在图6-1所示的代码中由于将“dim”输入为“dam”导致了错误发生。
图6-1 语法错误
当用户发生语法错误时候会弹出消息框,不过这个消息框仅仅是提示了这行代码中有错误而已,并没有能够真正地指出错误产生原因。
2. 编译错误
当VBA在编译代码的时候如果发生错误就会产生一个编译错误。编译错误产生的原因通常在于错误的对象方法属性,以及错误的分支结构和循环结构。在图6-2中,输入了“if i>5”并换行,由于在该行中没有“Then”,这就会产生一个编译错误。
图6-2 编译错误
3. 运行错误
运行错误是指在运行期间因为某些非法的操作而导致的错误。例如要对一个数开平方,但是这个数却小于0,此时就会产生一个运行错误。运行错误本身在语法结构上是正确的,只是在代码执行的过程中产生错误。
在图6-3中,设置了变量i,并为其赋值为-4,对变量i开平方,单击工具栏上的运行按钮,发生了图6-3所示的运行错误提示。
图6-3 运行错误
4. 逻辑错误
逻辑错误是指程序在运行后没有得到预期的结果。这是一种最难查找的错误,产生逻辑错误的原因非常多,但是发生逻辑错误的代码看起来仍旧在正常运行,只是运行的结果和预期发生了偏差。
由于逻辑错误没有错误提示,因此需要通过用户的经验和调试方法来找出错误原因。
6.1.2 预防错误发生
由于VBA代码很多时候是针对用户的操作做出相应的反应,因此设计者很难知道用户会做出什么样的操作。代码编写人员在编写的时候就要充分估计不同操作导致发生错误的可能,从而在源头上即加以预防。
例6-01:将单元格A1的值除以单元格A2的值,并将最终的结果用消息框的形式给出。现在给出了一段代码,要求指出代码的设计缺陷,并重新设计代码。
#001: Sub除法运算演示() #002: a = Range("a1") #003: b =Range("a2") #004: c = a / b #005: MsgBox c #006: End Sub
这段代码初看起来并没有什么问题,但是第4行代码中a除以b要获得一个正确的值是有前提的也就是说b不能为0,如果用户将A2单元格的值设置为0,那么程序的运行就会产生错误。
1. 除数为0时的处理方案
针对这种可能发生的错误,对上述代码进行修改,修改后的代码如下所示。
#001: Sub t除法运算演示() #002: a = Range("a1") #003: b =Range("a2") #004: If b = 0 Then #005: MsgBox "A2单元格的值不能为0" #006: Exit Sub #007: Else #008: c = a / b #009: MsgBox c #010: End If #011: End Sub
这段代码代码主动为A2设置了取值的门槛,防止了除数为0时错误的发生,在第4行到第10行代码中给出了一个判断分支结构。如果b等于0,那么第5行代码会给出一个错误提示,并执行第6行代码“Exit Sub”提前退出过程。如果变量b的值不等于0,那么就执行结果第8行的除法运算。
2. 除数不是数值时的处理方案
问题到此为止似乎解决了,不过实际上问题还依然存在结束,如果用户在A2单元格中输入的不是数字而是字符串,例如在A2中输入“abcd”,上述代码执行后仍旧会发生如图6-4所示的错误。
图6-4 错误提示
为了修改考虑到这个因素,需要对上述代码进一步修改,当用户输入了无法运算的数据时,就用消息框给出错误提示。
#001: Sub除法运算演示() #002: a = Range("a1") #003: b = Range("a2") #004: If IsNumeric(a) and IsNumeric(b) Then #005: If b = 0 Then #006: MsgBox "A2单元格的值不能为0" #007: Exit Sub #008: Else #009: c = a / b #010: MsgBox c #011: End If #012: Else #013: MsgBox "指定单元格的内容不能为非数值内容!" #014: End If #015: End Sub
第4行到第14行代码是一个复杂的分支结构,第4行代码中用“IsNumeric”函数来判断变量a和变量b的数据是否为数值类型。如果不能同时满足a和b都是数值类型,那么就用一个消息框来给出错误提示。如果a和b同时为数值类型,那么就进一步判断作为分母b其值是否为0。
到此为止上述代码基本上可以完成要求,当用户输入正确内容的时候能够给出正确的结果,如果输入了不能进行除法运算的内容时,就会用消息框的方式给出错误提示。
6.1.3 错误捕获
对代码中可能发生的错误用分支结构进行判断预防可以看做是“堵错误”的方法。但是我们也可以看到,即使是很简单的除法运算,其代码长度都很可观,更不要说其他更加复杂的问题了。为此仅仅采取预防错误发生的方法是不可行的,应该采取疏导错误的处理方法。
所谓疏导错误其思路就是用户设计的代码如果要运行无非是结果,一种结果是代码得出正确的结论,一种是代码发生了错误。一旦代码发生错误可以去执行错误处理代码从而使得整个程序运行不至于中断。
在VBA中一旦发生错误,就会产生一个错误对象Err,错误对象最显著的作用有两点:
● 提示用户发生错误,并将错误用消息框的方式显示出来。
● 终止了代码的运行。
图6-4所示的内容表示执行代码时发生了错误,此时产生了一个错误对象Err。该对象产生时会显示一个消息框,在消息框中显示错误编号和错误原因。在图6-4中,“运行时错误13”表示错误的编号,“类型不匹配”表示错误发生的原因。
说明
VBA给出的错误编号或者错误原因并不一定都是准确的,但是至少告诉了用户一点那就是代码发生了错误。
发生错误后,将不会执行发生错误处以后的代码。
用户可以通过代码来获得Err对象的信息,这个过程称为错误的捕获。要在代码中使用错误捕获功能,需要在可能发生错误的代码之前放置一句“On Error”。
例6-02:通过代码捕获除数为0时候的错误,并用消息框给出错误的原因和错误的编号。
#001: Sub错误捕获() #002: On Error Resume Next #003: a = Range("a1") #004: b = Range("a2") #005: c = a / b #006: MsgBox "错误原因是:" & Err.Description & vbNewLine _ #007: & "错误原编号是:" & Err.Number #008: End Sub
第2行代码用了“On Error Resume Next”代码中一旦发生错误就捕获错误对象,然后忽略错误。如果不忽略错误,那么在5行代码出错后,第6行和第7行代码根本不会执行。第6行代码中“Err.Description”表示捕获错误对象的内容,第7行代码中“Err.Number”表示捕获错误对象的编号。上述代码运行的结果会产生如图6-5所示的消息框。
图6-5 错误编号
说明
Err.Number也可以简略写为Err。
6.1.4 错误处理方式
对于已经捕获的错误对象,代码编写者处理时无非是两种态度。
● 忽略错误:有的错误是无关紧要的,完全可以通过忽略的方式来进行。在这种方式下,发生错误后会继续执行后续代码。忽略错误需要在代码中使用“On Error Resume Next”。
● 不忽略错误:让代码跳转到错误处理环节进行相关的错误处理。在代码中必须使用“On Error GoTo错误标签”。
1. 忽略错误
“On Error”的字面含义就是“当发生错误时”,“On Error”语句要放在可能发生错误的代码之前。
忽略错误是一种编程技巧。通过忽略错误可以简化判断过程。例如用代码删除工作表中的名为“Sheet1”的工作表,就是一个容易发生错误的环节,因为在删除工作表的时候,如果工作表中根本就没有一个名为“Sheet1”的工作表,那么就会产生一个错误。实际上这个错误并不严重,用户删除Sheet1工作表的本意无非是如果存在Sheet1工作表就删除它,如果不存在Sheet1工作表就忽略删除操作。
例6-03:不使用条件判断完成删除Sheet1工作表,并且在删除过程中不出现提示对话框。
#001: Sub删除工作表() #002: Application.DisplayAlerts = False #003: On Error Resume Next #004: Worksheets("sheet1").Delete #005: Application.DisplayAlerts = True #006: End Sub
第2行代码使用“Application.DisplayAlerts = False”的目的是删除工作表的时候不出现系统内置的提示对话框,它和第5行代码是成对出现的。第3行代码表示当错误发生时就忽略错误,接着执行错误后的代码。第4行代码使用Delete方法删除Sheet1,有关对于工作表的操作将在今后的章节中详细叙述。
由于有了第3行代码忽略错误,用户即使多次运行例6-03所示的代码也不会发生错误。
2. 不忽略错误
当然也并不是所有的错误都可以被忽略的,更多的时候发生一旦发生错误就要进入错误处理代码,用错误转向处理的方式进行处理。其语法结构如下所示:
On Error Goto错误标签
可能发生错误的代码
Exit Sub
错误标签:
……错误处理代码
错误标签可以用英文来表示,一般可以用一个有意义名称来表示,该名称可以是字母也可以用汉字来表示。错误标签后加上冒号“:”标示该标签以后的代码就是错误处理代码。
例6-04:仍旧是针对上述除法运算,在代码中使用“On Error GoTo ”的方法来进行错误处理。
#001: Sub除法运算演示() #002: On Error GoTo cuowu #003: a = Range("a1") #004: b = Range("a2") #005: c = a / b #006: MsgBox c #007: Exit Sub #008: cuowu: #009: MsgBox "指定单元格数值不合法!" #010: End Sub
第2行代码中,“cuowu”就是一个错误标签,表示如果在后续代码中发生错误就要转而执行第8行以后的代码。
第3行到第6行代码是一个正常的除法运算过程。第7行代码中使用了“Exit Sub”语句,提前退出Sub过程。之所以要有第7行代码存在的原因是因为VBA中代码的执行顺序是从上到下进行的,即使代码执行过程中没有发生错误,如果没有第7行代码,那么仍旧会执行第8行和第9行代码。例如在A1单元格内输入1,A2单元格内输入2,如果没有“Exit Sub”语句,那么最终执行的时候也会执行错误处理的代码,弹出“指定单元格数值不合法!”的消息框。为了避免出现这样的情况,就需要让其避开错误处理代码,用“Exit Sub”提前跳出过程。
第9行代码是错误处理代码,此处的错误处理比较简单,就是弹出一个消息框告诉用户错误的原因。