On Sat, Nov 17, 2012 at 8:40 AM, Carlo E. Prelz [email protected]
Subject: Re: Question on exceptions
Date: Sat 17 Nov 12 12:47:06PM +0900
Quoting tamouse mailing lists ([email protected]):
Do you (or anyone else who’d like to chime in!) have a … set of
heuristics, maybe? … that help you know when it’s appropriate and
when it’s not?
I will try to describe in words the fuzzy logic I follow.
I do not often use exceptions. In more than seven years of using Ruby,
I have probably only subclassed Exception once or twice.
Well, there are other uses of exceptions than defining your own.
One could even go as far as to say that you use exceptions if you use
code that throws exceptions - even if there is no trace of catching
exceptions in your code. That makes sense from a certain perspective
because you rely on the fact that the code you use will raise
exceptions if errors surface.
The Ruby interpreter’s usage of exceptions is mostly limited to grave
What exactly is a “grave event”?
I try to mimic this usage in my raising practices. Basically,
exceptions should only indicate situations that have to be debugged
out: an exception should never indicate a special, but acceptable,
I beg to disagree. An IO error which surfaces is usually not
something you will be tackling by debugging. You might be the invoker
of a script who learns that way the the file name you passed on the
command line was wrong. There may also be an network error because
your WLAN lost connection spuriously etc.
I come from C (I still use C). Practice in C tends to be that your
procedures return non-zero in case of errors - the non-zero value
indicates the type of error. This can be cascaded: you check the
return value, and return the same if negative, and so on. But it can
certainly escalate to awkwardness, especially if at some level your
function has to return other data.
What you describe neatly shows the major disadvantages of error
handling via return type checking:
- It is cumbersome (you need to check a lot return values).
- It has to be done manually (i.e. you can forget it).
- Propagation to invokers is tedious.
- The error handling code is intermixed with regular code which makes
the code harder to read.
Of course, in C you do not have much choices since there are no
exceptions in the language.
For me, the advantage of the exception mechanism is that it has its
own, separate distribution channel. If you do not catch an exception,
the program terminates, with VERY useful debugging data: the message
of the exception, and the backtrace.
When I encounter an exception, the catching option is not the
preferred one. If at all possible, I try to modify the code so that
the special case either does not happen, or is specifically handled,
in a programmatic way.
Often you cannot do that. For example in the IO error case mentioned
above the only thing you could do would be check the file for
existence and readability before opening it. But since other
processes have access to the file system as well that still does not
give you 100% safety that the file is still there when you are going
to open it. So just opening it and handling the exception (or just
propagating it up the call stack) is the simpler option because the
exception may come anyway. You end up with less code and a clearer
structure. And in a way you avoid redundancy (duplicate checks for
existence and accessibility).
If I catch an exception, I can do so at any of the steps of the
backtrace. Often it is useful to make use of the exception-raising
logic to get out of some of the levels of the call tree.
Or put it differently: often it makes sense to handle an exception
remote from to where it originated.
This iterative process is part and parcel of the testing phase that
all new code has to undergo. Every situation is different. If you
really care about the healthy operation of your code, every choice
should be tuned to the specific case. The process cannot be made
automatic. Automatic coding/debugging results in mediocre programs.
Right, and if it was possible at all, we would be without jobs.
In all cases, I would not use the exception mechanism in the example
that the original poster makes. Rather, the BankAccount#withdraw
method would return true (or possibly the new balance of the account)
if the withdraw method was successful, and false otherwise. Ruby has a
neat syntax for catching false/nil values:
account.withdraw(sum) or do_something_else
The condition of insufficient funds is maybe undesirable from the
point of view of the account holder, but it is a perfectly legitimate
situation; all mechanisms that handle withdrawals should foresee and
properly manage it. Withdrawals can fail. It should not be an
exception-al event from the point of view of the code.
Unless for example the program cannot verify itself that funds are
sufficient. It is not a too uncommon scenario to rely on database
functionality (constraints) to ensure a negative balance is not
reached. In this case the RDBMS driver’s exception is necessary to
learn of the violation because even if you do the check beforehand
there can still be a transaction modifying the balance between the
check and the withdraw (depending on concurrency model of the RDBMS of
course, in Oracle and Postgres, where reads are not blocked by writes,
you would either need explicit locking or work with the exception).
This is just an effort to put into words my craft. Every other
craftsman/woman will most probably follow different ways.
I think your stress of context is very important. The downside is
that it takes some time and experience to feel comfortable in all
situations to decide on the proper way.