设计错误处理程序

错误处理程序是应用程序中捕获和响应错误的例程。对于预感可能会出错的任何过程(应该假定任何 Basic 语句都可能导致错误,除非确知情况并非如此),均要对这些过程添加错误处理程序。设计错误处理程序的进程包括三步:

  1. 当错误发生时,通知应用程序在分枝点(执行错误处理例程的地方)设置或激活错误捕获。

    On Error 语句激活捕获并指引应用程序到标记着错误处理例程开始的标号处。

    在 Errors.vpb 示例应用程序中,FileExists 函数包含错误处理例程,该例程名为 CheckError

  2. 编写错误处理例程,这对所有能预见的错误都作出响应。如果在某些点,控件实际上分支进入捕获,则说捕获是活动的。

    CheckError 例程使用 If...Then...Else 语句处理错误,该语句响应 Err 对象 Number 属性中的值,该值为数值代码,对应于一个 Visual Basic 错误。在示例中,如果产生“磁盘未准备”错误,则信息提示用户关闭驱动器门。如果发生“设备不可用”错误,则会显示不同信息。如果发生任何其它错误,则显示恰如其分的描述并终止程序。

  3. 退出错误处理例程。

    在出现“磁盘未准备”错误的情况下,Resume 语句使代码后退到语句出错的地方分枝。然后,Visual Basic 再次执行那条语句。如果状态还未变化,则发生另一条错误,而且,执行后退到错误处理例程处分枝。

    在出现“装置不可用”错误的情况下,Resume Next 语句使代码在出错语句之后的那条语句处分枝。

这一主题余下的部分提供有关如何完成这些步骤的详细资料。阅读这些步骤时请参阅 FileExists 函数示例。

设置错误捕获

当 Visual Basic 执行 On Error 语句时激活错误捕获,On Error 语句指定错误处理程序。当包含错误捕获的过程是活动的时候,错误捕获始终是激活的,也就是说,直到该过程执行 Exit Sub、Exit 函数、Exit 属性、End Sub、End 函数或 End 属性语句时,错误捕获才停止。尽管在任一时刻任一过程中只能激活一个错误捕获,但可建立几个供选择的错误捕获并在不同的时刻激活不同的错误捕获。借助于 On Error 语句的特例 - On Error GoTo 0 也能停用某一错误捕获。

为设置一个跳转到错误处理例程的错误捕获,可用 On Error GoTo line 语句,此处,line 指出识别错误处理代码的标签。在 FileExists 函数示例中,标签是 CheckError。(虽然冒号是标签的一部分,但在 On Error GoTo line 语句中未使用它。)

详细信息 有关停用错误处理的详细信息,请参阅本章后面的“关闭错误处理”中的内容。

编写错误处理例程

书写错误处理例程的第一步是添加行标签,标志着错误处理例程开始。行标签应该有一个具有描述性的名称,其后必须加冒号。有这样一个公共约定,即把错误处理代码放置在过程末端,该过程在紧靠行标签前方处具有 Exit Sub、Exit 函数或 Exit 属性语句。这样,如果未出现错误,则过程可避免执行错误处理代码。

错误处理例程体包含实际处理错误的代码,通常以 Case 或 If...Then...Else 语句的形式出现。需要确定可能会发生什么错误并对每种错误提供操作方法,例如,在“磁盘未准备”错误的情况下,要提示用户插盘。为了处理所有意想不到的错误,总应使用 Else 或 Case Else 语句提供选项,例如,在 FileExists 函数示例中,该选项先警告用户,然后结束应用程序。

Err 对象的 Number 属性包含有数值代码,代码代表最新的运行时错误。借助 Err 对象与 Select Case 或 If...Then...Else 语句的组合,可对出现的任何错误采取针对性的操作。

注意 包含在 Err 对象 Description 属性中的字符串可以解释错误,该错误与当前的错误号相关联。在不同的 Microsoft Visual Basic 版本中,正确描述的措词也许会有变化。因此,请用 Err.Number 而不要用 Err.Description 识别已发生的具体错误。

退出错误处理例程

FileExists 函数示例使用错误处理程序内的 Resume 语句重新执行最初导致错误的那条语句,使用 Resume Next 语句把执行返回到出错语句之后的那条语句。还有其它退出错误处理例程的方法。跟据具体情况,可用下表中的任一语句实现这一操作。

语句 描述
Resume [0] 对于出错的语句,或者对于最近曾执行过程调用的语句(这里,过程包括错误处理例程),程序执行会恢复这些语句。改正了产生错误的条件后,可用它重复操作。
Resume Next 在紧接着出错语句之后的那条语句处恢复程序执行。如果错误发生在包含错误处理程序的过程之外,而且所调用的过程不具有激活的错误处理程序,则在调用了出现错误的过程之后的那条语句处,执行恢复。
Resume line line 指定的标签处恢复程序执行,此处,line 是行标签(或是非零的行号),必须与错误处理程序在同一过程中。
Err.Raise Number:= number 触发运行时错误。在错误处理例程内执行这一语句时,Visual Basic 搜索另一个错误处理例程的调用列表。(调用列表是为到达当前执行点而调用的过程链。请参阅本章后面的“ 错误处理的分层结构”一节的内容。)

