exception handling in complex Python programs

Richard Levasseur richardlev at gmail.com
Thu Aug 21 02:40:32 EDT 2008


One common-place thing I've noticed in a lot of python code is that
every package or module has a main Error class, and all sub-types
inherit from that class.  So you just catch mylib.Error, and you're
going to catch all the exceptions that package generates directly.

There seems to be a lot of concern about what exceptions a functions
might throw, and what to do when it happens.  The way I see it, there
are only two types of exceptions: those you can recover from, and
those you can't.

The only ones -really- worth catching are those you can recover from.
There's a middle-ground type of 'cleanup', to free any resources, but
those generally go in a finally block, not an except block.

For the ones you can't handle, it doesn't matter if you catch them or
not.  If you catch them, what do you do?  Log an error message, then
rethrow it.  You're still throwing an exception, so you haven't really
gained anything.  You might repackage it and put additional
information in the exception so you can do something at a higher
level.  What that is, I don't know.  I don't think I've ever passed
information up in an exception that was of use to the program, and I'm
hard pressed to think of any information you could provide that could -
fix- the problem.

If you can derive recoverable information, then why rethrow? Thats
pretty much a recoverable situation, so there's not need to rethrow.

In java, there are checked exceptions, which are nice - they tell you
what a function might throw, so you know what to catch.  I don't think
this improves the quality of anything, though.  It just annoys the
developer.  What they end up doing is writing an application-specific
exception class, and -everything- gets rethrown as that, and
everything begins to declare it throws AppError.  Whats worse is that
you have heavily, heavily repackaged exceptions:  SQLError -> AppError
-> MessageError -> AppError -> MessageError (yes, i've seen this
before).

That is almost completely useless.  Sure, you could dig down to
SQLError, but how do you know to do that?  If you knew how far you
should dig down, that means you know what the problem was, in which
case, you could have prevented it or aborted early.  Whats worse, if
you have all these re-packaging catch blocks and they just log
something generic, which becomes common with all the catching going
on.  "Couldn't do foo!",  "Bar operation failed!", or "Couldn't fetch
filters from database" (why?  We don't know, its catching an AppError
instead of something more specific), and then they rethrow the
exception.  While trying to debug something not-during development,
those messages are completely useless, in fact, they're more than
useless.  They're just more cruft to sift through in a log file.

Additionally, most root causes of an error are going to originate
where the input comes from the user.  Handling anything at levels
deeper than that isn't going to gain you much.  PrepareQuery threw an
error because of a missing field?  Thats great.  Where'd it come
from?  There are 100 calls to PrepareQuery.  There's only a few calls
to ReadUserInput(), and a single informative log message of "Query
failed, unknown field; fields=a, b, c" is much better than 100 lines
of traceback smattered with "Unknown field" and "Unable to prepare
query".

Finally, they give a false sense of security.  "I'm catching
everything it could throw, so everything will be ok if an exception is
thrown!"  I guess thats true.  I guess.  The only real advantage is
the whole program won't crash with the ever-helpful, single line of
"Segmentation fault."  An improvement, but it doesn't prevent
anything.

In a complex system, an error can occur in any function at anytime.
Adding 'throws' to a method definition doesn't change that.

I guess my point is: everything Chris Mellon said was spot on.



More information about the Python-list mailing list