My question is here about possibility of another(short) way solution;
In vba I have a procedure, which call numerous functions, and in that functions some of them also other functions. I need a universal error handler, which exits sub and writes err.Description to screen.
I know how to write it on each function.
On Error GoTo errorhand
'block of code
Exit Sub(function)
errorhand:
MsgBox Err.Description
Exit Sub
End Sub(function)
Is there any way, other than to write these piece of code on each function?
(Due to each error opens in specific function, I donot think writing this code to main procedure will lead to error handler to show up in every function where error happens)
Related
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".
I am writing a DLL that I will use to automate Excel. I would like to set up an extensive error handling module. What I can't figure out is how to exit the entire DLL on an error. I can't use the End statement in a DLL. Application.Quit does nothing either.
If I am in a low level sub, and I run an error handler and then Exit Sub, it will just go up another level etc. In .NET there are 'exceptions'. Anything similar in VB 6.0?
Thanks.
While what Plutonix says is true, there is something more similar to exception handling that you can use as well. You can create an vbObjectError of your own and raise that up the call stack via the error handling mechanism.
First, create some error in your mind. Best practice is to use a number that has vbObjectError (a very large negative number) added to it, so as not to collide with native VB errors. Then, do something like this:
Public Sub MySub()
On Error Goto errHandle
'do stuff
Exit Sub
errHandle:
Select Case Err.Number - vbObjectError
Case 1
'Do This
Case 2
'Do That
Case 1000
Err.Raise 1000 + vbObjectError, "Fatal Error, Really we Gotta Leave", _
"My DLL"
End Select
End Sub
So, the thing I think you are missing in all this is that if you raise an error in your error handler, it gets re-raised in the calling proc. If you keep re-raising the "exit error" like this in all your error handlers, the error will bubble up the call stack and eventually make it to your client procedure, the one that instantiates your DLL object. At that point, you will have exited your DLL object. In your client object's error handler, you can clean up your object references and so on as needed, and there you are.
You don't have to handle errors in low level subs as VB will bubble errors (which are very similar to exceptions) up the call stack as far as it can. Only if there are no errors at the highest level will your program crash.
Going down the call stack: Event > UI code > DLL code > Sub code (error occurs)
Coming up the call stack: Sub error handler > DLL error handler > UI error handler > Crash
That said it's bad practice to only put error handlers at the very top level as then you'll have a much harder time debugging problems.
As a DLL can have a limited set of public procedures as it's interface (you didn't make all those low level subs Public did you?) you can put an error handler in each public procedure and then either handle or raise the error as required. There are several common ways this has been handled over the years which don't really need iterating again, just search for 'how to handle errors' or something similar.
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