Ask HN: How do you debug your code?

89 points by s4chin ↗ HN
I have read a few methods of debugging code. What is the method that you use to efficiently do so?

149 comments

[ 4.5 ms ] story [ 168 ms ] thread
I work with mainly Javascript. I like to log everything, so I rarely need to use debuggers. When I have to, the Chrome developer is all you need. You can even use it to debug Node stuff
This is a great question and I am curious as to how others do it as well. I primarily code in nodejs and usually I just add console logs to give me visibility, however I feel like this is primitive and time consuming.

I have played around with node-inspector, but I have found that it's awfully slow, particularly with really large arrays or objects. So I eventually just abandoned it. It seems like a good idea, and might be worth revisiting in the future.

I can typically just add print statements and figure out the problem in less time than it would take to setup and attach a debugger. But occasionally, I will use the PyCharm debugger with Python. And even more occasionally, I'll use an assembly level debugger (especially if I'm interested in the system calls, and it is not convenient to attach a Python debugger).
>>I can typically just add print statements and figure out the problem in less time than it would take to setup and attach a debugger<<

this 99% of the time

Usually you only have to set up the debugger once for each project. Not every bug.

I use print statements > 50% of the time, but certain problems are better suited to the debugger. Especially if its code that I did not write.

Relentless application of logic.

Probably says something about my coding practices that I've gotten good at it.

for python simply "print"
For Java, eclipse
Python's pudb is extremely useful if you don't use a full blown IDE. It gives you an ncurses-based GUI debugger inside any Python app.

Snapshot debuggers like Google Cloud Debugger are probably the way forward. Alas it doesn't support Python 3 yet.

I use a lot of pudb and a lot of print('\n\n~~~!!!VARIABLE', variable)
Good debugging is a lot like good science. You start with a firm understanding of the system, form a hypothesis about what could be wrong with it, test your hypothesis, and then repeat. The stronger your initial understanding of the system, the faster you can debug things.

As a Python/JS developer a few print/console.log statements are usually all it takes for me to figure out what's wrong with something. For more thorny situations there's always PDB/chrome dev tools.

At the end of the day, the people who are the best at debugging things aren't that way because of the tools they use. They're the best because they can clearly visualize the system, how data is flowing through it, and where potential problems might arise. Intuition from dealing with other similar problems also helps.

I wrote a pretty long comment about the tools I use for debugging JS, but yeah, in the end the guy who's been here twice as long as me and wrote most of the system can usually debug things in a fraction of the time because he can guess what's causing a problem just from reading a description of the bug.

But I can't give that to people through a comment on HN, so I stuck to tools.

I'm the same, a few console.logs and I've usually found the problem. Meanwhile, others are attaching debuggers to step through code and try to understand it and I've already been done for 30 minutes and got a cup of tea and some crumpets.

Debugging for me is about my using my brain to step through code, not some fancy IDE that stops me from thinking. It wasn't always so easy though, but the first step is to stop using big tools to help you.

Uh, this might be true for simple programs, but please try and debug complex race conditions with console.logs. It doesn't work very well.
How do you use a debugger when your application is distributed across dozens of machines?
For threaded langs that might be true - but in js there are no race conditions in that sense, so console.logs are usually fine for debugging "out-of-order" stuff as well. This is ofc opinionated and based on preference, so YMMV.
Debugging complex race conditions isn't necessarily easy using a debugger either, since halting the passage of time has a tendency to make them go away. Why limit your toolbox? Logging statements and debuggers are both useful methodologies for resolving problems, but in the end, it's as the GP says -- your success is going to depend a lot more on your conceptual understanding of the issue and the application than on the debugging technology.
So you litter your code with console.logs?
You remove them when you've figured out what's wrong and fixed it
Or your build tooling can remove them automatically in production mode.
While I agree with your assertion that debugging should ultimately happen in the head instead if in the IDE, debuggers can still be useful for console.log folks like us. Because they show you the values of all the variables in scope, a good debugger is basically the ultimate console.log, and can sometimes speed up the diagnosis process by allowing you to check multiple hypotheses in a single program execution.

Also, sometimes even just using console.log can cause bugs to appear or disappear. I recently encountered a bug which was almost impossible to diagnose with console.log, because the string returned by the .toString() call didn't correspond to the real object's actual properties. Of course, this is a rare case, but it highlights the benefit of trying different tools!

