Хорошие шаблоны для обработки ошибок VBA

61

Какие хорошие шаблоны для обработки ошибок в 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) ...

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

Теги:
exception
exception-handling

11 ответов

86
Лучший ответ

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


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

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

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

Диапазон значений 513-65535 доступен для пользовательских ошибок. Для пользовательских ошибок класса вы добавляете vbObjectError к номеру ошибки.

Для не реализованных элементов интерфейса в производном классе вы должны использовать константу 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
  • 1
    это замечательно, но есть ли место, где перечислены все ошибки, чтобы я мог знать, существует ли уже мой или нужно ли его создать?
  • 3
    @PsychoData, вот список кодов ошибок support.microsoft.com/kb/146864
Показать ещё 1 комментарий
33

Я бы добавил:

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

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

  • On Error Resume Next является воплощением дьявола и его следует избегать, поскольку он молча скрывает ошибки.
  • 9
    +1 за предупреждение об Eror Resume Next. Вероятно, это одна из причин номер один, почему программы VB, как правило, так полны ошибок.
  • 10
    Не правда. При правильном использовании On Error Resume Next является эквивалентом try / catch. Правильное использование требует проверки или сохранения статуса ошибки после каждой строки . Это делает сложную проверку ошибок намного менее многословной. ОДНАКО, неправильно используется, все вышесказанное применимо.
Показать ещё 5 комментариев
15

Итак, вы можете сделать что-то вроде этого

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 для изменения потока метода по мере необходимости.

  • 1
    Именно так мы обрабатывали ошибки в больших приложениях VB6 в те времена. Работал относительно хорошо и был прост в использовании. IIRC, у нас был класс обработки ошибок, который вызывался вместо кода ошибки в функции. Таким образом, было намного проще изменить поведение.
  • 0
    Обычно рекомендуется помещать «On Error GoTo 0» после блока кода, в котором требуется обработка ошибок. Кроме того, любая ошибка в коде обработки ошибок заканчивается.
Показать ещё 2 комментария
10

Вот моя стандартная реализация. Мне нравятся ярлыки как самоописательные.

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
  • 1
    Что произойдет, если исключение возникло после завершения Finally: Итак, On Error GoTo 0 сразу после завершения Finally: возможно, необходимо исправить нежелательную рекурсию.
  • 2
    Если после блока Finally есть ошибка, он просто выдаст ошибку. Он не будет возвращаться к блоку « Finally . (Попробуйте, вы увидите.) Если вы хотите обработать ошибку после блока Catch2 , вам нужно добавить еще один On Error GoTo , но, возможно, с другой меткой, например Catch2 . Но здесь мы начинаем углубляться в методологию чистого кода -> чистому методу потребуется только один обработчик ошибок (и даже должен иметь свой собственный выделенный метод для перехвата ошибок).
Показать ещё 3 комментария
3

Здесь довольно приличный шаблон.

Для отладки: при возникновении ошибки нажмите 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. Вы можете поместить случай выбора в раздел ошибки VBA, если вы хотите ловушку для определенных номеров Err.

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

Professional Excel Development имеет довольно хорошую схему обработки ошибок. Если вы собираетесь проводить время в VBA, это, вероятно, стоит получить книгу. Существует ряд областей, где отсутствует VBA, и в этой книге есть хорошие предложения по управлению этими областями.

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

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

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

2

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

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

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
1

Также актуальным для обсуждения является относительно неизвестная функция 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 для получения действительно содержательной информации.

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

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

1

В приведенном ниже коде представлен альтернативный вариант, который гарантирует наличие только одной точки выхода для функции sub/.

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
1

Остерегайтесь ловушки слонов:

Я не упоминал об этом в этом обсуждении. [Access 2010]

Как ACCESS/VBA обрабатывает ошибки в объектах CLASS, определяется настраиваемой опцией:

Редактор кода VBA > Инструменты > Параметры > Общие > Ловушка ошибок:

Изображение 3742

1

Мой личный взгляд на выражение, сделанное ранее в этом потоке:

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

В разделе "Ошибка": "Возобновление ошибок" - это воплощение дьявола, которого следует избегать, поскольку оно скрывает ошибки.

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

Когда я делаю это, я добавляю глобальную переменную debugModeOn, и я устанавливаю ее в True. Затем я использую его следующим образом:

If not debugModeOn Then On Error Resume Next

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

Также использовать его при выполнении чего-то, что может потерпеть неудачу, как вызов DataBodyRange объекта ListObject, который может быть пустым:

On Error Resume Next
Sheet1.ListObjects(1).DataBodyRange.Delete
On Error Goto 0

Вместо:

If Sheet1.ListObjects(1).ListRows.Count > 0 Then 
    Sheet1.ListObjects(1).DataBodyRange.Delete
End If

Или проверить наличие элемента в коллекции:

On Error Resume Next
Err.Clear
Set auxiliarVar = collection(key)

' Check existence (if you try to retrieve a nonexistant key you get error number 5)
exists = (Err.Number <> 5)
  • 0
    > If not debugModeOn Then On Error Resume Next В этом случае лучше использовать условную компиляцию , как #If Hide_Errors > 0 Then On Error Resume Next и установите Hide_Errors в свойствах проекта VBA Conditional Complication Arguments соответственно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню