I recently re-read The Soul of A New Machine, and one of these days I should write about it in a little more detail – it’s one of those books that I would definitely recommend to anyone in the industry, even if some of the experiences chronicled there aren’t necessarily the best in hindsight. I recall a passage where the team is trying to debug their machine, which struck me as pretty relevant even now.
… The machine has failed. They have their pictures. They pull up their chairs and start studying snapshots of signals.
They are trying to figure out exactly what Gollum is doing when it fails. The pictures and the printed “listing” of the steps in the diagnostic program give them the answer.
Debugging is hard. It involves constructing a mental model of the system’s state, and a model of
what the system is doing. Having your software tell you exactly what
it’s doing is quite useful, that may mean inserting logging
statements, or even inserting a print
statement somewhere in the
code path you’re inspecting.
Sure, having modern tools – debuggers integrated into your development environment, providing watches and breakpoints – gives you a leg-up into understanding, inspecting, and sometimes altering the state of the system at any particular time. I’m not averse to using an IDE to debug the software I work on: I’m not a Luddite.
In a lot of cases though, it’s much simple to insert a print
statement (or a proper logging statement, if that piece of code has
logging enabled) somewhere in the code path you’re debugging, since it
usually has minimal effect on the rest of the system’s state. It’s
also sometimes the quickest way to visualize state between multiple
threads of execution: dump out just enough state into standard output.
Personally, I find it useful to use print
as a first-pass tool when
debugging: I break out the big guns, the debugger, when I need more
granular inspection of state, and if I need to step through execution
more precisely for instance, once I’ve found exactly where exactly to
dissect and probe using print
.
Like I mentioned previously, I’m not averse to using a debugger, but
often times debuggers introduce additional difficulties – separate
build modes, or separate execution modes. So, as a first-pass tool,
the simplicity of print
is a natural complement to the complexity of
the debugger.
However, there are times when you simply can’t use a debugger, or setting up your system for debugging is overkill: maybe the conditions that trigger the bug you’re trying to fix involve running several iterations through data, or waiting several minutes, and stepping through each iteration is tedious. Maybe you’re hunting a concurrency bug, a pretty time-sensitive affair, and the insertion of breakpoints or watches might perturb things enough to make things suddenly work.
In those cases, knowing that you can at least fall back to a much
simpler tool to figuring out the state of the system is
useful. Sometimes, all you really need is print
and patience.
Previously: Failure Modes