choose boring bugs
When there’s more than one way to do things, it can be rewarding to relentlessly polish code, but this sometimes causes trouble later on. Boring code tends to have boring bugs, and since bugs are inevitable, this suggests we should prefer boring code.
r = f(a(b()).c().map(d), g())
This marvel of engineering packs a lot of elegance into a single line.
B = b()
A = a(B)
C = A.c()
D = C.map(d)
G = g()
r = f(D, G)
This unaesthetic lump of exploded code is not nearly so pretty. Why would anybody prefer it?
What happens if something goes wrong? If you debug like a caveman with print statement, it’s a lot easier to insert them between lines. If you have a slightly more sophisticated debugger that supports breakpoints and single stepping, it’s a lot easier to see what’s happening at a high level by stepping over lines and inspecting locals than stepping into each function and hoping to catch the return the value.
But does either approach seem more likely to introduce bugs? Given enough effort, we can debug anything, but we might reasonably desire to minimize bugs to start.
The second approach has an obvious bug. It wastes memory. By assigning return values to local variables, we extend their lifetime to the end of the function, even though we have no further use for them. A short lived memory leak of sorts. Usually one doesn’t even notice such bugs until an exceptionally large message needs processing. Having dealt with a bug like this once, it was fairly easy to resolve once a reproducible test case had been identified. Then the code was massaged to avoid anchoring temporary memory. The good news is that the bug was consistent and reliable. It was easy to watch memory usage grow just by stepping through the code.
The inverse (converse? reverse?) of that bug is unanchored temporaries that get garbage collected prematurely. Somehow between a().b()
the garbage collector runs, eating the return value of a
before it shows up in b
. I’ve dealt with bugs of this nature (either in the VM runtime itself, or in library bindings) three times. The particular circumstances vary, but I’d describe the experience by starting the bidding at harrowing. Never showed up in isolated test cases. No outward signs of trouble until suddenly the process crashed. More than one promising hypothesis eventually disproved, bringing us no closer to a solution.
Of course, this starts trending towards superstition, with cargo culted workarounds for ancient bugs. There’s no guarantee that compiler bugs can be avoided by coding in a particular style. Nevertheless, I’ve noticed compiler and runtime bugs (read: exciting bugs) manifest less frequently in boring code.
Tagged: programming