Правильное обращение с ошибками в VBA (Excel)

Я уже давно работаю с VBA, но я все еще не очень уверен в обработке ошибок.

Хорошая статья – это CPearson.com

Однако мне все еще интересно, был ли способ, которым я использовал ErrorHandling, был / полностью ошибочен: Блок 1

On Error Goto ErrCatcher If UBound(.sortedDates) > 0 Then // Code Else ErrCatcher: // Code End If 

Предложение if, потому что, если оно истинно, оно будет выполнено, и если он не сработает, Goto войдет в часть Else, так как Ubound массива никогда не будет равным нулю или меньше, без ошибки, этот метод работал достаточно хорошо так далеко.

Если я правильно понял, это должно быть так: Блок 2

 On Error Goto ErrCatcher If Ubound(.sortedDates) > 0 Then // Code End If Goto hereX ErrCatcher: //Code Resume / Resume Next / Resume hereX hereX: 

Или даже вот так: Блок 3

 On Error Goto ErrCatcher If Ubound(.sortedDates) > 0 Then // Code End If ErrCatcher: If Err.Number  0 then //Code End If 

Самый распространенный способ, который я вижу, – это то, что ошибка «Catcher» находится в конце sub, а Sub фактически заканчивается раньше с помощью «Exit Sub», но, однако, это немного запутанно, если Sub вполне большой, если вы прыгаете наоборот, чтобы прочитать код?

Блок 4

Источник следующего кода: CPearson.com

  On Error Goto ErrHandler: N = 1 / 0 ' cause an error ' ' more code ' Exit Sub ErrHandler: ' error handling code' Resume Next End Sub 

Должно ли это быть в блоке 3?

Спасибо, что прочитали мой вопрос Приветствие skofgar

Я определенно не буду использовать Block1. Кажется, что неправильный блок Error в IF не связан с ошибками.

Блоки 2,3 и 4 Я думаю, это вариации темы. Я предпочитаю использовать блоки 3 и 4 более 2 только из-за неприязни к заявлению GOTO; Обычно я использую метод Block4. Это один из примеров кода, который я использую, чтобы проверить, добавлена ​​ли библиотека Microsoft ActiveX Data Objects 2.8, и если не добавить или использовать более раннюю версию, если 2.8 недоступен.

 Option Explicit Public booRefAdded As Boolean 'one time check for references Public Sub Add_References() Dim lngDLLmsadoFIND As Long If Not booRefAdded Then lngDLLmsadoFIND = 28 ' load msado28.tlb, if cannot find step down versions until found On Error GoTo RefErr: 'Add Microsoft ActiveX Data Objects 2.8 Application.VBE.ActiveVBProject.references.AddFromFile _ Environ("CommonProgramFiles") + "\System\ado\msado" & lngDLLmsadoFIND & ".tlb" On Error GoTo 0 Exit Sub RefErr: Select Case Err.Number Case 0 'no error Case 1004 'Enable Trust Centre Settings MsgBox ("Certain VBA References are not available, to allow access follow these steps" & Chr(10) & _ "Goto Excel Options/Trust Centre/Trust Centre Security/Macro Settings" & Chr(10) & _ "1. Tick - 'Disable all macros with notification'" & Chr(10) & _ "2. Tick - 'Trust access to the VBA project objects model'") End Case 32813 'Err.Number 32813 means reference already added Case 48 'Reference doesn't exist If lngDLLmsadoFIND = 0 Then MsgBox ("Cannot Find Required Reference") End Else For lngDLLmsadoFIND = lngDLLmsadoFIND - 1 To 0 Step -1 Resume Next lngDLLmsadoFIND End If Case Else MsgBox Err.Number & vbCrLf & Err.Description, vbCritical, "Error!" End End Select On Error GoTo 0 End If booRefAdded = TRUE End Sub 

У вас есть один замечательный ответ от ray023, но ваш комментарий о том, что это, вероятно, слишком много, является подходящим. Для «более легкой» версии ….

