The right mindset for debugging
Programming is hard. Translating a real world problem into computer code requires simultaneous understanding of 1) the real-world problem to be solved and 2) how the computer operates on the problem. For the latter, it means understanding the environment the program will execute in, knowledge of how data is represented in the program, and how the programming language operates on the data. Bugs are inevitable.
From my own development experience, plus working with interns as well as kids taking their first steps with programming, here are some tips I’ve come up with that help create the right mindset for working through and fixing your bugs. These are high-level directions on how to think about learning how to program, rather than nitty gritty technical details specific to one language or environment.
A note on analogies
Most of these tips are presented with the help of analogies. Analogies are a helpful tool to bring some concrete understanding into the abstract world of software. Relating software concepts to cars or buildings in a tradition I tend to maintain when talking about programming concepts. Even though I know little about how the details of an internal combustion engine or how a skyscraper is constructed, it can be easier to picture these familiar everyday items than some abstract software concepts.
Tip 0: Learn how to learn
Your first goal should be to understand how to learn. You need to understand how to take in new information, ensure you have a solid understanding, and remember it.
One thing to always keep in mind is programming (as with any discipline) builds on previously learned concepts. This is the old skyscraper analogy. Every floor is laid upon previous floors. If you don’t start with a solid foundation, things will crumble well before you reach the 50th floor. For example, when learning math, which has structured rules just like programming, it is critical to understand the basics before applying them to other sets of problems. You are going to continuously make errors when solving calculus problems if you are confused about how to apply the order of operations rules.
Now, “learn the basics first” might seem like a patently obvious suggestion, but experience has shown that a lot of programming bugs happen because of a misunderstanding of the basics. If the foundation of your skyscraper keeps crumbling, preventing you from building higher, it might be worth the time to go back and fix up properly rather than patching over the cracks.
An excellent book to read on the subject of “learning how to learn” is A Mind For Numbers by Barbara Oakley. Based on the book, there is the Coursera course Learning How to Learn: Powerful mental tools to help you master tough subjects. The techniques discussed in the book and course are fairly straightforward, but valuable to keep in mind.
Tip 1: Embrace your bugs
When learning to program, a bug can be a wonderful thing. And that’s even more wonderful, because you will encounter a lot of them.
A bug is when your program acts in a different way from your expectations. So how can that be a good thing? Well, with a little shift in your mindset, encountering a bug might be a the perfect learning opportunity. Your mind is primed with a good understanding of the context for the code under examination, your expectations about what the code should do, and the results of what the code is actually doing. This is the perfect “example problem”. As you methodically debug the code and eventually solve the problem, you are in a perfect state of mind to gain a deep understanding of a new concept or to correct a misunderstanding. You are going to learn something, and because of the effort required, you are much more likely to remember the concept.
Similarly, if you are reading a book and come across a term you are not familiar with, it’s the perfect time to look up the definition. Your brain realized there was a gap in your knowledge, and is eager to fill that gap.
A quick tip on debugging: a useful technique is rubber duck debugging. The main idea is to take a step back from your program and explain the execution of the code, line by line, to either to an inanimate object or another person. This change in perspective can help you think through the problem clearly and you may discover the cause of the bug. We often get caught up in repeated patterns and the change in perspective can help us see things we would otherwise skip over.
So if your goal is to learn, embrace your bugs and the learning opportunities they provide.
Tip 2: Break it down
Let’s say you have a flashlight that just won’t turn on. It worked just the other day, but today you press the switch and nothing happens. Even the least handy among us can approach this problem with a few potential fixes:
- Smack flashlight and try again.
- Open it up and ensure the battery is inserted correctly.
- Replace the battery with a fresh new one and try again.
- Unscrew the lightbulb and replace it.
- Swap the battery (and later, the bulb) into a known-working flashlight to try and isolate the problem.
- Disassemble the electronics and start probing with a multimeter to look for the loose connection.
Now let’s say you encounter a bug in your program. What steps do you take to fix it? Too many times I’ve seen developers try and debug their code with the “smack it and try again” technique. They leave the code intact, maybe putting in a silly little print statement, and run it again over and over getting the same erroneous result and try to trace through the code in their mental computer to find where it went wrong. Just like when you read a sentence over and over and don’t notice the the glaring error (such as repeating the word “the”), your mind gets stuck in a pattern and will be unlikely to spot the error. Next, you try the rubber duck debugging technique, but still can’t spot the error.
As with the flashlight, you need to break the problem into pieces and see if each piece works outside of the context of the whole problem. Take out the battery and bulb and swap them independently into a known working device. Step through the circuit with a multimeter (aka step-by-step debugging) if need be. And if you can’t find the problem when broken down into pieces, check for incompatibilities when reassembling. Look up the documentation for the battery and bulb and make sure they are compatible voltages. Ensure the form factor of the battery is compatible with the flashlight housing and that good contact is made when reassembled. Check your environment and ensure you aren’t trying to operate the flashlight in temperatures below the battery’s specification.
The point is, don’t be afraid of getting your hands dirty. The principle of breaking down and isolating variables to solve a problem is of course intuitive, but we all have to be reminded of this from time to time. Our brains naturally like being lazy, and sometimes they need a little smack themselves to turn the light on.
Tip 3: Working code vs. a working understanding
You come into your kitchen in the middle of the night for a quick snack. Upon turning on the lights you see what appears to be a cockroach scurry away and hide in the corner. Do you:
- Turn off the light, pretend you didn't see it, and go back to bed.
- Note that it isn't harming you at the moment, accept that cockroaches are a part of life, but vow to do something if the number of cockroaches increases to the point where you are forced to acknowledge that there is a problem.
- Grab a shoe and go after it.
Now think about how this applies to programming bugs. How do you typically handle them? If you are working as a professional developer, you may have a good pragmatic reason to choose options a or b. If your goal is to learn, you should consider option c. Grab your shoe and go after that bug!
Consider anything that makes you go “hmm” a bug.
- The code does what you want, but you are not sure why?hmm
- Mostly working code, but the output isn’t formatted quite right? hmm
- Code works fine, but is noticeably slower than you think it should be? hmm
- Everything works fine and fast, but there is a strange log output that you don’t understand? hmm
All of these are like the “check engine” light on your car. It could be a trivial problem like the gas cap isn’t tight enough. Or it could be that your car is leaking oil and will seize up within minutes. It seems responsible to check this out in the garage rather than take the vehicle on the highway. Similarly, it’s a good time to quash these hmm moments in development, rather than seeing if everything holds up in production.
A frequent example of having a poor understanding of the code one supposedly wrote is when one copies a Stack Overflow answer verbatim without spending the time to understand what the code is doing and how it solves the particular problem.
There is an old joke about this phenomenon involving a newlywed couple and a brisket.
The bride is trying her hand at making her mother's brisket recipe. She first cuts the ends of the roast, puts it in the pan, and out comes a delicious looking roast. The husband enjoys it immensely, but asks his wife "But why did you cut off the ends?". The wife says "I don't know, but that's the way my mother always made it".
Next week they are visiting her mother for dinner and notice that she cuts off the ends of the roast. The bride, now curious about this step, asks "Why do you cut off the ends?". Her mother answers "That's the only way it will fit in my pan!"
Some reasons Stack Overflow answers might need tweaking:
- Your problem might have similarities with the Stack Overflow problem, but the details may be different, meaning the given solution might not apply in your case.
- The answers may be out of date. If the answer is given for an older version of a language or library, the solution may not work anymore, or there might be a better way to accomplish the same goal.
- The answer may just be simply wrong or dangerous. The upvote/downvote count is helpful for the community to promote ‘good’ answers to the top, but it isn’t foolproof. Especially for obscure answers, one or two upvotes isn’t enough to blindly trust that the commenter is giving proper advice.
The point is, especially when learning how to code, you should understand every line of code that sits alongside your own code at the same level of abstraction. Would it be wise for an author to use a thesaurus to find a fancy word and use it even if they are unsure if its use is appropriate in the context? No, that would be insensate.
Tip 4: Give yourself a break
Frustration is not your friend. If you are stuck in traffic, angrily honking your horn is unlikely to clear the way ahead. If you are stuck on a programming bug, sometimes the best way forward is to take a rest.
Often giving yourself a rest and coming back at the problem brings a new perspective and the solution may reveal itself. Or we may realize that the problem we were trying to solve was the wrong one to begin with.
The aforementioned book A Mind For Numbers discusses two modes of thinking:
- The focused mode of thinking involves directing our attention intensely at a problem. Often this is the type of attention we mean when using the general term “thinking”. The focused mode may be necessary to work through some difficult problems, but it tends to work best on familiar problems or tightly bound problems.
- The diffuse mode of thinking is a more freewheeling way of letting ideas bounce around in our head, without straining to focus the thoughts on a bounded problem. This mode of thinking is more effective at coming up with creative solutions and helps us reach beyond the familiar patterns we can get stuck in with focused thinking.
We can only work in one of these modes at a time. So if you are getting frustrated at getting nowhere with a focused thought process, try letting your diffuse mode take a stab at the problem. Sleep on it, go for a walk, direct your attention somewhere else. Just stop banging your head against the wall. The key is to be mindful of this decision and process. If you recognize you are spinning your wheels, ease up on the gas and you might get some traction.
Conclusion
Our brains don’t work the same way a computer processor does, and bridging that gap is what makes computer programming hard. Before learning anything, it helps to understand how our brains take in new information and remember it. Coming at problems with an open mind can make it more pleasant when the computer is not doing what you expect, and it might make it easier to solve the problem if we aren’t clouded by frustration.
So, while computer programming can be challenging, the right mindset can help catalyze your learning and make you more effective overall.