VBA 程序之错误处理 | ExcelFans
现在的位置: 首页 > 精选转贴 > 正文

VBA 程序之错误处理

2014年06月28日 精选转贴 ⁄ 共 6222字 ⁄ 字号 暂无评论 ⁄ 阅读 1,297 次
  
本文非原创,转自网络 club.excelhome.net

 

关于程序错误处理,是一个常见而又非常被容易忽视的问题。错误处理,顾名思义就是程序在发生错误时的处理过程。为什么要有错误处理?如果没有错误处理又会怎么样呢?
我们首先来简单的说说上面两个问题。

 
    一、为什么要有错误处理呢?这是因为任何一个程序都不可能说是不会发生任何错误的(注意:我们这里说的错误只是指的狭义的逻辑错,并不包括语法错)。
    二、如果没有错误处理会发生什么呢?我们先来看一段简单的代码:

 

Sub Test()
    Dim M As Long
    Dim N As Long
    M = Val(InputBox("请输入一个整数", "M*N"))
    N = Val(InputBox("请输入一个整数", "M*N"))
    MsgBox CStr(M) & "×" & CStr(N) & "=" & CStr(M * N)
End Sub

 

这是一个输入两个整数然后输出这个两数乘积的过程,这段程序有没有错误呢?——没有。输入一个"3",再输入一个"2",程序立即输出了"3×2=6"。那这段程序能永远不出错吗?答案也是否定的,两次输入时任意一次输入"A"程序就会报错了,那么是否只要保证两次输入都是整数程序就不会出错了呢,答案还是否定的,只要输出的两个数乘积大于2147483647,程序还是会出错。这正验证了一句话:永远都不要相信用户的输入

 
    既然程序这么容易出错,那么错误处理就是一个程序必不可少的部分,任何一个好的程序员都不能忽视这一环节,谁也不会想让自己的程序随时都可能弹个错误对话框后程序就被非法关闭了。
那么,现在我们就面对了一个新的问题:如何处理错误?

 
    相信多数人立即想到了 On Error Goto XXXX(XXXX表示一个标签或是一个行号)和 On Error Resume Next。是的,这是两种最常见的错误处理方式。但是,有多少人真正了解这两条语句呢。我知道很多人天天都在用这两条语句,对它们的作用随口都能说出来。今天我们暂时就先不说明这两条语句的作用和意义了。我们先还是把它们拆开来看。On Error Goto XXXX 和 On Error Resume Next 我们就先把它们拆成 On ErrorGotoResume Next。我们先来看看 On Error的帮助。

 

On Error 语句


启动一个错误处理程序并指定该子程序在一个过程中的位置;也可用来禁止一个错误处理程序。

语法
On Error GoTo line 
On Error Resume Next
On Error GoTo 0
On Error 语句的语法可以具有以下任何一种形式:
              语句                                                             描述
On Error GoTo line              启动错误处理程序,且该例程从必要的 line 参数中指定的 line 开始。line 参数可以是任何行标签或行号。如果发生一个运行时错误,则控件会跳到 line,激活错误处理程序。指定的 line 必须在一个过程中,这个过程与 On Error 语句相同; 否则会发生编译时间错误。
On Error Resume Next        说明当一个运行时错误发生时,控件转到紧接着发生错误的语句之后的语句,并在此继续运行。访问对象时要使用这种形式而不使用 On Error GoTo。
On Error GoTo 0                 禁止当前过程中任何已启动的错误处理程序。


