# Error Handling
When a runtime error occurs, good code should handle it. The best error handling strategy is to write code that checks for error conditions and simply avoids executing code that results in a runtime error.
One key element in reducing runtime errors, is writing small procedures that do one thing. The fewer reasons procedures have to fail, the easier the code as a whole is to debug.
Avoiding runtime error 91 — Object or With block variable not set:
This error will be raised when an object is used before its reference is assigned. One might have a procedure that receives an object parameter:
If target isn’t assigned a reference, the above code will raise an error that is easily avoided by checking if the object contains an actual object reference:
If target isn’t assigned a reference, then the unassigned reference is never used, and no error occurs.
This way of early-exiting a procedure when one or more parameter isn’t valid, is called a guard clause.
Avoiding runtime error 9 — Subscript out of range:
This error is raised when an array is accessed outside of its boundaries.
Given an index greater than the number of worksheets in the ActiveWorkbook , the above code will raise a runtime error. A simple guard clause can avoid that:
Most runtime errors can be avoided by carefully verifying the values we’re using before we use them, and branching on another execution path accordingly using a simple If statement — in guard clauses that makes no assumptions and validates a procedure’s parameters, or even in the body of larger procedures.
# Custom Errors
Often when writing a specialized class, you’ll want it to raise its own specific errors, and you’ll want a clean way for user/calling code to handle these custom errors. A neat way to achieve this is by defining a dedicated Enum type:
Using the vbObjectError built-in constant ensures the custom error codes don’t overlap with reserved/existing error codes. Only the first enum value needs to be explicitly specified, for the underlying value of each Enum member is 1 greater than the previous member, so the underlying value of Err_BarNotInitialized is implicitly vbObjectError + 1025 .
# Raising your own runtime errors
A runtime error can be raised using the Err.Raise statement, so the custom Err_FooWasNotBarred error can be raised as follows:
The Err.Raise method can also take custom Description and Source parameters — for this reason it’s a good idea to also define constants to hold each custom error’s description:
And then create a dedicated private method to raise each error:
The class’ implementation can then simply call these specialized procedures to raise the error:
The client code can then handle Err_BarNotInitialized as it would any other error, inside its own error-handling subroutine.
Note: the legacy Error keyword can also be used in place of Err.Raise , but it’s obsolete/deprecated.
# Resume keyword
An error-handling subroutine will either:
- run to the end of the procedure, in which case execution resumes in the calling procedure.
- or, use the Resume keyword to resume execution inside the same procedure.
The Resume keyword should only ever be used inside an error handling subroutine, because if VBA encounters Resume without being in an error state, runtime error 20 "Resume without error" is raised.
There are several ways an error-handling subroutine may use the Resume keyword:
- Resume used alone, execution continues on the statement that caused the error. If the error isn’t actually handled before doing that, then the same error will be raised again, and execution might enter an infinite loop.
- Resume Next continues execution on the statement immediately following the statement that caused the error. If the error isn’t actually handled before doing that, then execution is permitted to continue with potentially invalid data, which may result in logical errors and unexpected behavior.
- Resume [line label] continues execution at the specified line label (or line number, if you’re using legacy-style line numbers). This would typically allow executing some cleanup code before cleanly exiting the procedure, such as ensuring a database connection is closed before returning to the caller.
# On Error Resume Next
The On Error statement itself can use the Resume keyword to instruct the VBA runtime to effectively ignore all errors.
If the error isn’t actually handled before doing that, then execution is permitted to continue with potentially invalid data, which may result in logical errors and unexpected behavior.
The emphasis above cannot be emphasized enough. On Error Resume Next effectively ignores all errors and shoves them under the carpet. A program that blows up with a runtime error given invalid input is a better program than one that keeps running with unknown/unintended data — be it only because the bug is much more easily identifiable. On Error Resume Next can easily hide bugs.
The On Error statement is procedure-scoped — that’s why there should normally be only one, single such On Error statement in a given procedure.
However sometimes an error condition can’t quite be avoided, and jumping to an error-handling subroutine only to Resume Next just doesn’t feel right. In this specific case, the known-to-possibly-fail statement can be wrapped between two On Error statements:
The On Error GoTo 0 instruction resets error handling in the current procedure, such that any further instruction causing a runtime error would be unhandled within that procedure and instead passed up the call stack until it is caught by an active error handler. If there is no active error handler in the call stack, it will be treated as an unhandled exception.
# On Error statement
Even with guard clauses, one cannot realistically always account for all possible error conditions that could be raised in the body of a procedure. The On Error GoTo statement instructs VBA to jump to a line label and enter "error handling mode" whenever an unexpected error occurs at runtime. After handling an error, code can resume back into "normal" execution using the Resume keyword.
Line labels denote subroutines: because subroutines originate from legacy BASIC code and uses GoTo and GoSub jumps and Return statements to jump back to the "main" routine, it’s fairly easy to write hard-to-follow spaghetti code if things aren’t rigorously structured. For this reason, it’s best that:
- a procedure has one and only one error-handling subroutine
- the error-handling subroutine only ever runs in an error state
This means a procedure that handles its errors, should be structured like this:
# Error Handling Strategies
Sometimes you want to handle different errors with different actions. In that case you will inspect the global Err object, which will contain information about the error that was raised — and act accordingly:
As a general guideline, consider turning on the error handling for entire subroutine or function, and handle all the errors that may occur within its scope. If you need to only handle errors in the small section section of the code — turn error handling on and off a the same level:
# Line numbers
VBA supports legacy-style (e.g. QBASIC) line numbers. The Erl hidden property can be used to identify the line number that raised the last error. If you’re not using line numbers, Erl will only ever return 0.
If you are using line numbers, but not consistently, then Erl will return the last line number before the instruction that raised the error.
Keep in mind that Erl also only has Integer precision, and will silently overflow. This means that line numbers outside of the integer range
(opens new window) will give incorrect results:
The line number isn’t quite as relevant as the statement that caused the error, and numbering lines quickly becomes tedious and not quite maintenance-friendly.
Name already in use
VBA-Docs / access / Concepts / Error-Codes / elements-of-run-time-error-handling.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
Elements of run-time error handling
Errors and error handling
When you are programming an application, you need to consider what happens when an error occurs. An error can occur in your application for one of two of reasons. First, some condition at the time the application is running makes otherwise valid code fail. For example, if your code attempts to open a table that the user has deleted, an error occurs. Second, your code may contain improper logic that prevents it from doing what you intended. For example, an error occurs if your code attempts to divide a value by zero.
If you’ve implemented no error handling, then Visual Basic halts execution and displays an error message when an error occurs in your code. The user of your application is likely to be confused and frustrated when this happens. You can forestall many problems by including thorough error-handling routines in your code to handle any error that may occur.
When adding error handling to a procedure, you should consider how the procedure will route execution when an error occurs. The first step in routing execution to an error handler is to enable an error handler by including some form of the On Error statement within the procedure. The On Error statement directs execution in event of an error. If there’s no On Error statement, Visual Basic simply halts execution and displays an error message when an error occurs.
When an error occurs in a procedure with an enabled error handler, Visual Basic doesn’t display the normal error message. Instead it routes execution to an error handler, if one exists. When execution passes to an enabled error handler, that error handler becomes active. Within the active error handler, you can determine the type of error that occurred and address it in the manner that you choose. Access provides three objects that contain information about errors that have occurred, the ADO Error object, the Visual Basic Err object, and the DAO Error object.
Routing execution when an error occurs
An error handler specifies what happens within a procedure when an error occurs. For example, you may want the procedure to end if a certain error occurs, or you may want to correct the condition that caused the error and resume execution. The On Error and Resume statements determine how execution proceeds in the event of an error.
On Error statement
The On Error statement enables or disables an error-handling routine. If an error-handling routine is enabled, execution passes to the error-handling routine when an error occurs.
There are three forms of the On Error statement: On Error GoTo label, On Error GoTo 0, and On Error Resume Next. The On Error GoTo label statement enables an error-handling routine, beginning with the line on which the statement is found. You should enable the error-handling routine before the first line at which an error could occur. When the error handler is active and an error occurs, execution passes to the line specified by the label argument.
The line specified by the label argument should be the beginning of the error-handling routine. For example, the following procedure specifies that if an error occurs, execution passes to the line labeled:
The On Error GoTo 0 statement disables error handling within a procedure. It doesn’t specify line 0 as the start of the error-handling code, even if the procedure contains a line numbered 0. If there’s no On Error GoTo 0 statement in your code, the error handler is automatically disabled when the procedure has run completely. The On Error GoTo 0 statement resets the properties of the Err object, having the same effect as the Clear method of the Err object.
The On Error Resume Next statement ignores the line that causes an error and routes execution to the line following the line that caused the error. Execution isn’t interrupted. Use the On Error Resume Next statement if you want to check the properties of the Err object immediately after a line at which you anticipate an error will occur, and handle the error within the procedure rather than in an error handler.
The Resume statement directs execution back to the body of the procedure from within an error-handling routine. You can include a Resume statement within an error-handling routine if you want execution to continue at a particular point in a procedure. However, a Resume statement isn’t necessary; you can also end the procedure after the error-handling routine.
There are three forms of the Resume statement. The Resume or Resume 0 statement returns execution to the line at which the error occurred. The Resume Next statement returns execution to the line immediately following the line at which the error occurred. The Resume label statement returns execution to the line specified by the label argument. The label argument must indicate either a line label or a line number.
You typically use the Resume or Resume 0 statement when the user must make a correction. For example, if you prompt the user for the name of a table to open, and the user enters the name of a table that doesn’t exist, you can prompt the user again and resume execution with the statement that caused the error.
You use the Resume Next statement when your code corrects for the error within an error handler, and you want to continue execution without rerunning the line that caused the error. You use the Resume label statement when you want to continue execution at another point in the procedure, specified by the label argument. For example, you might want to resume execution at an exit routine, as described in the following section.
Exiting a procedure
When you include an error-handling routine in a procedure, you should also include an exit routine, so that the error-handling routine will run only if an error occurs. You can specify an exit routine with a line label in the same way that you specify an error-handling routine.
For example, you can add an exit routine to the example in the previous section. If an error doesn’t occur, the exit routine runs after the body of the procedure. If an error occurs, then execution passes to the exit routine after the code in the error-handling routine has run. The exit routine contains an Exit statement.
Handling errors in nested procedures
When an error occurs in a nested procedure that doesn’t have an enabled error handler, Visual Basic searches backward through the calls list for an enabled error handler in another procedure, rather than simply halting execution. This provides your code with an opportunity to correct the error within another procedure. For example, suppose Procedure A calls Procedure B, and Procedure B calls Procedure C. If an error occurs in Procedure C and there’s no enabled error handler, Visual Basic checks Procedure B, then Procedure A, for an enabled error handler. If one exists, execution passes to that error handler. If not, execution halts and an error message is displayed.
Visual Basic also searches backward through the calls list for an enabled error handler when an error occurs within an active error handler. You can force Visual Basic to search backward through the calls list by raising an error within an active error handler with the Raise method of the Err object. This is useful for handling errors that you don’t anticipate within an error handler. If an unanticipated error occurs, and you regenerate that error within the error handler, then execution passes back up the calls list to find another error handler, which may be set up to handle the error.
For example, suppose Procedure C has an enabled error handler, but the error handler doesn’t correct for the error that has occurred. Once the error handler has checked for all the errors that you’ve anticipated, it can regenerate the original error. Execution then passes back up the calls list to the error handler in Procedure B, if one exists, providing an opportunity for this error handler to correct the error. If no error handler exists in Procedure B, or if it fails to correct for the error and regenerates it again, then execution passes to the error handler in Procedure A, assuming one exists.
To illustrate this concept in another way, suppose that you have a nested procedure that includes error handling for a type mismatch error, an error which you’ve anticipated. At some point, a division-by-zero error, which you haven’t anticipated, occurs within Procedure C. If you’ve included a statement to regenerate the original error, then execution passes back up the calls list to another enabled error handler, if one exists. If you’ve corrected for a division-by-zero error in another procedure in the calls list, then the error will be corrected. If your code doesn’t regenerate the error, then the procedure continues to run without correcting the division-by-zero error. This in turn may cause other errors within the set of nested procedures.
In summary, Visual Basic searches back up the calls list for an enabled error handler if:
An error occurs in a procedure that doesn’t include an enabled error handler.
An error occurs within an active error handler. If you use the Raise method of the Err object to raise an error, you can force Visual Basic to search backward through the calls list for an enabled error handler.
Getting information about an error
After execution has passed to the error-handling routine, your code must determine which error has occurred and address it. Visual Basic and Access provide several language elements that you can use to get information about a specific error. Each is suited to different types of errors. Since errors can occur in different parts of your application, you need to determine which to use in your code based on what errors you expect.
The language elements available for error handling include:
Err object
ADO Error object and Errors collection
DAO Error object and Errors collection
AccessError method
Error event
The Err object is provided by Visual Basic. When a Visual Basic error occurs, information about that error is stored in the Err object. The Err object maintains information about only one error at a time. When a new error occurs, the Err object is updated to include information about that error instead.
To get information about a particular error, you can use the properties and methods of the Err object:
- The Number property is the default property of the Err object; it returns the identifying number of the error that occurred.
- The Err object’s Description property returns the descriptive string associated with a Visual Basic error.
- The Clear method clears the current error information from the Err object.
- The Raise method generates a specific error and populates the properties of the Err object with information about that error.
The following example shows how to use the Err object in a procedure that may cause a type mismatch error:
Note that in the preceding example, the Raise method is used to regenerate the original error. If an error other than a type mismatch error occurs, execution will be passed back up the calls list to another enabled error handler, if one exists.
The Err object provides you with all the information you need about Visual Basic errors. However, it doesn’t give you complete information about Access errors or Access database engine errors. Access and Data Access Objects (DAO)) provide additional language elements to assist you with those errors.
Error object and Errors collection
The Error object and Errors collection are provided by ADO and DAO. The Error object represents an ADO or DAO error. A single ADO or DAO operation may cause several errors, especially if you are performing DAO ODBC operations. Each error that occurs during a particular data access operation has an associated Error object. All the Error objects associated with a particular ADO or DAO operation are stored in the Errors collection, the lowest-level error being the first object in the collection and the highest-level error being the last object in the collection.
When a ADO or DAO error occurs, the Visual Basic Err object contains the error number for the first object in the Errors collection. To determine whether additional ADO or DAO errors have occurred, check the Errors collection. The values of the ADO Number or DAO Number properties and the ADO Description or DAO Description properties of the first Error object in the Errors collection should match the values of the Number and Description properties of the Visual Basic Err object.
Use the Raise method of the Err object to generate a Visual Basic error that hasn’t actually occurred and determine the descriptive string associated with that error. However, you can’t use the Raise method to generate a Access error, an ADO error, or a DAO error. To determine the descriptive string associated with an Access error, an ADO error, or a DAO error that hasn’t actually occurred, use the AccessError method.
Use the Error event to trap errors that occur on an Access form or report. For example, if a user tries to enter text in a field whose data type is Date/Time, the Error event occurs. If you add an Error event procedure to an Employees form, then try to enter a text value in the HireDate field, the Error event procedure runs.
The Error event procedure takes an integer argument, DataErr. When an Error event procedure runs, the DataErr argument contains the number of the Access error that occurred. Checking the value of the DataErr argument within the event procedure is the only way to determine the number of the error that occurred. The Err object isn’t populated with error information after the Error event occurs. Use the value of the DataErr argument together with the AccessError method to determine the number of the error and its descriptive string.
[!NOTE] The Error statement and Error function are provided for backward compatibility only. When writing new code, use the Err and Error objects, the AccessError function, and the Error event for getting information about an error.
About the contributors