But how is writing a console.log statement quicker than simply clicking on a line number to set a break point and then inspecting what ever you want?
I'm not even sure if this is worth replying, but I for one use debugger way more than console.log. I also use debugger to try out multiple functions rather than saving code and waiting for a refresh each function I want to test.
You two probably work on dramatically different types of code.
>... is a lot like good science. You start with a firm understanding of the system, form a hypothesis about what could be wrong with it, test your hypothesis, and then repeat. The stronger your initial understanding of the system, the faster you can debug things.

>They're the best because they can clearly visualize the system, how data is flowing through it, and where potential problems might arise

This also applies very well to appsec/vulnerability finding.

One minor benefit of using a "real debugger" is that it makes it easier to stumble across situations or flows that you didn't expect just from a direct-reading of the code.

Granted, often those moments are cases where the code is working correctly but you misunderstood or misremembered things, but the fact that you identified (and resolved) the disconnect is valuable, particularly if you're doing a deep-dive to figure out a nearby problem.

In Python I use a small collection of built-ins frequently for debugging:

For any object foo:

    type(foo)     # show an object's type
    dir(foo)      # list an object's methods
    help(foo)     # pydoc
    id(foo)       # show an object's memory address
    foo.__dict__  # show an object's internal data structure
And I have a snippet triggered by "pdb<tab>" for pdb:

    import pdb ; pdb.set_trace()
WebStorm has a pretty good debugger. Also if you choose to use other JetBrains product it will feel very familiar.

I've never read about specific methods on how to do this "by the book". I'm usually doing ( for NodeJS ) :

1. Set a breakpoint inside my NodeJS 2-3 lines before the exception that I got.

2. Run debug mode

3. Do what I need in order to reach the breakpoint

4. Analyze the variables inside ( via watch ) or run code in that function ( via console )

Helps a lot more than `console.log(some_var_before_exception);` :D

Are you sure it helps more than console logging?

Would you be able to debug something without these tools?

Do you think potentially these tools abstract some of the work away from you?

Genuine questions, just interested

Let's see what happens next when you debug via logging :

When you actually have an exception and you put a `console.log(some_var);` in your code and then you reach it. Your next step is usually to fix your code and run it again. This time you see the corrected value in your log. Easy fix.

If the problem, though, is caused by some other part of the code, then you need to move the `console.log` statement up in your callers' functions until you see where the problem is. That sucks.

Now let's see what happens when you use a debugger :

When you reach that point, you check your variable, see that there is something wrong with it, check the caller, step inside the caller and manipulate the code, until you see everything is where it is supposed to be. One shot ( run ) fixes all.

Now straight to the point :

> Are you sure it helps more than console logging?

Until you get used to the debugger, you will ask yourself this question. Console logging is bad for recursive functions, loops, huge variables, etc. How many times have you write : `console.log(var);` and then one run later `console.log(var['some property'])` and even go deep?

> Would you be able to debug something without these tools?

Sure :) Sometimes I don't use it at all. e.g. when I'm doing a quick thing and use Sublime Text instead of WebStorm.

> Do you think potentially these tools abstract some of the work away from you?

I haven't run any benchmarks. Maybe I'm a bit slower with a debugger for 90% of the exceptions, but there is always an exception, hard to be console.logged, that compensates all that time.

> Until you get used to the debugger, you will ask yourself this question.

As a counterpoint to this, I was pretty much exclusively a debugger guy for the first 10-12 years of my career. Now I'm pretty much exclusively a logging kind of guy. Familiarity with a debugger has nothing to do with it.

> Console logging is bad for recursive functions, loops, huge variables, etc.

And debuggers are bad for focus/blur event handlers in UI code because they change the focus state of the thing you're trying to debug.

Ultimately, neither of them is perfect, not all problems are alike, one is not an objectively better tool than the other. They both have merits.

I'm sorta middle of the road.

I have a strong preference for having a debugger so I can set break points and testing assumptions in a REPL at that point in the code though.

I'd say it's pretty much required to do PHP (and especially Wordpress).

On that note, WP Core team, would it kill you to extend some basic array functions to WP_Query objects?

I think the print/logging tactic get a little bit of a bad-reputation mainly because of the minority out there who do it because it's the only approach they know how to take.
For PHP: xdebug. For JavaScript: Chrome debug tools.

Logging doesn't give you anywhere near the power the a good debugger does.

