Хорошие шаблоны для обработки ошибок VBA - PullRequest
70 голосов
/ 24 июня 2009

Каковы хорошие шаблоны для обработки ошибок в VBA?

В частности, что мне делать в этой ситуации:

... some code ...
... some code where an error might occur ...
... some code ...
... some other code where a different error might occur ...
... some other code ...
... some code that must always be run (like a finally block) ...

Я хочу обработать обе ошибки и возобновить выполнение после кода, в котором может возникнуть ошибка. Кроме того, код finally в конце должен ВСЕГДА выполняться - независимо от того, какие исключения вызываются ранее. Как мне достичь этого результата?

Ответы [ 12 ]

97 голосов
/ 26 июня 2009

Обработка ошибок в VBA


  • On Error Goto ErrorHandlerLabel
  • Resume (Next | ErrorHandlerLabel )
  • On Error Goto 0 (отключает текущий обработчик ошибок)
  • Err объект

Свойства объекта Err обычно сбрасываются на ноль или строку нулевой длины в процедуре обработки ошибок, но это также можно сделать явно с помощью Err.Clear.

Ошибки в процедуре обработки ошибок заканчиваются.

Диапазон 513-65535 доступен для ошибок пользователя. Для пользовательских ошибок класса вы добавляете vbObjectError к номеру ошибки. См. Документацию MS о Err.Raise и списке номеров ошибок .

Для не реализованных элементов интерфейса в производном классе следует использовать константу E_NOTIMPL = &H80004001.


Option Explicit

Sub HandleError()
  Dim a As Integer
  On Error GoTo errMyErrorHandler
    a = 7 / 0
  On Error GoTo 0

  Debug.Print "This line won't be executed."

DoCleanUp:
  a = 0
Exit Sub
errMyErrorHandler:
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub

Sub RaiseAndHandleError()
  On Error GoTo errMyErrorHandler
    ' The range 513-65535 is available for user errors.
    ' For class errors, you add vbObjectError to the error number.
    Err.Raise vbObjectError + 513, "Module1::Test()", "My custom error."
  On Error GoTo 0

  Debug.Print "This line will be executed."

Exit Sub
errMyErrorHandler:
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
  Err.Clear
Resume Next
End Sub

Sub FailInErrorHandler()
  Dim a As Integer
  On Error GoTo errMyErrorHandler
    a = 7 / 0
  On Error GoTo 0

  Debug.Print "This line won't be executed."

DoCleanUp:
  a = 0
Exit Sub
errMyErrorHandler:
  a = 7 / 0 ' <== Terminating error!
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub

Sub DontDoThis()

  ' Any error will go unnoticed!
  On Error Resume Next
  ' Some complex code that fails here.
End Sub

Sub DoThisIfYouMust()

  On Error Resume Next
  ' Some code that can fail but you don't care.
  On Error GoTo 0

  ' More code here
End Sub
35 голосов
/ 24 июня 2009

Я бы тоже добавил:

  • Глобальный Err объект - самый близкий к объекту исключения
  • Вы можете эффективно «выбросить исключение» с помощью Err.Raise

И просто для удовольствия:

  • On Error Resume Next - воплощение дьявола, которого следует избегать, так как он молча скрывает ошибки
16 голосов
/ 24 июня 2009

Так что вы могли бы сделать что-то вроде этого

Function Errorthingy(pParam)
On Error GoTo HandleErr

 ' your code here

    ExitHere:
    ' your finally code
    Exit Function

    HandleErr:
        Select Case Err.Number
        ' different error handling here'
        Case Else
            MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "ErrorThingy"
        End Select


   Resume ExitHere

End Function

Если вы хотите печь в пользовательских исключениях. (например, те, которые нарушают бизнес-правила), используйте приведенный выше пример, но используйте goto, чтобы при необходимости изменить поток метода.

11 голосов
/ 12 июня 2014

Вот моя стандартная реализация. Мне нравится, чтобы ярлыки были информативными.

Public Sub DoSomething()

    On Error GoTo Catch ' Try
    ' normal code here

    Exit Sub
Catch:

    'error code: you can get the specific error by checking Err.Number

End Sub

Или с блоком Finally:

Public Sub DoSomething()

    On Error GoTo Catch ' Try

    ' normal code here

    GoTo Finally
