debugMajor
Try catch statement in VBA using the standard VBA error handling statements
Viewed 0 times
handlingtheerrorstatementstatementscatchstandardusingvbatry
Problem
The code below is pretty self explanatory: just copy and paste it all into a module and run it, it provides a few use cases and many explanatory comments in the text. (It works but I'm interested to know what other people make of it and for any suggestions you might like to make.)
The most important facts to realise are:
-
When you use on error goto Label1 the procedure enters into a state of "I'm handling an error" as an exception has been raised. When it is in this state, if another "On Error Goto" label2 statement is executed it will NOT goto label2, but raises and error which is passed to the code that called the procedure.
-
You can stop a procedure being in the "I'm handling an error" state by clearing the exception (setting err to nothing so the err.number property becomes 0) by using
(NOTE that
Also important to note is that Err.Clear resets it to zero but it is actually equivalent to:
ie Err.Clear removes an "On Error Goto" that is currently in place. So therefore it is mostly best to use:
as using Err.clear You would often need to write
I use the above techniques with various labels to simulate the sometimes useful functionality that Visual basic TRY CATCH blocks give, which I think have their place in writing readable code.
Admittedly this technique creates a few more lines of code than a nice VB try catch statement, but it's not too messy and pretty easy to get your head around.
PS. Also of interest might be the procedure ManageErrSource which makes the Err.Source property store the procedure where the error occurred.
```
Option Compare Database
Option Explicit
Dim RememberErrNumber As Long
Dim RememberErrDescription As String
Dim RememberErrSource As String
Dim RememberErrLine As Integer
Priva
The most important facts to realise are:
-
When you use on error goto Label1 the procedure enters into a state of "I'm handling an error" as an exception has been raised. When it is in this state, if another "On Error Goto" label2 statement is executed it will NOT goto label2, but raises and error which is passed to the code that called the procedure.
-
You can stop a procedure being in the "I'm handling an error" state by clearing the exception (setting err to nothing so the err.number property becomes 0) by using
Err.clear
or
On Error Goto -1 ' Which I think is less clear!(NOTE that
On Error Goto 0 is different from the above)Also important to note is that Err.Clear resets it to zero but it is actually equivalent to:
On Error Goto -1
On Error Goto 0ie Err.Clear removes an "On Error Goto" that is currently in place. So therefore it is mostly best to use:
On Error Goto -1as using Err.clear You would often need to write
Err.Clear
On Error Goto MyErrorHandlerLabelI use the above techniques with various labels to simulate the sometimes useful functionality that Visual basic TRY CATCH blocks give, which I think have their place in writing readable code.
Admittedly this technique creates a few more lines of code than a nice VB try catch statement, but it's not too messy and pretty easy to get your head around.
PS. Also of interest might be the procedure ManageErrSource which makes the Err.Source property store the procedure where the error occurred.
```
Option Compare Database
Option Explicit
Dim RememberErrNumber As Long
Dim RememberErrDescription As String
Dim RememberErrSource As String
Dim RememberErrLine As Integer
Priva
Solution
The problem is that runtime errors in VBA are not exceptions, and error-handling in VBA has very little in common with exception handling.
The
I like how you said it yourself:
..but why isn't that applied to the rest of the code? No offense, but this is spaghetti logic, written in procedures that clearly and shamelessly violate the Single Responsibility Principle. No SRP-compliant code would ever need two of such "try-catch" blocks.
This smells:
It means one of two things: either you have error-handling code that runs in non-error contexts, or you have dead code that should be deleted.
This smells reeks:
Firstly, a colon (
Speaking of
Neil Stephenson thinks it's cute to name his labels 'dengo'
I don't like how I need to jump between labels to follow the code. IMO it is messy and needlessly spaghettified.
Fine, smartypants. So, how does one cleanly handle errors in VBA then?
Adhere to best practices, and write small procedures that do one thing, and do it well.
Abstraction and encapsulation are two of the 4 pillars of OOP, and they're fully supported in VBA. Polymorphism is also somewhat of an option; only proper inheritance is ruled out, but that doesn't prevent one from abstracting concepts in class modules and instantiating specialized objects.
Procedural code written in standard modules (.bas) should be tiny little public methods (macro "hooks") that create the objects required to run the functionality.
So, how does that even remotely relate to proper error-handling?
Given code that adheres to the above points, there's no reason to not implement error-handling the idiomatic VBA-way.
This pattern is analoguous to a "try-catch-finally" in the following way:
If your error-handling subroutine can raise an error, then you're not adhering to SRP. For example, writing to a log file is a concern of its own, that should be abstracted into some
RememberErrLine = Erl()The
Erl function is a hidden member of the VBA.Information module for a reason - it returns 0 unless the error occurred on a numbered line. And if you're using line numbers in VBA, you have been living in a cave for 25 years and are probably using GoSub statements instead of writing procedures. Line numbers are supported for legacy/backward-compatibility reasons, because code written in the 1980's required them.I like how you said it yourself:
' THEREFORE KEEP THE CODE HERE VERY SIMPLE!..but why isn't that applied to the rest of the code? No offense, but this is spaghetti logic, written in procedures that clearly and shamelessly violate the Single Responsibility Principle. No SRP-compliant code would ever need two of such "try-catch" blocks.
This smells:
Case 0: ' No Error, do NothingIt means one of two things: either you have error-handling code that runs in non-error contexts, or you have dead code that should be deleted.
This smells reeks:
GoTo CatchBlock2_End:
CatchBlock2_Start:Firstly, a colon (
:) that isn't specifying a line label, is an instructions separator. Turns out a new line is also an "instructions separator", so the colon at the end of GoTo CatchBlock2_End is utterly useless and confusing, especially given the indentation level of the GoTo statement.Speaking of
GoTo...Neil Stephenson thinks it's cute to name his labels 'dengo'
I don't like how I need to jump between labels to follow the code. IMO it is messy and needlessly spaghettified.
Fine, smartypants. So, how does one cleanly handle errors in VBA then?
- Write clean code in the first place.
Adhere to best practices, and write small procedures that do one thing, and do it well.
- Write Object-Oriented code.
Abstraction and encapsulation are two of the 4 pillars of OOP, and they're fully supported in VBA. Polymorphism is also somewhat of an option; only proper inheritance is ruled out, but that doesn't prevent one from abstracting concepts in class modules and instantiating specialized objects.
Procedural code written in standard modules (.bas) should be tiny little public methods (macro "hooks") that create the objects required to run the functionality.
So, how does that even remotely relate to proper error-handling?
- Embrace idiomatic error handling, don't fight it.
Given code that adheres to the above points, there's no reason to not implement error-handling the idiomatic VBA-way.
Public Sub DoSomething()
On Error GoTo CleanFail
'method body
CleanExit:
'cleanup code goes here. runs regardless of error state.
Exit Sub
CleanFail:
'handle runtime error(s) here.
'Raise Err.Number '"rethrow" / "bubble up"
Resume CleanExit
Resume 'for debugging - break above and jump to the error-raising statement
End SubThis pattern is analoguous to a "try-catch-finally" in the following way:
- The body is the "try" part, that does what the method name says and nothing more
CleanFailis the "catch" part, that only runs if an error is raised
CleanExitis the "finally" part, that runs regardless of whether or not an error was raised... unless you're rethrowing. But then if you need to bubble up an error for the calling code to handle, you shouldn't have much cleanup code to execute, and you should have a very very very good reason to do so.
If your error-handling subroutine can raise an error, then you're not adhering to SRP. For example, writing to a log file is a concern of its own, that should be abstracted into some
Logger object that lives to deal with logging concerns, and exposes methods that handle their own errors. Error-handling subroutine code should be trivial.Code Snippets
RememberErrLine = Erl()' THEREFORE KEEP THE CODE HERE VERY SIMPLE!Case 0: ' No Error, do NothingGoTo CatchBlock2_End:
CatchBlock2_Start:Public Sub DoSomething()
On Error GoTo CleanFail
'method body
CleanExit:
'cleanup code goes here. runs regardless of error state.
Exit Sub
CleanFail:
'handle runtime error(s) here.
'Raise Err.Number '"rethrow" / "bubble up"
Resume CleanExit
Resume 'for debugging - break above and jump to the error-raising statement
End SubContext
StackExchange Code Review Q#94415, answer score: 28
Revisions (0)
No revisions yet.