4

I'm thinking that I could simplify my life by making an exception handling class that I can just ship all exceptions to and it will handle appropriately.

Ideally:

def dostuff():
  try:
    dothis()
  except Exception e:
    Handler.handle(e)

To avoid

def dostuff():
  try:
    dothis()
  except ValueError e:
    #handle ValueError
  except IndexError e:
    #handle IndexError

The Handler class would have all the logic to handle exception appropriately and you could pass some arguments to specify how to handle the error, but there would be reasonable defaults to make thing easy.

This seems like either a great idea or a terrible one. Its possible that I will find it lacks the usefulness I'm looking for. But I feel like there may be some serious drawbacks that I'm not seeing. I don't think I've seen this approach often. Is there a reason?

I'm working in Python right now, but I think its not really a language specific question.

Dan
  • 161
  • 6
  • 3
    In my personal experience, it's very rare that you can do anything with an exception other than write something to the logs and return an error code, in which case there's obviously no benefit to this. I suspect this is true for many programmers. Good question though, hopefully someone with a different experience comes along. – Ixrec Dec 22 '15 at 00:16
  • [Are exceptions as control flow considered a serious antipattern? If so, Why?](http://programmers.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why) – gnat Dec 22 '15 at 04:39

3 Answers3

6

What you lose is context. You're passing the exception object e, but Handle.handle has no idea whether (say) IndexError happened at an input sanitiser, in which case it might trigger some kind of defensive mechanism; or whether it happened deep in an algorithm after sanitisation, in which case it could flag a problem to the developer. Because it doesn't have the context in which the exception was raised, Handle.handle wouldn't know which action to take.

Lawrence
  • 637
  • 3
  • 10
  • I though about the context issue and I was hoping there would be a way to keep the context. Like with a stack trace or something. But it is a good point. – Dan Dec 22 '15 at 02:06
  • @dan08 The only way to "keep the context" is to pass it into your master error handler (which implies the master handler has a switch statement for all possible context types; thereby completely missing the point of a master handler) or write code that parses the stack trace to extract something about the context (very bad, very hacky, please for your own sanity never do this). – Ixrec Dec 24 '15 at 15:16
  • 1
    `Exception.Data` == context. Well, in C# anyway. – radarbob Dec 25 '15 at 17:39
  • @radarbob I'm not familiar with Exception.Data, but reading up on it [here](http://stackoverflow.com/questions/5843285/system-exception-data-property) and [there](http://stackoverflow.com/questions/144957/using-exception-data), it seems to be a mechanism for passing custom key-value pairs. This leads back to the situation that *lxrec* commented on above. If the OP implements a master exception handler though, carrying user-defined context inside the exception object certainly helps, particularly if the implementation grows to include chained exception handlers. – Lawrence Dec 26 '15 at 00:45
4

My conjecture is that if you feel a need for such a helper, you're probably catching too many exceptions in the first place. What useful action can you take on an exception? You either know the cause of the error from the context (that is, you expect the exception) and can provide a reasonable fall-back strategy, then your generic handler won't help. Or you don't know what to do with the exception, then you should simply let it bubble up until somebody eventually will be able to act upon it in a reasonable way. As a last resort, you might have a “catch all” handler in your main function that says “sorry”, logs the exception and terminates the program. For many unexpected errors, this is the appropriate reaction.

Python is a little different from most other languages I know in that it encourages you to use exceptions much more as a form of ordinary control flow but I don't see how that would change the overall picture. Just as you don't have a generic helper function to handle return values, you probably don't want a generic exception handler.

Let me illustrate this with some example.

This is a reasonable use of catching an exception that can't really be automated away.

def get_foo_factor(fallback=10):
    try:
        return int(os.getenv('FOO_FACTOR'))
    except TypeError | ValueError:
        # Environment variable not set or not an integer.
        return fallback

This one is completely ridiculous.

def append_to_file(filename, message):
    try:
        with open(filename, 'a') as ostr:
            print(message, file=ostr)
    except TypeError:
        print("Not a string?", file=sys.stderr)
    except AttributeError:
        print("This should never happen!", file=sys.stderr)

If your code has a lot of such except cascades, the best refactoring would probably be to simply delete them. For those few that remain, you'd probably find that your automated handler won't be of much use. The language already has a specialized syntax for making exception handling as convenient as possible, after all.

The only use-case for a handler I can see is if you're interfacing with a library that throws various exceptions and you want to translate them into your own scheme. In this case, something like the following might be appropriate.

def translate_libfoo_exception(exception):
    try:
        raise exception
    except FooErrorOne as e:
        return MyError(e)
    except FooErrorTwo as e:
        return MyOtherError(e)
    except FooErrorThree as e:
        return MyOtherError(e)
    except FooError as e:
        return MyGenericError(e)
    except Exception as e:
        return e

It could then be used like this.

try:
    foo.bar()
except FooErrorThree:
    # Needs special handling
    pass
except Exception as e:
    # Cannot be handled, translate and re-raise
    raise translate_libfoo_exception(e)
5gon12eder
  • 6,956
  • 2
  • 23
  • 29
2

A master error handler is akin to a god class, an anti pattern in modular design.

On the other hand, there is a design pattern called Error Handler which allows you to centralize control of errors that are the same.

Fuhrmanator
  • 1,435
  • 9
  • 19