Блок 1 есть, ИМХО, плохая практика. Как уже указывалось osknows, смешивание ошибок с кодом нормального пути не является хорошим. Во-первых, если возникает новая ошибка, в то время как есть условие ошибки, вы не получите возможности справиться с ней (если вы не вызываете из процедуры, которая также имеет обработчик ошибок, где выполнение «пузырится», ).

Блок 2 выглядит как имитация блока Try / Catch. Это должно быть хорошо, но это не путь VBA. Блок 3 – это вариант на блоке 2.

Блок 4 – голой вариант VBA Way. Я бы настоятельно советовал использовать его или что-то в этом роде, потому что это то, что ожидает любой другой программист VBA, наследующий код. Позвольте мне представить небольшое расширение, хотя:

 Private Sub DoSomething() On Error GoTo ErrHandler 'Dim as required 'functional code that might throw errors ExitSub: 'any always-execute (cleanup?) code goes here -- analagous to a Finally block. 'don't forget to do this -- you don't want to fall into error handling when there's no error Exit Sub ErrHandler: 'can Select Case on Err.Number if there are any you want to handle specially 'display to user MsgBox "Something's wrong: " & vbCrLf & Err.Description 'or use a central DisplayErr routine, written Public in a Module DisplayErr Err.Number, Err.Description Resume ExitSub Resume End Sub 

Обратите внимание, что второе Resume . Это трюк, который я недавно узнал: он никогда не будет выполняться при нормальной обработке, так как оператор Resume отправит выполнение в другом месте. Тем не менее, это может быть находкой для отладки. Когда вы получите уведомление об ошибке, выберите «Отладка» (или нажмите «Ctl-Break», затем выберите «Отладка», когда вы получите сообщение «Исполнение было прервано»). Следующим (выделенным) оператором будет либо MsgBox либо следующий оператор. Используйте «Установить следующее заявление» (Ctl-F9), чтобы выделить голый Resume , а затем нажмите F8. Это покажет вам , где именно была выбрана ошибка.

Что касается вашего возражения против этого формата, «прыгающего вокруг», A) это то, что ожидают программисты VBA, как было сказано ранее, и B) ваши подпрограммы должны быть достаточно короткими, чтобы не заскочить.

Две основные цели для обработки ошибок:

  1. Ошибки ловушки, которые вы можете предсказать, но не можете контролировать пользователя (например, сохранение файла на флэш-накопителе при удалении флэш-накопителей)
  2. Для непредвиденных ошибок, представляйте пользователя с формой, которая сообщает им, в чем проблема. Таким образом, они могут передать это сообщение вам, и вы сможете дать им обход, когда будете работать над исправлением.

Итак, как бы вы это сделали?

Прежде всего, создайте форму ошибки для отображения при возникновении непредвиденной ошибки.

Это может выглядеть примерно так (FYI: Mine называется frmErrors): Форма ошибки компании

Обратите внимание на следующие метки:

  • lblHeadline
  • lblSource
  • lblProblem
  • lblResponse

Кроме того, стандартные командные кнопки:

  • игнорировать
  • Retry
  • Отмена

В коде для этой формы нет ничего впечатляющего:

 Option Explicit Private Sub cmdCancel_Click() Me.Tag = CMD_CANCEL Me.Hide End Sub Private Sub cmdIgnore_Click() Me.Tag = CMD_IGNORE Me.Hide End Sub Private Sub cmdRetry_Click() Me.Tag = CMD_RETRY Me.Hide End Sub Private Sub UserForm_Initialize() Me.lblErrorTitle.Caption = "Custom Error Title Caption String" End Sub Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) 'Prevent user from closing with the Close box in the title bar. If CloseMode <> 1 Then cmdCancel_Click End If End Sub 

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

