We're a place where coders share, stay up-to-date and grow their careers.
Oh man. One of my favorite topics :D Imma assume "google and sob over stack overflow" is already solidly in everyone's wheelhouses.
Take a break. An actual one. Probably the most important step. If you're like me, you probably need to actually go to a friend's house, or at the very least, physically remove yourself from the ability to google and code more and perpetuating the perseveration that goes with this stuff. I'm really, really bad at this myself, and I think it's the hardest step.
Another big one: when did it last work? Are you sure? Did that last point deal with the part that isn't working now?
Talk to yourself. Out loud, and in comments. Remember, this doesn't have to be pristine--you can clean up the code later. Comment EVERYTHING. So many comments as you think "out loud" and walk yourself through. I don't mean this as a "God, you should have commented more" scenario--I mean as you're debugging, write down what you're testing. Why? What's weird? What's wrong? What's so bizarre about it? What have you tried? What haven't you? Write down EVERYTHING. Save a separate copy of the file to fill with this stuff--when I am HardCore tracking down a bug, I end up with mostly comments and code very sparsely interspersed. (Code-folding is a beautiful thing, because otherwise it's impossible to read then.) My debugging notes--I've got a DOOZY of one right now, but at least it's still at the exhilarating stage (side project, so that's a luxury)--often, at this point in the game, read like a study. I legit start to write down hypotheses, what I can test, etc.
Talk to people about it. This is common advice, but I think it's hard to know how to do it. For something really tricky, I find it helps to get a couple people--ideally of different levels.
-- Find one person with zero coding knowledge to listen, because they might make suggestions you would never dream of--and in explaining why those can't happen, you might realize that oh hey, they actually could, or in explaining why they really can't, you learn more about what's going on.
-- Find someone who has the same or similar amounts of knowledge as you. Similar frame of reference helps. I had a classmate from a semester behind me watch me code--I had some crazy-ass convoluted thing to switch casing and names for some columns of a table--and they went "Why wouldn't column aliases work?" and you know, they totally did, I had just totally forgotten they existed. Waaaay easier, too.
-- Find someone who knows more than you! Even if it's in a different language, or who just has more experience.
Corollary to talking to other coders--this one makes me crazy and makes the people I help crazy when I make them do it, because it's so annoying, but--read what the code says, out loud, line by line. Not what you THINK it is doing, or what it SHOULD be doing, just read EXACTLY what is in the code. Don't interrupt yourself to explain more--if you start having to genuinely justify it, that's prob not a great sign--and ask them not to interrupt you, unless one of you suddenly Realizes it. Human brains are SO GOOD at filling in the gaps and that is so fab and awesome and dammit, the computer is doing what you tell it to every single goddamn time, even if we don't reaaally want it to be doing that. I have been telling students in a class I'm helping out in all week when they feel dumb about making an error that I spent a good hour or so this week minimum trying to figure out why a query wasn't working... I had the line commented out. Yup, that'll do it! (Sigh.)
Print everything out. I know printing out error stuff is a "rookie" move--or I've certainly run into that attitude on a lot of sites (not this one!)--but man, sometimes the best thing to do is get rid of all the fancy debugging shit, and go line by line, and print out what the hell is in your variables, what's in them BEFORE, what's in them AFTER, where you are in the code. Go back to basics. If it's doable, and I'm assuming it is--again, this isn't something that's live (hopefully)--even get to the point of ye old printing out stuff like "About to enter the loop", "out of the loop", "just entered ABC function", etc.
Try and only change one thing at a time before you retest. I know how tempting it is especially if we start to think there's something that legit needs refactoring to start running with it--but resist. Change one. little. thing. and retest. The bug in my head right now I'm working on is in Scala, thus compilation, and part of a package, so I have to build it, then get it onto the VM that runs it, then delete the old version, rerun it, wait 5+ literal minutes while it runs and does what it does, and my computer totally doesn't have enough memory to be running the stuff on the VM that I need for it that I'm running so it takes ages. I reaaaaaaaally get the desire to do it faster but don't, because then you can't actually start to untangle it.
If you're starting to grasp at straws--break it on purpose. Get it to start throwing errors. Especially when things AREN'T happening that should be, or when nothing is showing up and everything just dies silently, or is Off, start making errors on purpose. CLEARLY mark them. Especially for the really creepy quiet errors that just peter out and gasp and you don't quite realize it until everything is just... not... it can be really disorienting, and I legit find it really reassuring to start doing ridiculous stuff to reassure my coding lizard brain that The Laws Of Coding Nature are not broken and that yes, Java WILL yell at me for not declaring some type, and that this isn't the coding equivalent of when you wake up in the middle of a weird nap and it's too quiet and you wonder if the world has ended and this is maybe a freezeframe--it sounds dumb, but it can be really reassuring to just get in there and make sure that things that SHOULD fail do fail, that things that should error are erroring, that strings of a known length are the right length, all that stuff.
Write down literally every idea you have, no matter how unrelated or stupid it seems. It probably isn't, and even if it is... well, it's not, because you get data regardless, right? Like -- I'm working on a weird one, in a language I don't really know on a system I don't really know--so needless to say, the error is on my end, and it's probably Mistake vs a Slip (friend of mine taught me that one) too--and I got a big breakthrough in it when it a fit of annoyance I basically went "OKAY WELL, FINE, THEN SHOW ME THE DAMN DATABASE EVERY TIME YOU GO INTO THAT LOOP AND WE'LL JUST START --oh. The... the database... doesn't... exist... anymore in the... loop. Okay. Well, that's Disturbing to say the least, but that *does* mean that it makes sense the query isn't working..." If I had been trying too hard, I never would've done something as ridiculous as checking to confirm the database I had been querying succesfully in a variety of ways with the same line of code lines earlier still existed--I mean... why would I? And clearly, it was the right choice. (Hey, I wasn't kidding when I said this one thing I'm trying to figure out is getting really weird...)
If you're still really stuck and you can't break out of it, save a copy of the file--sure, start another branch, whatever, but also just save a damn manual zip or whatever and stash it somewhere, hell, seriously email it to a friend depending on how worked up you are--and start over, either that function/method/section or what. It's very Burn It To The Ground, but sometimes that's what you need to do--very rarely is it the right thing code-wise, but usually as you start redoing it, you'll go "OH. WAIT--" and then you'll be super glad you saved a copy. (Depending on how upset and emotional yuo get, seriously email a zip to a friend. Sometimes when we get REALLY WORKED UP about a thing, I know I at least will rage delete my own code--thankfully I've gotten much, much better about versioning--but the only thing more heartbreaking and painful is having legit erased stuff out of fury and frustration and then thirty minutes later realized it and try to recreate it all from scratch.)
-- Change your syntax highlighting. Try a bunch of different ones in vaguely rapid fire. They all do different things, even if you hate them, the visual novelty can help you see new stuff.
-- Reformat your code. Beautify it, add stupid linebreaks (where it won't break), make the font huge, resize the window, whatever. Again, visual novelty! There's a really good reason the person who sees that missing semicolon instantly isn't the person who's been staring at it for a long time.
-- Linter, if applicable. Even if the issue is beyond a syntax one, even if you hate linters, seeing stuff brought up in a different manner can help.
-- Are you in the same running environment? if not, what's different? Same build environment?
-- Are you working in the right files/branch/whatever?
-- Do you have assignment lurking somewhere in a conditional?
-- Operator precedence! We're used to this one for math stuff but not for others I think.
-- Any chance you're looking at the wrong documentation--whether too new or too old?
(1) Totally did this once because I need a Break badly.
What a fantastic read, it would be worth a read as a stand alone article.
Clean it up
In addition, what I do to solve hard-to-find bugs, is to clean up the code. More often than not, the code is a mess with duplicate code, unneeded variable, wrong naming etc. Cleaning up gives me a chance to look differently at the source and sometimes - when your lucky - cleaning up actually solves the problem.
Tear it apart
Other option is to take out portions of the code and test them separately. When doing this, I often place the code back in its own function since it is more or less abstracted away from that point on.
Ha! I've done all of these. Nice list. You should think about writing a full article to this effect. :3
Here's a few more things I do.
(Incidentally, I also sometimes print source when I'm reading particularly complex source written by other people.)
Desk check! Get out a piece of paper and become the compiler. Start reading through the source code exactly as the computer would. Resist the urge to skip forward. Take nothing for granted. Write down each variable name, type, and value, and change it exactly as instructed by the code. 9 times out of 10, this allows me to catch the error.
Break out the LEGOs. Sometimes a paper-based desk check isn't enough. For particularly complex algorithms, I'll use my LEGOs and other desk toys to fill in for certain code structures, elements, pointers, objects, etc. There's no rules to this: just do something that makes sense. This is especially helpful when debugging search and sort algorithms. Besides that, it's fun.
I have to emphasize one of the points already made: talk to a non-coder. My mother, who is also my business partner, knows just enough about programming to help oversee project operations, but she isn't a coder by any means. On one occasion, after three days of my trying to debug something, she walked in and said "I bet you forgot a semicolon".
Yup. I'd forgotten a semicolon - right in the middle of a for-loop definition - thereby creating an infinite loop.
Thanks! :D I really like the idea of printing it out. I always forget about paper...
We're a place where coders share, stay up-to-date and grow their careers.