说明:  如果不使用 On Error 语句,则任何运行时错误都是致命的;也就是说,结果会导致显示错误信息并中止运行。
一个“允许的”错误处理程序是由 On Error 语句打开的一个处理程序;一个“活动的”错误处理程序是处理错误的过程中允许的错误处理程序。如果在错误处理程序处于活动状态时(在发生错误和执行 Resume、Exit Sub、Exit Function 或 Exit Property 语句之间这段时间)又发生错误,则当前过程的错误处理程序将无法处理这个错误。控件返回调用的过程。如果调用过程有一个已启动的错误处理程序,则激活错误处理程序来处理该错误。如果调用过程的错误处理程序也是活动的,则控件将再往回传到前面的调用过程,这样一直进行下去,直到找到一个被允许的但不是活动的错误处理程序为止。如果没有找到被允许而且不活动的错误处理程序,那么在错误实际发生的地方,错误本身是严重的。错误处理程序每次将控件返回调用过程时,该过程就成为当前过程。在任何过程中,一旦错误处理程序处理了错误,在当前过程中就会从 Resume 语句指定的位置恢复运行。
注意 一个错误处理程序不是 Sub 过程或 Function 过程。它是一段用行标签或行号标记的代码。

 
大家看到这里是不是对On Error已经有了一个比较清晰的了解了吧。如果还不是很清楚的话可以再看看下面的过程,它演示了On Error Goto XXXX和On Error Resume Next,相信大家看完后就能立即明白这两条语句了。

 

Sub TestError1()
    Dim I As Long
    On Error Resume Next ' 指定发生错误时不处理,直接运行下一条语句
    I = "A1" ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回直接运行下一条语句。
    Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
    Err.Clear ' 清空所有错误记录
    On Error GoTo ERROR1 ' 指定下面的错误发生时直接跳转至Error1标号处
    I = 2147483648# ' 发生错误,由于指定了跳转,故直接转至Error1,而不会再执行下面的语句
    I = 100
    Debug.Print "程序正常返回,当前I的值=" & I
    Exit Sub
ERROR1:
    Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
End Sub

 

相信现在大家已经完全明白了这两条语句的用法了。但是,大家也看到了,第二条语句出错时,输出错误代码后程序直接退出了,或许有人又会想,如果当第二次错误发生时能不能输出错误代码后再返回到出错的下一条语句再断续执行程序呢?这时我们就要使用到 Resume Next 了。还是老规矩,把它拆成 ResumeNext来看,我们看看 Resume 的帮助。

 

Resume 语句
在错误处理程序结束后,恢复原有的运行。
语法
Resume [0]
Resume Next 
Resume line
Resume 语句的语法可以具有以下任何一种形式:
              语句                                                             描述
Resume                        如果错误和错误处理程序出现在同一个过程中,则从产生错误的语句恢复运行。如果错误出现在被调用的过程中,则从最近一次调用包含错误处理程序的过程的语句处恢复运行。
Resume Next                如果错误和错误处理程序出现在同一个程序中,则从紧随产生错误的语句的下个语句恢复运行。如果错误发生在被调用的过程中,则对最后一次调用包含错误处理程序的过程的语句(或 On Error Resume Next 语句),从紧随该语句之后的语句处恢复运行。
Resume line                在必要的 line 参数指定的 line 处恢复运行。line 参数是行标签或行号,必须和错误处理程序在同一个过程中。


说明: 在错误处理程序之外的任何地方使用 Resume 语句都会导致错误发生。

 
      我们看到了 Resume Next,但是很明显,它只是 Resume 三种调用方式中最常见的一种,不过上面的信息已经足够了,我们使用 Resume Next 就可以达到上面的要求了。

 

Sub TestError2()
    Dim I As Long
    On Error Resume Next ' 指定发生错误时不处理,直接运行下一条语句
    I = "A1" ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回。
    Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
    Err.Clear ' 清空所有错误记录
    On Error GoTo ERROR2 ' 指定下面的错误发生时直接跳转至Error2标号处
    I = 2147483648# ' 发生错误,由于指定了跳转,故直接转至Error1,而不会再执行下面的语句
    I = 100 ' 由于在Error2后面指定了Resume Next,所以程序还会再次返回到这里开始执行,这就是和过程TestError1不同之处
    Debug.Print "程序正常返回,当前I的值=" & I
    Exit Sub
ERROR2:
    Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
    Resume Next ' 返回发生错误处的下一条语句,继续执行
End Sub

如果是在错误处理过程中再发生错误怎么办?或许你想到了用 On Error Resume Next,于是我们就有了这样一段程序

Sub TestError3()
    Dim I As Long
    On Error GoTo ERROR3 ' 指定发生错误时不处理,直接运行下一条语句
    I = "A1" ' 发生错误,由于指定了跳转,故直接转至Error3,而不会再执行下面的语句
    Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
