Part of the fun of producing web pages and applications is that we get to be medicine men: wave our hands, write some code, and suddenly the Golem springs to life. The truth of the matter is that we’re closer to Ed Norton’s character in the beautiful but mostly pointless movie The Illusionist. Behind every stage trick is a logical (and sometimes overwrought) explanation. The only real mysteries lie within our own hearts and desires, and we can master our environments by recognizing the distinction between mind and machine.
For my part, I am mystified by engineers who use cajoling as a debugging tactic. These are the people who talk to their dev environments like Han Solo, begging more speed from the Millennium Falcon: “C’mon baby, work for me… (screen refresh)… nooooooo! Why god, why?!”
The very term “bug” bespeaks a rational attitude towards the initially inexplicable. Software presents an extremely controlled engineering environment, making scripting bugs even easier to detect than the original moth in the vacuum tube. When a hacker anthropomorphizes or spiritualizes their product, they’re ignoring that useful fact, abdicating responsibility for their domain and retreating into a comfortably passive mindset where success is incidental rather than created. They join the unwashed masses.
Rather than stare at tea leaves or rely on emotional appeals, two faster, more effective approaches to debugging simple scripts:
1. Start at the beginning of the script, and follow along until it breaks.
This is how many debuggers function, and it’s easily handled by adding alerts (JavaScript) or trace (ActionScript) calls into your code, line by line. Eventually, you’ll hit the point where the function doesn’t alert the expected information. Typically, there’s a breakdown in a call to another object – it doesn’t exist, or you’ve handled scope badly, or it lives in an unexpected form.
This technique is less useful when you have many scripts or functions interacting simultaneously or nesting, in which case you may want to try the second approach…
2. Observe the buggy behavior, and list the possible causes.
This is the Sherlock Holmes method – assume that you’re assuming something incorrect. This is an inverse approach to the first one: start at the broken output and work your way upstream. Define the buggy behavior and list all of the possible causes for that behavior. The trick is not to think within the constraints of the script as you understand it: instead of thinking “what could I have done to turn the screen blue?”, try “what are all of the possible ways to turn a screen blue?”
This is a great method for debugging CSS: IE is unexpectedly in quirks mode, an unexpected style rule is affecting a DOM node. It’s also useful for more complex UIs where multiple scripts are present. It’s not as useful when there are a lot of possible causes for the phenomenon (as in with a blue screen on a Windows machine).
That’s it. The theme between the two techniques is that you iterate through enlarging and reducing a list of possible causes until you strike upon a solution. No magic there, just a simple list edit.
Next time you see a bug, instead of wondering about the morality of the situation, pause and think “now how could that happen?” Usually, there’s an extra factor that you didn’t anticipate in your controlled environment. Identify it, change it, and listen to the peasants ooh and aah.
i’m equally mystified by those folks who curse at their computers when a piece of code they wrote breaks. pull out the debugger, spend a few moments, and spare us the drama, no?
The original bug was a moth, not a cockroach 🙂 http://www.history.navy.mil/photos/images/h96000/h96566kc.htm.
Another useful method for debugging is the amputation route. Hack out parts of the code and re-run the whole. If the whole functions without the amputated part then you can localize the error to the bloody stump you’ve hacked out.
Another (much more useful but much less used) approach is to unit test. For each new feature or function, you develop the function in a vacuum and write test scripts that will pass in expected input then report output. Once you know that your unit works in a vacuum you can generally rule that errors are problems with input. This allows you to skip over hunting for things like syntactical errors. Of course, if one function is getting unexpected input, but still producing output (only mangled) which is traveling downstream and creating a problem you may still have to resort to one of the above methods. Unit testing in hardly ever done because it takes extra time, but it’s much easier to debug your code before you release it in the wild because you can control the factors surrounding it much more easily. Also, unit tests have the added feature that if you change a function you still have a unit test framework to check it with. So you can potentially write the unit test once and re-use it over many functional iterations.
Personally I just blame Chewbacca 🙂
Thanks J!