[Tutor] Try except really better than if?
Steven D'Aprano
steve at pearwood.info
Mon Jan 10 11:58:56 CET 2011
Alan Gauld wrote:
> However, using exceptions like if/else blocks is bad practice.
I'm afraid I disagree strongly with that!
> You need to use them like a commit in SQL - to protect a whole
> block of code. Putting try/except around every line of code
> defeats the purpose, wrap atomic blocks of code not the
> individual lines.
Wrapping an atomic block of code, so long as it actually *can* be
treated as atomic (including backing out any changes if it fails), is a
good use of try...except. But otherwise, this is risky advice. Take your
example:
> try:
> something
> something else
> another
> the last
> except Error1: # do something
> except Error2: # do something
> except Error3: # do something
> finally: # tidy up
Provided the entire try block needs to be treated as a single operation
(in which case, why isn't it a function?) then that's not too bad. But
the risk comes from people who wrap large blocks of code *without*
making them atomic transactions:
try:
get_job_id()
query_database()
send_results()
push_ticket()
update_counter()
except KeyError:
print "job failed"
And now an error in push_ticket means that the counter fails to be
updated even though the results were sent. Problems occur.
The try block must contain ONE operation. It doesn't matter if the
operation is a simple, atomic operation:
try:
y = x + 1
except...
or a large, complex transaction, so long as it can safely be treated as
a single operation. That is, the individual sub-tasks should have no
side-effects, or they must be backed out off on failure.
The other problem is that large try blocks can mask bugs. Here's my
example again:
try:
get_job_id()
query_database()
send_results()
push_ticket()
update_counter()
except KeyError:
print "job failed"
The idea being that get_job_id or query_database might raise KeyError.
But update_counter should never raise KeyError. If it does, that
indicates a bug in update_counter, which will then be masked by the
except as a normal error in processing. This would be better:
try:
get_job_id()
query_database()
except KeyError:
print "job failed"
else:
send_results()
push_ticket()
update_counter()
Now the else block is only executed if there is no KeyError, and any
exceptions in those three will be detected and not masked.
Of course, you can then wrap the entire code block in another
try...except, try...else or try...finally block. Something like this
perhaps?
try:
try:
get_job_id()
query_database()
except KeyError:
print "job failed"
else: # no exception caught
send_results()
push_ticket()
update_counter()
failed = True
finally:
if transaction_succeeded():
commit_transaction()
else:
rollback()
And now you are beginning to see why databases are so complicated.
> is better to maintain than
>
> try: something
> except Error1: # do something
> try: something else
> except Error2: # do something
> try: another
> except Error1: # and again
> try: the last
> except Error3: do it
> tidy up
That's what you have to do if something, something else, etc. need to be
treated as independent operations that have to be done regardless of the
success or failure of those that come before. Otherwise:
try:
something
try:
something_else
try:
another
except Error1: ...
except Error2: ...
except Error1: ...
It has been said that error handling is ten times harder than handling
the rest of your code.
--
Steven
More information about the Tutor
mailing list