ERROR3:
    Err.Clear ' 清空所有错误记录
    On Error Resume Next ' 指定下面的错误发生时忽略错误,直接跳转至下一条语句继续执行
    I = 2147483648# ' 发生错误,由于已经指定了发生错误时不处理,故Err对象应立即返回,
    ' 但是由于Err对象本身因上一条I = "A1"错误而处于激活状态,这条错误并不能像我们所预期的
    ' 一样直接忽略错误跳到下一条,而是产生了一条严重错误,将会弹出错误提示对话框,程序挂起
    Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
    I = 100
    Debug.Print "程序正常返回,当前I的值=" & I
End Sub

 

执行它后,我们发现了一个严重问题,程序还是抛出了一个异常并被挂起。这是怎么回事?难到是 Resume Next 不起作用了?带着这个疑问我们再次返回到 On Error 的帮助,有这么一段话:

 

一个“活动的”错误处理程序是处理错误的过程中允许的错误处理程序。如果在错误处理程序处于活动状态时(在发生错误和执行 ResumeExit SubExit Function 或 Exit Property 语句之间这段时间)又发生错误,则当前过程的错误处理程序将无法处理这个错误 

 

原来如此,错误处理过程只能是处理别人的错误,一旦处理过程本身发生错误,由于错误处理过程已经处于激活状态,就会导致无法再继续处理错误了。我们正是因为在错误处理过程中又产生了错误,所以程序还是被挂起了。那么,我们该怎么处理才能得到自己想要的结果呢?既然只是错误处理过程在激活状状态下无法处理错误,那么我们是不是只要让错误处理过程返回到非激活状态下,就可以了呢?我们只好请出 Resume line 上场。

 

Sub TestError4()
    Dim I As Long
    On Error GoTo ERROR4 ' 指定发生错误时不处理,直接运行下一条语句
    I = "A1" ' 发生错误,由于指定了跳转,故直接转至Error3,而不会再执行下面的语句
    Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
ERROR4:
    Err.Clear ' 清空所有错误记录
    Resume ERROR4_Next ' 指定错误陷井处理完毕并返回ERROR4_Next,
    ' 这样就做的目的就是为了释放Err对象错误处理,使下面的On Error Resume Next生效
ERROR4_Next:
    On Error Resume Next ' 指定下面的错误发生时忽略错误,直接跳转至下一条语句继续执行
    I = 2147483648# ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回
    Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
    I = 100
    Debug.Print "程序正常返回,当前I的值=" & I
End Sub

 

运行后,我们终于看到了自己想要的结果。
有了上面的一系列实验,我们再回头来看On Error Goto 0Resume 0就非常容易理解了。我们还是再用两段程序来实验一下吧:

 

Sub TestError5()
    Dim I As Long
    On Error Resume Next
    I = 2147483648# ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回
    Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
    On Error GoTo 0 ' 停止在当前过程中处理错误。
    I = "A1" ' 由于错误处理被停止,所以程序会在这里弹出错误,并挂起。
    Debug.Print "程序正常返回,当前I的值=" & I
End Sub


Sub TestError6()
    Dim I As Long
    Dim D As Double
    On Error GoTo ERROR6 ' 指定发生错误时跳转至标号Error6处开始执行
    D = 2147483650#
    I = D ' 发生隐性错误,D的值大于Long能表达的最大值 2,147,483,647
    Debug.Print "程序正常返回,当前I的值=" & I
    Exit Sub
ERROR6:
    Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I & " 当前D的值=" & Format(D, "###,###")
    D = D - 1
    Resume ' Resume等价于 Resume 0 ,即返回上次出错的语句继续执行,注意区分它与 Resume Next 的区别。
    ' 由于前面的 D=D-1 使得发生一次错误就让D自减1,
    ' 这样就使得程序在D的等于2,147,483,647时正常返回退出,但要小心使用这种方式,
    ' 因为如果D是小于Long能表达的最小负数 -2,147,483,648 时会使得程序不断循环出错,
    ' 最终停止在D溢出错误上,相信我,这一定会是个非常痛苦的等待 ^_^
End Sub
 

这两段代码非常的明了,只要运行后就可以直白的看到结果了。

给我留言

您必须 [ 登录 ] 才能发表留言!