Catch:

    'error code

Finally:

    'cleanup code

End Sub
4 голосов
/ 25 июня 2009

Профессиональная разработка Excel имеет довольно хорошую схему обработки ошибок . Если вы собираетесь провести какое-то время в VBA, возможно, стоит приобрести книгу. Есть ряд областей, в которых VBA не хватает, и в этой книге есть хорошие советы по управлению этими областями.

PED описывает два метода обработки ошибок. Основной является система, в которой все процедуры точки входа являются подпроцедурами, а все остальные процедуры являются функциями, возвращающими логические значения.

Процедура точки входа использует операторы On Error, чтобы фиксировать ошибки почти так же, как это было задумано. Процедуры без точки входа возвращают True, если ошибок не было, и False, если были ошибки. Процедуры без точки входа также используют On Error.

Оба типа процедур используют центральную процедуру обработки ошибок, чтобы сохранить ошибку в ее состоянии и зарегистрировать ошибку.

3 голосов
/ 05 декабря 2017

Также имеет отношение к обсуждению относительно неизвестная функция Erl. Если у вас есть числовые метки в вашей процедуре кода, например,

Sub AAA()
On Error Goto ErrorHandler

1000:
' code
1100:
' more code
1200:
' even more code that causes an error
1300:
' yet more code
9999: ' end of main part of procedure
ErrorHandler:
If Err.Number <> 0 Then
   Debug.Print "Error: " + CStr(Err.Number), Err.Descrption, _
      "Last Successful Line: " + CStr(Erl)
End If   
End Sub 

Функция Erl возвращает самую последнюю встречаемую метку числовой строки. В приведенном выше примере, если во время выполнения возникает ошибка после метки 1200:, но до 1300:, функция Erl вернет 1200, поскольку это наиболее недавно успешно встреченная метка строки. Я считаю хорошей практикой размещать метку строки непосредственно над блоком обработки ошибок. Я обычно использую 9999, чтобы указать, что основная часть процедуры достигла ожидаемого сотрясения.

ПРИМЕЧАНИЯ:

  • Метки строк ДОЛЖНЫ быть положительными целыми числами - метка типа MadeItHere: не распознается Erl.

  • Метки строк совершенно не связаны с фактическими номерами строк в VBIDE CodeModule. Вы можете использовать любые положительные числа в любом порядке. В приведенном выше примере имеется всего около 25 строк кода, но номера меток строк начинаются с 1000. Нет никакой связи между номерами строк редактора и номерами строк, используемыми с Erl.

  • Номера меток строк не обязательно должны быть в каком-либо определенном порядке, хотя, если они расположены не в порядке возрастания сверху вниз, эффективность и польза от Erl значительно уменьшаются, но Erl все равно будет сообщать о правильный номер.

  • Метки строк относятся к процедуре, в которой они появляются. Если процедура ProcA вызывает процедуру ProcB и возникает ошибка в ProcB, которая передает управление обратно на ProcA, ErlProcA) вернет самый последний встреченный номер метки строки в ProcA до того, как это звонит ProcB. Из ProcA вы не можете получить номера меток строк, которые могут появиться в ProcB.

Соблюдайте осторожность при размещении меток с номерами строк в цикле. Например,

For X = 1 To 100
500:
' some code that causes an error
600:
Next X

Если код, следующий за меткой строки 500, но до 600, вызывает ошибку, и эта ошибка возникает на 20-й итерации цикла, Erl вернет 500, даже если встречено 600 успешно в 19 предыдущих циклах.

Правильное размещение меток строк в процедуре имеет решающее значение для использования функции Erl для получения действительно значимой информации.

В сети имеется любое количество бесплатных утилит, которые автоматически вставляют числовые метки строк в процедуру, поэтому вы получаете подробную информацию об ошибках при разработке и отладке, а затем удаляете эти метки после запуска кода.

Если ваш код отображает информацию об ошибке для конечного пользователя при возникновении непредвиденной ошибки, предоставление значения из Erl в этой информации может значительно упростить поиск и устранение проблемы, чем если значение Erl не сообщается.

3 голосов
/ 12 октября 2017

Код ниже показывает альтернативу, которая обеспечивает только одну точку выхода для подфункции / функции.

