What is debugging
Most of us are familiar with the doer mindset which is about living in the world of action: writing code, creating music, meeting new people, taking on a new hobby, starting an organization, etc. When something goes wrong though, you need to go from a doer mindset to a debugging mindset.
You’ve probably heard of debugging from programmers when they are working to remove software bugs1. However, debugging is not just a programmer state, but instead a mindset that is distinct from the doer mindset. The debugging mindset is about spending time thinking and analyzing. When something goes wrong, you need to get inqusitive about what’s going on, and often slow down. You need to switch from simply doing, doing, doing to assessing.
Debugging is the process of identifying and resolving bugs. A bug is something in the system that causes it to produce an incorrect output or to behave unexpectedly.
The person debugging can be referred to as the debugger. The objective of the debugger is to figure out what is wrong in the system that is causing it not to function as they want it to.
Here I talk about debugging not just code, but also organizations, people, and yourself.
Why debugging is important
Everyone gets bugs. While novice programmers struggle to resolve syntax errors, experts create system-level errors.
- As a general rule, the more complex the system, the harder the debugging. To some extent, tools and tasks also vary from using a simple compiler to performing lengthy data collection and analysis.
If we want to “go fast and break things”, we need to get good at debugging. The tradeoff for not having a perfect system is bugs. By getting good at debugging, we can maintain the velocity when things do break.
Debugging is also important because it is hard to automate – leaving the onus on you.
- The reason debugging is hard to automate is that the bugs are often human errors. A program generally doesn’t know what you intend it to do. If you have an extra negative symbol or a misplaced if-else, it thinks you intended them.
- Until a program knows your values or intention, it will be hard for debugging to not involve humans.
Programmers are taught a debugging mindset and know which tools are available to them2. However, what tools do we have for debugging organizations, people, and ourselves? Code is produced by people. People make up teams. Teams make up organizations. The debugging mindset has merit and should move up from the bottom.
Debugging in code often has short and clear feedback loops. Once you’ve identified what’s wrong, you can fix it and then rerun the program (or restart the system).
Once you’ve done this, you can check the output or a test case, and see if it produces the desired result immediately.
There are many debugging tactics which include:
- interactive debugging
- log file analysis
- monitoring at the application or system level
- control flow analysis
- memory dumps
- unit testing
- integration testing
You can also use a “debugger” which can be quite a powerful tool (sometimes overkill) for debugging your code.
There are many ways to go about debugging code and many good reads on techniques.
My process for debugging code usually looks like this:
- Describe the bug
- This is asking: “What did you want your code to do, and what is it actually doing?"
- Try the obvious things first.
- Usually, this includes things like verifying that you’ve compiled the right file or executed the right command. Other nightmares that can happen are unsaved files, overloaded computercaches, or interferance from other programs, requiring a restart.
- I also quickly check for common syntax bugs if I have an idea of where the bug may be.
- If I have an error message, I’ll also drop it in Google to see if there are common solutions, eg. from Stack Overflow.
- Reproduce the bug
- This can be a non-trivial task, since specific user environment issues (notorious python), browser versions, or Node versions can make it difficult to reproduce the bug.
- Scope the bug
- This is asking: “Which area of the code is causing the bug?"
- Usually when I debug, I start from the beginning of the execution flow (eg. entry file in a Node app) and continue to where I think the bug is.
- If I already know where the bug is, I remind myself, and make it clear, what assumptions I’m making. Eg. before this method is called, this variable should be non-null. I often double check this with quick print statements.
- If I know what the correct state of the program in this method would look like, I hard code it and rerun the program to check if the subsequent code works.
- I also simplify the input of the program. For example, sometimes a large body in a POST request is causing an issue but can be simplified to a specific key or a value. The process I use for this is simply divide-and-conquer.
- Similarly, in a web app, I pinpoint which user interaction or DOM element is causing the issue.
- Examine the bug area
- Before trying out solutions, I spend some time examining the area where the bug is and make sure I didn’t miss any obvious errors, like misspelled variable names.
- Try out solutions
- Once I’ve found the bug area, I check the logic by explaining it to myself (rubber duck debugging), and then try out solutions, and see how they affect my code.
This 5 step process often goes by quickly, but in more convoluted scenarios, takes a lot more time. Often, if you’re working with libraries (eg. Node or Python packages), you have to dive into the package’s implementation code. In even more complicated cases, some reverse engineering may be involved.
I remember our high school counsellor telling us once how most people think they are different and that their problems are unique, and how that isn’t true. It’s often similar for bugs. This is why the availability of Stack Overflow, Github Issues, and other Q&A forums really help programmers quickly fix bugs.
I didn’t talk about debugging hardware systems here, but tools also exist for them; oscilloscopes, logic analyzers, and circuit emulators are often used to debug electronic hardware as well as low-level software and firmware.
Just like how great programmers are also great software debuggers, great leaders are also great organizational and people debuggers.
Debugging becomes more common as you scale an organization and complexity grows. The tool box for debugging organizations include business knowledge, management tools, processes, and models. In a way, consulting is also debugging. The reason it can be effective, similar to code, is that you may not be able to see the obvious errors in front of you.
Tools like Scrum provide transparency and help managers analyze the system and find bugs – sort of like code debuggers.
Since organizations are often complex due to people and processes, models help with thinking.
One model that I’ve used to debug the non-profit that I run, YouthComputing, and projects I’ve worked on is “The Managing Complex Change Model” (M. Lippitt - 1987):
Since organizations are made up of people, we should look into debugging people.
Leaders debug people, doctors debug people, therapists also debug people, but I argue that so do all of us when we help our friends and give advice to them.
- Similar to how it helps to have someone else look at your code sometimes, it helps to get external debugging on yourself (feedback).
The bugs in people are often in the form of cognitive biases, such as confirmation bias. New babies are in a way like new programmers, in that they don’t have mental models of the world yet, and make, what adults would consider, trivial mistakes (syntax mistakes). As you get older, the bugs become more complex and thus are in the form of biases.
Debugging people is usually harder than code, because there are long feedback loops, and we don’t always have a model for the complicated system.
When friends come to me for advice on a problem, I follow a similar process I described in coding of spending time really understanding them first and gathering information by asking questions; then, proposing solutions.
In coding, an error message usually gives you a place to start – maybe even a filename and line number. But we don’t have this when debugging people. The error may exist deep down in a value or a belief that the person is holding. And this is why you need to get good at debugging yourself so that you can help debug others.
Debugging yourself is crucial for any self improvement. You need to be able to debug your decision-making and your thinking. This process can lead to small but compounding lifestyle improvements.
Debugging ourselves can be quite difficult for beginners, hence why people don’t do it and live with bugs3. Personal change here can be analogues to refactoring.
I would recommend making small changes and then having checks in place. When I’m debugging code, I’ll change a few lines then check my print statements to see if I got the desired result. A similar process can be used for personal change.
Since debugging yourself is about self-introspection and increasing self-awareness, collecting data on yourself can help here. This is why I subscribe to the quantified self community. However, I will say:
All this quantified self stuff makes you feel cool. But it’s one thing to be self-aware and a totally different thing to actually being able to change your behaviour from that data.
Journaling is one of the best ways to debug yourself. The reason journaling works is because it’s similar to rubber duck debugging.
- When you explain your code to the duck (or to a friend), what you think or what you hope your code is actually doing, you get an opportunity to hear yourself through. During this process, you either have an “aha” moment when you realize your code actually doesn’t do what you are saying it’s supposed to do or vice versa, you realize you’re describing it incorrectly altogether.
- The exact same thing happens when you journal your thoughts.
Add tools like RoamResearch, which allow you to surface connections between your vacation thoughts and engineering thoughts, and you have one of the most powerful self debuggers.
You may have heard the quote:
“When debugging, novices insert corrective code; experts remove defective code.” - Richard Pattis
Similarly, novice self debuggers end up taking on new activities or new models when what would often help more is resolving the existing defective models.
Overall, I’ve found debugging to be a lot easier if you really understand the system at a first principle level. This is why novice programmers often struggle with bugs – they don’t have the mental models yet.
Debugging is not just limited to coding. Debugging is a mindset and a skill that is valuable and important to debug organizations, people, and yourself.
Thanks to Curtis Chong, Dhruv Patel, Narayan Subramoniam, Varun Kundra, and Vidhi Patel for reading drafts of this.
While Admiral Grace Hopper and her associates were working on the Mark II computer at Harvard University (1940s), they discovered a moth stuck in a relay. Hopper remarked that they were “debugging” the system and thus the terms “bug” and “debugging” are popularly attributed to her. ↩︎
Varun points out that: “Many bugs only exist if we find them. They are completely subjective, and moreover they only exist in our perception if they cause issues or are clearly not fitting with our mental model for what we want to create (like accidentally using an else when it should have been else if). So almost definitely all major production software has tons of undiscovered bugs and issues and that’s totally fine and acceptable because bugs are only as bad as how perceivable they are. It’s like the human body I guess. We have many mutations in our genome every day (apparently it’s on the order of trillions) but it’s all fine and good and no one cares until you have mutation(s) that causes cancer” ↩︎