DMP Photo Booth: Throw It All Out

Another week goes by, another major refactor. This time on the chopping block: return codes. As you may know if you’ve been following development, I’ve been using integer return values to indicate success and failure of functions. This was working for a while, but as we all know, the worst problems don’t rear their ugly heads until some time has gone by. After working around them, I’ve realized this strategy has 3 main issues:

  • It’s really easy to just not check the return value, and not notice when some function returns DMP_PB_OMG_WERE_ALL_GONNA_DIE
  • It can be problematic if you have to return an integer as an actual result
  • You have to settle on some “failure” return value for functions that return values, and this can be a problem if you can’t guarantee some value will never be valid

Too bad I didn’t pick a modern language with exception handling…

GError To The Rescue

Luckily for me, GLib has the answer once again. GLib has a feature called GError, that is its answer to exception handling. I’d tell you all about how to use GError, but there is no need. GLib’s documentation is on par with Java’s documentation, and the GError documentation page is a shining example of how good documentation can make the difference between good and great.

Seriously, if anybody from Gnome is reading this: thank you for writing some decent documentation. This is a huge pet peeve of mine. No, your function called do_stuff(some_struct * zanzibar) is not self documenting, because if you don’t write a comment telling me if it has side effects, or if I remain responsible for zanzibar, then I have to look at your code to be sure. But I digress…

The basic idea behind GError is that any function that can throw has a GError ** as its final argument. If you’d like to call a function that can throw, you need to pass in an unallocated pointer to the function:

void some_func() { GError * error = NULL; dangerous_func(&error); ...

At this point, dangerous_func will be called, and return. Afterwards, if your GError pointer is no longer NULL, then an exception was thrown:

if (error != NULL) { //what now? }

This test is equivalent to a catch {} block. At this point, you traditionally have 3 options: Handle the exception, re-throw the exception, or wrap the exception in some other exception type and throw.

Personally, I’m a huge fan of Java’s checked exceptions. Sure, it can be annoying having to catch 15 different exceptions because some library author thought they needed that many on one method, but it sure beats having to magically know if some function throws, as C++ handles things. I feel that GError strikes a good balance. If a function has a GError ** argument, then it throws. At this point, you can check the docs to see if the author felt fit to say what GErrors they set, or you can just read the error and see what it is. You don’t have to catch some specific Exception class as in traditional exception handling, and you can determine exception type without resorting to instanceof.

Now, let’s examine our options…

Handle It

if (error != NULL) { printf("%s %d: %s\n", g_quark_to_string(error->domain), error->code, error->message); g_error_free(error); }

A GError has 3 public fields: domain, code, and message. Domain is a GQuark that is [NAMESPACE]_[MODULE]_ERROR. Think of this as your exception base class: i.e. BaseBeanFactoryBeanException. Code is an integer that represents a specific error. Think of this as your derived exception class: i.e. FluffyPinkBeanFactoryBeanException Finally, we have message. This is a human readable message for the error, and is equivalent to a Java call to Exception.getMessage().

GError has a few methods to ease working with these, but any C programmer worth their salt should be able to “make do” with these 3 fields.

Re-throw It

if (error != NULL) { g_propagate_error(this_funcs_gerror, error); return; }

Yes, it is that simple. Of course, in this case, the function signature would be:

void some_func(GError ** this_funcs_gerror)

…but aside from that, there’s not much going on here. The GError will be copied into the passed-in GError **. At this point you can (must) return; the error is now the caller’s problem!

Wrap It

if (error != NULL) { g_set_error(this_funcs_gerror, error->domain, error->code, "I know better, so I'm wrapping this!\n"); g_error_free(error); return; }

Pretty self-explanatory. As with re-throwing the exception, this requires a modified function signature. Also, this is how you’d throw a new exception as well, minus the call to g_error_free.

The Empty Catch Block

We’re all guilty of this, don’t try to act like you’re not. It’s just so much easier to do this:

try { fancyObj.someDangerousMethod(); } catch (Exception ex) {}

Don’t worry, you’re among friends. GLib has you covered on this front as well. If you pass NULL to a function that throws, it will not attempt to set an error, and you can just go on with your life:

void some_func() { dangerous_func(NULL); //catch (Exception ex) {} }

Feel free to omit that comment, I promise it’ll still work. As we all know, sometimes you just don’t care about some exception. In these cases, you aren’t forced to care, you can just pass NULL and get on with your life.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: