Better error messages

Wednesday 28 July 2010

A simple piece of advice: If you are throwing an exception (or logging an error) about a value being incorrect in some way, include the value itself. It will make it so much easier for the poor sap who has to figure out why the exception is happening.

I found myself in this situation, this code throwing an exception:

if not isinstance(key, str):
    raise Client.MemcachedStringEncodingError, ("Keys must be str()'s, not"
            "unicode.  Convert your unicode strings using "
            "mystring.encode(charset)!")

There are a few things wrong with this message, the first being that the multi-line string concatenation is missing a space, so the message actually has the word “notunicode” in it. Why are we so sure the wrong value is Unicode in the first place? And of course, it should include the actual value:

if not isinstance(key, str):
    raise Client.MemcachedStringEncodingError, (
        "Keys must be str()'s: %r" % key
        )

If you want to be paranoid, you can limit the amount of repr text that will appear in the message:

if not isinstance(key, str):
    raise Client.MemcachedStringEncodingError, (
        "Keys must be str()'s: %.60r" % key
        )

If you are really paranoid, you’re worried that getting the repr of your unknown object could itself throw an exception:

def safe_repr(o):
    try:
        return repr(o)
    except:
        return "??norepr?"

...

if not isinstance(key, str):
    raise Client.MemcachedStringEncodingError, (
        "Keys must be str()'s: %.60s" % safe_repr(key)
        )

or even:

def safe_repr(o):
    try:
        return repr(o)
    except Exception, e:
        return "??norepr (%s)?" % e

Good error handling is always a pain, but it’s worth it when things start hitting the fan and you have to figure out what’s going on.

Comments

[gravatar]
Robert Brewer 10:36 AM on 28 Jul 2010

You won't need safe_repr quite as often if you remember to never use `%r` with a single interpolated value, because this can happen:

>>> v = ("bar", "baz")
>>> "foo %r" % v
Traceback (most recent call last):
  File "", line 1, in 
TypeError: not all arguments converted during string formatting
Always use "foo %s" % repr(v) instead.

Another trick I like to use quite often is to add values to exceptions other people raise that don't have enough info yet:
try:
    encode(v)
except Client.MemcachedStringEncodingError, e:
    e.args += (v,)
    raise

[gravatar]
ot 6:33 PM on 28 Jul 2010

Who says that e.__str__() in the last example can't raise exceptions? :)

[gravatar]
Christopher Swenson 1:25 AM on 1 Aug 2010

I always try to mention (and remember myself) that you should pretty much never use a blanket "except": you might catch SystemExit or KeyboardInterrupt with that (if someone hits Ctrl-C while you are executing that in a tight loop).

[gravatar]
Ned Batchelder 7:06 AM on 1 Aug 2010

@ot and @Christopher: both good points! Like I said, this stuff is hard!

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.