Except the Unexpected

by Marty Alchin on November 21, 2007 about Django

No, that title isn’t a typo. Predictably, yesterday’s post drew out an opposing view, and I’m very glad for it. While I haven’t changed my mind on the subject, Cedric did raises some reasonable points that I neglected in my original post. Maybe it’s just that I’m growing tired of posting every day, but I didn’t adequately explain my views, and for that I apologize. Only for not explaining, though, not for the views themselves. Today, I’ll try to be more detailed in my thoughts on the subject, and offer more recommendations than just “don’t return None” and “embrace exceptions”.

Also, I will apologize for the length of this post. I hope it helps, though.

Defining “exception”

A quick dictionary search for the word “exception” provides numerous options for defining the word. Removing useless definitions (“the act of excepting or the fact of being excepted”) and more specialized uses (criticism and legal usage), we’re left with a few variations on a theme:

The recurring theme here is that exceptions generally require a rule from which to deviate. But we’re programmers here, so what kind of rules are we talking about? In a nutshell, the rule is whatever action a piece of code is expected to perform. This might be defined by a function’s name, documentation, usage examples, or other communication, but it will be specific to each piece of code.

Any time you write a function, you’re designating a purpose for it, a task it should perform. That task, however simple or complicated, however blunt or subtle, is its rule. Deviations from that rule are exceptions. (To throw in more tongue-twisters for fun, consider this: exceptions violate expectations; they are excepted from what’s expected.)

However, just defining the concept of an exception doesn’t really do much for anybody. It’s like one of those zen sayings that doesn’t make any sense unless you’re expressly looking for meaning in the universe. And even then, it doesn’t really provide an answer, it just makes you appreciate the question. So how should we apply the concept of exceptions to programming? Well, exactly how it works will depend on the rule, and thus on each function itself.

So, it seems to me that the best designs focus on defining the rule, rather than the exceptions. Any required exceptions will be defined naturally as a side-effect of having a well-defined rule in place.

Defining a rule

When you write a function, you’re defining a task, or set of tasks, that the function will perform (some systems may make this definition more formal by designing by contract, but that’s beyond the scope of this article). Exactly how it’s performed is irrelevant to this discussion, and since rules will be different for different functions, the real key here is just to make sure that you do at least consciously define a rule for what the function will do. A few examples:

First, consider the dictionary. By using Python’s standard dictionary syntax, x[i], you’re implicitly calling __getitem__. So when using this syntax, the rule specifies that the key supplied must match a value. If this isn’t the case, it’s an exception to the rule, and Python reacts accordingly by raising a KeyError. If you supply a value for the key that can’t be used as a key (such as a list; try x[[]]), it can’t even try to look it up as a key, so that’s another exception: TypeError. These cases aren’t covered by the rule, so they’re considered to be exceptions.

Dictionaries provide another option, however, which allows for keys to be missing. This is a different function, and thus a different rule. By including “if such a value is present” in its rule, the get method must handle the inverse of that condition. If an appropriate value isn’t present, it’s no longer an exception, but an anticipated aspect of the rule, and it handles this by returning None. *gasp* Yes, this violates my previous post, and that’s why I agreed with Cedric that I should clarify my point. This situation isn’t evil, because returning None is appropriate within the rule defined for the function.

It’s also important to note that you may choose to call get instead of __getitem__, it’s not automatic. The “standard” dictionary access technique uses __getitem__, with get being generally reserved for more specialized situations which need to follow a different rule.

In the case of the get method of Django’s model managers, you’ll notice the rule is very complex, and thus has many potential points of failure. Just going by the rule I laid out above, here are the ways it could go wrong:

Of course, there are more things that can go wrong, but those are mostly ipmlementation details or things that are out of Django’s control. Those listed above are based solely on the rule I provided, and of course, that rule is probably a bit oversimplified as well.

Some words of advice

So when should your rules include provisions for None? How inclusive should your rules be? How should you convey the nature of these rules to programmers? These are all good questions, and while I don’t pretend to have a perfect answer (in fact, I doubt there is one), I’ll offer some advice based on my own experiences.

Conclusion (finally!)

As with anything else, I won’t pretend to be perfect in this regard myself. I came from a PHP background, and I wrote some of my Python code before I fully understood some of these philosophical concepts. But once you know, always try to be a better programmer, and this is one why I feel we could all be better programmers.