Link provided by the UtterAccess community.
VBA On Error – Error Handling Best Practices
VBA Error Handling refers to the process of anticipating, detecting, and resolving VBA Runtime Errors. The VBA Error Handling process occurs when writing code, before any errors actually occur.
VBA Runtime Errors are errors that occur during code execution. Examples of runtime errors include:
- Referencing a non-existent workbook, worksheet, or other object (Run-time Error 1004)
- Invalid data ex. referencing an Excel cell containing an error (Type Mismatch – Run-time Error 13)
- Attempting to divide by zero
VBA On Error Statement
Most VBA error handling is done with the On Error Statement. The On Error statement tells VBA what to do if it encounters an error. There are three On Error Statements:
- On Error GoTo 0
- On Error Resume Next
- On Error GoTo Line
On Error GoTo 0
On Error GoTo 0 is VBA’s default setting. You can restore this default setting by adding the following line of code:
When an error occurs with On Error GoTo 0, VBA will stop executing code and display its standard error message box.

Often you will add an On Error GoTo 0 after adding On Error Resume Next error handling (next section):
On Error Resume Next
On Error Resume Next tells VBA to skip any lines of code containing errors and proceed to the next line.
Note: On Error Resume Next does not fix an error, or otherwise resolve it. It simply tells VBA to proceed as if the line of code containing the error did not exist. Improper use of On Error Resume Next can result in unintended consequences.
A great time to use On Error Resume Next is when working with objects that may or may not exist. For example, you want to write some code that will delete a shape, but if you run the code when the shape is already deleted, VBA will throw an error. Instead you can use On Error Resume Next to tell VBA to delete the shape if it exists.
Notice we added On Error GoTo 0 after the line of code containing the potential error. This resets the error handling.
In the next section we’ll show you how to test if an error occurred using Err.Number, giving you more advanced error handling options.
VBA Coding Made Easy
Stop searching for VBA code online. Learn more about AutoMacro — A VBA Code Builder that allows beginners to code procedures from scratch with minimal coding knowledge and with many time-saving features for all users!