Resume 和 Resume Next 语句间的差别

Resume 和 Resume Next 之间的差别如图 13.1 所示。

图 13.1 Resume 和 Resume Next 的程序流程

一般来说,错误处理程序无论何时修正错误,都可使用 Resume,而在错误处理程序不能修正错误时使用 Resume Next。可编写这样一个错误处理程序,使用户看不到运行时存在错误的,又不为用户显示错误信息,也不允许用户进行修改。

例如,在以下代码示例中,Function 过程用错误处理来对参数施行“安全”除法,却不揭示可能发生的错误。当施行除法时,可能产生的错误有:

错误 原因
“被零除” 分子非零,但分母为零。
“溢出” 分子和分母都为零(在浮点除法中)。
“非法的过程调用” 分子或分母不是数值(或不能认为是数值)。

在这三种情况中,以下 Function 过程捕获这些错误并返回 Null:

Function Divide (numer, denom) as Variant
   Dim Msg as String
   Const mnErrDivByZero = 11, mnErrOverFlow = 6
   Const mnErrBadCall = 5
   On Error GoTo MathHandler
      Divide    = numer / denom
      Exit Function
MathHandler:
   If Err.Number = MnErrDivByZero Or _
   Err.Number = ErrOverFlow _
   Or Err = ErrBadCall Then
      Divide = Null   ' 如果错误是被零除、溢出或非法过程调用,
                     ' 则返回 NULLElse
      '显示意想不到的错误信息。
      Msg = "Unanticipated error " & Err.Number
      Msg = Msg & ": " & Err.Description
      MsgBox Msg, vbExclamation
   End If         ' 不管什么情况, Resume Next
   Resume Next      ' 总在 Exit 函数语句处继续执行。
End Function

在指定行恢复执行

Resume Next 也可用于发生在循环内的错误,此时,需要重启动操作。还可使用 Resume line ,它将控件返回到指定的行标签。

以下示例说明了 Resume line 语句的用法。前面所举示例 FileExists 的变异,如果文件存在的话,该函数允许用户进入函数返回的文件规格说明。

Function VerifyFile As String
   Const mnErrBadFileName = 52, _
   mnErrDriveDoorOpen = 71
   Const mnErrDeviceUnavailable = 68, _
   mnErrInvalidFileName = 64
   Dim strPrompt As String, strMsg As String, _
   strFileSpec As String
   strPrompt = "Enter file specification to check:"
StartHere:
   strFileSpec = "*.*"   ' 用缺省的指定启动。
   strMsg = strMsg & vbCRLF & strPrompt
   ' 允许用户修改缺省。
   strFileSpec = InputBox(strMsg, "File Search", _
   strFileSpec, 100, 100)
   ' 如果用户删除缺省则退出。
   If strFileSpec = "" Then Exit Function
   On Error GoTo Handler
      VerifyFile = Dir(strFileSpec)
      Exit Function
Handler:
   Select Case Err.Number   ' 分析错误代码并加载消息。
      Case ErrInvalidFileName, ErrBadFileName
         strMsg = "Your file specification was "
         strMsg = strMsg & "invalid; try another."
      Case MnErrDriveDoorOpen
         strMsg = "Close the disk drive door and "
         strMsg = strMsg & "try again."
      Case MnErrDeviceUnavailable
         strMsg = "The drive you specified was not "
         strMsg = strMsg & "found. Try again."
      Case Else
         Dim intErrNum As Integer
         intErrNum = Err.Number
         Err.Clear            ' 清除 Err 对象。
         Err.Raise Number:= intErrNum   ' 重新产生该错误。
   End Select
   Resume StartHere   ' 跳回 StartHere 标号,用户可尝试另一文件名。
End Function

如果找到了与说明书匹配的文件,则函数返回文件名。如果未找到匹配文件,则函数返回零长度的字符串。如果一个预期的错误发生,则信息被赋给 strMsg 变量,而且将跳回到 StartHere 标签。这为用户提供了另外一个进入有效路径和文件规格说明的机会。

如果是意想不到的错误,则 Case Else 段重新生成错误,以便调用列表中的下一个错误处理程序能捕获该错误。这是非常必要的,因为,如果不重新生成错误,则代码将继续在 Resume StartHere 行处执行。重新生成错误,实际上就是再次产生错误;这样,在调用堆栈的下一层将捕获新的错误。

详细信息 请参阅本章后面“错误处理的分层结构”中的内容。

注意 虽然使用 Resume line 是写代码的合法方法,但是,增加向行标签的跳转可能使代码难以理解和调试。