Затем создайте модуль обработчика ошибок, который будет использоваться в вашем приложении VBA:

 '**************************************************************** ' MODULE: ErrorHandler ' ' PURPOSE: A VBA Error Handling routine to handle ' any unexpected errors ' ' Date: Name: Description: ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '03/22/2010 Ray Initial Creation '**************************************************************** Option Explicit Global Const CMD_RETRY = 0 Global Const CMD_IGNORE = 1 Global Const CMD_CANCEL = 2 Global Const CMD_CONTINUE = 3 Type ErrorType iErrNum As Long sHeadline As String sProblemMsg As String sResponseMsg As String sErrorSource As String sErrorDescription As String iBtnCap(3) As Integer iBitmap As Integer End Type Global gEStruc As ErrorType Sub EmptyErrStruc_S(utEStruc As ErrorType) Dim i As Integer utEStruc.iErrNum = 0 utEStruc.sHeadline = "" utEStruc.sProblemMsg = "" utEStruc.sResponseMsg = "" utEStruc.sErrorSource = "" For i = 0 To 2 utEStruc.iBtnCap(i) = -1 Next utEStruc.iBitmap = 1 End Sub Function FillErrorStruct_F(EStruc As ErrorType) As Boolean 'Must save error text before starting new error handler 'in case we need it later EStruc.sProblemMsg = Error(EStruc.iErrNum) On Error GoTo vbDefaultFill EStruc.sHeadline = "Error " & Format$(EStruc.iErrNum) EStruc.sProblemMsg = EStruc.sErrorDescription EStruc.sErrorSource = EStruc.sErrorSource EStruc.sResponseMsg = "Contact the Company and tell them you received Error # " & Str$(EStruc.iErrNum) & ". You should write down the program function you were using, the record you were working with, and what you were doing." Select Case EStruc.iErrNum 'Case Error number here 'not sure what numeric errors user will ecounter, but can be implemented here 'eg 'EStruc.sHeadline = "Error 3265" 'EStruc.sResponseMsg = "Contact tech support. Tell them what you were doing in the program." Case Else EStruc.sHeadline = "Error " & Format$(EStruc.iErrNum) & ": " & EStruc.sErrorDescription EStruc.sProblemMsg = EStruc.sErrorDescription End Select GoTo FillStrucEnd vbDefaultFill: 'Error Not on file EStruc.sHeadline = "Error " & Format$(EStruc.iErrNum) & ": Contact Tech Support" EStruc.sResponseMsg = "Contact the Company and tell them you received Error # " & Str$(EStruc.iErrNum) FillStrucEnd: Exit Function End Function Function iErrorHandler_F(utEStruc As ErrorType) As Integer Static sCaption(3) As String Dim i As Integer Dim iMCursor As Integer Beep 'Setup static array If Len(sCaption(0)) < 1 Then sCaption(CMD_IGNORE) = "&Ignore" sCaption(CMD_RETRY) = "&Retry" sCaption(CMD_CANCEL) = "&Cancel" sCaption(CMD_CONTINUE) = "Continue" End If Load frmErrors 'Did caller pass error info? If not fill struc with the needed info If Len(utEStruc.sHeadline) < 1 Then i = FillErrorStruct_F(utEStruc) End If frmErrors!lblHeadline.Caption = utEStruc.sHeadline frmErrors!lblProblem.Caption = utEStruc.sProblemMsg frmErrors!lblSource.Caption = utEStruc.sErrorSource frmErrors!lblResponse.Caption = utEStruc.sResponseMsg frmErrors.Show iErrorHandler_F = frmErrors.Tag ' Save user response Unload frmErrors ' Unload and release form EmptyErrStruc_S utEStruc ' Release memory End Function 

