In the last couple of posts I’ve talked about a programmer’s greatest enemy: getting stuck. I talked about the various levels of how stuck you can get, from getting stuck while trudging through documentation to getting stuck because you don’t have the resources—passwords, files—that you need to perform a task.
This leads me to my ultimate advice for how to avoid getting stuck and how to get unstuck.
The secret to getting and staying unstuck is to keeping taking steps forward. Simple as that sounds, it is more difficult than it seems, and many programmers never master it.
In order to keep taking steps forward you first have to know what forward is. What are you trying to accomplish? What’s the real task? Tragically, programmers often get stuck on tasks that aren’t really even what they’re supposed to be doing—aren’t even really all that valuable. When you’re chasing down a bug in a tool that’s meant to help you find an optimization for a class that you’re trying to fix in order to solve a user experience complaint, it’s easy to forget that the user experience complaint is your true task, not the bug in the tool.
From what I’ve observed, programmers, in general, tend to obsess. We like to dive down into the nitty-gritty of things. And we tend to get distracted, entranced by the pretty pixels on screen, the annoying obstacles, the intriguing puzzles that programming throws our way. Pretty soon, we’re pulling our hair out over a problem that doesn’t even matter.
This seems to happen to me whenever I use awk. Awk is a small programming language that lets you manipulate text easily within Unix. I often use it to manipulate groups of files. Say, for example, I’ve got a folder full of images. I want to rename all of the images so that each name has an ascending number at the end. Awk is great at this kind of thing. You can write a simple, two-line awk program to read the file names, keep a tally, append the tally number to the filename, and then direct the renaming.
But I don’t know awk that well. So whenever I want to write an awk program, I have to go back to the documentation, study up again a little bit, and then experiment with solutions. Pretty soon I’m debugging some archaic little piece of awk code that won’t quite do what I want. Two hours later I remember, “Oh yeah, I was really just wanting to rename these files. I suppose I could just rename them by hand….”
To avoid getting stuck, you’ve got to remember which way is forward. So before launching off on a long Google search or a marathon debugging session, ask yourself, “What am I really working on today? Is this long, arduous sub-sub-task really essential to what I need to accomplish?” Often, it’s not. And when it’s not, pull up. Get clear, Wedge. You can’t do any more good back there. Go back to focusing on what is truly crucial.
Once you know what forward is, then you can take steps forward. A step forward is an action that actually moves toward resolving the problem. It’s not just a step. It’s a step forward.
One example of this is in debugging. I see lots of programmers go round and around for hours chasing down a single bug. They tell themselves that they’re spending hours because the bug is really difficult. But then a stronger programmer comes along and fixes the bug in minutes. Ouch.
What’s the difference between the junior programmer who spends hours and the master who spends minutes? The difference is that a master debugger knows how to make a strong, positive step every time he or she runs the program.
Many programmers debug by guessing, dabbling with the code, then running the application to see what their dabbling did. Two hundred iterations of dabbling later, they’re still searching for the problem.
The master programer doesn’t dabble. Each step is a clear, targeted, incisive experiment to test a particular hypothesis about what is causing the bug.
The junior programmer performs an O(n) algorithm. Make a random guess. Test it. Make another random guess. Test it. Follow your intuition. Use your instincts. Go with the flow.
This particular kind of flow leads straight to stuck.
The master programmer performs an O(logn) algorithm at worst. At worst, each experiment invalidates half of the possible causes. Some experiments, thoughtfully chosen, eliminate a much larger swath. Therefore even if there are millions of possible causes for the bug—millions of lines of code—the tests must ultimately converge somewhere. And they will do so in a relatively small number of steps.
Always take steps forward, not just around.
Finally, take clear, distinct steps. Programmers often work on intuition, and that’s great. Intuitions about what classes to write, what functions to implement, whether a variable should be an int or an unsigned int or a char. Intuitions are fine, and not every intuition has to be rigorously examined.
But when you’re faced with a big problem that can cost you hours out of your life, you need more than intuition. You need clear, distinct progress. You need discrete steps that enable you to see whether you’re moving forward, backward, or not at all.
When I’m debugging a big bug, for example, I tend to write down each step in the debugging process. Here, for example, are some notes from a bug I fixed earlier this year. They won’t mean a whole lot to you—it was a crash caused by a bug in a smart pointer class I had written—but it will give you a sense for the kinds of notes I take. Each line represents a finding that I documented after performing a round of hypothesizing and testing.
Occurs in Object::Release(). Assertion failure. The problem is that m_nReferences is 0 when the function is called.
The function is called in this case by ObjectSingleton< AssetManager>::~ObjectSingleton(), which nullifies the s_pInstance smart pointer, which calls Release().
The destructor is called, ironically, by Object::Release(), again where this == the asset manager.
So in other words, the asset manager’s release function is called, bringing the ref count to 0, which causes the object to be deleted, which causes the self-pointer to be set to 0, which causes Release() to be called again.
I’m not sure yet whether this data describes the whole problem or if there is more to look at (e.g. why is the first Object::Release() called?). But let’s look at what we have so far….
About 2-3 minutes passed between each of those lines. Those minutes were filled with setting up a test, then quickly compiling and running the code to confirm the results of the test. By writing down my findings after each step, I was made more conscious of the actions I was taking and how to keep moving forward. I had a handy log of where I had been so that I wasn’t tempted to backtrack. And by turning my thoughts into sentences I made sure I knew what I was doing and why.
Intuition is great, but it tends to drift around. Drifting isn’t good for making progress. To avoid getting stuck, I corral my intuition with notes. And it really helps. Even the severe bugs that used to take me days now take at most hours. And it’s a rare bug that’s that severe. When I take discrete steps forward, few bugs take more than a few minutes to find and fix.
I’ve been talking about debugging as an example of taking clear, forward steps. But I find that these ideas apply in any kind of programming task. Whether it’s designing a class, working out a new algorithm, publishing an app to the App Store, or even reading through API documentation, knowing what forward is, and then taking clear steps that forcefully move you forward is the greatest antidote to getting stuck.
If you want to minimize the stuckness in your life as a programmer, make sure you know what “forward” is, then keep taking steps in that direction.
Comments are closed.