12

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.

Jean-François Corbett
  • 37,420
  • 30
  • 139
  • 188
  • this may as well part of your question but what *kind* of solution are you looking for? Do you want to shorten your code? or do you want to build a reusable piece of library/set of functions to dealt with the erorrs? Would you consider using an external library to pass the errors to and get *something* back from it? Ie. having a COM `ErrorHanlder` class and pass the error number to it then send something back. This would only shorten your code and give you a reusable piece of error handler but I am not sure this is the *type* of solution you look for. –  Dec 09 '13 at 12:03

4 Answers4

3

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.

Jean-François Corbett
  • 37,420
  • 30
  • 139
  • 188
3

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.

html_programmer
  • 18,126
  • 18
  • 85
  • 158
  • I appreciate your opinion, but sometimes trapping errors is the way to go ([example](http://stackoverflow.com/questions/6901991/how-to-return-the-number-of-dimensions-of-a-variant-variable-passed-to-it-in-v)). That is what my question is about. – Jean-François Corbett Dec 11 '13 at 07:38
  • Sure no problem. The example code in the question does not really suggest the exceptional example that you shared though - they seem to suggest usage of the the error handler as an anti-pattern to solve common problems (find in array - two persons with same name) - hence my confusion. If I understand the question right, though I'm not certain, I will try to formulate a better answer that directly addresses the error handling problem on a lower level. – html_programmer Dec 11 '13 at 09:33
  • 1
    On second thought, maybe my thing *is* an anti-pattern. I'm not sure. Well, the bounty has got to go to someone, and you answer has given me the most to think about, so there you go! – Jean-François Corbett Dec 16 '13 at 07:35
  • Thanks @JF. I hope it will help. – html_programmer Dec 16 '13 at 08:05
3

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.

html_programmer
  • 18,126
  • 18
  • 85
  • 158
2

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
Community
  • 1
  • 1
  • "root of all evil" is an unjustified blanket statement. `On Error Resume Next` is fine if used judiciously. In fact the [very link you cite](http://www.cpearson.com/excel/errorhandling.htm) gives a legitimate example (also [here](http://www.cpearson.com/excel/vbaarrays.htm) from same source). – Jean-François Corbett Dec 10 '13 at 07:46
  • Okay, so you've sent control to `ARRAY_ERROR_HANDLER` instead of dealing with the error locally. Same difference. What if an unexpected error such as `234`, or `345`, or something else, occurs? Will you copy-paste those error handlers from `COMMON_ERROR_HANDLER` to `ARRAY_ERROR_HANDLER`? Duplicate code is Bad. (That's why I was looking to rethrow the error to have the regular error handler deal with it as usual.) – Jean-François Corbett Dec 10 '13 at 07:51
  • @Jean-FrançoisCorbett: 1st: Yes, On Error Resume Next can definately be used wisely... (all guns in the world are used only & only for self defense, right?) 2nd: How can Error 234 & 345 be caused in ARRAY_ERR_HANDLER ? will it not be an array error if so. But lets say if they can be caused then it will be handled by the same handler and if not we can always redirect it to the different handler. –  Dec 10 '13 at 13:13
  • 1
    I'm not sure you've thought this through. Let's suppose error `234` occurs after `On Error GoTo ARRAY_ERROR_HANDLER`. Control passes to `ARRAY_ERROR_HANDLER` which has no idea what to do with it. The code to deal with `234` is in `COMMON_ERROR_HANDLER`. That is my conundrum. The gun analogy, I don't get. – Jean-François Corbett Dec 11 '13 at 07:35