< previous page page_370 next page >

Page 370
One source of errors is mismatched actual and formal parameter lists. The C++ compiler ensures that the lists have the same number of parameters and that they are compatible in type. It's the programmer's responsibility, however, to verify that each actual parameter list contains the correct items. This is a matter of comparing the formal parameter declarations to the actual parameter list in every call to the function. This job is much easier if you give each formal parameter a distinct name and describe its purpose in a comment. You can avoid mistakes in writing an actual parameter list by using descriptive variable names in the calling code to suggest exactly what information is being passed to the function.
Another source of error is the failure to ensure that the precondition for a function is met before it is called. For example, if a function assumes that the input file is not at EOF when it is called, then the calling code must ensure that this is true before making the call to the function. If a function behaves incorrectly, review its precondition, then trace the program execution up to the point of the call to verify the precondition. You can waste a lot of time trying to locate a bug in a correct function when the error is really in the part of the program prior to the call.
If the parameters match and the precondition is correctly established, then the source of the error is most likely in the function itself. Trace the function to verify that it transforms the precondition into the proper postcondition. Check that all local variables are initialized properly. Parameters that are supposed to return data to the caller must be declared as reference parameters (with a & attached to the data type name).
One helpful technique for debugging a function is to use your system's debugger program, if one is available, to step through the execution of the function. Alternatively, you can insert debug output statements to print the values of the parameters immediately before and after calls to the function. It also may help to print the values of all local variables at the end of the function. This information provides a snapshot of the function (a picture of its status at a particular moment in time) at its two most critical points, and it is useful in verifying traces.
To test a function thoroughly, you must arrange the input data so that the precondition is pushed to its limits; then the postcondition must be verified. For example, if a function requires a parameter to be within a certain range, try calling the function with values in the middle of that range and at its extremes.
Testing a function also involves trying to arrange the data to violate its precondition. If the precondition can be violated, then errors may crop up that appear to be in the function being tested, when they are really in the main function or another function. For example, function PrintData in the Graph program assumes that a department's sales will not exceed $25,000. If a figure of $250,000 is entered by mistake, the main function does not check this number before the call, and the function thus tries to print a row of 500

 
< previous page page_370 next page >