Prev | Next |
Measure twice, cut once |
---|
Since computer programs are easy to change and to run, it is tempting to throw something together and begin testing and debugging right away. If you do that, you will spend far too long debugging. Try to be cautious about how you program. After you have written a function definition, for example, reread the definition, and convince yourself anew that the function is written correctly. Look for errors, such as memory allocation errors. Shoot for having the function work the first time, without going to extremes of hand checking that take too long. |
The best laid plans... |
---|
In spite of your best planning, you will make mistakes, and you should plan for them. To fix your program, you will need to use debugging strategy and tactics. Debugging strategy tells you the large steps involved in debugging. The steps are as follows.
Debugging tactics are particular ways to accomplish the goals of a strategy. Usually, isolating the error is the hardest thing to do. Tactics that follow are some tactics for isolating errors. They also help in step 3, determining exactly what is wrong. |
Tracing |
---|
Add prints to your program to show what is happening. Direct the prints to a file, not to the standard output. You might let the main program open a file, and make that file available to all modules using an extern declaration. This is one place where using a global variable is justified. Be sure that you do not print raw data. Each added print should show who did the print, where it is, and what is being printed. For example, if you insert a print at the top of a loop body in function copy, and you are printing the value of an integer variable n, you might write fprintf(trace_file, "copy: Loop top: n = %d\n", n);If you are printing an array A of n integers at the beginning of function insert, you might write {int i; fprintf(trace_file, "insert: Begin, A =\n"); for(i = 0; i < n; i++) { fprintf(trace_file, "A[%d] = %d\n", i, A[i]); } } Hint: If you define a preprocessor symbol such as #define tprint fprintfthen you can use tprint in place of fprintf in your trace prints. The advantage is that you can then search for tprint using a text editor or search utility, and quickly find all of the trace prints. Hint: You can leave trace prints in your program if you surround them by preprocessor conditions. For example, you might write #ifdef DEBUG_COPYING fprintf(trace_file, "Begin copy\n"); #endifNow if you have said #define DEBUG_COPYINGthen this print will be compiled in. If not, then it will not be compiled. |
Using a debugger |
---|
A debugger can be a powerful tool if used correctly. There are many different debuggers, each with its own characteristics. Typical things that a debugger will do for you are
Debuggers rely on extra information being put into executable files, telling the names of functions and variables. When you compile a file, you should be sure to tell the compiler to include debugging information. In Unix, for example, you use option -g on the compile command line. For example, you might say g++ -g -o myprog program.ccto compile C++ program program.cc, and put the executable code into file myprog. An example of a debugger is the Gnu debugger (gdb). To debug program myprog using gdb, you type gdb myprogThe debugger has its own language. Use the following with gdb.
Type ctl-C to pause a program while running it via gdb. |
Successive refinement |
---|
Successive refinement is a method of developing a program gradually. After each stage, you have a working program that does part of the job, or that at least incorporates some of the parts that the full program will hold. To develop a program by successive refinement, start with a refinement plan. Think about the parts, and how they can be tested. Write each part, and test each before moving on. Often, your plan calls for developing a sequence of approximations. Each approximation does a little more of what you want the entire program to do. An approximation might add one more feature, for example. Make sure each approximation is working before moving to the next one. A big advantage of this is that isolation of errors is usually easy. The error is usually in the (small) part that you have just written. |
Code inspections |
---|
One way to find an error is to read your program carefully to see if it looks correct. Try hand executions. For some errors, this approach will show you the error faster than any other method. But be sure not to give up if code inspection does not reveal to you what is wrong. You can fall back on tracing or running a debugger. |
Which should I use? |
---|
You will need to develop, though experience, a feel for which is more useful in each situation, a debugger, tracing or code inspection.
|
Testing |
---|
Never assume that your program works. Always test it. When you design a program or a part of a program, you often have one or two example inputs in mind, to help you think about the problem. Since you understand those example inputs, you test your program on them. It is very tempting to think that your program works once it has successfully handled those examples. But You should not assume that your program works after trying only one or two examples.Try several examples, including some that you think might be difficult, or might expose a bug. Check the results, and see whether they are right. Try extreme examples. Keep in mind that whoever uses your program will probably use it on examples that are unlike your original one or two test cases. Write your software for testing, and keep your tests around, even after they have been passed. You might modify the program later, and need to retest it to see that your modifications have not made it fail on cases where it previously worked. Wherever possible, write testers that do not require human interaction. Software designers use regression tests, which are a collection of tests that can be run automatically. Each time you make a modification, you run all of the tests again. A makefile is a good way to control running the tests. You put commands to run all of the tests in a makefile. Then running the tests is as hard as typing make test |
Prev | Next |