syque.com

The Psychology of Quality and More

| Menu | Books | Share | Search | Settings |

C Style: Standards and Guidelines (contents)

CHAPTER 10 : Programming Usage

PART 4 : USAGE

CHAPTER 10 : Programming Usage
10.1 Elegant programming
10.2 Performance programming
10.3 Defensive programming
10.4 Error handling
10.5 Diagnostics
10.6 Integrity and Recovery
10.7 Testability
10.8 Portability
10.9 Localization
10.10 Usability
10.11 Summary

<--Prev page | Next page -->

 

10.4  Error handling

Error handling is a specific part of defensive programming that is such a broad topic that it deserves a section of its own. The starting point for error handling is from when the error has been discovered, and addresses the question, "What do I do now?"

The most important thing to standardize on in error handling is that all errors should be handled. "It'll never happen!" is a common excuse for not checking for and handling errors, as is, "I couldn't do much about it anyway." In fact, this is seldom true - it just takes more effort.

It is also important to standardize on the method of handling errors, as a consistent approach will result in more reliable, as well as more maintainable code.

There are two basic ways errors appear. Called functions may return an error, which may be from local program calls or from external system calls. Otherwise the error must be detected from other code conditions (such as variables going out of range) which may stem from expected or unexpected causes.

10.4.1  Trapping errors

When unrecoverable errors occur, there are two tasks that usually must be done: cleaning up any partially completed items and getting out! One way to do this is to use an error variable which is tested in all loops:

 

while ( (pFreeBlock <> pLastBlock) && (ErrNo == FB_NO_ERROR) )
{...
    do
    {...
        if ( pFreeBlock != NULL )
           ErrNo = FB_LOST_BLOCK;
        else
        {...
            if ( pNextBlock == NULL )
               ErrNo = FB_BROKEN_CHAIN;
            else
            {...
            }
        }
    } while ( pFreeBlock->IsInUse && (ErrNo == FB_NO_ERROR) )
}

/*---- clean up after errors --------------------------------------------*/
switch ( ErrNo )
{
case FB_LOST_BLOCK:
    RestoreBlocks( pFreeBlocks );
    break;
case FB_BROKEN_CHAIN:
    ResetChain( pFreeBlock, pNextBlock );
    RestoreBlocks( pFreeBlocks );
    break;
...
}
return ( ErrNo );

------------------------------------------------------------------------------

This results in a single return point, but the loop is complicated by extra nesting and comparisons. An alternative is to immediately pass control back to the function caller:

 

while ( pFreeBlock <> pLastBlock )
{...
    do
    {...
        if ( pFreeBlock != NULL )
        {
            RestoreBlocks( pFreeBlocks );           /* clean up      */
            return( FB_LOST_BLOCK );                /* error return  */
        }
        ...
        if ( pNextBlock == NULL )
        {
            ResetChain( pFreeBlock, pNextBlock );   /* clean up      */
            RestoreBlocks( pFreeBlocks );
            return( FB_BROKEN_CHAIN );              /* error return  */
        }
        ...
    } while ( pFreeBlock->IsInUse )
}
return( FB_NO_ERROR );                              /* normal return */
      

This simplifies the nesting and conditions, but it requires any clean-up code to be executed before each return. If the clean-up code is significant, it will make the main code less readable. This can be simplified by using goto to create a simple exception handler, cleaning up and handling errors at the end of the function:

 

while ( pFreeBlock <> pLastBlock )
{...
    do
    {...
        if ( pFreeBlock != NULL )
            goto ErrLostBlock;
        ...
        if ( pNextBlock == NULL )
            goto ErrBrokenChain;
        }
        ...
    } while ( pFreeBlock->IsInUse )
}
return ( FB_NO_ERROR );                    /* normal return */

/*---- clean up after errors ---------------------------------------------*/
ErrLostBlock:
RestoreBlocks( pFreeBlocks );
return ( FB_LOST_BLOCK );

ErrBrokenChain:
ResetChain( pFreeBlock, pNextBlock );
RestoreBlocks( pFreeBlocks );
return ( FB_BROKEN_CHAIN );

 

A further technique is to use setjmp and longjmp library functions in conjunction with a program-wide exception handler to enable common errors to be handled in one place.

10.4.2  Error identification

Errors are commonly returned as coded integers. There are assorted schemes for what these mean and errors are variously indicated by negative, zero or positive numbers. Although the inconsistency of libraries cannot be changed, it makes sense to use a standard method of identifying errors within a program.

For simple pass/fail situations, zero, which also indicates 'false', may used to flag failure; a simple enum scheme can be useful for error values (see 9.10.5).

Some system error codes will be found in the global variable errno. It is hazardous to leave them there 'for later', as they may be overwritten. It is better to handle them immediately or to copy them to a private error variable for later use.

In a program, keeping a central store of error numbers/messages enables errors to be uniquely identified. Subsystem errors may be identified by allocated number ranges.

In complex situations, passing back a single error number can be inadequate, particularly if the error has a non-unique number and has been passed back through several functions. A standard error structure, typically 'owned' by the outer calling function, may be used here:

 

struct STD_ERROR
{
    BOOL    IsOk;            /* TRUE = data below is valid */
    int     SystemNo;        /* system error number        */
    int     LocalNo;         /* subsystem error number     */
};
...
struct STD_ERROR DB_Err;
...
DB_Reset( pChkDb, &DB_Err );
if ( !DB_Err.IsOK )
    printf( "System error %d; Database error %d\n",
            DB_Err.SystemNo, DB_Err.LocalNo);

 

This requires error structures to be passed around everywhere, which, although inconvenient, may be preferable to the alternative of using global variables.

10.4.3  Final handling

At some stage the error must be handled in some manner other than to pass it back to a higher function. This will be at some consistent point where the higher function can add no value to the handling process. At this point, control may be passed to an error handler function. This simplifies the function in which the error is detected, and maintains functional cohesiveness.

There will be two types of error: those that enable the program to continue, and those that require the program to be terminated.

If the program can continue, which means the integrity of all data must be certain, then a consistent diagnostic error message may be given (see 10.5.1) before continuing.

If the program cannot continue, then it should shut down gracefully, preserving as much user data as possible, and giving as much diagnostic information as possible, to help the cause of the problem to be discovered and fixed.

 

<--Prev page | Next page -->

 

Site Menu

| Home | Top | Settings |

Quality: | Quality Toolbook | Tools of the Trade | Improvement Encyclopedia | Quality Articles | Being Creative | Being Persuasive |

And: | C Style (Book) | Stories | Articles | Bookstore | My Photos | About | Contact |

Settings: | Computer layout | Mobile layout | Small font | Medium font | Large font | Translate |

 

You can buy books here

More Kindle books:

And the big
paperback book


Look inside

 

Please help and share:

 

| Home | Top | Menu |

© Changing Works 2002-
Massive Content -- Maximum Speed