本文将详细地讨论在Visual Basic中处理错误发生的On Error命令家族,它们的任务就是安装错误代理并解决错误的发生,并解释错误代码的含义。阅读完本文后,你将能编写基本的错误代理程序,有效地防止以外错误。
作者:甘冀平
2000-11-03
本文包括以下几节内容:
使用On Error语句
如何离开错误处理程序
定义错误常数
将错误处理程序单独存放
理解错误处理的范围
不要嵌套使用错误处理程序 使用On Error语句
Visual Basic程序使用On Error命令来登记错误处理程序,它有下面3种形式:
On Error GoTo 0
On Error Resume Next
On Error GoTo line
These forms tell Visual Basic what it should do when the program encounters an error. The three forms are described in the following sections.
这些代码告诉Visual Basic程序当遇到错误时应该做什么。下面详细介绍这3种语句。
On Error GoTo 0
On Error GoTo 0比较直接,它仅仅中止任何当前安装的错误处理代理(比如在前面安装的On Error GoTo line或者On Error Resume Next语句)。如果程序在其后遇到错误,那么就会崩溃。
On Error Resume Next
这种方法使程序忽视发生的错误,当遇到一个错误时,程序继续在其后执行。如果程序使用了On Error Resume Next命令,那么就应该在每次可能产生错误的操作后面检查一下Err对象的数值。如果Err.Number不为零,就表明操作产生了错误,程序可以对此采取特殊的动作。注意:在可能发生问题的语句后,程序应该立即检查Err.Number的数值,因为,其他的错误操作将复位Err对象并清除前面的错误信息。
许多开发人员在给用户一个通用对话框后使用On Error Resume Next命令。 CommandDialog控件的CancelError属性指明了当用户Cancel对话框时是否产生一个错误。下面的代码段显示了程序如何使用CancelError判断是否继续一个操作,比如装入一个文件。
' 如果用户选择了cancel,就产生一个错误
dlgOpenFile.CancelError = True
' 忽视错误的发生
On Error Resume Next
' 显示对话框
dlgOpenFile.ShowOpen
' 判断是否有错误
If Err.Number = cdlCancel Then
' 如果是用户canceled,就什么也执行
Exit Sub
ElseIf Err.Number <> 0 Then
' 如果是未知错误,就采取另外的行为
:
End If
' 恢复正常的错误处理
On Error GoTo 0
On Error GoTo Line
使用这个方法,将登记一个新的错误处理代理。如果程序遇到错误,控制将转移到定义的程序行,然后再执行特殊的处理。
请看下面的代码:
Private Sub DoSomething()
' 安装错误处理代理
On Error GoTo UnexpectedError
' 日常操作
:
' 使程序不进入下面的错误处理代理程序
Exit Sub
UnexpectedError:
'将错误信息描述给用户
MsgBox "Unexpected error" & _
Str$(Err.Number) & _
" in subroutine DoSomething." & _
vbCrLf & _
Err.Description
Exit Sub
End Sub
如何离开错误处理程序
有下面几种方法可以使程序离开错误处理代理程序的控制,然后返回到正常的执行代码处:
Resume
Resume Next
Exit Sub/Function/Property
End Sub/Function/Property
Err.Raise 下面对它们进行详细地描述。
Resume
执行Resume语句后,将重复执行发生错误的那条命令。如果那条命令仍然不正确,程序将再次发生错误,这将使程序进入一个不停的循环操作中。为了避免这种循环发生,一般不要使用Resume命令,除非在错误处理代理程序中能对错误进行修复。
例如,下面的代码试图装入一个可能存储在软盘上地文件。如果失败,将报告错误,询问用户是否再试一试。如果磁盘不在软驱中,用户可以将之插入然后点击Retry按钮。这里的程序就是使用了Resume命令实现再次打开文件。当程序又一次失败时,就会再次转到错误处理代理程序中,给用户再次修正错误的机会。最终,直到用户修正了错误,或者点击了Cancel按钮。如果点击了Cancel按钮,程序退出,没有打开文件。
Private Sub LoadData(ByVal filename As String)
Dim fnum As Integer
'打开文件
fnum = FreeFile
On Error GoTo OpenError
Open filename For Input As fnum
' 读取数据
On Error GoTo ReadError
:
' 关闭文件
On Error GoTo CloseError
Close fnum
Exit Sub
OpenError:
’打开文件失败,询问用户是否再次打开
If MsgBox("Error" & _
Str$(Err.Number) & _
" opening file " & filename & "." & _
vbCrLf & Err.Description & vbCrLf & _
"Check that the disk is properly " & _
"inserted and click the Retry button.", _
vbRetryCancel, _
"Error opening file") = vbRetry _
Then
'再次打开文件
Resume
End If
’否则,选择Cancel退出这个过程
Exit Sub
ReadError:
MsgBox "Error" & _
Str$(Err.Number) & _
" reading file " & filename & "." & _
vbCrLf & Err.Description
'关闭文件
Close fnum
Exit Sub
CloseError:
' 关闭文件发生错误
MsgBox "Error" & _
Str$(Err.Number) & _
" closing file " & filename & "." & _
vbCrLf & Err.Description
Exit Sub
End Sub
Resume Next
执行Resume Next命令使程序在发生错误那条命令后继续执行,这对于程序和用户不能合理地修正错误时很有意义,但同时程序可能执行不完整。
比如,下面的代码使用CDate函数转换一个字符串为日期格式,如果失败了,错误处理代理程序就分配给start_date变量的值为当前的日期:
Private Sub ValidateStartDate(ByVal date_string As String)
Dim start_date As Date
' 安装错误处理代理
On Error GoTo InvalidDate
' 转换字符串为日期格式
start_date = CDate(date_string)
' 执行关于这个日期的一些操作
:
' 不进入下面的错误处理代理程序中
Exit Sub
InvalidDate:
' 如果日期非法,就使用当前日期
start_date = Date
Resume Next
End Sub
请注意,尽量不要使用这种方法处理错误,因为它采取了“安静”的方式,而不是明显地告诉用户发生了错误。应该是这样,当用户输入了非法日期后,就提示他发生了错误,并要求输入一个新的数值。
Exit Sub/Function/Property
如果程序不能继续它的任务,可以立即使用 Exit Sub、 Exit Function 或者 Exit Property 实现退出。下面的代码是上面那段程序的一个新版本,如果日期字符串非法,就告诉给用户,然后退出程序:
Private Sub ValidateStartDate(ByVal date_string As String)
Dim start_date As Date
' 安装错误处理代理
On Error GoTo InvalidDate
' 转换字符串为日期
start_date = CDate(date_string)
' 执行关于这个日期的一些操作
:
' 不进入下面的错误处理代理程序中
Exit Sub
InvalidDate:
' 如果日期非法,就告诉用户,然后退出
MsgBox "The start date """ & _
date_string & _
""" is invalid. Please enter a new one."
Exit Sub
End Sub
按照这种方式退出程序,调用的程序就不可能告诉用户错误发生了。这意味着:不管程序是否成功,调用程序能够合适地继续执行时,才适于采用这个方法。如果调用者必须要知道错误发生了,那么代码中就必须要使用Err.Raise命令来简短地进行一下描述。
End Sub、 End Function End Property
如果错误处理代理程序最后接触到程序的 End Sub、 End Function 或者 End Property 语句,就会产生自然执行Exit命令的效果。比如,上面那段代码的最后部分可以这么编写:
InvalidDate:
' 如果日期非法,就告诉用户,然后退出
MsgBox "The start date """ & _
date_string & _
""" is invalid. Please enter a new one."
End Sub
可是有时候,这么处理会带来一些混淆。因为,也可能要处理新的错误,那么再随后添加新的错误处理代理程序时,就有可能注意不到上一个错误处理代理程序没有使用Exit语句做为结尾,这样,就会发生上一个错误处理代理程序直接执行到下一个代码段中。比如,下面的代码段中,如果遇到一个非法日期,将会显示2段出错提示信息,而不是实际应该存在的1个:
InvalidDate:
' 如果日期非法,就告诉用户,然后退出
MsgBox "The start date """ & _
date_string & _
""" is invalid. Please enter a new one."
ReadFileError:
'读取文件错误
MsgBox "Error reading the data."
:
End Sub
因此,为了避免这种错误的发生,不要让错误处理代理程序直接接触到过程的End命令做为结束。要使用Exit命令完成离开程序的功能!
Err.Raise
Err对象提供了一个Raise方法,它允许程序产生一个新错误,或者再次产生上一次的错误。它的语法如下:
Err.Raise Number, [Source], [Description], [Helpfile], [Helpcontext]
Number
错误代码。为了在类模块中建立一个新的错误代码,将 vbObjectError 加上实际的代码。
比如: vbObjectError + 1001。
Source
产生错误的对象或者应用的名字。对于对象,使用Project.Class格式。对于程序,使用Project.Routine格式。比如,MyProgram.LoadData.
Description
关于错误的描述字符串。
Helpfile
关于错误详细信息的帮助文件名字。error.
Helpcontext
帮助文件中对应这个错误的上下文ID。
错误信息处理策略
如果程序本身不能处理错误,就应该生成一个新的错误,其中带有相关环境的信息。比如,下面的程序视图读取一个数据文件,如果没有找到文件,FileOpenError错误处理代理程序就生成了myappErrNoInputFile错误,这将提供给调用程序比Visual Basic原本错误信息更多的资料。Visual Basic对这个错误的信息就是“文件没有发现”,然而经过处理后的新出错信息是“输入数据文件没有发现”。甚至于,Err.Description域可以包含没有发现的文件名。
' 定义应用程序错误常量
Private Const myappErrNoInputFile = vbObjectError + 1000
:
'定义Visual Basic错误常量
Private Const vbErrFileNotFound = 53
:
Private Sub ReadInputData(ByVal file_name As String)
Dim file_number As Integer
'打开文件
file_number = FreeFile
On Error GoTo FileOpenError
Open file_name For Input As file_number
' 操作文件出错
On Error GoTo FileReadError
:
' 操作文件
:
'关闭文件
Close file_number
Exit Sub
FileOpenError:
' 打开文件错误
If Err.Number = vbErrFileNotFound Then
'如果文件没有找到,转换错误信息为myappErrNoInputFile.
Err.Raise myappErrNoInputFile, _
"MyApp.ReadInputData", _
"Could not open input file """ & _
file_name & """."
Else
'其他错误处理
Err.Raise Err.Number, _
Err.Source, _
Err.Description, _
Err.HelpFile, _
Err.HelpContext
End If
Exit Sub
FileReadError:
'读取文件错误
:
Exit Sub
End Sub
程序可以使用与下面类似的代码来执行。错误处理代理使用保存在Err对象中的信息,这个信息由Raise方法产生。
On Error GoTo DataInputError
ReadInputData "c:mydata.dat"
Exit Sub
DataInputError:
‘装载数据错误
MsgBox "Error" & Str$(Err.Number) & _
" loading the input data." & vbCrLf & _
Err.Description
如前所示,程序格式化了出错提示信息。为了简单化处理,程序可以不格式化由Raise命令产生的错误描述。比如,
Err.Raise myappErrNoInputFile, _
"MyApp.ReadInputData", _
"Error" & Str$(myappErrNoInputFile) & _
" opening the input file."
当错误发生时,扑捉到错误的代理程序将可能显示类似下面的信息:
Error -2147220504 loading the input data.
Error -2147220504 opening the input file.
将格式化信息的任务交给实际记录错误信息并给用户提供显示信息的程序。
定义错误常数
微软定义的通常错误信息代码范围是从1到65,535,其中从1到1000做保留使用,从31,000到31073被Visual Basic使用。其余范围内的值就是你可以自定义的了。
微软同时建议:定义新的错误常数时,最好在常数vbObjectError上加上一定的数值,比如:
Private Const myclassErrNoInputFile = vbObjectError + 1000
如果遵循了这些原则,自定义的错误代码就不会与微软公司的相覆盖。
但是很不幸,这并不能保证你所定义的错误代码不与其他开发者或者用到的库函数中定义的错误常数发生冲突。为了防止这个冲突,可以定义一个基值,比如vbObjectError,然后再按一定的次序定义新错误常数。比如:
Public Const rayErrorBase = 45300
Public Const rayParametersNotSet = rayErrorBase + 1
Public Const rayInvalidSphereFormat = rayErrorBase + 2
Public Const rayLightAtEye = rayErrorBase + 3
:
如果随后发现新定义的错误常数与其他程序发生冲突,就可以迅速地通过改变基值实现重新定义了所有的错误常量。
将错误处理程序单独存放
请使用Resume、Resume Next、Exit Sub/Function/Property、End Sub/Function/Property 或者 Err.Raise来结束错误处理代理程序的编写。永远不要发生从一个错误代理进入到另一个错误代理的代码处理过程。
比如,下面的代码就发生了这种不好的情况:
Private Sub LoadData(ByVal filename As String)
Dim fnum As Integer
‘文件还没有打开
On Error GoTo FileIsClosed
’打开文件
fnum = FreeFile
Open filename For Input As fnum
‘文件被打开了
On Error GoTo FileIsOpen
’读取数据
:
进入错误处理代理,关闭文件
On Error Resume Next
FileIsOpen:
‘关闭文件
Close fnum
FileIsClosed:
’执行其他任务
:
‘结束程序
End Sub
这段代码就存在几个问题。首先,它让人感到困惑。当添加新的错误处理代理程序时,可能就会发生误会。同时,代码中没有显示错误信息,相反,代码“安静”地执行,就好像没有发生任何错误。所以,为了防止这些,一定记住,单独地编写每块错误处理代理程序。
理解错误处理程序的作用范围
当程序遇到错误时,Visual Basic检查在当前程序段中是否安装了错误处理代理程序。如果安装了,控制将转移到那个程序段中。如果没有发现,Visual Basic上移到调用堆栈中的调用相应,检查是否在那个程序段中安装了错误处理代理程序。如果安装了,系统将在那个错误代理中恢复执行。如果仍没有发现,就一直上移,直到找到一个安装的错误处理代理程序。如果最终没有找到,程序将崩溃。
因此,所有Visual Baisc代码执行时,开始要设有一个事件处理代理或者一个Main程序。这意味着,如果在每个事件处理和Main程序中放置错误处理代理程序,就能防御所有的错误发生。然后,不管程序在哪里发生错误,控制最终都能转移到这里。 不要嵌套使用错误处理程序
错误处理代理程序运行上与其他程序有些不同,在一段错误处理代理的代码中,不可能激活另外的错误处理代理程序。换言之,一段错误处理代理程序中不能使用On Error GoTo来定义扑捉它自身错误的代码。如果其中使用了这个语句,只有当这个错误处理代理程序执行完成并返回到主代码中时,新的错误处理代理程序才可能发生效果。
嵌套使用错误代理的事情非常容易使人困惑。在下面的代码中,如果Subroutine2产生一个错误,就不能确定是否控制将转移到Error1还是Error2中。如果Subroutine1运行正确,控制将转到Error1,但是当Subroutine1发生错误时,控制将转移到Error2:
On Error GoTo Error1
Subroutine1
Subroutine2
Exit Sub
Error1:
On Error GoTo Error2
MsgBox "Error1:" & Str$(Err.Number) & "." & vbCrLf & _
Err.Description
Resume Next
Error2:
MsgBox "Error2:" & Str$(Err.Number) & "." & vbCrLf & _
Err.Description
Resume Next
因此,不要在一段错误处理代理程序中使用On Error语句,应该将所有的错误处理代理程序段按照顺序编写。