处理部件中的错误

在编写部件时,应该准备处理三种类型的错误:

在内部处理错误

处理三种类型错误的第一种,对于部件和其它由 Visual Basic 开发的应用程序来说没有什么区别。这种类型的错误处理在《Visual Basic 程序员指南》的第十三章“调试代码和处理错误”中讨论。这种类型的错误处理在《Visual Basic 程序员指南》的“调试代码和处理错误”中讨论。

把错误传递给客户应用程序

如“由部件生成错误”所述,可以使用 Err 对象的 Raise 方法,在调用该部件提供的方法的客户应用程序中生成错误。对于客户应用程序中生成的错误,必须在方法的错误处理例程中调用 Raise,或者使错误处理失效。如下面假想的 Widget 对象的 Run 方法代码段。

Public Sub Run()
   '使用内部错误处理。
   On Error Resume Next
   '(省略了 Run 方法的多行代码。)
   '如果下面的测试失败了,将在
   '调用该方法的客户应用程序中产生错误。
   If intWidgetState <> STATE_RUNNING Then
      '使内部错误处理无效。
      On Error Goto 0
      Err.Raise _
         Number:=(ERR_WDG_HALTED + vbObjectError), _
         Source:=CMP_SOURCENAME, _
         Description:=LoadResString(ERR_WDG_HALTED)
   End If
   '(继续 Run 方法的代码。)
End Sub

对无效参数值产生的错误,可以简单地把 Err.Raise 语句放在方法的第一个 On Error 语句的前面。对于验证属性值的代码来说也一样,把它放在 Property Let 或 Property Set 的首部。

由错误处理程序产生错误

属性和方法一般含有处理内部错误的代码。这些代码有时无法产生明确的内部响应。在这种情况下,属性或者方法会失败并向客户应用程序返回错误是合理的:

   On Error Goto ErrHandler
   '(省略的代码。)
   Exit Sub

ErrHandler:
   Select Case Err.Number
      '可以处理的错误(没有显示)。
      Case 11
         '除零错误无法以任何明确的方式处理。
         Err.Raise ERR_INTERNAL + vbObjectError, _
            CMP_SOURCENAME, _
            LoadResString(ERR_INTERNAL)
      '其它错误(没有显示)。
   End Select

大多数情况下,向客户应用程序返回原始的“除数为零”错误没有什么意义。客户应用程序不知道在属性和方法中为何发生这样的错误,因此在寻找解决办法时,该消息没有任何用处。

如果因为提供了很好的文档,使客户应用程序的开发人员知道发生这个内部错误的可能性,那么就有可能编写一些代码来处理它,并向应用程序的最终用户显示有用的错误消息。

如果开发人员不处理这个错误,ERR_INTERNAL 至少会在错误消息文本中包含方法参数以供诊断,作为传递给部件开发者的信息。

处理来自其它部件的错误

如果部件使用由其它部件提供的对象,那么必须处理调用这些对象的方法时可能产生的错误。从其它部件的文档中可以获得这些错误的列表。

在部件中处理错误而不是把它们返回给调用部件的客户应用程序,这一点非常重要。客户应用程序只是使用该部件,并对该部件的错误代码作出反应。编写客户应用程序的用户或者编程人员可能对于该部件使用的其它部件一点都不了解。

错误封装

客户应用程序只能接收它直接调用的部件的错误,这个思想反映了面向对象中封装的概念。对于错误来说,封装意味着对象是自包含的。尽管部件中的对象可能使用了由其它部件提供的对象,但客户应用程序不需要知道它们。

因此,在处理从其它部件来的错误时,一般要指定 Raise 方法的 source 参数。否则,客户应用程序 Err 对象的 Source 属性将包含您的部件所调用的部件的名称。

注意 不要使用 Err 对象的 Source 属性决定程序的流向。虽然调试时,Source 属性可能会提供有用的上下文信息,但这些信息可能会依赖其版本。

下面的代码段是使用其它部件对象的方法的错误处理程序。当遇到无法处理的错误或未预料到的错误时,错误处理程序使用自己的错误号和描述信息为客户应用程序产生一个错误。

'假想 Widget 对象 Spin 方法的代码。
Public Sub Spin(ByVal Speed As Double)
   On Error Goto ErrHandler
   '包含调用 Gears 部件的对象的代码(省略)。
   Exit Sub

ErrHandler:
   Select Case Err.Number
      '其它错误(未显示)。
      '---  Gears 部件来的错误。 ---
      Case vbObjectError + 2000
         '2000 号错误交给其它 Gear 
         '处理(代码未显示)。
      Case vbObjectError + 3000
         '3000 号错误引起向下旋转;
         '错误必须返回给调用者。
         Err.Raise ERR_SPN_SPINDOWN + vbObjectError, _
            CMP_SOURCENAME, _
            LoadResString(ERR_SPN_SPINDOWN)
      '--- 从部件来的未预料到的错误。 ---
      Case vbObjectError To (vbObjectError + 65536)
         Err.Raise ERR_SPN_FAILURE + vbObjectError, _
            CMP_SOURCENAME, _
            LoadResString(ERR_SPN_FAILURE)
      '其它错误(未显示)。
   End Select
End Sub

注意 有些部件使用老的错误返回机制。这些部件返回的错误号可能是从 0 到 65535。

Gears 部件产生 3000 号错误时,Spin 方法必须向调用它的应用程序返回一个错误。返回的错误是 Spin 方法的错误。使用 Spin 方法的应用程序不知道 Spin 是用 Gears 部件实现的,因此无法处理 3000 号错误。它所要知道就是产生了向下旋转的条件。

前面的例子中,全局常数 CMP_SOURCENAME 作为 Raise 方法的 source 参数使用。如果在错误处理程序之外产生了错误而没有指定 source 参数,Visual Basic 将使用在“工程属性”对话框“通用”选项卡的“工程名称”字段中的名称,与类模块的 Name 属性共同使用。

了解这个情况可能在调试部件时会有帮助,对于已编译的部件,最好为它产生的所有错误的 source 参数指定一个常数值。

注意 在调用部件时使用内部错误处理常常更加容易,因为调用的方法或调用时的环境不同,同样的错误可能需要不同的反应。如果调用两个不同部件,对于不同的错误它们有可能使用同一个错误号。

转变封装规则

有些情况下,次级部件返回的错误消息可能包含一些客户应用程序最终用户的使用信息。例如错误消息可能包含某个文件的文件名。

此时,需要转变封装规则来传递这些消息。安全的办法是在自己的错误消息文本中包含 Err 对象 Description 属性的文本,如下面的错误处理代码段:

'3033 号错误是由对其它部件的调用返回的;
'消息文本包含了有助于解决问题的文件名。
Case vbObjectError + 3033
   '产生一个 Print Run Failure 错误给客户应用程序,
   '在后面加上由 3033 号错误得到的消息文本。
   Err.Raise _
      ERR_PRINTRUNFAILURE + vbObjectError, _
      CMP_SOURCENAME, _
      LoadResString(ERR_PRINTRUNFAILURE) _
         & Err.Description

某些情况下,知道哪个部件产生了错误、原始的错误号和错误消息文本是什么对最终用户可能会有帮助。您可以把这些信息包含在您产生的错误的描述信息中。

详细信息 请参阅《Visual Basic 程序员指南》的“调试代码和处理错误”或者《语言参考》里的“Erro”或“Err”。