Patterns: The If-Else Error Chain

In languages like Java, we have a standardized error-handling paradigm in the try-catch expression. Effectively, this hands all error-handling off to the computer, which monitors all code in the try loop for any and every kind of possible error. While we are able to restrict the range of errors in many cases, the fact is that this bloats the program in both memory footprint and time required to operate.

In C, we have no such paradigm, so we have to use alternative methods to handle errors.

Common Practices

There are any number of ways to deal with errors outside of a rigidly defined paradigm.

Some (like those who operate predominantly in C++) may define supervisor functions that simulate the try-catch expression. This is less than common, because in defining the supervisor function you usually begin to appreciate the range of all possibilities. When you start trying to handle every possible error with one megafunction, you start to appreciate the simplicity of catching errors manually.

The most common practice is to test the output of functions and related operations. Whenever you call an allocation function like malloc() or calloc(), you test the output to ensure that space was properly allocated. When you pass-by-reference into a function, you test the inputs to ensure that they make sense in the context of your program. Methods like these allow us to manually handle both the flow and the error-handling of our code.

However, in most cases we have a “multiple-breakout” pattern of tests. These patterns look something like this:

char * blueberry = (char * ) malloc(50*sizeof(char))
if(blueberry == NULL)
    return -1;
int pancake;
do_thing(blueberry, "there is stuff", pancake);
if(pancake < 0 || pancake > 534)
    return -2;
do_other_thing(pancake, time() );
if(pancake < 65536)
    return -3;
...
return 0;

This pattern runs the risk of terminating before memory is properly freed and parameters are properly reset. The only ways to avoid this terrible condition are to manually plug the cleanup into every error response (terrible) or to use goto (not terrible, but not strictly kosher).

If-Else Chain

There is one method for handling errors that is consistent with another pattern we cover (error-orientation): we build a great if-else chain of tests.

This pattern is confusing to many for two reasons:

  • If fundamentally reorients the code away from the “happy-case” paradigm (in which all error-handling is a branch off the main path) to a “failure case” paradigm (in which the happy-case is the result of every test in the chain failing)
  • All our happy-path code finds itself inside of an if() statement – nothing can be permitted to break the chain

It’s a bit hard to describe this pattern without an example, so bear with me:


int copy(const char * const input, int size, char * output)
{
    int code = 0;
    if( input == NULL )
    {
        code = -1;
    }
    else if ( output = (char *) malloc (size * sizeof(char) ) , output == NULL )
    {
        code = -2;
    }
    else if ( strncpy(output, input, size), 0 )
    {
        //impossible due to comma-spliced 0
    }
    else if (strncmp(output, input))
    {
        code = -3;
    }
    else if ( printf("%s\n", output) < 0 )
    {
        //printf returns the number of printed characters
        //Will only be less than 0 if write error occurs
        code = -4;
    }
    else
    {
        //could do something on successful case, but can't think of what that would be
    }
    //Normally we would pass output back, but let's just free him here for fun
    //This is where we do all our cleanup
    if(output != NULL)
        free(output);
    return code;
}

As we can see, each step in the error-handling function is treated as a possible error case, each with its own possible outcome. The only way to complete the function successfully is to have every error test fail.

Oh, and because this code is fundamentally modular, it is very easy to add and remove code by adding another else-if statement or removing one.

Lesson: Switching to an if-else chain can improve error awareness and accelerate your programs in operation, without requiring much additional time to design and code.

Facebook Auto Publish Powered By : XYZScripts.com