23.5. Debugging Tools-Obvious and Not-So-Obvious

 < Free Open Study > 

23.5. Debugging Tools Obvious and Not-So-Obvious

You can do much of the detailed, brain-busting work of debugging with debugging tools that are readily available. The tool that will drive the final stake through the heart of the defect vampire isn't yet available, but each year brings an incremental improvement in available capabilities.

Cross-Reference

The line between testing and debugging tools is fuzzy. See Section 22.5 for more on testing tools and Chapter 30 for more on software-development tools.


Source-Code Comparators

A source-code comparator such as Diff is useful when you're modifying a program in response to errors. If you make several changes and need to remove some that you can't quite remember, a comparator can pinpoint the differences and jog your memory. If you discover a defect in a new version that you don't remember in an older version, you can compare the files to determine what changed.

Compiler Warning Messages

One of the simplest and most effective debugging tools is your own compiler.


Set your compiler's warning level to the highest, pickiest level possible, and fix the errors it reports It's sloppy to ignore compiler errors. It's even sloppier to turn off the warnings so that you can't even see them. Children sometimes think that if they close their eyes and can't see you, they've made you go away. Setting a switch on the compiler to turn off warnings just means you can't see the errors. It doesn't make them go away any more than closing your eyes makes an adult go away.

Assume that the people who wrote the compiler know a great deal more about your language than you do. If they're warning you about something, it usually means you have an opportunity to learn something new about your language. Make the effort to understand what the warning really means.

Treat warnings as errors Some compilers let you treat warnings as errors. One reason to use the feature is that it elevates the apparent importance of a warning. Just as setting your watch five minutes fast tricks you into thinking it's five minutes later than it is, setting your compiler to treat warnings as errors tricks you into taking them more seriously. Another reason to treat warnings as errors is that they often affect how your program compiles. When you compile and link a program, warnings typically won't stop the program from linking, but errors typically will. If you want to check warnings before you link, set the compiler switch that treats warnings as errors.

Initiate projectwide standards for compile-time settings Set a standard that requires everyone on your team to compile code using the same compiler settings. Otherwise, when you try to integrate code compiled by different people with different settings, you'll get a flood of error messages and an integration nightmare. This is easy to enforce if you use a project-standard make file or build script.

Extended Syntax and Logic Checking

You can use additional tools to check your code more thoroughly than your compiler does. For example, for C programmers, the lint utility painstakingly checks for use of uninitialized variables (writing = when you mean = =) and similarly subtle problems.

Execution Profilers

You might not think of an execution profiler as a debugging tool, but a few minutes spent studying a program profile can uncover some surprising (and hidden) defects.

For example, I had suspected that a memory-management routine in one of my programs was a performance bottleneck. Memory management had originally been a small component using a linearly ordered array of pointers to memory. I replaced the linearly ordered array with a hash table in the expectation that execution time would drop by at least half. But after profiling the code, I found no change in performance at all. I examined the code more closely and found a defect that was wasting a huge amount of time in the allocation algorithm. The bottleneck hadn't been the linearsearch technique; it was the defect. I hadn't needed to optimize the search after all. Examine the output of an execution profiler to satisfy yourself that your program spends a reasonable amount of time in each area.

Test Frameworks/Scaffolding

As mentioned in Section 23.2 on finding defects, pulling out a troublesome piece of code, writing code to test it, and executing it by itself is often the most effective way to exorcise the demons from an error-prone program.

Cross-Reference

For details on scaffolding, see "Building Scaffolding to Test Individual Classes" in Section 22.5.


Debuggers

Commercially available debuggers have advanced steadily over the years, and the capabilities available today can change the way you program. Good debuggers allow you to set breakpoints to break when execution reaches a specific line, or the nth time it reaches a specific line, or when a global variable changes, or when a variable is assigned a specific value. They allow you to step through code line by line, stepping through or over routines. They allow the program to be executed backwards, stepping back to the point where a defect originated. They allow you to log the execution of specific statements similar to scattering "I'm here!" print statements throughout a program.

