As a followup to my recent post on debugging, when confronted with an extremely difficult bug, I find there are essentially two mental processes you mix:
Deductive: Spend enough time to fully work through all of the code paths to try and find an intuitive solution to the problem. In some cases, you can eliminate the debugging step entirely, particularly if you sleep on it and take the time to unleash your subconscious problem solving abilities. How awesome when you wake up or get out of the shower with the fix ready to apply?
Inductive: Figure out how to get as much context for the problem as quickly as possible in the form of either log files or a targeted debugging session before spending too much time on difficult thought processes. Try to break the debugger in the affected code path when it is in as close a state as possible to the problematic code path and step around. You may trip across the bug or at least guide your deductive thinking with more information.
Back in college, my professors taught us to focus on the deductive process. You should understand your problem domain, ensure it is well designed from top to bottom. Avoid relying on the narrow insights you gain by observation to guide code changes. Leaping into the debugger may help you quickly patch the problem but lead to an overall less stable system. Maybe the fix you find in the debugger only addresses one case of many or a category of similar bugs could be fixed at a higher level with a different change.
But to counter that wisdom, it’s often much faster to understand the problem with a more targeted approach. Time spent visualizing the code paths in your head may be more productive with a debug session in front of you so you can more quickly trace code paths, and avoid drawing out data structures.
In debugging, either approach may waste time. For the inductive process, you can waste time writing unnecessary logging code or throwaway code to catch a complex code condition with a breakpoint, or stepping through a morass of code and learning nothing. Your deductive approach may lead you to waste time relearning a piece of code at a level of detail you’ll quickly forget again when the problem is something simple or unrelated to your initial assumptions. We programmers tend to find it hard to account for debugging time so learning how to debug more efficiently is key to making deadlines. So let me reveal the key to debugging productivity: use induction to focus deducation and deduction to focus induction. Make sense?
Some more related tips. When your system’s turnaround time between code change and test is low, your ability to use inductive processes increases, leading to less frustrating time on thinking which derails other creative thought, like the next new feature. But don’t let ideal conditions lead you to lazy thinking. You’d be a fool to eliminate deductive reasoning during debugging entirely. When your system’s turnaround time is high, use deductive thinking while waiting to plan each test to make most use of your next session.
I’ve found the best state is to let your intuition guide you and mix approaches at least enough to avoid wasting too much time on the other one. A debugging session to learn more or writing simple logging or conditional breakpoint code can be a mental break from too much deductive thinking. Some deductive thinking can lead to inspiration when you are mentally taxed by stepping through too much code.
Now go make your stuff work so we have less crappy software to infuriate our lives 🙂