Err.Number, Err.Clear, and Catching Errors
Instead of simply skipping over a line containing an error, we can catch the error by using On Error Resume Next and Err.Number.
Err.Number returns an error number corresponding with the type of error detected. If there is no error, Err.Number = 0.
For example, this procedure will return “11” because the error that occurs is Run-time error ’11’.

Error Handling with Err.Number
The true power of Err.Number lies in the ability to detect if an error occurred (Err.Number <> 0). In the example below, we’ve created a function that will test if a sheet exists by using Err.Number.
Note: We’ve added a On Error GoTo -1 to the end which resets Err.Number to 0 (see two sections down).
With On Error Resume Next and Err.Number, you can replicate the “Try” & “Catch” functionality of other programming languages.
On Error GoTo Line
On Error GoTo Line tells VBA to “go to” a labeled line of code when an error is encountered. You declare the Go To statement like this (where errHandler is the line label to go to):
and create a line label like this:
Note: This is the same label that you’d use with a regular VBA GoTo Statement.
Below we will demonstrate using On Error GoTo Line to Exit a procedure.
On Error Exit Sub
You can do this by placing the error handler line label at the end of your procedure:
Err.Clear, On Error GoTo -1, and Resetting Err.Number
After an error is handled, you should generally clear the error to prevent future issues with error handling.
After an error occurs, both Err.Clear and On Error GoTo -1 can be used to reset Err.Number to 0. But there is one very important difference: Err.Clear does not reset the actual error itself, it only resets the Err.Number.
What does that mean? Using Err.Clear, you will not be able to change the error handling setting. To see the difference, test out this code and replace On Error GoTo -1 with Err.Clear:
Typically, I recommend always using On Error GoTo -1, unless you have a good reason to use Err.Clear instead.
VBA On Error MsgBox
You might also want to display a Message Box on error. This example will display different message boxes depending on where the error occurs:
Here you would replace Err.Raise(11) with your actual code.
VBA IsError
Another way to handle errors is to test for them with the VBA ISERROR Function. The ISERROR Function tests an expression for errors, returning TRUE or FALSE if an error occurs.
If Error VBA
You can also handle errors in VBA with the Excel IFERROR Function. The IFERROR Function must be accessed by using the WorksheetFunction Class:
This will output the value of Range A10, if the value is an error, it will output 0 instead.
VBA Error Types
Runtime Errors
As stated above:
VBA Runtime Errors are errors that occur during code execution. Examples of runtime errors include:
- Referencing a non-existent workbook, worksheet, or other object
- Invalid data ex. referencing an Excel cell containing an error
- Attempting to divide by zero

