A drawback to handling errors via exceptions

October 31, 2014

Recently I discovered an interesting and long standing bug in DWiki. DWiki is essentially a mature program, so this one was uncovered through the common mechanism of someone using invalid input, in this case a specific sort of invalid URL. DWiki creates time-based views of this blog through synthetic parts of the URLs that end in things like, for example, '.../2014/10/' for entries from October 2014. Someone came along and requested a URL that looked like '.../2014/99/', and DWiki promptly hit an uncaught Python exception (well, technically it was caught and logged by my general error code).

(A mature program usually doesn't have bugs handling valid input, even uncommon valid input. But the many forms of invalid input are often much less well tested.)

To be specific, it promptly coughed up:

calendar.IllegalMonthError: bad month number 99; must be 1-12

Down in the depths of the code that handled a per-month view I was calling calendar.monthrange() to determine how many days a given month has, which was throwing an exception because '99' is of course not a valid month of the year. The exception escaped because I wasn't doing anything in my code to either catch it or not let invalid months get that far in the code.

The standard advantage of handling errors via exceptions definitely applied here. Even though I had totally overlooked this error possibility, the error did not get quietly ignored and go on to corrupt further program state; instead I got smacked over the nose with the existence of this bug so I could find it and fix it. But it also exposes a drawback of handling errors with exceptions, which is that it makes it easier to overlook the possibility of errors because that possibility isn't explicit.

The calendar module doesn't document what exceptions it raises, either in general or especially in the documentation for monthrange() in specific (where it would be easy to spot while reading about the function). Because an exception is effectively an implicit extra return 'value' from functions, it's easy to overlook the possibility that you'll actually get an exception; in Python, there's nothing there to rub your nose in it and make you think about it. And so I never even thought about what happened if monthrange() was handed invalid input, in part because of the usual silent assumption that the code would only be called with valid input because of course DWiki doesn't generate date range URLs with bad months in them.

Explicit error returns may require a bunch of inconvenient work to handle them individually instead of letting you aggregate exception handling together, but the mere presence of an explicit error return in a method's or function's signature serves as a reminder that yes, the function can fail and so you need to handle it. Exceptions for errors are more convenient and more safe for at least casual programming, but they do mean you need to ask yourself what-if questions on a regular basis (here, 'what if the month is out of range?').

(It turns out I've run into this general issue before, although that time the documentation had a prominent notice that I just ignored. The general issue of error handling with exceptions versus explicit returns is on my mind these days because I've been doing a bunch of coding in Go, which has explicit error returns.)


Comments on this page:

By dozzie at 2014-10-31 09:32:38:

Erlang (and other functional languages, I guess) has this pretty much solved. Erlang uses pattern matching:

{ok, Conn} = gen_tcp:connect("example.net", 80, [{packet, line}, {active, false}]).

If gen_tcp:connect() fails, it returns {error, Something}, which causes pattern matching fail and exception to be raised. If the code needs to handle such failures, programmer can write it like this:

case gen_tcp:connect("example.net", 80, [{packet, line}, {active, false}]) of
  {ok, Conn} ->
    do_something();
  {error, Reason} ->
    error_handling()
end.
By Jeremy at 2014-10-31 16:24:37:

Seems like more of a dynamic vs static typing problem than exceptions vs return values.

A dynamic language could return an undocumented error code that you could miss. A static language with checked exceptions can force you to deal with them. That's even more extreme of course, since you actually have to do work to ignore errors that you don't care about.

Written on 31 October 2014.
« Quick notes on the Linux iptables 'ipset' extension
With ZFS, rewriting a file in place might make you run out of space »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Oct 31 01:00:38 2014
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.