sub something()
    on error goto errHandler

    ' start of code
    ....
    ....
    'end of code

    ' 1. not needed but signals to any other developer that looks at this
    ' code that you are skipping over the error handler...
    ' see point 1...
    err.clear

errHandler:
    if err.number <> 0 then
        ' error handling code
    end if
end sub
3 голосов
/ 30 октября 2015

Вот довольно приличный шаблон.

Для отладки: при возникновении ошибки нажмите Ctrl-Break (или Ctrl-Pause), перетащите маркер разрыва (или как он там называется) вниз на строку возобновления, нажмите F8, и вы перейдете к строке «скинул» ошибку.

ExitHandler - это ваше "Наконец-то".

Песочные часы будут убивать каждый раз. Текст строки состояния будет очищаться каждый раз.

Public Sub ErrorHandlerExample()
    Dim dbs As DAO.Database
    Dim rst As DAO.Recordset

    On Error GoTo ErrHandler
    Dim varRetVal As Variant

    Set dbs = CurrentDb
    Set rst = dbs.OpenRecordset("SomeTable", dbOpenDynaset, dbSeeChanges + dbFailOnError)

    Call DoCmd.Hourglass(True)

    'Do something with the RecordSet and close it.

    Call DoCmd.Hourglass(False)

ExitHandler:
    Set rst = Nothing
    Set dbs = Nothing
    Exit Sub

ErrHandler:
    Call DoCmd.Hourglass(False)
    Call DoCmd.SetWarnings(True)
    varRetVal = SysCmd(acSysCmdClearStatus)

    Dim errX As DAO.Error
    If Errors.Count > 1 Then
       For Each errX In DAO.Errors
          MsgBox "ODBC Error " & errX.Number & vbCrLf & errX.Description
       Next errX
    Else
        MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
    End If

    Resume ExitHandler
    Resume

End Sub



    Select Case Err.Number
        Case 3326 'This Recordset is not updateable
            'Do something about it. Or not...
        Case Else
            MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
    End Select

Он также перехватывает ошибки DAO и VBA. Вы можете поместить Select Case в раздел ошибок VBA, если хотите перехватить определенные номера ошибок.

Select Case Err.Number
    Case 3326 'This Recordset is not updateable
        'Do something about it. Or not...
    Case Else
        MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
End Select
3 голосов
/ 30 октября 2014

Я использую кусок кода, который я разработал сам, и он довольно хорош для моих кодов:

В начале функции или подпрограммы я определяю:

On error Goto ErrorCatcher:

и затем я обрабатываю возможные ошибки

ErrorCatcher:
Select Case Err.Number

Case 0 'exit the code when no error was raised
    On Error GoTo 0
    Exit Function
Case 1 'Error on definition of object
    'do stuff
Case... 'little description here
    'do stuff
Case Else
    Debug.Print "###ERROR"
    Debug.Print "   • Number  :", Err.Number
    Debug.Print "   • Descrip :", Err.Description
    Debug.Print "   • Source  :", Err.Source
    Debug.Print "   • HelpCont:", Err.HelpContext
    Debug.Print "   • LastDLL :", Err.LastDllError
    Stop
    Err.Clear
    Resume
End Select
2 голосов
/ 18 июня 2018

Я считаю, что лучше всего работает следующее, называемое центральным подходом к обработке ошибок.

Преимущества

У вас есть 2 режима запуска приложения: Отладка и Производство . В режиме Debug код остановится при любой непредвиденной ошибке и позволит вам легко выполнить отладку, перейдя к строке, где это произошло, дважды нажав клавишу F8. В режиме Production пользователю будет отображаться значимое сообщение об ошибке.

Вы можете выдать преднамеренные ошибки, подобные этой, что остановит выполнение кода с сообщением пользователю:

Err.Raise vbObjectError, gsNO_DEBUG, "Some meaningful error message to the user"

Err.Raise vbObjectError, gsUSER_MESSAGE, "Some meaningful non-error message to the user"

'Or to exit in the middle of a call stack without a message:
Err.Raise vbObjectError, gsSILENT

Осуществление

Вам необходимо «обернуть» все подпрограммы и функции любым значительным объемом кода со следующими верхними и нижними колонтитулами, указав ehCallTypeEntryPoint во всех ваших точках входа. Обратите внимание также на константу msModule, которую необходимо поместить во все модули.

