Do you need a debugger?
You might laugh at this question because your answer is of course, who wouldn't! Equally, you might laugh at this question, because your answer is obviously no! You write clean code that reads like well-written prose and the behaviour of your code is clearly documented in automated tests.
If your answer is the latter, good for you! And I mean that. I used to belong to that group. Or at least I wanted to belong there.
For more than ten years, I barely used a debugger. Why? In the first place, I didn’t know how to. Thinking back to those years, I clearly lacked the understanding of fundamental things.
So what did I do?
I did like everyone else, I put "debug statements" along the suspected execution path. Preferably comments that stand out in the logs, or at least that are easily searchable, but not too embarrassing in case I accidentally include them in a pull request.
And that worked fine.
Sometimes someone asked me if I used a debugger. I usually revealed my dirty little secret with a bit of shame. The reaction was always compassionate. Most people I knew barely used the debugger. At least, not for more than just setting a few breakpoints.
After a few years I became familiar with the concepts of clean code and test-driven development.
Clean Code is a philosophy and set of best practices aimed at writing readable, maintainable, and efficient code. It was popularized by Robert C. Martin (Uncle Bob) in his book Clean Code: A Handbook of Agile Software Craftsmanship.
Test-Driven Development (TDD) is a software development approach where you write tests before writing the actual implementation. The core cycle follows these three steps:
Write a failing test – Define a small, testable behavior before writing any code.
Write the minimal code – Implement just enough to make the test pass.
Refactor – Clean up the code while ensuring tests still pass.
Finally, I got an excuse for not using a debugger.
A fair excuse!
I didn’t only get a new excuse, my code indeed became better! I delivered code with more tests and fewer bugs. For the rest, debug statements were usually enough. I also switched teams and I ended up with a more complex codebase. My approach was still enough both for existing bugs and new code I wrote.
In parallel, our ecosystem and documentation also evolved, and it turned out that attaching the debugger to tests was easy. As a result, from time to time I used this opportunity, but barely for the code that I wrote. Given the sometimes long, yet straightforward flows of our codebase, my approach of adding debug statements was enough.
Then I changed jobs and for nearly 2 years, I didn't even deal with features or bugs. I dealt with architectural questions. I didn’t even need debug statements, not to mention a debugger.
But then I joined a new team and I was lucky enough to meet some of the folks in person early on. I sat down to pair with one of the engineers to learn about our codebase, which in less than a minute turned into an embarrassing experience:
- Please just build the desktop app and attach a debugger.
- I don't know how to. Can you show me?
- I don't know how it works with your IDE, but wait... If you don't use a debugger, then how do you debug?
- I don't need to. I use the tests.
I received some rolling eyes for sure.
During the coming months, I realized that no matter how confident or even smug I can be about depending on tests, it's not enough. I used to work with an 800kLOC codebase where I thrived without using a debugger. But with this much smaller codebase, I simply could not survive without using a debugger.
To understand what goes on with bugs reported, debug statements are simply not enough - though they can be useful! Given the architecture and the tons of callbacks, it's often very difficult to figure out where execution flows go.
Instead of throwing debug statements all around, it's much easier to set a breakpoint near the entry point and step through the codebase.
Yet… even this approach cannot do miracles! Sometimes I got completely lost and asked a colleague with extensive experience on the codebase to pair with me. He pointed out some other pieces of code which we dind’t step through, yet the problem lied there. Well, that’s the magic of multithreaded programming.
Don’t forget that debug statements can still be important even if you use a debugger!
There are plenty of people out there who say that they haven’t used a debugger in decades. When I think about them, I have to scratch my head.
Maybe they didn't have to work with such complex systems?
Maybe they were around from the inception of such complex systems and made sure that everything was well documented?
Maybe it’s about something else?
I don't have an answer. But nowadays there is nobody around me who wouldn’t step through code with the debugger regularly.
My advice is to learn how to use the debugger early on. This doesn’t mean that you cannot use debug statements, they come in handy especially with multi-threaded code. It doesn’t mean either that you shouldn’t write tests, you definitely should. Try to rely on them as much as possible!
Feel free to use Test-Driven Development! If done right, it will ease your life! Writing tests, following TDD, using a debugger are not mutually exclusive. They are just different tools in your toolbox. Just like a standard drill and a hammer drill have different uses and purposes, the listed tools will also come in handy in different situations.
Most importantly, don’t be so smug as I was.