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

Office Excel VBA 程序之错误处理 基础教程

我们首先来简单的说说上面两个问题。

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

二、如果没有错误处理会发生什么呢?我们先来看一段简单的代码:

SubTest()

DimMAsLong

DimNAsLong

M=Val(InputBox("请输入一个整数","M*N"))

N=Val(InputBox("请输入一个整数","M*N"))

MsgBoxCStr(M)&"×"&CStr(N)&"="&CStr(M*N)

EndSub

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

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

那么,现在我们就面对了一个新的问题:如何处理错误?

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

OnError语句

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

语法

OnErrorGoToline

OnErrorResumeNext

OnErrorGoTo0

OnError语句的语法可以具有以下任何一种形式:

语句描述

OnErrorGoToline启动错误处理程序,且该例程从必要的line参数中指定的line开始。line参数可以是任何行标签或行号。如果发生一个运行时错误,则控件会跳到line,激活错误处理程序。指定的line必须在一个过程中,这个过程与OnError语句相同;否则会发生编译时间错误。

OnErrorResumeNext说明当一个运行时错误发生时,控件转到紧接着发生错误的语句之后的语句,并在此继续运行。访问对象时要使用这种形式而不使用OnErrorGoTo。

OnErrorGoTo0禁止当前过程中任何已启动的错误处理程序。

说明:如果不使用OnError语句,则任何运行时错误都是致命的;也就是说,结果会导致显示错误信息并中止运行。

一个“允许的”错误处理程序是由OnError语句打开的一个处理程序;一个“活动的”错误处理程序是处理错误的过程中允许的错误处理程序。如果在错误处理程序处于活动状态时(在发生错误和执行Resume、ExitSub、ExitFunction或ExitProperty语句之间这段时间)又发生错误,则当前过程的错误处理程序将无法处理这个错误。控件返回调用的过程。如果调用过程有一个已启动的错误处理程序,则激活错误处理程序来处理该错误。如果调用过程的错误处理程序也是活动的,则控件将再往回传到前面的调用过程,这样一直进行下去,直到找到一个被允许的但不是活动的错误处理程序为止。如果没有找到被允许而且不活动的错误处理程序,那么在错误实际发生的地方,错误本身是严重的。错误处理程序每次将控件返回调用过程时,该过程就成为当前过程。在任何过程中,一旦错误处理程序处理了错误,在当前过程中就会从Resume语句指定的位置恢复运行。

注意一个错误处理程序不是Sub过程或Function过程。它是一段用行标签或行号标记的代码。

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

SubTestError1()

DimIAsLong

OnErrorResumeNext’指定发生错误时不处理,直接运行下一条语句

I="A1"’发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回直接运行下一条语句。

Debug。Print"被忽略的错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

Err。Clear’清空所有错误记录

OnErrorGoToERROR1’指定下面的错误发生时直接跳转至Error1标号处

I=2147483648#’发生错误,由于指定了跳转,故直接转至Error1,而不会再执行下面的语句

I=100

Debug。Print"程序正常返回,当前I的值="&I

ExitSub

ERROR1:

Debug。Print"发生错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

EndSub

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

Resume语句

在错误处理程序结束后,恢复原有的运行。

语法

Resume[0]

ResumeNext

Resumeline

Resume语句的语法可以具有以下任何一种形式:

语句描述

Resume如果错误和错误处理程序出现在同一个过程中,则从产生错误的语句恢复运行。如果错误出现在被调用的过程中,则从最近一次调用包含错误处理程序的过程的语句处恢复运行。

ResumeNext如果错误和错误处理程序出现在同一个程序中,则从紧随产生错误的语句的下个语句恢复运行。如果错误发生在被调用的过程中,则对最后一次调用包含错误处理程序的过程的语句(或OnErrorResumeNext语句),从紧随该语句之后的语句处恢复运行。

Resumeline在必要的line参数指定的line处恢复运行。line参数是行标签或行号,必须和错误处理程序在同一个过程中。

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

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

SubTestError2()

DimIAsLong

