Hacker News new | ask | show | jobs
by root_axis 2712 days ago
I think debuggers are not worth the cost for many kinds of debugging scenarios. They're great for stepping through projects you're not really familiar with or in situations where code seems to be in violation of baseline expectations, but fiddling around with breakpoints and watches and other UI particularities of the debugger carries more cognitive overhead than "print debugging". Additionally, I think the problem is better solved by using thoughtful and contextualized logging with appropriate severity levels. Couple this with a TDD approach to development and you'll end up in a workflow that is just faster than stepping through lines of code when you could have your assumptions verified through logs and test assertions.
3 comments

Agreed! Personally I've found that I can find and fix problems much faster with a few print statements than with a debugger--debuggers can make it harder to trace through the full execution of a program.
> debuggers can make it harder to trace through the full execution of a program.

Except you often don't want a full execution, you often just want a partial execution where you suspect the problem arises. I can assure you that a good UI debugger is extremely helpful. Command line debuggers less so.

Yeah, I feel like people who don't like debuggers either don't use IDE's with amazing debuggers, or don't take the time to learn and understand how to use them. You can do powerful things with a debugger in seconds. I still use print statements under other environments. I think 'debug' logging is useful. I prefer logging over "print" any day of the week.
This is why unit tests are so helpful. You only debug the part of the code that's broken. It's a kind of a different way of thinking and people often write what I consider to be "bad" tests -- i.e. tests that don't actually exercise real portions of the code, but rather make tautological statements about interfaces. I spend a considerable amount of time designing my code so that various scenarios are easy to set up. If you find yourself reaching for a fake/mock because it is hard to set up a scenario with real objects, it's an indication of a problem.

This extra work pays off pretty quickly, though. When I have a bug, I find a selection of tests that works with that code, add a couple of printf-equivalents and then rerun the tests. Usually I can spot the error in a couple of minutes. Being an older programmer (I worked professionally for over 10 years before Beck published the first XP book), I'm very comfortable with using a debugger. However since I started doing TDD, I have never once used one. It's just a lot faster to printf debug.

The way I've explained it before is that it's like having an automated debugger. The tests are just code paths that you would dig into if you were debugging. The expectations in the tests are simply watch points. You run the code and look at the results, only you don't have to single step it -- it just runs in a couple of seconds and gives you the results.

You may think that the overhead of writing tests would be higher than the amount saved with debugging and if it were only debugging, I think that would be true. However, one of the things I've found over the years is that I'm actually dramatically faster writing code with tests compared to writing it without tests (keep in mind that I've got nearly 20 years of TDD experience -- yes... I started that early on). I'm pretty good at it.

The main advantage is that when you are writing code without tests, usually you sketch together a solution and then you run the app and see if it works. Sometimes it does pretty much what you want, but usually you discover some problems. You use a debugger, or you just modify the code and see what happens. Depending on the system, you often have to get out of the context of what you are doing, re-run the app, enter a whole bunch of information, etc, etc. It takes time. Debuggers that can update information on the fly are great time savers, but you still have to do a lot of contextual work.

It takes me some extra time to write tests, but running them is super quick (as long as you aren't writing bad tests). Usually I insist that I can run the relevant tests in less than 2 seconds. Ideally I like the entire suite to run in less than a minute, though convincing my peers to adhere to these numbers is often difficult. That 2 seconds is important, though. It's the amount of time it takes your brain to notice that something is taking a long time. If it's less than 2 seconds (and run whenever you save the file), usually you will barely notice it.

In that way, I've got better focus and can stay in the zone of the code, rather than repeatedly setting up my manual testing and looking at what it is doing. Overall, it's a pretty big productivity improvement for me. YMMV.

I really like the idea of tests as materialized debugging sessions.
Any multi-instance, multithread-capable language must have an appropriate debugger.

> [src/main.rs:4] x = 5

How to differentiate the value of 'x' for a given thread/instance/whatever? Don't add the info by hand to the debug message.

edit: typo

Using a debugger for testing multi-threaded code is particularly painful. Tests and logs are especially superior in this case because you can make complex assertions that capture emergent behavior of a multi-threaded application. Pausing threads to step through them can often make it harder to observe the behavior you might expect to see when multiple threads are working together in real time.
> I think debuggers are not worth the cost for many kinds of debugging scenarios.

That just means your debugger has a prohibitively high cost to use. If it takes more than 2 clicks to launch a full debugging session of your project, you need a new IDE.

The cost isn't in starting the debugger, it's getting into what you think is the right point in the execution of the program, and then stepping through one step at a time until you notice something is off.

With printf debugging, you can put print statements everywhere you think something might be wrong, run the program, and quickly scan through the log to see if anything doesn't match your expectations.

Most debuggers have conditional breakpoints and print-breakpoints, no need to step through line by line manually. If something strange happens just right-click and insert a print as the program is running.

With print-debugging if you find a bug you have to stop your app, insert print lines, recompile, redeploy, relaunch, click through your app to reach buggy location and then scan through log. This really feels like stone-age once you've ever used a IDE.