This question already has answers here:
How to check which line of VBA code is causing errors
(3 answers)
Closed 3 months ago.
Question:
In VBA, is there any way to execute some clean up code after an error is raised without either A) losing the detail of the native error messages by manually raising the error, or B) inserting & maintaining line numbers throughout my code? Edit for clarification: The detail being lost is the 'debug' button on the native error message which then highlights the line causing the error.
More Background/Info:
I want to change the calculation mode to manual at the start, but I want to ensure it's changed back before the code exits. This is fine if there's no error, but the only way I can think to ensure it's raised even if an error is encountered is to change the exception handler to something like On Error Resume Next, ensure the calculation mode is restored, then manually raise the error. But doing this loses details like where the error occurred; making it more difficult to resolve issues.
The only 'solution' I've been able to find is to manually (or using a tool) add line numbers which can then be used in the manually raised error; but this seems a pain to maintain and (imo) makes the code a bit messy.
If these truly are the only two options (and my fruitless searching seems to suggest that's the case; but sorry if this has been answered somewhere and I just failed to find it) then so be it; I'll have to opt for one of them. But I just wanted to see if anyone knows of another option?
Dummy Code:
(Just a quick mock-up of what it might look like with the 'raise the error manually and lose the details' method). Edit for clarification: The error in the below code is simple just to have something throwing an error; here it would be obvious where the error was, but in the 'real' problem it might not be obvious where the error was triggered (which is where the debug option on the native error message comes in handy).
Sub Example()
Dim OrigCalcMode as XlCalculation
OrigCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
On Error Resume Next
' Main code would go here, but to simply induce an error...
Dim Val as Integer
Val = 1 / 0
If Err.Number <> 0 Then
MsgBox ("Error Number: " & Err.Number & vbCrLf & vbCrLf & _
"Error Description: " & Err.Description)
Err.Clear
End If
On Error GoTo 0
Application.Calculation = OrigCalcMode
End Sub
Your example is maybe a little too simple - typically there's the consideration of (eg) how to handle errors in calling code, and how to decide if an error is "fatal" (all subsequent steps should be aborted) or recoverable.
A typical approach might be something like:
Sub Example()
Dim OrigCalcMode As XlCalculation
OrigCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
On Error GoTo haveError
' Main code would go here, but to simply induce an error...
Dim Val As Integer
Val = 1 / 0
cleanExit:
Application.Calculation = OrigCalcMode 'make sure to reset this
Exit Sub 'don't run into the error handler
haveError:
MsgBox "Error Number: " & Err.Number & vbCrLf & vbCrLf & _
"Error Description: " & Err.Description
'<Make any decisions about what to do with this specific error>
Resume cleanExit 'Resume clears the Err object
End Sub
Related
This question already has answers here:
Why VBA goes to error handling code when there is no error?
(5 answers)
Closed last year.
Example Code:
On Error GoTo Line3
More code
Line3:
Some code
Some code
More code
Even when there is no error, the "some code" underneath Line3 still gets run even though I don't want it to run. Otherwise when there is an error, Line3 gets run appropriately (skipping code that comes before it like it should).
Your code runs as it should. Code dealing with errors should be written outside of the main procedure, keeping in mind that your code should also try and spot potential errors and deal with them before they cause an error.
At the moment your error isn't being cleared when you jump to it, and because it's in the main body of code it's executed when your first set of More Code is finished and it reached the Line3 label.
Sub Test1()
'Ignore the error.
'MsgBox won't appear, but code won't know an error occured.
'MsgBox says all's good anyway, even though the error is still present.
Dim Rng As Range
On Error GoTo SkipLineWithError
MsgBox Rng.Address
SkipLineWithError:
MsgBox "All good, error number is " & Err.Number
End Sub
A better way is to try and catch the error before it happens:
Sub Test2()
'Checks that Rng won't throw an error if referenced.
'Code has dealt with the error and says all's good.
Dim Rng As Range
If Not Rng Is Nothing Then
MsgBox Rng.Address
Else
MsgBox "Range not set"
End If
MsgBox "All good, error number is " & Err.Number
End Sub
Sometimes, errors do occur though and they need to be dealt with properly. For this you jump out of the main procedure, deal with the error and jump back in again.
With this code notice the Exit Sub - the code between Exit Sub and End Sub is where your error handling goes. The main body of code ends when it reaches Exit Sub.
Resume tells your code where to jump back to - on its own it jumps back to the line that caused the error, Resume Next is the line after the error and Resume <label> jumps back to a label you've entered such as Resume Line3
Sub Test3()
Dim Rng As Range
On Error GoTo ErrorHandler
MsgBox Rng.Address
MsgBox "All good, error number is " & Err.Number
TidyExit:
'Close connections, general tidy up before ending procedure.
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 91 'Deal with the error if it happens.
'For this we'll give Rng a default address.
Set Rng = Sheet1.Range("A1")
Resume
Case Else
MsgBox "Error couldn't be handled... display an error message."
Resume TidyExit 'Jump to end of main body of code.
End Select
End Sub
Edit: Have updated code based on comment by #VBasic2008. Was being lazy with my first code and missed a key point.
I've barely scraped the surface here, the links below should help.
on-error-statement
vba-error-handling
Using Gotos to manage execution paths is almost always a poor design choice. You could consider the following approach that does not rely so heavily on Goto labels. It is easier to see the intended logic flow (and error handling) without having to visually parse any Goto statements.
Sub Example()
If Not TryMorecode1() Then
Somecode1
Somecode2
End If
Morecode2
End Sub
Private Function TryMorecode1() As Boolean
'catch errors locally within this function
'return False if an error is generated
End Function
Private Sub Morecode2()
End Sub
Private Sub Somecode1()
End Sub
Private Sub Somecode2()
End Sub
The code will still run, it's just a label you can point the execution to if need be.
Try this to avoid your problem but without knowing your exact requirement, you’ll likely need to tweak the code structure to suit your needs.
My suggestion would be to put Line3 at the bottom, not in the middle of the routine. That way you just do a clean up and then get out. It tends to make more sense from a readability perspective. Of course, you should only do that if it makes sense to get out after the error occurs.
On Error GoTo Line3
More code
Goto Line4
Line3:
Some code
Some code
Line4:
On Error GoTo 0
More code
The trick with GoTo statements (if you intend to use them) is to use them sparingly.
I've been trying to make this work and I just can't seem to figure it out. I have this test piece of code that I got from here.
Sub UsingErr()
On Error GoTo eh
Dim total As Long
total = "aa"
Done:
Exit Sub
eh:
Debug.Print "Error number: " & err.Number _
& " " & err.Description
End Sub
This is supposed to help me understand error management but when it's ran I get a: Compile Error: Expected Function or Variable on err.Number; it specifically highlights err.
Any help would be appreciated.
One minor thing I noticed in your code is the "err" is lower case. When I pasted your code into a blank workbook and ran it, Excel automatically capitalized "Err" in the code. As people mentioned in the comments, this probably means that you have something else open which has a conflicting definition of "err" in it. Try running your code without any other files open.
I have the followin code. But the error does not get handled
Sub Some_sub()
Some code here
On Error GoTo resumeCode
If Workbooks("Some file.xlsm").ReadOnly Then
GoTo readOnlyError
End If
resumeCode:
On Error GoTo fileNotOpen
Workbooks("Some file.xlsm").Activate
some more code here
Exit Sub
fileNotOpen:
MsgBox "Error: Claims followup.xlsm is not open." & Chr(10) & Chr(10) &
"Open the file in read/write"
Exit Sub
End Sub
When I run debug mode it shows me this line: Workbooks("Some file.xlsm").Activate in yellow. Instead of handling the error and going to the label.
Within VBA under Tools -> Options -> General Tab: Break on Unhandled Errors active.
When I have the file open it runs the code like it should. When it is closed it does not handle the error.
What am I doing wrong?
Thanks in advance for your help
That's it. As I said in the comments, when an error occurs, and you handle it, you cannot setup a new On Error mechanism until you explicitly invoke the Resume keyword in any way.
One possible way to achieve it, if you dont want to change the flow of your routine, is just to add a label before your new On Error statement and Resume on it. Like this, for example:
Sub Some_sub()
' Some code ...
On Error GoTo resumeCode
If Workbooks("Some file.xlsm").ReadOnly Then
GoTo readOnlyError
End If
' Some more code ...
resumeCode:
Resume ResumeHere ' <---------------------------------------- Invoke Resume
ResumeHere: ' <------------------------------------- Add some label
On Error GoTo fileNotOpen
Workbooks("Some file.xlsm").Activate
Exit Sub
fileNotOpen:
msgBox "Error: Claims followup.xlsm is not open." & Chr(10) & Chr(10) & "Open the file in read/write"
Exit Sub
End Sub
You should be careful though, the keyword Resume will itself raise an error if the error status is blank and it is reached from normal flow, In that case you should put the error handling sections, each apart, in the end of your routine and resume at any labels within the normal flow from there. This is generally the usual approach.
I have a Function that has some bug in it somewhere causing it to return #VALUE when I try to execute it in excel.
I have no idea where the error is, and stepping through the code is just tedious. So I'd like the debugger to break as soon as an error occurs.
I tried going to Tools->options->General->"Break on All Errors" but noticed no change.
How do I get the VBA IDE to break on an error?
Just add an error handler in your function like the one below.
If an error occurs, the IDE will print the error description in the immediate window and stop on Debug.Assert 0.
Then press F8 two times to go to the line where the error occured.
Function Test() As Variant
On Error GoTo ErrHandler
Dim v(), n&, r&, c&
For r = 1 To 3
For c = 1 To 4
n = n + 1
ReDim Preserve v(1 To r, 1 To c)
v(r, c) = n
Next c
Next r
Test = v
Exit Function
ErrHandler:
Debug.Print Err.Number & ": " & Err.Description
Debug.Assert 0
Resume
End Function
Something like:
Public Function dividddeee(a As Variant, b As Variant) As Double
On Error GoTo wtf
dividddeee = a / b
Exit Function
wtf:
On Error GoTo 0
MsgBox "Houston, we've had a problem here"
MsgBox a & vbCrLf & b
End Function
If you add error handlers, you can take advantage of Debug.Assert to force a break if you don't want the standard handler to execute. You can define a compiler constant to just let your error handlers deal with it when you release it to the wild. If you want to see what specific line caused the error, you can put Resume Next after the Debug.Assert. When you step through it, it will take you to the line immediately after the one that caused the error.
Drop this small demo into Module and run the Sub with both Release = True and Release = False for an example:
Option Explicit
#Const Release = False
Private Sub Demo()
Debug.Print DivByZero(5)
End Sub
Public Function DivByZero(inValue As Integer) As Double
On Error GoTo Handler
DivByZero = inValue / 0
Exit Function
Handler:
#If Release Then
MsgBox Err.Description & " in DivByZero"
#Else
Debug.Assert False
Resume Next
#End If
End Function
If you call a VBA function as UDF the settings of the VBA IDE are not involved. So no chance for error debugging this way.
Try calling the function from a test Sub. Then the error debugging will work.
But there are some things a function cannot do as a UDF but can do called from a Sub. If one of those things is the reason for the #VALUE error, then no way around setting a breakpoint in the function and stepping forward until the next step is not possible. The last line in stepping is the error line.
You should really mention if the function is
Called from an Excel cell.
Has an event handler.
Show how your variables are declared.
If called from a cell, the inputs to the function can cause you problems with different inputs. Sometimes preventing the call of the function if the types significantly change to something unexpected. For example a parameter declared as variant in its signature will pass in an error but fail in the function. You may be trapping this error and returning #VALUE in the code. No way for us to know that.
If you have an event handler, for a quick test, you could put a 'Stop' in the event handler to stop like you are asking. If not you can put one in as already stated. Assertions are nice, I like them and use a lot of them - but here since you know the function and are working on this explicit problem a Stop should be good enough for your testing. Just don't save it for production code. Make a Test copy of the book.
I have the following two lines of code:
On Error Resume Next
myWorkbook.Sheets("x").Columns("D:T").AutoFit
I've stepped into the macro and executed the line On Error Resume Next and then on the next line myWorkbook... it does the following:
Why doesn't the compiler resume the next line of code?
On Error has been liberally used throughout the procedures code; I realize best practice is to use this as little as possible but it seems to fit the purpose of this macro.
Reading this SO QUESTION it says that you can't have one set of error trapping within another. How can I guarantee that one set of error trapping has been "closed off" before the code moves on - does On Error Goto 0 reset the error trapping? If it does reset then why doesn't resume work in the following?:
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error GoTo 0
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
There is also a VBA setting that will cause On Error ... statements to be ignored and that dialog box to always appear. See this answer for more details on checking/changing the option: https://stackoverflow.com/a/3440789/381588
An example of an error when the initial error is not closed out.
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
As you have found, within the same function or subroutine, On Error Resume Next doesn't override On Error Goto ... if it's still active.
You are correct that On Error Goto 0 restores the default error handler.
There are some cases where On Error is the most appropriate way to handle an exceptional condition. I prefer to use the following structure:
On Error Resume Next
statement which might fail
On Error Goto 0
if statement has failed then ...
This keeps everything together, but in other cases a generic error handler at the end of the procedure can be better.
I've found that in functions/subs that iterates over nested objects, errorhandling might be a drag in VBA. A solution that works for me to better handle complex iterations is separating setting of objects in their own functions, e.g.
main function/sub:
set FSOfolder = SetFSOFolder(FSOobject, strFolder)
Private Function SetFSOFolder(FSO as scripting.FileSystemObject, strFolder as string) as Scripting.Folder
on error resume Next
set SetFSOFolder = FSO.GetFolder(strFolder)
on error goto 0
End Function
and then in the next line in main function:
if (not fsofolder is nothing) then
I agree using On Error Resume Next is not best practice but most/many of my Access apps are not overly sensitive to minor nuances in data integrity (i.e. analytical or reporting, not transactions and not audited). For this reason I use OERN quite often because VBA is very sensitive to some situations that you cannot anticipate entirely.
1 - will the error cause data corruption. If yes handle it. Many routines I use are just crunching a large volume of records and there may be errors in imported data that haven't been fixed. Usually I have a lot of conversion processes that will let the system clean up its own data eventually.
2 - is the error frequent and non-critical (ie not a key). If yes it's OERN otherwise the error may not be predicable and you end up crashing out or have to write a bunch of I-T-E or Select Case logic to trap it.
Don't use On Error Resume Next instead write code that shouldn't crash.
Note: I am being careful how I phrase that because you never guaranty code doesn't crash. But if you use On Error Resume Next then part of the natural flow of your code is for it to crash, which is wrong, big time wrong.