You can “error handle” runtime errors using the methods discussed above.
Syntax Errors
VBA Syntax Errors are errors with code writing. Examples of syntax errors include:
- Mispelling
- Missing or incorrect punctuation
The VBA Editor identifies many syntax errors with red highlighting:

The VBA Editor also has an option to “Auto Syntax Check”:

When this is checked, the VBA Editor will generate a message box alerting you syntax errors after you enter a line of code:

I personally find this extremely annoying and disable the feature.
Compile Errors
Before attempting to run a procedure, VBA will “compile” the procedure. Compiling transforms the program from source code (that you can see) into executable form (you can’t see).
VBA Compile Errors are errors that prevent the code from compiling.
A good example of a compile error is a missing variable declaration:


Other examples include:
- For without Next
- Select without End Select
- If without End If
- Calling a procedure that does not exist

Syntax Errors (previous section) are a subset of Compile Errors.
Debug > Compile
Compile errors will appear when you attempt to run a Procedure. But ideally, you would identify compile errors prior to attempting to run the procedure.
You can do this by compiling the project ahead of time. To do so, go to Debug > Compile VBA Project.

The compiler will “go to” the first error. Once you fix that error, compile the project again. Repeat until all errors are fixed.
You can tell that all errors are fixed because Compile VBA Project will be grayed out:

