- Item 9: Set Yourself Up for Debugging Success
- Item 10: Enable the Efficient Reproduction of the Problem
- Item 11: Minimize the Turnaround Time from Your Changes to Their Result
- Item 12: Automate Complex Testing Scenarios
- Item 14: Consider Updating Your Software
- Item 15: Consult Third-Party Source Code for Insights on Its Use
- Item 16: Use Specialized Monitoring and Test Equipment
- Item 17: Increase the Prominence of a Failure's Effects
- Item 18: Enable the Debugging of Unwieldy Systems from Your Desk
- Item 19: Automate Debugging Tasks
- Item 20: Houseclean Before and After Debugging
- Item 21: Fix All Instances of a Problem Class
Item 19: Automate Debugging Tasks
You may find yourself with many possible discrete causes for a failure and no easy way to deduce which of them is the culprit. To identify it, you can write a small routine or a script that will perform an exhaustive search through all cases that might cause the problem. This works well when the number of cases would make it difficult to test them by hand, but possible to go through them in a loop. Iterating through 500 characters is a case that can be automated; doing an exhaustive search of all strings of user input is not.
Here is an example. After an upgrade, a computer began to delay the execution of the which command. Changing the long command search path (the Windows and Unix PATH environment variable) to /usr/bin removed the delay but left the question: Which of the path’s 26 elements was causing it? The following Unix shell script (run on the Windows machine through Cygwin) displayed the elapsed time for each path’s component.
# Obtain path echo $PATH | # Split the :-separated path into separate lines sed 's/:/\n/g' | # For each line (path element) while read path ; do # Display elapsed time for searching through it PATH=$path:/usr/bin time -f "%e $path" which ls >/dev/null done
Here is (part of) the script’s output:
0.01 /usr/local/bin 0.01 /cygdrive/c/ProgramData/Oracle/Java/javapath 0.01 /cygdrive/c/Python33 4.55 / 0.02 /cygdrive/c/usr/local/bin 0.01 /usr/bin 0.01 /cygdrive/c/usr/bin 0.01 /cygdrive/c/Windows/system32 0.01 /cygdrive/c/Windows 0.01 .
As you can clearly see, the problem is caused by an element consisting of a single slash, which had inadvertently crept into the path. Tracing the execution of the which command (see Item 58: “Trace the Code’s Execution”), revealed the problem’s root cause: The which command appended a slash to each path element, and on Windows a path starting with a double slash triggered a discovery process for network drives.
If it’s difficult to perform the exhaustive search by scripting the software you’re investigating, you can embed in the program a small routine for the same purpose. The routine can generate all the cases algorithmically (e.g., by iterating through some values). Alternately, it can read them from an external file, where you can generate them via a more sophisticated script or by scrapping data from existing execution logs.
Finally, there are also tools that can instrument your code to detect API violations, memory buffer overflows, and race conditions (see Item 59: “Use Dynamic Program Analysis Tools” and Item 62: “Uncover Deadlocks and Race Conditions with Specialized Tools”). For some of these tools, the analysis of a test run that used to take a few seconds can take tens of minutes. Nevertheless, the debugging time they can save you is well worth the wait.
Things to Remember
Automate the exhaustive searching for failures; computer time is cheap, yours is expensive.