У вас могут быть ошибки, которые будут настраиваться только для вашего приложения. Обычно это будет короткий список ошибок, которые относятся только к вашему приложению. Если у вас еще нет модуля констант, создайте тот, который будет содержать ENUM ваших пользовательских ошибок. (ПРИМЕЧАНИЕ. Office '97 НЕ поддерживает ENUMS.). ENUM должен выглядеть примерно так:

 Public Enum CustomErrorName MaskedFilterNotSupported InvalidMonthNumber End Enum 

Создайте модуль, который будет генерировать ваши пользовательские ошибки.

 '******************************************************************************************************************************** ' MODULE: CustomErrorList ' ' PURPOSE: For trapping custom errors applicable to this application ' 'INSTRUCTIONS: To use this module to create your own custom error: ' 1. Add the Name of the Error to the CustomErrorName Enum ' 2. Add a Case Statement to the raiseCustomError Sub ' 3. Call the raiseCustomError Sub in the routine you may see the custom error ' 4. Make sure the routine you call the raiseCustomError has error handling in it ' ' ' Date: Name: Description: ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '03/26/2010 Ray Initial Creation '******************************************************************************************************************************** Option Explicit Const MICROSOFT_OFFSET = 512 'Microsoft reserves error values between vbObjectError and vbObjectError + 512 '************************************************************************************************ ' FUNCTION: raiseCustomError ' ' PURPOSE: Raises a custom error based on the information passed ' 'PARAMETERS: customError - An integer of type CustomErrorName Enum that defines the custom error ' errorSource - The place the error came from ' ' Returns: The ASCII vaule that should be used for the Keypress ' ' Date: Name: Description: ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '03/26/2010 Ray Initial Creation '************************************************************************************************ Public Sub raiseCustomError(customError As Integer, Optional errorSource As String = "") Dim errorLong As Long Dim errorDescription As String errorLong = vbObjectError + MICROSOFT_OFFSET + customError Select Case customError Case CustomErrorName.MaskedFilterNotSupported errorDescription = "The mask filter passed is not supported" Case CustomErrorName.InvalidMonthNumber errorDescription = "Invalid Month Number Passed" Case Else errorDescription = "The custom error raised is unknown." End Select Err.Raise errorLong, errorSource, errorDescription End Sub 

Теперь вы хорошо подготовлены, чтобы ловить ошибки в своей программе. Вы, суба (или функция), должны выглядеть примерно так:

 Public Sub MySub(monthNumber as Integer) On Error GoTo eh Dim sheetWorkSheet As Worksheet 'Run Some code here '************************************************ '* OPTIONAL BLOCK 1: Look for a specific error '************************************************ 'Temporarily Turn off Error Handling so that you can check for specific error On Error Resume Next 'Do some code where you might expect an error. Example below: Const ERR_SHEET_NOT_FOUND = 9 'This error number is actually subscript out of range, but for this example means the worksheet was not found Set sheetWorkSheet = Sheets("January") 'Now see if the expected error exists If Err.Number = ERR_SHEET_NOT_FOUND Then MsgBox "Hey! The January worksheet is missing. You need to recreate it." Exit Sub ElseIf Err.Number <> 0 Then 'Uh oh...there was an error we did not expect so just run basic error handling GoTo eh End If 'Finished with predictable errors, turn basic error handling back on: On Error GoTo eh '********************************************************************************** '* End of OPTIONAL BLOCK 1 '********************************************************************************** '********************************************************************************** '* OPTIONAL BLOCK 2: Raise (aka "Throw") a Custom Error if applicable '********************************************************************************** If not (monthNumber >=1 and monthnumber <=12) then raiseCustomError CustomErrorName.InvalidMonthNumber, "My Sub" end if '********************************************************************************** '* End of OPTIONAL BLOCK 2 '********************************************************************************** 'Rest of code in your sub goto sub_exit eh: gEStruc.iErrNum = Err.Number gEStruc.sErrorDescription = Err.Description gEStruc.sErrorSource = Err.Source m_rc = iErrorHandler_F(gEStruc) If m_rc = CMD_RETRY Then Resume End If sub_exit: 'Any final processing you want to do. 'Be careful with what you put here because if it errors out, the error rolls up. This can be difficult to debug; especially if calling routine has no error handling. Exit Sub 'I was told a long time ago (10+ years) that exit sub was better than end sub...I can't tell you why, so you may not want to put in this line of code. It's habit I can't break :P End Sub 

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

BTW, если вам когда-нибудь понадобится, чтобы я сделал логотип вашей компании, найдите меня на http://www.MySuperCrappyLogoLabels99.com

Я делаю все просто:
На уровне модуля я определяю две переменные и присваиваю имя самому модулю.

  Private Const ThisModuleName As String = "mod_Custom_Functions" Public sLocalErrorMsg As String 

Внутри каждой Sub / Function модуля я определяю локальную переменную

  Dim ThisRoutineName As String 

Я установил ThisRoutineName в имя суб или функции

 ' Housekeeping On Error Goto ERR_RTN ThisRoutineName = "CopyWorksheet" 

Затем я отправляю все ошибки в ERR_RTN: когда они происходят, но я сначала установил sLocalErrorMsg, чтобы определить, что такое на самом деле, и предоставить некоторую информацию для отладки.

  If Len(Trim(FromWorksheetName)) < 1 Then sLocalErrorMsg = "Parameter 'FromWorksheetName' Is Missing." GoTo ERR_RTN End If 

В нижней части каждой суб / функции я направляю логический stream следующим образом

  ' ' The "normal" logic goes here for what the routine does ' GoTo EXIT_RTN ERR_RTN: On Error Resume Next ' Call error handler if we went this far. ErrorHandler ThisModuleName, ThisRoutineName, sLocalErrorMsg, Err.Description, Err.Number, False EXIT_RTN: On Error Resume Next ' ' Some closing logic ' End If 

Затем у меня есть отдельный модуль, который я включил во все проекты под названием «mod_Error_Handler».

  ' ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Subroutine Name: ErrorHandler ' ' ' ' Description: ' ' This module will handle the common error alerts. ' ' ' ' Inputs: ' ' ModuleName String 'The name of the module error is in. ' ' RoutineName String 'The name of the routine error in in. ' ' LocalErrorMsg String 'A local message to assist with troubleshooting.' ' ERRDescription String 'The Windows Error Description. ' ' ERRCode Long 'The Windows Error Code. ' ' Terminate Boolean 'End program if error encountered? ' ' ' ' Revision History: ' ' Date (YYYYMMDD) Author Change ' ' =============== ===================== =============================================== ' ' 20140529 XXXXX X. XXXXX Original ' ' ' ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Public Sub ErrorHandler(ModuleName As String, RoutineName As String, LocalErrorMsg As String, ERRDescription As String, ERRCode As Long, Terminate As Boolean) Dim sBuildErrorMsg As String ' Build Error Message To Display sBuildErrorMsg = "Error Information:" & vbCrLf & vbCrLf If Len(Trim(ModuleName)) < 1 Then ModuleName = "Unknown" End If If Len(Trim(RoutineName)) < 1 Then RoutineName = "Unknown" End If sBuildErrorMsg = sBuildErrorMsg & "Module Name: " & ModuleName & vbCrLf & vbCrLf sBuildErrorMsg = sBuildErrorMsg & "Routine Name: " & RoutineName & vbCrLf & vbCrLf If Len(Trim(LocalErrorMsg)) > 0 Then sBuildErrorMsg = sBuildErrorMsg & "Local Error Msg: " & LocalErrorMsg & vbCrLf & vbCrLf End If If Len(Trim(ERRDescription)) > 0 Then sBuildErrorMsg = sBuildErrorMsg & "Program Error Msg: " & ERRDescription & vbCrLf & vbCrLf If IsNumeric(ERRCode) Then sBuildErrorMsg = sBuildErrorMsg & "Program Error Code: " & Trim(Str(ERRCode)) & vbCrLf & vbCrLf End If End If MsgBox sBuildErrorMsg, vbOKOnly + vbExclamation, "Error Detected!" If Terminate Then End End If End Sub 

Конечным результатом является всплывающее сообщение об ошибке, сообщающее мне, в каком модуле, что такое soubroutine и какое сообщение об ошибке было конкретно. Кроме того, он также введет сообщение об ошибке Windows и код.

Блок 2 не работает, потому что он не сбрасывает обработчик ошибок, потенциально вызывающий бесконечный цикл. Для правильной работы с ошибкой в ​​VBA вам понадобится инструкция Resume чтобы очистить обработчик ошибок. Resume также активирует предыдущий обработчик ошибок. Блок 2 терпит неудачу, потому что новая ошибка вернется к предыдущему обработчику ошибок, вызывающему бесконечный цикл.

Блок 3 выходит из строя, потому что не существует оператора Resume поэтому после попытки ошибки обработка ошибок завершится неудачей.

Каждый обработчик ошибок должен быть завершен путем выхода из процедуры или оператора Resume . Обычное выполнение маршрутизации вокруг обработчика ошибок сбивает с толку. Вот почему обработчики ошибок обычно находятся внизу.

Но вот еще один способ справиться с ошибкой в ​​VBA. Он обрабатывает встроенный код ошибки, например Try / Catch в VB.net. Есть несколько подводных камней, но правильно управляемый он работает очень хорошо.

 Sub InLineErrorHandling() 'code without error handling BeginTry1: 'activate inline error handler On Error GoTo ErrHandler1 'code block that may result in an error Dim a As String: a = "Abc" Dim c As Integer: c = a 'type mismatch ErrHandler1: 'handle the error If Err.Number <> 0 Then 'the error handler has deactivated the previous error handler MsgBox (Err.Description) 'Resume (or exit procedure) is the only way to get out of an error handling block 'otherwise the following On Error statements will have no effect 'CAUTION: it also reactivates the previous error handler Resume EndTry1 End If EndTry1: 'CAUTION: since the Resume statement reactivates the previous error handler 'you must ALWAYS use an On Error GoTo statement here 'because another error here would cause an endless loop 'use On Error GoTo 0 or On Error GoTo  

Источники:

Ключом к выполнению этой работы является использование оператора Resume немедленно, за которым следует другой оператор On Error . Resume находится в обработчике ошибок и переводит код на метку EndTry1 . Вы должны немедленно установить другую инструкцию On Error чтобы избежать проблем, поскольку предыдущий обработчик ошибок «возобновится». То есть, он будет активным и готовым к обработке другой ошибки. Это может привести к тому, что ошибка повторится и введите бесконечный цикл.

Чтобы избежать повторного использования предыдущего обработчика ошибок, вам нужно установить On Error на новый обработчик ошибок или просто использовать On Error Goto 0 чтобы отменить все обработки ошибок.

  • Захват выходного значения из команды оболочки в VBA?
  • Что делает ReDim Preserve?
  • В Excel, сделав «Стоп-кавычки» в правом столбце вместо самого левого?
  • Excel VBA - функция QueryTable AfterRefresh, которая не вызывается после завершения обновления
  • В Excel VBA, как проверить, полностью ли загружена веб-страница?
  • Выполнение SQL-запросов в таблице Excel в рабочей книге с помощью макроса VBA
  • VBA извлекает данные XML в Excel
  • Скопируйте рабочий лист Excel и поддерживайте относительную ссылку на ячейки в формулах
  • Как объединить значения из нескольких строк в одну строку в Excel?
  • Сравните двойную точность в задаче точности VBA
  • Как вы проверяете время работы кода VBA?
  • Interesting Posts

    Разница между @import и ссылкой в ​​CSS

    Почему BitmapFactory.decodeByteArray возвращает значение null?

    Невозможно скомпилировать проект, когда я использую Lombok под IntelliJ IDEA

    Невозможно установить Windows 7 Share из Linux с помощью CIFS: Ошибка монтирования (13): разрешение отклонено

    Необходимость указателя на указатель

    путают autoload_paths vs eager_load_paths в рельсах 4

    максимальная память, которую malloc может выделять

    favicon.png vs favicon.ico – почему я должен использовать PNG вместо ICO?

    Несколько параметров для сценария bash

    Запуск Firefox в «хромированный» режим из командной строки

    Как отключить представление совместимости в IE WebBrowserControl в приложении WinForms?

    Dd создает поврежденные образы ISO на Snow Leopard (независимо от того, вызваны ли они из скрипта или оболочки)

    Добавьте водяной знак в видео после объединения видео и аудио активов в один из Swift3 iOS

    Разделить столбец на несколько двоичных фиктивных столбцов

    Верхний индекс в CSS?

    Давайте будем гением компьютера.