VBA中的错误处理
从理论上讲,VBA没有提供任何的错误处理机制,这种被用在微软Office产品中的以Visual Basic语言为基础的脚本语言根本就不要任何的错误处理,当程序出现错误或产生异常情况时,VBA会自动定位到出错的代码行,然后提示用户出错的可能原因。这是典型的脚本语言的错误提示,联想到javascript语言,在浏览器中如果出现脚本错误,浏览器会给出提示信息,但这并不影响整个程序的正常运行,最多也就是出现错误之后的脚本不被继续解释而已。不过即便如此,javascript还是提供了较为良好的错误处理机制,例如常见的try catch语句和alert提示,以及后来支持的debugger调试信息等,javascript在支持面向对象语言特性的同时也逐渐改善了它的错误处理和调试方法。
然而Visual Basci却没有这么幸运,从诞生之初,Visual Basic就没有提供一个比较好的错误处理机制,尽管我们在实际应用中总会遇到这样或那样的运行时错误(例如错误删除文件、磁盘驱动器空间不够、网络通信发生异常等),但是对于Visual Basic的过程来说根本就没有错误处理,当错误产生时程序便停止运行,直到异常被清除。有关比较详细的介绍Visual Basic的错误处理和调试方法的文章,读者可以参考下面这个链接。
http://www.officexy.com/Articles/office/VBABasic/20061026103436501.htm
VBA的语言特性类似于Visual Basic,应该说它们属于同一家族,所以,用来在Visual Basic中处理程序异常的方法也同样可以被用在VBA中。
在Visual Basic中,常用的程序错误处理的方式是设置或使用错误陷阱,以告诉应用程序当错误发生时转移到何处(或处理当错误发生时要运行的代码),通过在代码中定义标签来告知应用程序当错误发生时要转到的地方。这一点和C系列语言的错误处理方式是相同的。基本步骤如下:
1. 设置一个有效的错误陷阱,以告诉应用程序发生错误时转移到何处继续运行。Visual Basic中的On Error语句可以使错误陷阱有效,并为应用程序指定错误处理的入口。
2. 在错误程序的入口处编写响应错误的具体实现,如继续尝试执行之前的代码、或告知用户出错的具体原因以让用户尝试去解决等。
3. 退出错误处理。
有关如何使用Visual Basic的错误处理和On Error语句的具体含义,读者可以仔细阅读上面给出的那个链接的文章,里面有非常详细的介绍。我在这里会结合实际应用来讲讲在VBA中如何具体使用错误处理。
先看一个简单的示例。
On Error GoTo Err_Handle
Dim a As Integer
Dim b As Integer
Dim c As Integer
a = 10
b = 0
c = a / b '除数为0会导致运行时错误
MsgBox c
Exit Sub
Err_Handle:
MsgBox Err.Description
End Sub
在上述过程中,我们首先通过On Error语句设置了一个错误陷阱,该错误陷阱将自动被激活,同时错误陷阱指向了代码中定义的标签Error_Handle。当过程被调用时,如果出现异常,程序会自动运行标签所指向的代码段,这里会给用户一个提示。Err对象为系统对象,其中包含了当错误发生时的描述信息和错误编号,根据Err对象提供的这些简单信息我们也许可以告知用户应用程序发生了什么事情,从而最终找出出错的具体原因。
在Visual Basic中,我们通过On Error语句设置并激活了一个错误陷阱,直到程序退出过程或方法,该错误陷阱会一直有效。也就是说,我们需要给每一个过程或方法在需要的时候设置单独的错误陷阱,这个有点类似于C的代码中在需要的地方插入try catch语句,错误处理程序在过程或方法内部定义的标签开始的地方,在程序运行时如果错误没有出现,则标签之后的代码应该不会被执行到,因此我们通常都需要在错误处理代码前插入退出语句,例如End Sub或方法的返回语句。
幸运的是我们通常没有必要为每一个过程或方法定义错误陷阱,在VBA中,往往只有一少部分过程或方法需要定义错误陷阱,但是不排除复杂的VBA应用程序,当代码量达到上千行,过程或方法上百个时,应该不亚于一个小的VB系统,这个时候编写一个专门的错误处理函数还是很有必要的。在需要进行错误处理的过程或方法中设置好错误陷阱和用于处理错误的标签,然后在标签后调用错误处理函数并传入Err对象,由错误处理函数专门处理程序中各种不同的错误。这个程序看起来大致是下面这个样子。
Private Const ErrNoPermissions = -2147217900
Private Const ErrCannotLocateURL = -2146697211
Private Const ErrDbDenyConnect = -2147217843
Private Const ErrCannotFoundDbProvider = 3706
'--------------------
' Errors handle:
' Return Description
' 0 Resume
' 1 Resume Next
' 2 Error
Function ErrorsHandle() As Integer
Dim intMsgType As Integer, intResponse As Integer, strMsg As String
Dim myDoc As Worksheet
Set myDoc = ActiveSheet
Select Case Err.Number
Case ErrCannotLocateURL ' Error -2146697211
strMsg = "Cannot connect to the website specified. Please make sure the URL is correct and the website is available."
intMsgType = vbRetryCancel
Case ErrNoPermissions ' Error -2147217900
myDoc.Protect 'Protect the sheet if current user doesn't have permissions to access the data
strMsg = "User " & glUserName & " doesnt have permissions to access the data."
intMsgType = vbExclamation
Case ErrCannotFoundDbProvider ' Error 3706
strMsg = "Please ensure 'Microsoft SQL Server Native Client for SQL2005' installed at first." & _
"You can download at :http://download.microsoft.com/download/4/4/D/" & _
"44DBDE61-B385-4FC2-A67D-48053B8F9FAD/sqlncli.msi"
intMsgType = vbExclamation
Case ErrDbDenyConnect ' Error -2147217843
myDoc.Protect 'Protect the sheet if current user doesn't have permissions to connect the database
strMsg = "Database login failed for user '" & glUserName & "'."
intMsgType = vbExclamation
Case Else
strMsg = "Fatal error." & Err.Description & ".The error number is " & Err.Number
intMsgType = vbCritical
End Select
intResponse = MsgBox(strMsg, intMsgType)
Select Case intResponse
Case 4, 6 ' Retry And Yes
ErrorsHandle = 0
Case 5 ' Ignore
ErrorsHandle = 1
Case Else ' Cancel and Abort
ErrorsHandle = 2
End Select
End Function
稍微做一下解释。当程序发生错误时,Err对象的Number属性会返回一个错误代码,ErrorsHandle函数得到这个错误代码并通过Select Case语句逐一比对错误代码,找到事先定义好的错误处理方法从而返回给用户最准确的信息。程序的一开始定义了一组常量用来描述错误代码所表示的具体含义,在Select Case语句中根据不同的错误代码返回给用户不同的错误描述信息,并且根据错误的种类弹出不同类型的提示框(如确定、重试、取消等),这个是由MsgBox常数所决定的,该常数分为很多种类,可以弹出各种不同类型的提示框,读者可以自己查阅Office帮助文档。如果需要,我们可以随时在Select Case语句中补充更多的内容来定制内容更丰富的错误处理方法,而只需要确认何种错误代码代表何种具体的错误信息即可,这个错误信息我们也可以通过Err.Description属性来获取,尽管这个描述信息通常都并不那么精确。最后ErrorsHandle函数会返回三种不同的结果(当然如果需要你可以让这个函数返回更多的值),用以表示调用它的过程或方法如何继续处理,是终止程序运行,还是尝试再次运行,或者忽略错误继续运行下面的代码等。下面是调用的代码。
Release = 0
Debugger = 1
End Enum
Public Mode As DebugMode
Private Sub CommandButton1_Click()
Mode = DebugMode.Release 'Change the Mode value to Debugger or Release
If Mode = Release Then
On Error GoTo Error_Handle
End If
' TODO Something
Exit Sub
Error_Handle:
errNum = ErrorsHandle
If errNum = 0 Then
Resume
ElseIf errNum = 1 Then
Resume Next
Else
Exit Sub
End If
End Sub
我省略了过程中的具体实现,实际上你可以在这个过程中实现任何功能,只要程序出错,ErrorsHandle方法就会被调用,并且按照事先定义好的错误处理方法告知用户错误产生的具体原因并询问用户如何进行下一步操作。Resume Next语句会跳过错误行继续运行下面的代码,Resume语句则会尝试再次运行出错的代码,其次则是直接退出过程结束程序。为了方便代码的调试,我在程序的最开始设置了一个开关,该开关是一个枚举变量,拥有两个值Release和Debugger,值为Release时设置错误陷阱,值为Debugger时不设置错误陷阱,这个时候VBA会自动定位到出错的代码行并要求用户调试代码(事实上用户根本就不会调试任何代码,这个一般都是留给开发人员来用的)。这样,每次我只需要修改这个枚举变量的值就可以在调试模式和非调试模式之间切换,而不需要每次都注释掉很多代码,毕竟在程序开发过程中调试是非常重要的,我们往往都需要知道程序究竟发生了什么。
这种错误处理的方式我在VBA应用中经常会用到,唯一不方便的就是我们需要在每一个可能出错的过程或方法中设置错误陷阱并调用错误处理函数,可能还要做一些额外的操作,例如上面讲到的设置调试模式的开关、判断错误处理函数的返回值并进行相应的操作等。
其实,On Error语句除了在VBA中被用来进行错误处理外,还有一些特殊的用途,下面是一个例子。
Public Function KeyExists(col As Collection, Key) As Boolean
On Error Resume Next
Dim x As Variant
x = col.Item(Key)
KeyExists = Not (Err.Number = 5)
On Error GoTo 0
End Function
VBA支持集合类型的对象,如Dim country As New Collection,我们可以往集合对象中添加项或移除项,以及查找特定的项。但是VBA中的集合对象功能很有限,它居然没有提供判断集合中的Key(关键字)是否存在的方法。对于集合来说,这个方法非常重要,当你往集合中添加项时,如果Key重复,VBA是会抛出异常的,因此我们需要自己编写一个用来检测集合中的Key是否存在的方法。借助于On Error和Resume Next语句可以实现这个功能,我们可以反复查找集合中的项,如果要查找的项不存在则会抛出异常,这时利用Resume Next语句继续执行下一条语句继续进行查找,直到找到要查找的项。On Error GoTo 0用来关闭错误陷阱。
类似于这样的应用还有很多,我们可以自己控制当程序出错时如何进行下一步操作,从而更方便的管理我们自己的应用程序。
另外,VBA的编辑器也提供了一些辅助功能帮助我们调试代码,如常用的在代码中设置断点、添加和切换书签,以及运行时的对象监视等,读者可以自己去体会。