Python – a good approach to handling exceptions

Architecturedesignexceptionspythonweb-development

I have trouble reconciling "best practices" and real-world approaches to handling exceptions. In my day to day routine, I find myself running into the following examples:

try:
    do_something()
except Exception:
    log_error()

and even

try:
    do_one_thing()
    try:
        do_another_thing()
    except Exception:
        repair_stuff()
        log_error()
    do_more_risky_stuff()
except:
    log_error()

The most obvious thing is catching the generic Exception type, which is a recurring theme in all "don't do this" programming books/articles on the subject. Furthermore, the nested example – I find it unreadable (or at least "could-be-more-readable"). Finally, having try..except blocks littered everywhere seems… plain wrong. I'm aware that I could be just beating a dead horse here.

I have brought up my concerns to my lead and they haven't exactly been welcomed. Not that they've been unreasonably dismissed (to my perception), it's rather that I can't offer any better approach.

So I have several questions on the matter:

  1. Is catching generic exceptions that wrong an approach? Had a lot of cases (been burnt trying to catch specific ones) where we did not know what to anticipate, while the behavior would be the same for all, e.g. log and continue with execution.
  2. Wrapping everything in try..except: log blocks is code repetition. Or is it? Solving this in any way that would not seem like over-engineering is beyond me.
  3. Handling nested try blocks could maybe be solved by separating them to their own scope (e.g. a function), however this hasn't proved itself as a reliable solution as oftentimes the caller might be desiring a different result on exception (empty result, alternative result, the exception itself etc…)

Best Answer

This code:

try:
    do_something()
except Exception:
    log_error()

is dangerous. Not because you caught a generic Exception but because you suppressed the exception without doing any recovery or halting the system. Now the system is in an undefined state. It might be about to corrupt the database, format the hard drive, or send the president threatening emails. But hey, at least you logged the error first.

try:
    do_one_thing()
    do_another_thing()
    do_more_risky_stuff()
except specific_error1:
    log_error()
    recover_from_specific_problem_regardless_of_where_it_came_from()
except specific_error2:
    log_error()
    raise # Don't know how to recover at this level so kick it upstairs
    

Do it this way and either the problem is handled cleanly here or made into someone else's problem. If you really needed to know which methods those exceptions came from to recover then those methods should have recovered from the exceptions themselves.

Following the rule about keeping functions short should make this easy to debug.