Question

I've been seeing a lot of this construct throughout my application:

def doSomething():

    try:
        # setup some variables

        try:
            # do something that could throw an OSError
        except OSError as err:
            # log and handle it

        # do some more things

        try:
            # do something that could throw ValuError
        except ValueError as err:
            # log and handle it

    except Exception as err:
        # log the unexpected error

    finally:
        # close the logger

However, there are a number of posts that say nesting try-except blocks should be avoided, but I view that in this case:

try:
    # throw
except:
    try:
        # throw
    except:
        try:
            # throw
        except:
            ...

In my former example, the entire method body is wrapped in a single try-except to ensure that in the event an unexpected error is encountered, it's not fatal and is logged before cleanup. Further, in the outermost try-catch, the nested blocks are handled and I continue on; I therefore fail to see how I would replace that with functions.

Was it helpful?

Solution

It seems to me that you're falling into "catch obsession", trying to catch every exception as soon as it arises.

That's not what exceptions are meant for.

Exceptions are meant to signal to a caller that some function call failed (did not fulfill its contract). So, if a function returns normally, we rely on the fact that it completed its job.

Typically, in case of failure the caller cannot fulfill its contract, and so on, up to some top-level place where the program can continue with the next action, even after the failure of the previous one.

Catch an exception only if you know a way how to turn the failure of the called function into success of the caller function. Otherwise, let the exception just ripple through to your caller.

Coming back to your code snippet:

Do you think you can still successfully complete doSomething() if an OSError happened in that code block? If not (and that typically is the answer to these questions), you must inform the caller of doSomething() about its failure, and as doSomething() can't succeed anyway, there's no point in executing the rest of the function.

So, wrapping the piece of code in try / except OSError is probably wrong:

  • It lets the rest of the function pointlessly execute (and maybe even produce persistent nonsense).
  • It doesn't inform the caller about the failure of doSomething().

OTHER TIPS

Nested try-except blocks can make the code harder to understand, especially if, as in the example you used, the normal flow continues after handling the first nested try-except block.

If there is any reason that a reviewer or maintainer (or you in 6 months time) could think that the # do some more things from your example should belong to the handling of the OSError, then it might be good to seriously look at refactoring your code such that this misunderstanding can be prevented. But other than that I see no reason to refactor your code to avoid having nested try-except blocks within a single function.


One statement that does cause me some concern is

In my former example, the entire method body is wrapped in a single try-except to ensure that in the event an unexpected error is encountered, it's not fatal and is logged before cleanup.

Exceptions are meant to be fatal if you don't know how to handle them. And just logging that an exception occurred is not handling the exception. Handling an exception is bringing your application back into a state where it can resume normal operation. If that is not possible, it is better to let the users know in a way that can not be ignored rather than logging it to a logfile that is largely ignored and trying to slog on in a broken state.

Licensed under: CC-BY-SA with attribution
scroll top