I'm lead to believe that the vba IDE doesn't allow proper breaking on errors. I asked a question on how to identify runtime errors here:
How do I break on errors?
The solution / workaround seems to be to use On Error GoTo ErrorHandler or similar. I'm trying to make that work but haven't had much success.
According to Microsoft On Error GoTo will send you to the specified code of region when a runtime error occurs (https://msdn.microsoft.com/en-us/library/5hsw66as.aspx). Based on my experience, that's not the whole story. This question is about how to actually make that solution work.
So I have the following function:
Function generateTimeseries() As Variant
On Error GoTo ErrorHandler
Dim currentArray As Range
currentArray = Selection.Range ' this doesn't work - I don't care why
generateTimeseries = currentArray.Rows
Return
ErrorHandler:
Debug.Assert False
Resume ' break here if you want to catch errors
End Function
This function never enters the ErrorHandler code. Instead, it falls over on Selection.Range, with the function simply returning #VALUE. I don't actually care why this breaks, I just want to know how I can get the IDE to tell me that it actually fell over on that line, without me jumping through the code manually.
I just tested and your error handler works, it breaks correctly on
Debug.Assert False
--> check the debugger options.
However this is not a correct way to handle errors in vBA, because if you compile your application and forget to adapt such error handler, when a user will encounter an error in the procedure, the application will fall into an infinite loop.
In mostly all situations, you have to throw a msgbox to let the user know that an error has occurred, and terminate the current procedure. The only situation where this is not applicable is when you do something that can trigger an error but you know it and deliberately want to bypass the error. It's a rare situation.
I have never used Assert method and this is how I handle my errors in every procedure
Function generateTimeseries() As Variant
On Error GoTo ErrorHandler
Dim currentArray As Range
currentArray = Selection.Range ' this doesn't work - I don't care why
generateTimeseries = currentArray.Rows
Exit_Function:
Exit Function
ErrorHandler:
MsgBox Err.Description, vbCritical, "Error " & Err.Number
Resume Exit_Function ' breakpoint here if you want examine the code after error, otherwhise it terminates the function
Resume ' Once the code breaks on the above line, move to this instruction and F8 to return to the statement in error
End Function
I had enabled the feature to "Break on all Errors" in an earlier attempt to break on all errors.
I turned this off by going to Tools->Options->General and replacing Break on All Errors with Break on Unhandled Errors.
It turns out that Break on All Errors is Windows for "Break on Some Errors, but just return garbage if it's a UDF error".
Related
I have a bunch of vba code that was written using the .Raise and On Error methods to deal with exceptions. In most cases this works well, but in some functions we want the execution to continue notwithstanding the exception. In such cases, the developers used On Error GoTo 0. The issue is that execution continues only after the error message box is dismissed.
Thus my question is: is there a way to suppress all message boxes created by the .Raise method for a certain range of code? I've tried Application.DisplayAlerts = False but the alerts from .Raise are still displayed.
Thanks!
On Error GoTo 0 restores the normal error handling. You use it to cancel a previously set On Error Goto ....
What you want is On Error Resume Next.
Sometimes a certain bit of code will raise an error in an expected way, and it's most convenient to handle it locally rather than throw it to an error handling routine where it will get mixed up with other errors of the same type. Yet you don't want unexpected errors to be swallowed; you want them to be raised as usual.
In the (slightly contrived) example below, the FindInArray function can raise different types of error. One of them, ERR__ELEMENT_NOT_FOUND_IN_ARRAY, is more or less expected and so I want to handle it locally. But other error numbers may also occur, and if so I want them to be dealt with by the error handling routine.
I find that if I deal with some expected error numbers locally, I can't easily "rethrow" unexpected error numbers to be dealt with elsewhere.
How do I segregate the expected errors I want to deal with locally, from unexpected errors to be dealt with in error handling routine (or elsewhere)?
On Error GoTo ErrorHandler
'Some code...
'Here I want to trap a likely/expected error locally, because the same
'error may occur elsewhere in the procedure but require different handling.
On Error Resume Next
personIndex = FindInArray(personName, personArray)
If Err.Number = ERR__ELEMENT_NOT_FOUND_IN_ARRAY Then
MsgBox "Name not found in person array. Using default person."
Else
'What if it's a different kind of error?
' .e.g. ERR__ARRAY_CONTAINS_TWO_PERSONS_WITH_SAME_NAME
'I want to rethrow it, but can't because On Error Resume Next swallows it.
End If
On Error GoTo ErrorHandler 'back to normal
'I can't rethrow it here either, because On Error Goto cleared the Err object.
'-----------------------
ErrorHandler:
Select Case Err.Number
Case ERR__ELEMENT_NOT_FOUND_IN_ARRAY
'The error number doesn't give me enough info
'to know what to do with it here!
Case ERR__ARRAY_CONTAINS_TWO_PERSONS_WITH_SAME_NAME
'Existing code to deal with this error
Case ...
I guess I could "save" the error Number, Source, Description, etc. in some other variable / object, and use those to raise an error after On Error GoTo ErrorHandler 'back to normal, (and in fact I have implemented this just to see) but that seems terribly inconvenient and clumsy.
I made a user-defined type that has the same members as the Err object (Number, Source, Description, etc.). The SaveErr function will basically copy the values of the Err object properties into a variable of this type, and RaiseSavedErr will raise an error using those property values.
Of course the exact same thing could be done using a class and methods instead of a user-defined type and functions/subs. But the idea would be the same.
Example:
On Error Resume Next
personIndex = FindInArray(personName, personArray)
savedErr = SaveErr(Err) 'Save values of Number, Source, Description, etc.
On Error GoTo ErrorHandler
'Segregate error handling strategies here using savedErr
If savedErr.Number = ERR__ELEMENT_NOT_FOUND_IN_ARRAY Then
MsgBox "Name not found in person array. Using default person."
Else
RaiseSavedErr savedErr 'rethrows the error
End If
I'd like to know if there is a more standard or elegant way of doing this.
This answer is my opinion on the problem at hand, perhaps viewed from a slightly different angle.
When considering this block of code:
On Error Resume Next
personIndex = FindInArray(personName, personArray)
If Err.Number = ERR__ELEMENT_NOT_FOUND_IN_ARRAY Then
MsgBox "Name not found in person array. Using default person."
Else
End If
You mention: "expected errors" in the title.
But the thing is that no error should be thrown if you know in advance that it may occur.
They are a form of validation that should in my opinion be built in into the functions in the form of conditional statements.
The before mentioned code block would be something like this on a basic level:
If Not (in_array(vArray, "Jean-Francois")) Then
MsgBox "Name not found in person array. Using default person."
End If
Which in my opinion is a lot cleaner and readable.
With a custom function that is not part of the base code, but that does your check behind the scenes. Reusable functions can be wrapped in a module that you use in a way that is very similar to a static class.
Public Function in_array(vArray As Variant, sItem As String) As Boolean
Dim lCnt As Long
in_array = False
Do Until lCnt = UBound(vArray) + 1
If StrComp(vArray(lCnt), sItem, CompareMethod.Text) = 0 Then
in_array = True
Exit Function
End If
lCnt = lCnt + 1
Loop
End Function
Even better would be to use the in_array() function from within the findInArray() function and have only 1 line of code in the basesub, which would be:
personIndex = FindInArray(personName, personArray)
Let the functions in the back handle the rest and intercept exceptions that you can foresee.
This is only an example, obviously you write the functions and return values that are useful for you and you could probably add more extensive validation.
My point is that these return values are return messages that are a part of the application / validation logic, I don't see them as technical errors - hence, I don't see any benefit in using an error handler for them as a custom created function exactly fits your needs in a (my opinion) much cleaner structure.
I consider it a technical error when you pass for example three arguments into the function call while it only accepts two. The error handler notifies you, after which the developer may decide to make the current function more dynamic by allowing eg. optional parameters and fixing the bug.
Though I am a bit confused by the question asked (and I've read it over quite a lot of times by now :-)), I have a very strong feeling that the source of this dilemma lies within function scopes.
If it's ok, I will use some basic examples that show a pattern but are not 1-1 on par with your code.
How do I segregate the expected errors I want to deal with locally,
from unexpected errors to be dealt with in error handling routine (or
elsewhere)?
I kind of feel that the answer lies within the question itself.
Error handlers are functional within the local scope of sub routines / function that you call from a lower level sub routine or function.
I find that if I deal with some expected error numbers locally, I
can't easily "rethrow" unexpected error numbers to be dealt with
elsewhere.
You can if you delegate the code that you want to check for local errors to external functions / sub routines that you place on top of a certain level in the call stack. Since they handle errors within their own scope, they won't mix up with each other.
Consider this code:
Sub baseSub()
Dim n As Integer
n = checkDivision(1, 0)
n = 1 / 0 ' cause an error
End Sub
Public Function checkDivision(iNumerator As Integer, iDenominator As Integer)
On Error Resume Next
checkDivision = iNumerator / iDenominator
If Err.Number <> 0 Then
checkDivision = Err.Number
Exit Function
End If
End Function
On the contrary: when applying On Error Resume Next from a baseSub, all functions that are placed on top of the call stack will ignore the errors as well. But, it doesn't work the other way around.
I think you may use this to your advantage.
So to conclude, I believe that you can solve the problem by trapping
the expected errors in the delegated functions that you place on higher levels
of the call stack.
If this doesn't work, then I'm out of ideas.
On Error Resume Next is the root of all evil in VBA ;)
I have not seen your entire code but what you have asked in the question can easily be catered by using MULTIPLE ERROR HANDLERS & RESUME. It is much simpler than creating your custom Err object and raising error events...
Public Sub sixsixsixBytes()
On Error GoTo COMMON_ERROR_HANDLER
'Some code...
On Error GoTo ARRAY_ERROR_HANDLER
Call Err.Raise(123) 'lets say error occured in personIndex = ....
'it will jump to 2nd error handler and come back
'some code again...
'If statement is not required at all
Call Err.Raise(666)
On Error GoTo COMMON_ERROR_HANDLER:
'some code again...
Call Err.Raise(234)
Exit Sub
'# MULTIPLE ERROR HANDLERS
COMMON_ERROR_HANDLER:
Select Case Err.Number
Case 234: MsgBox 234
Case 345: MsgBox 345
End Select
ARRAY_ERROR_HANDLER:
Select Case Err.Number
Case 123:
MsgBox "Name not found in person array. Using default person."
Resume Next 'or Resume after changing a value (as per your need)
Case 666:
MsgBox "Some other error"
Resume Next
End Select
End Sub
I've been using VBA in Excel for a while, and I use a custom error handler for all of my procedures. I find myself, for the first time, in the position of needing to useErr.Raise (to deal with a Case Else situation in a Select Case block)., and I can't figure out how pass the error to the custom error handler. Instead of passing the raised error to the custom handler, VBA pops up its own ugly and fairly useless error dialog. If anyone can tell me a way to get around this I'd be very appreciative.
Below is a genericized version of the code I'm using (function/variable names have been changed to protect the innocent). The gErrorHandler object is a globally dimensioned class module variable which handles errors from any and all procedures.
Public Function MyFunction(dblInputParameter As Double) As Double
On Error GoTo Err_MyFunction
Dim dblResult as Double
Select Case dblInputParameter
...Several case statements go here...
Case Else
Err.Raise vbObjectError + 1000, "MyProjectName.MyObjectName", "Error Description"
End Select
MyFunction = dblResult
Exit_MyFunction:
Exit Function
Err_MyFunction:
gErrorHandler.DisplayError Err.Number, Err.Description, Erl, csModule, "basMyModuleName", "MyFunction"
Resume Exit_MyFunction
End Function
And here's the error dialog I get instead of having the error passed to the custom handler:
As Tim pointed out in his comment, the answer was that the VBA IDE was configured to break on all errors. Changing it to break on unhandled errors only gave me the behavior I desired.
I copy a piece of code from SO as an example. The subroutine contains an error handler. Should one make an error handler for all Subs?
Public Sub SubA()
On Error Goto ProcError
Connection.Open
Open File for Writing
SomePreciousResource.GrabIt
ProcExit:
Connection.Close
Connection = Nothing
Close File
SomePreciousResource.Release
Exit Sub
ProcError:
MsgBox Err.Description
Resume ProcExit
End Sub
And by the way, how does the flow of the control inside a subroutine when the code executor encounter a Exit Sub, End Sub and Resume? And when it encounters a label such as ProcError: during the execution, does it execute it, or does it skip it?
The short answer is: No, not only do you not need to have an error handler in each procedure, but in fact you would usually not want an error handler in each procedure.
You will want to do the error handling where it makes most sense to do it. Often, you would only want an error handler in the highest-level procedure, i.e. the one that calls all the others; lower-level procedures should kick the problem upstairs and let errors "bubble up" to the higher-level procedure. Sometimes you will want some error handling in lower-level procedures.
For more, I refer you to these two excellent answers by #jtolle:
VBA Error "Bubble Up"
Handling errors in math functions
Also, an internet search will reveal that there is a whole literature on the web about error handling. Some of it is quite wrong, in my opinion! But if it sticks to what I wrote in the first two paragraphs, then it's worth considering.
Exit Sub and End Sub are fairly intuitive: the former stops execution of the current Sub and returns control to the procedure that called it (or stops execution entirely if the procedure was not called by another procedure). The latter is just a indication to the compiler that this where the code for this particular Sub ends -- and if executed, End Sub behaves like Exit Sub.
Resume specifies what should happen next, after an error-handling routine is finished. Plain Resume returns to the same statement that caused the error and tries to execute it again. Resume Next skips the statement that caused the error, and instead goes to the statement immediately following it. Resume mylabel goes to label mylabel:.
If a label such as your ProcError: is encoutered in the course of execution, then nothing special happens, and execution moves on to the next statement after the label. Of course in your example, ProcError: will never get executed directly (i.e. not unless an error is raised) because there's an Exit Sub just before it.
By the way, the ProcExit: block should probably start with an On Error Resume Next (i.e. keep on closing everything and exiting regardless of any errors) or alternatively, as pointed out by #Phydaux, an On Error Goto 0 (on error, stop execution), otherwise if something in there triggers an error, you may get into an infinite ping-pong loop between the error handler and the ProcExit: code.
ProcExit:
On Error Resume Next ' or, alternatively, On Error Goto 0
Connection.Close
Connection = Nothing
Close File
SomePreciousResource.Release
Exit Sub
Exit Sub will exit the subroutine immediatly like return in Java
End Sub is just the marker for the end of the sub routine block like } in Java
A label is simply a mark in the code wich is used to define a jump destination. In case you did not jump to the label but arrived there "regularly" the label itself will be ignored but the code after the label will be executed as if there was no label, the code in your example will be executed all the way to the Exit Sub statement as long as no error occurs. If one occures it will jump to ProcError
Resume will in this case execute ProcExit see more here
I haven't read much about it, but the author at the link below recommends that I don't use "bubble up" to centralize error handling in VBA.
Excel Programming Weekend Crash Course via Google Books
But I'm not sure why he recommends that, and he doesn't really explain.
Can someone tell me why I should put error handling in EVERY procedure instead of using "bubble up"? Or at least, do you know why the author says not to?
Thanks.
The short answer to your first question is "you shouldn't put an error handler in EVERY procedure". But usually some routines do need them!
To say that "every procedure must have an error handler" is in general terrible advice. The flaws with VBA error handling have been much discussed elsewhere. Conceptually, though, it's not all that different from the more standard form of exception handling found in other languages. Most of the best practices from those languages apply. You should handle errors at the lowest level where handling them makes sense. Sometimes this is in the procedure where the error occurred, many times not.
Often the most meaningful thing an internal routine can do when an error occurs is just let it pass on up the stack so it can reach code that knows what to do with it. It really depends on the routine and how it fits with the rest of the program.
Consider these examples:
A handler in a calling procedure will handle all errors raised by the routines it calls. So if a particular routine doesn't need any cleanup, then don't put any error-handler code there:
Sub Caller()
On Error GoTo HANDLER
ChildProc
On Error GoTo 0
Exit Sub
HANDLER:
Debug.Print Error, "Parent cleanup - something happened in either this procedure or a procedure that it called"
End Sub
Sub ChildProc()
Debug.Print 10 / 0 ' causes error
'Don't bother handling errors here since there's nothing this routine can do about them
End Sub
On the other hand, you may need cleanup tasks, in which case you need an error handler.
Sub Caller()
On Error GoTo HANDLER
ChildProc
On Error GoTo 0
Exit Sub
HANDLER:
Debug.Print Error, "Parent cleanup"
End Sub
Sub ChildProc()
'Pretend this routine gets ahold of some resource that must be cleaned up when it's done
call get_resources()
On Error GoTo HANDLER
Debug.Print 10 / 0 ' causes error
On Error GoTo 0
'Clean up once we're done
call release_resources()
Exit Sub
HANDLER:
Debug.Print Error, "Child cleanup"
'Clean up in case of an error
call release_resources()
'Raise another error if necessary to let callers know something went wrong
Err.Raise 10000, "ChildProc", Error
End Sub
The above examples are just meant to illustrate the point about why you might need or not need an error handler in a given routine. So it's worth noting that in real code the "resource" example is usually handled better with an RAII technique where the error handling is encapsulated with the resource acquisition and release - see https://stackoverflow.com/a/3792280/58845 for a VBA example. And things like whether to re-raise a caught error are also situation-dependent. Sometimes it's possible to handle the error entirely locally and then there is no need to tell callers that anything went wrong.
The answer to your second question is that the author doesn't seem to understand exception handling very well. He admits that error handling is context specific, but then seems to suggest that every procedure should locally decide between "correct the problem right here and resume execution" and "terminate the program". He leaves out the usually correct option, which is "clean up locally and kick the problem upstairs". So routines with no need to clean up locally should just let errors "bubble up".
My 2 cents:
You should put error handlers on all Public Procedures and Events. This means that the procedure at the bottom of the call stack will always have an error handler. Then add error handlers in your other procedures as it makes sense. If an error occurs in a procedure that does not have an error handler, it will "bubble up" to the top level error handler where it be logged/displayed in a professional fashion.
A scenario where you might want to add an error handler to a private (lower level) procedure is this:
The code needs to be fast. You have a rare condition that can be avoided, but will force you to perform an expensive logical test inside of a loop (or worse a nested loop).
You might perform the logical test in the error handler, and if it's said "rare occurrence" make the correction and resume. As the condition is rare, you will see performance gains for most conditions. If the error handler can't figure out and correct the problem then re-raise the error to bubble it on up the stack.
Obviously this is just one scenario.
I'm not sure what the default error handling of VBA is, but since its Visual Basic for Applications, and those applications include things like excel and word, I assume just a dialog box will appear which will not be helpful to the user.
I assume that the author has been bitten by code not handling errors so he now recommends all procedures to handle errors.
The full answer is that you have to be aware of every error that can occur and to have code in place to handle it, whether it is as low as possible (where you may not know what to do), or as high as possible (which means less effort writing error handling code, but not knowing why the error occurred), or strategically (which is just in the right places where you should be able to recover from most common errors) or just everywhere (which may be just too much development effort).
I see at least one reason in his explanation: because doing so deprives you from the benefit of Resume (next).
Plus you won't know in which module the error happened.
It is better not to use "bubble-up" part of error handling because errors should be handled & if it is known as to what to do, if such an error occurs - is better known to procedure as to what to do than the calling procedure.
Sub test()
On Error GoTo e
Dim c As Integer
Dim d As Integer
c = add(5, 0)
d = divideWhichManagedItsOwnErrorHandling(5, 0)
d = divide(5, 0)
Exit Sub
e:
MsgBox "error occurred somewhere for which I don't know what to do: " + Err.Description
End Sub
Function add(a As Integer, b As Integer) As Integer
add = a + b
End Function
Function divide(a As Integer, b As Integer) As Integer
divide = a / b 'if error occurs, it will "bubble-up" to the caller.
End Function
Function divideWhichManagedItsOwnErrorHandling(a As Integer, b As Integer) As Integer
On Error Resume Next
Dim result As Integer
result = a / b
If Err.Number = 11 Then 'if divide by zero occurred, user must have passed 0 for b
result = 0 ' return 0 if the divide by zero occurs.
End If
divideWhichManagedItsOwnErrorHandling = result
End Function