Option Explicit
Const msModule As String = "<Your Module Name>"

' This is an entry point 
Public Sub AnEntryPoint()
    Const sSOURCE As String = "AnEntryPoint"
    On Error GoTo ErrorHandler

    'Your code

ErrorExit:
    Exit Sub

ErrorHandler:
    If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE, ehCallTypeEntryPoint) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

' This is any other subroutine or function that isn't an entry point
Sub AnyOtherSub()
    Const sSOURCE As String = "AnyOtherSub"
    On Error GoTo ErrorHandler

    'Your code

ErrorExit:
    Exit Sub

ErrorHandler:
    If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Содержимое модуля центрального обработчика ошибок:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: Error handler code.
'
'           Run SetDebugMode True to use debug mode (Dev mode)
'           It will be False by default (Production mode)
'
' Author:   Igor Popov
' Date:     13 Feb 2014
' Licence:  MIT
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Option Explicit
Option Private Module

Private Const msModule As String = "MErrorHandler"

Public Const gsAPP_NAME As String = "<You Application Name>"

Public Const gsSILENT As String = "UserCancel"  'A silent error is when the user aborts an action, no message should be displayed
Public Const gsNO_DEBUG As String = "NoDebug"   'This type of error will display a specific message to the user in situation of an expected (provided-for) error.
Public Const gsUSER_MESSAGE As String = "UserMessage" 'Use this type of error to display an information message to the user

Private Const msDEBUG_MODE_COMPANY = "<Your Company>"
Private Const msDEBUG_MODE_SECTION = "<Your Team>"
Private Const msDEBUG_MODE_VALUE = "DEBUG_MODE"

Public Enum ECallType
    ehCallTypeRegular = 0
    ehCallTypeEntryPoint
End Enum

Public Function DebugMode() As Boolean
    DebugMode = CBool(GetSetting(msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, 0))
End Function

Public Sub SetDebugMode(Optional bMode As Boolean = True)
    SaveSetting msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, IIf(bMode, 1, 0)
End Sub

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: The central error handler for all functions
'           Displays errors to the user at the entry point level, or, if we're below the entry point, rethrows it upwards until the entry point is reached
'
'           Returns True to stop and debug unexpected errors in debug mode.
'
'           The function can be enhanced to log errors.
'
' Date          Developer           TDID    Comment
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 13 Feb 2014   Igor Popov                  Created

Public Function CentralErrorHandler(ErrObj As ErrObject, Wbk As Workbook, ByVal sModule As String, ByVal sSOURCE As String, _
                                    Optional enCallType As ECallType = ehCallTypeRegular, Optional ByVal bRethrowError As Boolean = True) As Boolean

    Static ssModule As String, ssSource As String
    If Len(ssModule) = 0 And Len(ssSource) = 0 Then
        'Remember the module and the source of the first call to CentralErrorHandler
        ssModule = sModule
        ssSource = sSOURCE
    End If
    CentralErrorHandler = DebugMode And ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE And ErrObj.Source <> gsSILENT
    If CentralErrorHandler Then
        'If it's an unexpected error and we're going to stop in the debug mode, just write the error message to the immediate window for debugging
        Debug.Print "#Err: " & Err.Description
    ElseIf enCallType = ehCallTypeEntryPoint Then
        'If we have reached the entry point and it's not a silent error, display the message to the user in an error box
        If ErrObj.Source <> gsSILENT Then
            Dim sMsg As String: sMsg = ErrObj.Description
            If ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE Then sMsg = "Unexpected VBA error in workbook '" & Wbk.Name & "', module '" & ssModule & "', call '" & ssSource & "':" & vbCrLf & vbCrLf & sMsg
            MsgBox sMsg, vbOKOnly + IIf(ErrObj.Source = gsUSER_MESSAGE, vbInformation, vbCritical), gsAPP_NAME
        End If
    ElseIf bRethrowError Then
        'Rethrow the error to the next level up if bRethrowError is True (by Default).
        'Otherwise, do nothing as the calling function must be having special logic for handling errors.
        Err.Raise ErrObj.Number, ErrObj.Source, ErrObj.Description
    End If
End Function

Чтобы установить себя в режиме Debug , выполните следующее в окне Immediate:

SetDebugMode True
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...