Like the other commenter, I mainly use JS. The Chrome Development Tools are all I need. Breakpoints, conditional breakpoints, and walking through the call stack are the main tools. Adding watch expressions is pretty handy sometimes so I can quickly run through a loop with a breakpoint, just hitting continue repeatedly, and watch the values change while just hitting F8.

Next most important thing is the network requests tab-- seeing what data is coming back from the server, if any, is indispensable.

If I'm debugging minified code that we haven't set up source maps for yet, I'll find a string literal in the source to match up the minified code to unminified code so I can see what I'm doing anyway by looking back and forth.

When I have to reproduce a bug, I often use the FormFiller extension for Chrome to quickly navigate our forms without having to fill them out.

I use EditThisCookie (another Chrome extension) to modify or view the cookie as I work, or to delete it to start a session from scratch. I don't like Incognito mode because I don't have my extensions and it doesn't maintain breakpoints when the tab is closed and reopened.

With regards to the call stack, being able to black-box certain scripts is awesome. You can right click a script in the sources explorer on the left side of the DevTools and black-box it, which will stop it showing up in the call stack. No more jQuery / Angular / Underscore calls cluttering my callstack!

What else...whenever I'm debugging CSS I always just do it in DevTools so I can see changes on the fly to figure out the problem.

I also used to use the handy "debugger" statement now and then, although I use it less and less these days since it's the same as a breakpoint but takes slightly more effort. Mostly only use it when I already have the code open in my editor and don't feel like manually finding that point in execution in the DevTools....it's kind of like "go-to this line."

Ctrl+P in sources of DevTools allows fuzzy search among files. Which is awesome.

There have been times I've used the XHR breakpoints, Event Listener breakpoints, and DOM breakpoints, but it's really rare for me. Definitely though there are cases where I'm not sure where an event is originating from and these have very much come in handy at those times. Underneath those on the right of the sources you can also see a total list of all active event listeners, which is also nice.

I'll add more thoughts if I think of anything else...I guess I'm mostly talking about my tools here. With regards to my thought process, that's more complex...hmm. I guess I try to figure out what the desired behavior is, try to see what the actual behavior is and how consistent it is, then see if I can find the code that controls that part of the application. If I don't already know, I Inspect Element on the page, find an element id or something, then look it up in our code and follow the trail to whatever's controlling the page and relevant behavior. From there it's just careful examination of the logic to find the flaw, using all the tools above.

(comment deleted)
I occasionally have to use incognito to test without cookies (Recaptcha uses my Google login for instance) so just wanted to mention that you can selectively enable extensions in Incognito mode. Go to settings->extensions and click on 'Allow in Incognito' for whichever extensions you need.
Thank you for mentioning FormFiller. I had never even considered using an extension for this. I usually just quickly put garbage in and tab my way through the form. This will save me some time! Aside from that my workflow is actually incredibly similar to yours.

As a side note: I find the "debugger" statement does not always trigger properly, making me manually set a breakpoint anyway.

As a python dev, I either debug it in PyCharm, or I run it in the shell (especially if its a situation only in production).
Doesn't have to be rocket science really, (unless that is what you are doing). For web development, it's code, refresh browser, repeat. With a tail -f on the application/server logs usually does the trick.

But I think IDE's can't beat real-time debuggers, like console.log or Ruby's bettererrors gem, having full real-time access to application logic/code at the spot, you can't beat that.

console.log and dev tools all the way!
I'm using debug logging ( that isn't deleted ) more and more as I code. It's useful not only for solving the current problem you're experiencing but also helps the next person understand what the code is/isn't doing, adding to its self documenting nature.

Debuggers are great, but the knowledge gained by using them to solve a problem is completely lost once that close button has been pressed.

Also if I'm having to use a debugger to work out what's going on, usually it's a good sign my code is overly complicated...

I still use a lot of printfs, but I make heavy use of the debugger. In general, my strategy for difficult bugs is to find a point A where you know the code is working correctly, find point B where you know it's broken and can catch it in the debugger or log, and step forwards from A and backwards from B until you find the bug in the middle.

When debugging difficult, intermittent problems (e.g. non-repro crashes) my strategy is to keep a log of when it occurs, add lots of diagnostics and asserts around where I think the problem is, until hopefully I can catch it in the debugger or notice a pattern.

90% of the work of debugging is creating a quickly reproducible test case. Once you have that you can usually solve it.

IntelliJ has great debugging integration with JS / PHP / Scala (the tools I use currently).

Set a breakpoint in the code, refresh the browser, and all the variables in the scope will be annotated with their value at break time.

