Say I have the following class:
class Class1
Sub error1(): Err.Raise 1, "", "Error1": End Sub
Sub error2(): Err.Raise 1, "", "Error2": End Sub
end class
Now I have this module:
Sub ErrorInClass()
Dim c As New Class1
'Raise a handled exception
On Error Resume Next
Call c.error1
On Error GoTo 0
'Raise an unhandled exception
Call c.error2
End Sub
Below are some observations:
- If I run this with “Break in Class Module” – It will break runtime inside the class AND will break on both Error1 and Error2; despite Error1 being handled.
- If I run this with “Break on Unhandled Errors” – It will break runtime outside the class (in a top level module) but will only break on Error2.
Sometimes we have classes in classes in classes, with multiple calculation steps in between, such that breaking on a module makes little sense. So we might opt for “Break in Class Module”; but then we aren’t able to handle errors at all… What is the best VBA design pattern out there to get around this issue?
2
Unsure what the best course for action is at the moment. But one idea I did have is use a OnErrorResumeNext
class flag:
class Class1
Private LastErrorText as String
Public OnErrorResumeNext as Boolean
Public Property Get ErrorText() as String
ErrorText = LastErrorText
End Property
Sub Error1()
Call RaiseError("Error1")
End Sub
Sub Error2()
Call RaiseError("Error2")
End Sub
Private Sub RaiseError(ByVal message as string)
if OnErrorResumeNext then
LastErrorText = message
else
Err.Raise vbObjectError, "Class1", message
end if
End Sub
end class
Sub ErrorInClass()
Dim c As New Class1
'Raise a handled exception
On Error Resume Next
c.OnErrorResumeNext = true
Call c.error1
c.OnErrorResumeNext = false
On Error GoTo 0
'if needed check ErrorText
Debug.Print "ERROR: " & c.ErrorText
'Raise an unhandled exception
Call c.error2
End Sub
Or, export this functionality to a seperate class:
Class ErrorHandler
predeclared_id = true
...
End Class
Class Class1
Sub Error1()
Call ErrorHandler.last.Raise("Error1")
End Sub
Sub Error2()
Call ErrorHandler.last.Raise("Error2")
End Sub
End Class
Sub ErrorInClass()
Dim c As New Class1
'Raise a handled exception
On Error Resume Next
With ErrorHandler.Create()
Call c.error1
End With
On Error GoTo 0
'if needed check ErrorText
Debug.Print "ERROR: " & ErrorHandler.last.text
'Raise an unhandled exception
Call c.error2
End Sub
At the very least this method allows usage of “Break in Class Module” and does keep the callstack mostly intact, while also allowing for resuming handled errors in classes…
2024-09-19 Edit:
Yet another alternative… If methods in the class have error handling themselves, these won’t cause code to break and errors to occur. I think I subconciously knew this but forgot to include in this post. So the other alternative would be, in order to make some errors optional and others not to return a result type or error parameter. I.E.
Public Property Get Name(Optional byref errorOccurred as boolean) as string
On Error GoTo Catch
Name = This.iaccessible.accName
Exit Property
Catch:
errorOccurred = true
Name = ""
end property
In this way, calling code can break if wanted, or continue if wanted:
Sub DisplayInfo()
This.nameBox.value = acc.name 'No change
End Sub
Sub AutomateSomething()
Dim hadError as Boolean
Dim el as acc: set el = getElement()
Dim name as string: name = el.name(hadError)
if hadError then Call Err.Raise(Err.Number,Err.Source,Err.Description)
End Sub