Good debuggers allow full examination of data, including structured and dynamically allocated data. They make it easy to view the contents of a linked list of pointers or a dynamically allocated array. They're intelligent about user-defined data types. They allow you to make ad hoc queries about data, assign new values, and continue program execution.

You can look at the high-level language or the assembly language generated by your compiler. If you're using several languages, the debugger automatically displays the correct language for each section of code. You can look at a chain of calls to routines and quickly view the source code of any routine. You can change parameters to a program within the debugger environment.

The best of today's debuggers also remember debugging parameters (breakpoints, variables being watched, and so on) for each individual program so that you don't have to re-create them for each program you debug.

System debuggers operate at the systems level rather than the applications level so that they don't interfere with the execution of the program being debugged. They're essential when you are debugging programs that are sensitive to timing or the amount of memory available.

Given the enormous power offered by modern debuggers, you might be surprised that anyone would criticize them. But some of the most respected people in computer science recommend not using them. They recommend using your brain and avoiding debugging tools altogether. Their argument is that debugging tools are a crutch and that you find problems faster and more accurately by thinking about them than by relying on tools. They argue that you, rather than the debugger, should mentally execute the program to flush out defects.

An interactive debugger is an outstanding example of what is not needed it encourages trial-and-error hacking rather than systematic design, and also hides marginal people barely qualified for precision programming.

Harlan Mills

Regardless of the empirical evidence, the basic argument against debuggers isn't valid. The fact that a tool can be misused doesn't imply that it should be rejected. You wouldn't avoid taking aspirin merely because it's possible to overdose. You wouldn't avoid mowing your lawn with a power mower just because it's possible to cut yourself. Any other powerful tool can be used or abused, and so can a debugger.

The debugger isn't a substitute for good thinking. But, in some cases, thinking isn't a substitute for a good debugger either. The most effective combination is good thinking and a good debugger.


cc2e.com/2368

Checklists: Debugging Reminders

Techniques for Finding Defects

  • Use all the data available to make your hypothesis.

  • Refine the test cases that produce the error.

  • Exercise the code in your unit test suite.

  • Use available tools.

  • Reproduce the error several different ways.

  • Generate more data to generate more hypotheses.

  • Use the results of negative tests.

  • Brainstorm for possible hypotheses.

  • Keep a notepad by your desk, and make a list of things to try.

  • Narrow the suspicious region of the code.

  • Be suspicious of classes and routines that have had defects before.

  • Check code that's changed recently.

  • Expand the suspicious region of the code.

  • Integrate incrementally.

  • Check for common defects.

  • Talk to someone else about the problem.

  • Take a break from the problem.

  • Set a maximum time for quick and dirty debugging.

  • Make a list of brute-force techniques, and use them.

Techniques for Syntax Errors

  • Don't trust line numbers in compiler messages.

  • Don't trust compiler messages.

  • Don't trust the compiler's second message.

  • Divide and conquer.

  • Use a syntax-directed editor to find misplaced comments and quotation marks.

Techniques for Fixing Defects

  • Understand the problem before you fix it.

  • Understand the program, not just the problem.

  • Confirm the defect diagnosis.

  • Relax.

  • Save the original source code.

  • Fix the problem, not the symptom.

  • Change the code only for good reason.

  • Make one change at a time.

  • Check your fix.

  • Add a unit test that exposes the defect.

  • Look for similar defects.

General Approach to Debugging

  • Do you use debugging as an opportunity to learn more about your program, mistakes, code quality, and problem-solving approach?

  • Do you avoid the trial-and-error, superstitious approach to debugging?

  • Do you assume that errors are your fault?

  • Do you use the scientific method to stabilize intermittent errors?

  • Do you use the scientific method to find defects?

  • Rather than using the same approach every time, do you use several different techniques to find defects?

  • Do you verify that the fix is correct?

  • Do you use compiler warning messages, execution profiling, a test framework, scaffolding, and interactive debugging?


 < Free Open Study > 


Code Complete
Code Complete: A Practical Handbook of Software Construction, Second Edition
ISBN: 0735619670
EAN: 2147483647
Year: 2003
Pages: 334

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net