OnErrorResumeNext’指定发生错误时不处理,直接运行下一条语句

I="A1"’发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回。

Debug。Print"被忽略的错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

Err。Clear’清空所有错误记录

OnErrorGoToERROR2’指定下面的错误发生时直接跳转至Error2标号处

I=2147483648#’发生错误,由于指定了跳转,故直接转至Error1,而不会再执行下面的语句

I=100’由于在Error2后面指定了ResumeNext,所以程序还会再次返回到这里开始执行,这就是和过程TestError1不同之处

Debug。Print"程序正常返回,当前I的值="&I

ExitSub

ERROR2:

Debug。Print"发生错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

ResumeNext’返回发生错误处的下一条语句,继续执行

EndSub

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

SubTestError3()

DimIAsLong

OnErrorGoToERROR3’指定发生错误时不处理,直接运行下一条语句

I="A1"’发生错误,由于指定了跳转,故直接转至Error3,而不会再执行下面的语句

Debug。Print"发生错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

ERROR3:

Err。Clear’清空所有错误记录

OnErrorResumeNext’指定下面的错误发生时忽略错误,直接跳转至下一条语句继续执行

I=2147483648#’发生错误,由于已经指定了发生错误时不处理,故Err对象应立即返回,

‘但是由于Err对象本身因上一条I="A1"错误而处于激活状态,这条错误并不能像我们所预期的

‘一样直接忽略错误跳到下一条,而是产生了一条严重错误,将会弹出错误提示对话框,程序挂起

Debug。Print"被忽略的错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

I=100

Debug。Print"程序正常返回,当前I的值="&I

EndSub

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

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

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

SubTestError4()

DimIAsLong

OnErrorGoToERROR4’指定发生错误时不处理,直接运行下一条语句

I="A1"’发生错误,由于指定了跳转,故直接转至Error3,而不会再执行下面的语句

Debug。Print"发生错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

ERROR4:

Err。Clear’清空所有错误记录

ResumeERROR4_Next’指定错误陷井处理完毕并返回ERROR4_Next,

‘这样就做的目的就是为了释放Err对象错误处理,使下面的OnErrorResumeNext生效

ERROR4_Next:

OnErrorResumeNext’指定下面的错误发生时忽略错误,直接跳转至下一条语句继续执行

I=2147483648#’发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回

Debug。Print"被忽略的错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

I=100

Debug。Print"程序正常返回,当前I的值="&I

EndSub

运行后,我们终于看到了自己想要的结果。

有了上面的一系列实验,我们再回头来看OnErrorGoto0和Resume0就非常容易理解了。我们还是再用两段程序来实验一下吧:

SubTestError5()

DimIAsLong

OnErrorResumeNext

I=2147483648#’发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回

Debug。Print"被忽略的错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I

OnErrorGoTo0’停止在当前过程中处理错误。

I="A1"’由于错误处理被停止,所以程序会在这里弹出错误,并挂起。

Debug。Print"程序正常返回,当前I的值="&I

EndSub

SubTestError6()

DimIAsLong

DimDAsDouble

OnErrorGoToERROR6’指定发生错误时跳转至标号Error6处开始执行

D=2147483650#

I=D’发生隐性错误,D的值大于Long能表达的最大值2,147,483,647

Debug。Print"程序正常返回,当前I的值="&I

ExitSub

ERROR6:

Debug。Print"发生错误,错误代码:"&Err。Number&"错误信息"&Err。Description&"错误源:"&Err。Source&"当前I的值="&I&"当前D的值="&Format(D,"###,###")

D=D-1

Resume’Resume等价于Resume0,即返回上次出错的语句继续执行,注意区分它与ResumeNext的区别。

‘由于前面的D=D-1使得发生一次错误就让D自减1,

‘这样就使得程序在D的等于2,147,483,647时正常返回退出,但要小心使用这种方式,

‘因为如果D是小于Long能表达的最小负数-2,147,483,648时会使得程序不断循环出错,

‘最终停止在D溢出错误上,相信我,这一定会是个非常痛苦的等待^_^

EndSub

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

 

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注