Quantcast
Channel: Tao Effect Blog » code
Viewing all articles
Browse latest Browse all 2

Error handling conventions

$
0
0

Programmers have many options available to them when it comes to error handling.  A very common convention among C programmers is to make a function return a non-zero value if an error has occurred.  This post is about what to do with that non-zero value.

Let’s start with a simple example:

    1 OSStatus initKeychainAccess()
    2 {
    3     OSStatus err;
    4 
    5     err = SecKeychainSetUserInteractionAllowed(TRUE);
    6 
    7     if ( err ) {
    8         log_err("couldn’t enable keychain user interaction");
    9         return err;
   10     }
   11 
   12     err = SecKeychainUnlock(gKeychain, 0, NULL, FALSE);
   13 
   14     if ( err ) {
   15         log_err("couldn’t unlock keychain");
   16     }
   17     else {
   18         err = SecKeychainAddCallback(MyKeychainCallback, kSecEveryEventMask, NULL);
   19         if ( err ) {
   20             log_err("couldn’t set callback for keychain");
   21         }
   22     }
   23 
   24     if ( err == 0 ) {
   25         doThatFancyThingYouDo();
   26     }
   27 
   28     return err;
   29 }

This example demonstrates three common error handling techniques that I’ve encountered in the wild:

  1. Return immediately (line 9)
  2. Building nested if-else clauses (lines 14-22)
  3. Repeatedly checking error status (line 24)

This is just a simple, short example, but these error checking patterns can really add up in more complicated code, making it unwieldy and unnecessarily complex, not to mention a pain to maintain and debug. There’s also the nuisance of having to write a custom error message for each situation as well, and most of the time developers tend to just avoid doing that altogether, making it difficult to troubleshoot problems when they occur on a remote system.

To get around these problems developers often use macro’s with goto’s. Here’s one technique that I’ve seen:

    1 OSStatus initKeychainAccess()
    2 {
    3     OSStatus err;
    4     err = SecKeychainSetUserInteractionAllowed(TRUE);
    5     require_noerr(err, fail_label);
    6     err = SecKeychainUnlock(gKeychain, 0, NULL, FALSE);
    7     require_noerr(err, fail_label);
    8     err = SecKeychainAddCallback(MyKeychainCallback, kSecEveryEventMask, NULL);
    9     require_noerr(err, fail_label);
   10     doThatFancyThingYouDo();
   11 fail_label:
   12     return err;
   13 }

Now that’s certainly an improvement, we went from 29 lines down to 13, and the code is much more readable. That’s pretty good, but I think we can do better, here’s my version:

    1 OSStatus initKeychainAccess()
    2 {
    3     OSStatus err;
    4     DO_FAILABLE(err, SecKeychainSetUserInteractionAllowed, TRUE);
    5     DO_FAILABLE(err, SecKeychainUnlock, gKeychain, 0, NULL, FALSE);
    6     DO_FAILABLE(err, SecKeychainAddCallback, MyKeychainCallback, kSecEveryEventMask, NULL);
    7     doThatFancyThingYouDo();
    8 fail_label:
    9     return err;
   10 }

In the event of an error, you’ll get all of the important information that you need to pinpoint exactly what happened: the function that failed, the error code, and the line number. Here are the definitions for DO_FAILABLE and a few variants thereof:

#define DO_FAILABLE(_errVar, _func, args...) do { \
    if ( (_errVar = _func(args)) != 0 ) { \
        log_err(#_func ":%d returned: %d\n", __LINE__, (int)_errVar); \
        goto fail_label; \
    } \
} while (0)

// useful when the error code isn't the return value
// ex: DO_FAILABLE_SUB(err, errno, setuid, getuid());
#define DO_FAILABLE_SUB(_errVar, _subst, _func, args...) do { \
    if ( (_errVar = _func(args)) != 0 ) { \
        _errVar = _subst; \
        log_err(#_func ":%d resulted in: %d\n", __LINE__, (int)_errVar); \
        goto fail_label; \
    } \
} while (0)

// just note that an error occurred, but don't do anything about it
#define FAILABLE(_errVar, _func, args...) do { \
    if ( (_errVar = _func(args)) != 0 ) { \
        log_err(#_func ":%d returned: %d\n", __LINE__, (int)_errVar); \
    } \
} while (0)

This cuts down on the number of lines of code by implicitly assuming that fail_label exists (more often than not, a single fail label is enough). Also note the cast to int, this is necessary because sometimes your error value might be stored in a type that will cause gcc to give a warning (e.g. if you have it enabled via -Wall) because of a mismatch between the type and the %d in the printf statement.

Another nice thing about these macros is that they’re very easy to adopt, oftentimes you could easily throw them into your code via a regex find&replace. You simply prepend DO_FAILABLE( in front of the error assignment, convert the equals sign into a comma, and replace the first open parenthesis in the function call with another comma. Then add a fail_label somewhere.

The real fun comes afterward, when you get to delete hundreds of lines of unnecessary error-checking code. :-)


Viewing all articles
Browse latest Browse all 2

Trending Articles