I just spent the better part of a week debugging a problem in a piece of code that required the programmer—me—to rerun the same 100 lines of code, adding more and more debugging statements until the bug finally revealed itself. Since this particular code was in a serverless environment, a traditional debugger was not an option. Instead, I had to fire off the function, over and over and over and over again. Eventually, my eyes would glaze over, and I would get up, walk around my home office, and sit down to start all over again. I somehow cannot believe that this is the right way to attack a problem like this, but when I asked other members of my team for help, they told me that tedium was to be expected. Clearly that cannot be so, can it?
Teed Off at Tedium
So many clichés relate to this topic, I feel I could just quote them all here and call it a day—but that seems lazy and unfair.
Most of us understand computers are great at doing the same thing over and over and over and over again. On bad days, KV feels this is the only thing computers are actually good for and he is trapped inside a surreal, Dr. Seuss nightmare landscape, in which strangely drawn, brightly colored characters spew nonsense while causing all my code to crash in ways that are clearly not possible.
Debugging code is often iterative, and iterative processes fall into the over-and-over category. You run the function, it gives the wrong output, you scratch your head and think, "Oh, it must be X," and you change whatever X is and run it again, and get another wrong result. Then you think, "Oh, it's not X, it's Y," and you change X back and then try Y, run the code again, and … . Down this path lies madness, with or without microdosing, but it is a well-worn path in our industry that we have all walked many times.
There are several strategies for leaving this path of madness.
You have already alluded to the first way off the mad path, and that is to use a debugger. Unfortunately, there are too few good debuggers in the world, and programmers have rarely been inclined to improve this situation, because—as any venture capitalist will tell you—there is very little money to be made in software tooling.
One might think that some of us who work in the industry would work on better debuggers just for our own sanity, but as KV has pointed out before, programmers are optimists: We always think that this time we have got it right and we can write more code rather than debugging what is in front of us now. This is not to say that there are no debuggers; there are, and some of them even work—a few even work well—but those that do are few and far between.
KV continues to grind his teeth as he sees code loaded with debugging statements that would be totally unnecessary if the programmers who wrote the code could be both confident in and proficient with their debuggers. If one is lucky enough to have access to a good debugger, one should give extreme thanks to whatever they normally give thanks to and use the damn thing! When KV was a much younger programmer, he worked for a boss who insisted all code be run in the debugger first—and when KV can, that is what he does. If the program works well in the debugger, then you can try it without the training wheels and see how it goes.
Another way off the mad path, and the one that is probably most relevant to your debugger-less environment, is to add some form of assertion to each and every line of the failing function. As the programmer, you certainly should know what the function is supposed to do, so assert that! A good language would let you do this and then let you hide the assertions from view, but I have rarely seen a language do that.
More often, there is an assertion library or 10 of them (because people are so lazy, they will not do a search for what they need before just hacking up their own) that you can use to do this. Do not go down the iterative path to madness by adding assertions only to the things you think are causing the bug. Assert every blasted line in the function and then—if the stars align—you will find the bug on the next run of the code, because you are checking every bit of the function line by line.
Finally, if you are fighting with a function that fails only one out of n times, you will have to take advantage of the fact that computers really are good at doing things over and over and automate the execution of the failing function so that you can catch the failure. Step up a level, write a loop, ensure it can catch the failure, and then go back.
Wanton Acts of Debuggery
Debugging on Live Systems
The Digital Library is published by the Association for Computing Machinery. Copyright © 2022 ACM, Inc.
No entries found