OverFlow Error
The VBA OverFlow Error occurs when you attempt to put a value into a variable that is too large. For example, Integer Variables can only contain values between -32,768 to 32,768. If you enter a larger value, you’ll receive an Overflow error:

Instead, you should use the Long Variable to store the larger number.
Other VBA Error Terms
VBA Catch Error
Unlike other programming languages, In VBA there is no Catch Statement. However, you can replicate a Catch Statement by using On Error Resume Next and If Err.Number <> 0 Then. This is covered above in Error Handling with Err.Number.
VBA Ignore Error
To ignore errors in VBA, simply use the On Error Resume Next statement:
However, as mentioned above, you should be careful using this statement as it doesn’t fix an error, it just simply ignores the line of code containing the error.
VBA Throw Error / Err.Raise
To through an error in VBA, you use the Err.Raise method.
This line of code will raise Run-time error ’13’: Type mismatch:

VBA Error Trapping
VBA Error Trapping is just another term for VBA Error Handling.
VBA Error Message
A VBA Error Message looks like this:

When you click ‘Debug’, you’ll see the line of code that is throwing the error:

VBA Error Handling in a Loop
The best way to error handle within a Loop is by using On Error Resume Next along with Err.Number to detect if an error has occurred (Remember to use Err.Clear to clear the error after each occurrence).
The example below will divide two numbers (Column A by Column B) and output the result into Column C. If there’s an error, the result will be 0.
VBA Error Handling in Access
All of the above examples work exactly the same in Access VBA as in Excel VBA.