This is really what you're after when you're println debugging - it has the advantage of showing you everything in a minimally intrusive way which is helpful when you don't know what you're looking for exactly.

If you're doing something like play in scala you usually need the program running in the Intellij context, but it can also hook up to the debugging port of a JVM or to the PHP interpreter running inside of a VM.

IntelliJ is a pretty complete suite of a tools, a pleasure to use (has VIM mode too :P)

It always helps if you can see the code run line by line. For instance in Django, I use ipdb (import ipdb; ipdb.set_trace()) to check the state of variables and check where they are not what they were supposed to be. Similarly in JS, by adding break points. For compiled languages it has been a tough job, however its more or less the same.
I use my eyes.

If I can narrow it down to what line, or even file, is throwing an error I just take a few minutes, read all the code and all the code of branching methods, and then can narrow it down to a single line.

From there it is actually developing a fix. As you mess around with more and more languages, you will notice that most compilers lend something far from a helping hand.

This only works, and I will stress this, for programs under 1 million lines. Past that mark, you need to do some extra steps.

When I debug one million line projects, I narrow it down to a file. I pull out the code from the file, and I mock all of the methods that file calls (This gets REALLY hard with networked code. Trust me). From this small subset, I slowly break the functionality of the external method until I resemble the error being made in the main project. From that I now know the method(s) that are actually causing the problem.

But, there is one thing that this makes an assumption about: your compiler is working.

Put blatantly, they're crap.

Usually they won't show you the correct file causing the error or they will not generate a helpful error. Runtime errors are even worse.

The best thing to do is avoid making the tricky errors. Make unit tests, using fuzzing tests, and well made documentation.

Documentation alone, that details all of the possible output and input states of the function will save you days on some bugs.

In Java, the @Nullable tag is a godsend. Use these features, they WILL help.

If you do tests, fuzzing, and documentation.

Using your brain and some things to make your brain's job easier will make you faster at debugging then any debugger like your buds GDB/DDD setup.

I use vim as my editor, so I tend to use print statements.

I do use the perl debugger though when I am writing perl and when there is a need. The benefit to this debugger is that it comes built into the language.

GDB and some fprintf calls cut it most of the time. The worst kind of bug is when the program segfaults into oblivion and works fine under GDB but in 99% of the cases that's related to uninitialized memory, which might be a bit hard to find but is quite simple to fix (it's the worst because it sometimes takes a while to figure out where you forgot to initialize variables when it's unrelated to a new change and happened to just not occur during previous changes).
With memory you can usually get a good idea of the problem with valgrind.

It's actually my first line of defense, and then after that printf statements and then gdb + frama-c.

One really nice tool is

  frama-c -cg <files>
which generates a call graph written in dot.
- If I know what's the problem just by looking at the bug, I go fix it.

- If I do not know what's the problem, I do everything in my power to reproduce the bug and maybe write a test (as small as possible) that triggers the bug. I enable logging or write a special log function to track relevant state in case the bug is rare.

- Once I know what triggers the bug, I should know the general direction of where in the code it is. I start printing various variables and parameters in case it's a low-hanging fruit like wrong sign or stuff like that.

- If I do not succeed with that, I comment out half of the code and look if the bug persists. If it does, then I know it's in the other half. If it does not, then I know it's in this half. I proceed with this binary search until I am down to 1 statement, which takes a logarithmic amount of steps. I found the bug. I fix it. (This does not work if the bug is in two places or if the bug is a bad memory operation that triggers a bug later)

- Real debuggers like valgrind are rarely necessary if you're familiar enough with the program. In fact, unless you're dealing with the hairiest of hairy memory problems and heisenbugs, you probably do not need a debugger at all. Debuggers are useful to debug e.g. generated assembly when you write a compiler.

Just a note that Valgrind is not usually considered a debugger, AFAIK. Also, If you are coding in C or C++, I highly recommend running your programs through Valgrind.
Thank you for pointing that out, Ono-Sendai. You're right, Valgrind is not a debugger. Can you tell me more about why you recommend running my C and C++ programs through Valgrind? The last time I needed Valgrind was a couple of years ago.
I recommend running programs through Valgrind, because it's great at finding bugs :) It will find many things like memory leaks, uninitialised variables, writes past end of arrays etc.. that aren't easy to find otherwise.
Print statements, or logging. Using a debugger is an extremely rare occurance, once a year at most.