I use to think that debug statements were ugly. I looked forward to deleting them from my code so that I could reveal the assignments and method calls between them for their simple elegance as was intended. I've come to understand though that a debug statement is merely a step along the way of the continuous refactoring required to build and maintain robust code. This is a brief story about how I learned to stop worrying and to love my debug statements.
First a brief example I hope might serve to demonstrate a number of points in this meditation:
#!/usr/bin/perl -w use strict; use Carp qw/FatalsToBrowser/; my $obj = MyPackage->new(); my $argument = "Here is a scalar."; print $argument,"\n"; my $result = $obj->method($argument); print "My argument still is: $argument. \n"; print "My \$result is now $result.\n"; 1;
Robustness refers to the resilence of code, to its capacity to handle the useless data that real world users throw at it, while protecting the integrity o its data and still guiding its user to accomplish the task it was designed to facilitate.
Earlier this year I was working with a coding partner on a cgi application. I was churning out code to accomplish the client's requirements. My partner was writing tests to break my work.
During one of our phone calls, in a frustrated moment, he said to me: "The error path is as important as the path that gets the work done."
I was churning on the "prototype it quick, show the client, get early feedback" development paradigm. I wanted to see it work, even if I had to hold the user's hand and warn them against doing things that would break the code.
One of my early mentors in my development as a programmer had a version of tourettes syndrome that oddly only affected his debug messages. He would create code that looked like this:
Working with his code, I would search the script for the latest explicative to show up in the logs. And then have to study the code itself to get some sense of why an error was thrown.my $result = $obj->method($argument); print "My <explicative deleted> argument still is: $argument. \n"; if($result eq 'ERROR') { print STDERR qw|!@#$%^&*(*&^$#$, still broken.|; } else { print STDERR "Yeah! It worked."; }
As I would do this and deconstruct the meaning of the error messages, I took it upon myself to rewrite his messages in a way which would mean something to and hopefully not offend a real user. Pronouns were replaced with their antecedants. His explicatives were substituted with more descriptive, if less colorful language.
Now, as my code develops over time, quick and dirty debug statements begin to evolve. They get rewritten and perhaps send to the logs. My routine begins to look more like this:
I used to think that a series of calls to object methods unadorned by ugly debug statements made for the prettiest code. It looks great on the screen, with short lines that don't wrap and an easy to follow logic. But if I'm not sitting with my user to warn them against trying unsupported actions with my code, it probably makes sense to mess up my programs with conditionals that test return values and guide my users along to success in my absence.#!/usr/bin/perl -w use strict; use Carp qw/FatalsToBrowser/; . . . my $obj = MyPackage->new(); my $argument = "Here is a scalar."; print $argument,"\n"; my $result = $obj->method($argument); if($verbosity eq "DEBUG"){ print LOG "My argument still is: $argument. \n"; print LOG "My \$result is now $result.\n"; } if($result eq 'ERROR') { print LOG qw|The \$obj->method() is returning an error, it is still +broken.|; } else { print STDERR "The \$obj->method() ran without errors."; } 1;
I used to write simple conditionals like:
Now I'm more likely to write something like this:if($test) { $obj->method(); }
In fact, I've cultivated the habit of closing braces and parentheses before filling them in. A new conditional is likely to look like this, at first brush:if($test) { print LOG "The test was successful, now invoking \$obj->method()."; $obj->method(); } else { print LOG "The test was unsuccessful, so we won't invoke the \$obj-> +method()."; }
I then return to the if line and add my conditional test between the parentheses. I'll code both paths, even if the failure path merely reports its invocation to the logs. Maybe some real work will have to be done on failure of the condition. But I want to see the failure first before I start coding around it.if(){ } else { }
By closing my braces from the start, I've had fewer syntax errors to debug. By writing an else clause for every if clause, by paying attention to the failure path as much as to the success path, my code has grown more robust.
I've seen errors that all look the same. But if there is something broken in my code and my only clue is a debug message which reads: "There has been an error.", a string used to report any of fifty different error conditions accounted for in the logic, I'll have my work cut out for me just finding the code in need of attention.
When I'm churning on the "prototype it fast" mode, I may throw in an error like: "We've encountered an error at line 35." By the time that this error gets rewritten to actually describe the nature of the error to a real human user and what that user might do to avoid the error in the future, it may no longer be on line 35, but may have shifted to line 48 or so.
But eventually I shift gears and translate. To a user, "Error code #53." or "Error on line 35." is pretty meaningless. But "Error code #53: An invalid date can not be processed by ->some_method() of MyPackage. Please use your back button to return to the form and enter a valid date before resubmitting your entries." keeps my user moving. An informative error message empowers a user to successfully use my software.
Here are some guidelines for error messages I try to code by:
-- Hugh
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: The Evolution of the Lowly Debug Statement
by Anonymous Monk on Nov 10, 2006 at 08:12 UTC | |
|
Re: The Evolution of the Lowly Debug Statement
by cLive ;-) (Prior) on Nov 11, 2006 at 01:21 UTC | |
|
Re: The Evolution of the Lowly Debug Statement
by Anonymous Monk on Nov 10, 2006 at 10:43 UTC | |
by Jenda (Abbot) on Nov 10, 2006 at 18:38 UTC | |
|
Re: The Evolution of the Lowly Debug Statement
by roboticus (Chancellor) on Nov 13, 2006 at 15:51 UTC | |
|
Re: The Evolution of the Lowly Debug Statement
by doom (Deacon) on Nov 16, 2006 at 05:25 UTC |