VBA Code Examples Add-in
Easily access all of the code examples found on our site.
Simply navigate to the menu, click, and the code will be inserted directly into your module. .xlam add-in.
On error resume next vba как работает
Поискав по рунету материал на тему обработки ошибок в VBA, не увидал на первых двух страницах результатов поиска чего-то, что мне понравилось. Может плохо смотрел, но решил написать на эту тему свою статью.
Простите, но — немного словоблудия 🙂
Ошибки в программе
Ошибки времени исполнения программы возникают, когда среда программирования не может выполнить то, что вы хотите. Таких ситуаций может быть много. Например:
Вы обращаетесь к объекту по имени, а объекта с таким именем в коллекции нет
Вы хотите выделить ячеку на одном листе, а этот лист в данный момент не является активным (типичнейшая ошибка новичков в Excel VBA)
Вы хотите удалить отфильтрованные автофильтром строки, а фильтр вообще не вернул записей и удалять нечего
Вы ссылаетесь на элемент массива, который находится за пределами его границ.
Вы пытаетесь присвоить переменной значение, которое оно не может хранить. Например, переменной типа Long нельзя присвоить строковую константу или переменной типа Integer присвоить знанчение превышающее число 32767 .
На любую из этих и сотни других ситуаций среда выполнения реагирует стандартно — прерывает ход выполнения программы на том операторе, где возникла ошибка или, как ещё принято говорить, исключение. На экран выводится информация о возникшей ошибке и предлагаются стандартные варианты для продолжения работы:
Continue (продолжить) — этот пункт во время возникновения ошибки всегда не активен. Он активен, когда по ходу выполнения программы вы использовали оператор Stop . Кстати это очень полезный оператор для отладки программы.
End (завершить) — завершение исполнения программы
Debug (отладка) — переход в режим отладки, в котором можно посмотреть, на каком операторе возникла ошибка, что содержат переменные, можно даже перетащить жёлтую полоску, подсвечивающую текущий оператор, назад, и модифицировать знанчение переменных через окно Immediate window (впрочем это экзотика). В общем случае кнопка Debug позволяет посмотреть, где случилась ошибка и попытаться понять почему так случилось.
Если вы — автор программы, в которой случилась ошибка, то вы, должно быть, в начале будете рады увидеть подобное окно, ибо только так вы сможете отловить основные ошибки, скрытые в вашем коде. Однако, если эту ошибку видит пользователь, то для него это, мягко говоря, безрадостное и малопонятное зрелище. Ещё хуже, если за эту программу вам заплатили деньги. Поэтому в среде худо-бедно профессиональных программистов принято предусматривать обработку ошибок в своих программах.
Почему вообще в коде возникают ошибки?
Много ошибок во время написания кода возникает по невнимательности или не совсем адекватного понимания того, что делаешь. Таких ошибок, как правило, очень много, особенно у начинающих программистов, но эти ошибки довольно легко отловить и исправить, так как, пока вы их не исправите, ничего не работает. Ну, например, вы должны извлечь данные из 5-го столбца, а вы извлекаете из 6-го, а их там банально нет. Ясно, что вы это очень быстро заметите.
Вторая группа ошибок — это ошибки оптимиста. Когда программа написана в целом правильно, но алгоритм не готов к ударам судьбы в виде неожиданных действий со стороны пользователя, ошибок ввода-вывода (вы рассчитывали считать данные из файла, а файла с таким именем не оказалось, либо он заблокирован другим приложением), особенностей конфигурации компьютера (разные версии ОС или офиса, которые в некоторых мелочах отличаются).
Тонкие логические ошибки. Чем сложнее программа, тем больше шансов, что модель задачи в вашей голове, ваша программа и реальность не совсем согласованы между собой. Пока вы не достигните достаточного погружения в задачу вы такие ошибки не найдёте и не исправите. Порой на это уходит много времени. Но это характерно для сложных задач.
Ошибки на стыке вашего приложения и сервисов ОС, приводящие к неожиданным крахам приложения. Такого вообще возникать не должно, но как мы понимаем, и ОС и офис содержат ошибки, да и вы (что более вероятно) можете пользоваться системными вызовами не правильно. Подобные ошибки — сущий кошмар, особенно когда они проявляются лишь на некоторых конфигурациях, при определенных условиях, их трудно поймать и надёжно воспроизвести.
Задачи механизмов обработки ошибок
Обеспечить стабильную работу программы . Возникновение ошибки, появление которой вы не предусмотрели, приведёт в большинстве случаев к аварийному завершению всей программы или её части. При определенном уровне подобных ситуаций это ведёт к тому, что программой пользоваться становится невозможно.
Информирование . Мало обработать ошибку и предотвратить завершение программы. Надо ещё и адекватно проинформировать пользователя о причинах нестандартного поведения программы. Частно причиной ошибок в программе являются некорректные действия пользователя, поэтому важно сообщать ему о них.
Защита данных от повреждения . Программа обязана защищать от непреднамеренных повреждений результаты своей или пользовательской работы. Деструктивные действия должны быть снабжены соответствующими предупредительными диалоговыми окнами. Часто ошибка, не обработанная должным образом может повредить нужные данные.
Файл примера
Скачать
Код без обработки ошибок
Вот простой пример с потолка. Если вызвать Example_00 , то она прекрасно отработает без ошибок и вернёт это:

В функцию GetCalories передаётся строка с блюдом, а она должна вернуть его калорийность, сверившись с таблицей в A1:B7 .

Давайте поищем слабые места в этом коде. Первое, что должно прийти в голову — если мы ищем, то, что произойдёт, если мы не найдём? А произойдёт, конечно же, ошибка. Её инициирует метод Match .

Ещё одно слабое место этой подпрограммы: функция возвращает вещественный тип Double , и даже, если поиск оказался удачным, то в Cells(intRow, 2) может случайно находиться текстовая строка, а потому, когда вы числовому типу попытаетесь присвоить строковый тип, также произойдёт ошибка. И, если вы второй ошибки сможете избежать за счёт дополнительного оператора if с проверкой через IsNumber (), то избежать первой ошибки таким способом нельзя. Что же делать? А вот тут на сцену выходят операторы обработки ошибок.
Есть 2 подхода к обработке ошибок: автономный подход и выносной . Эти термины я придумал только что, чтобы проще было их обсуждать.
Автономный подход
Смысл автономного подхода в том, чтобы не выносить сор из избы. Если в подпрограмме возникла ошибка, то мы должны предположить, на каком месте она возникнет и поджидать её там с дубиной. С ошибкой, в этом случае, разбираются обычно в операторе, идущем сразу после потенциально опасного места. Давайте смотреть, как это может выглядеть:
Итак, что тут сделано:
Сразу после объявления функции GetCalories_v1 идёт оператор on error resume next , который в случае возникновения в каком-либо месте ошибки, предписывает VBA просто передавать управление на следующий оператор, идущий после ошибочного.
Мы объявили переменные. Необъявленные переменные получают тип Variant и значение по умолчанию Empty . Объявленные переменные числовых типов инициируются нулём, строковые — пустой строкой, то есть я наперёд знаю, что они содержат, а это хорошо для обработки ошибок.
На вызове метода WorksheetFunction.Match у нас возникает ошибка, так как искомого значения в таблице нет. А это, между прочим, был оператор присваивания ( = ). Прежде, чем левой части оператора присваивания ( intRow ) что-то будет присвоено, необходимо вычислить правую часть оператора присваивания ( WorksheetFunction.Match . ), а поскольку в процессе этого вычисления возникает ошибка, то переменная intRow остаётся такой, какой была! А, как я уже сказал, VBA автоматически её инициализирует нулём до начала исполнения подпрограммы. Получается, что, если в этом операторе возникнет ошибка, то в intRow будет ноль. Если ошибки во время поиска не возникнет, то ноля там не будет ни при каких раскладах, так как строки на листе нумеруются с единицы.
И вот этот ноль мы и контролируем, добавляя оператор If . Если intRow больше нуля, то WorksheetFunction.Match отработала штатно, а если нет — то работу подпрограммы надо прерывать, но об этом чуть позже.
Далее мы помним, что Cells(intRow, 2) может теоретически вернуть строковое значение, которое вызовет ошибку Type missmatch при присвоении переменной типа Double ( GetCalories_v1 ), поэтому мы вставляем дополнительную проверку промежуточной переменной varTemp тому, что она числовая. И если это так, то присваиваем GetCalories_v1 значение из varTemp .
В случае возникновения любой ошибки внутри GetCalories_v1 она просто вернёт ноль. Почему ноль? Потому что переменная GetCalories_v1 тоже инициализируется нулём и об этом не надо заботиться, а в случае ошибки она останется в неприкосновенности.
Соответственно родительский код (в нашем случае его роль играет процедура Example_01 ) должен проверить, а не вернёт ли GetCalories_v1 ноль, и быть готовым к этой ситуации.
А вот теперь тонкий момент, который не все понимают. Почему я использовал промежуточные переменные intRow и varTemp ? Вроде бы есть очевидный ответ — чтобы не вычислять значение выражений с Match и Cells 2 раза. Отчасти это, конечно, так. Но это, в данном случае, не главная причина. Главная причина в том, что такой код
вызовет неправильное поведение программы. Если у нас Match вызовет исключение, то VBA передаст управление на СЛЕДУЮЩИЙ оператор, а следующий оператор в данном случае это то, что идёт после Then — присваивание переменной varTemp значения. Таким образом наша проверка на наличие ошибки сработает с точностью до наоборот, передав управление в ту часть кода, которая должна быть защищена от ситуации, когда Match не нашла строку в таблице. Вот почему важно в операторе If не иметь ничего такого, что могло бы вызвать ошибку.
Как видите, в этом подходе мне зачастую даже нет необходимости проверять объект Err , чтобы понять, что произошла ошибка, так как я ориентируюсь на то, что промежуточные переменные остаются неинициализированными, что является показателем наличия ошибки.
Выносной подход
Данный метод основан на том, что, когда возникает ошибка, то VBA передаёт управление на специальный участок кода — обработчик ошибок, который обычно размещают в конце подпрограммы. Это может выглядеть так:
Обратите внимание, что:
Оператор on error теперь в случае ошибки предписывает передавать управление на метку ErrorHandler , которая объявлена в конце кода процедуры GetCalories_v2
В коде мы никак не заботимся о каких-либо проверках. Возникла ошибка? Иди на метку — там разберутся.
Если ошибки не случилось, то, чтобы программа не стала исполнять строчки, предназначенные для обработки ошибок, перед меткой ErrorHandler обычно ставят оператор Exit Sub или Exit Function (в зависимости от типа подпрограммы).
Принципиальный момент — наличие оператора On Error Resume Next сразу после метки ErrorHandler . Дело в том, что после того, как вы перешли на метку ErrorHandler , очень опасно иметь действующим оператор On Error GoTo ErrorHandler , так как, если у вас в обработчике ошибки случится любая ошибка, то управление будет передано опять на метку и, как нетрудно понять, образуется бесконечный цикл. Поэтому сразу после метки мы возможность возникновения цикла ликвидируем оператором On Error Resume Next .
Что лучше?
Какой метод лучше применять зависит от ваших предпочтений и конкретных ситуаций. Грамотную обработку ошибок можно сделать и так и эдак. Вот несколько соображений по преимуществам и недостакам данных подходов:
Автономный подход
| Преимущества | Недостатки |
| Есть возможность точно идентифицировать каждую конкретную проблему (если вы её предусмотрели), возникающую во время исполнения, что позволит вам дать самые точные инстркции пользователю для предотвращения появления исключения в будущем. | Достаточно трудоёмок, так как подразумевает наличие большого количества проверок в коде. Каждое потенциально опасное действие должно быть снабжено соответствующим оператором If , в котором контролируется значение переменной или код ошибки. |
| Надо хорошо представлять себе ситуации, где могут возникнуть ошибки, в противном случае ряд ошибок вы просто не заметите на этапе отладки. | |
| Необходимо больше кода, а также требуется опыт и фантазия. | |
| Необходимо больше промежуточных переменных |
Выносной подход
| Преимущества | Недостатки |
| Ни одна ошибка не проскочит незамеченной. | Не смотря на то, что вы перехватите все ошибки, отреагировать на них правильно затруднительно, так как вы, по большому счёту, не знаете, на каком операторе произошла ошибка и почему. |
| Удобнее организовывать централизованный сбор логов по ошибкам в приложении. Однозначно, фаворит для больших проектов. |
Средства VBA для обработки ошибок
Кратко пробежимся по операторам, функциям и объектам VBA, которые предназначены для обработки ошибок времени исполнения программы.
Операторы
On Error
Оператор on error управляет тем, на какой участок вашего кода будет передано управление в случае возникновения ошибки. Данный оператор можно вставить в любое место вашей программы или подпрограммы. Есть 3 варианта:
On error goto label — после того, как этот оператор выполнен, ошибка, возникшая на других операторах программы приведёт к переходу на метку label.
On error resume next — после такого оператора, VBA будет игнорировать возникшую ошибку и передавать управление на следующий оператор, стоящий за тем, в котором возникла ошибка.
On error goto 0 — это режим по-умолчанию. В случае возникновения ошибки данный режим приведёт к появлению на экране стандартного обработчик ошибок VBA с кнопками End и Debug .
Resume
Данный оператор возобновляет выполнение программы. Применяется в выносном методе обработки ошибок.
resume label — возобновление с метки label
resume next — возобновление со следующего оператора
resume или resume 0 — возобновление с оператора, вызвавшего ошибку. Это имеет смысл, если вы устранили ошибку в своём обработчике. На мой взгляд, на практике такое применяется крайне редко.
Goto label
Переход на метку. Может пригодиться, однако, использование меток в коде для чего-то большего, чем обработка ошибок, считается страшным моветоном.
Досрочный выход из циклов (Do или For) и досрочный выход из подпрограмм (функции или процедуры). Могут пригодиться при обработке ошибок, но вообще это операторы и без того чрезвычайно полезны.
Объект Err
Err — глобальный объект (его не надо объявлять, а можно сразу пользоваться), который содержит информацию о последней ошибке, случившейся в вашей программе. Проверяя Err сразу после возникновения исключения или после ситуации, которая могла привести к исключению, вы можете понять, что имело место на самом деле.
Свойство Err.Number — содержит числовой код ошибки, по которому их различают в программе. Поскольку Number — свойство по умолчанию, то вы можете его опускать, то есть Err и Err.Number — это эквиваленты. Значение ноль говорит о том, что ошибки не произошло.
Err.Description — содержит англоязычное краткое описание ошибки
Err.Source — возвращает имя модуля, в котором возникла ошибка
Err.Clear — сбрасывает последнюю ошибку. Err сбрасывается также при выполнении оператором Resume , Exit (любого типа кроме Do и For) и On Error .
Err.Raise — искусственно вызывает исключение указанного в переданном параметре типа. Можно использовать для тестирования вашей подсистемы обработки ошибок.
Лично я привык в своих программах использовать автономный подход и, возможно, поэтому я не совсем осознаю все преимущества выносного подхода. Буду рад прочесть в комментариях ваше мнение на этот счёт. Тема обработки ошибок данной статьёй, конечно, быть исчерпана не может, но она послужит вам хорошей стартовой точкой в этом важном деле.