SyntaxHighlighter

Monday 20 August 2012

Debugging (and why you shouldn't do it (yet))


I hate debugging!

I don't mean the removing of bugs from software, I mean firing up one of those nasty debugger applications like the one in Visual Studio, or WinDbg. The ones that run your software like it's on some kind of life support machine.

I know a lot of people enjoy it. They must do. They've spent years learning all the debugger tricks, all the keyboard shortcuts, how to set complicated watch and break points, they even know what "~*kp; ~*e !CLRStack -p; ~*e !dso; !SyncBlk" does in WinDbg. You wouldn't go through all that if you didn't enjoy it.

But to me it just means I'm too stupid to understand what's going on without having it spelled out to me step by step. There will be times when you need to resort to a debugger, but there are also some pretty good reasons to avoid it:

Reproducing the bug

If you want examine an issue with a debugger the first thing you need to do is reproduce it. This is not always easy, sometimes the steps to reproduce the bugs are not obvious, they may require being on a live system, may require some data that might be sensitive for other people or may even require some piece of hardware that you don't have. All these things can make using a debugger, time consuming and expensive.

Heisenbugs

Heisenbugs are where the act of trying to observe a bug makes it go away. The term is great if you want to annoy physicists, but it is in common enough use now that they know to just put up with it. There is a tendency to put these issues down to bad luck or "timing" issues, but there are also features of debuggers that can cause them, for example, in .Net attaching a debugger to a process changes when the garbage collector considers objects to be available collection. Most of the time you don't notice this, but have some badly behaved native code mixed in and you will come across it eventually.

"Insert fix here" coding

Debuggers give a very deep view of the software, and when your down in the noughts and ones it very easy to miss where you are in the architecture. This can lead to what I call "Insert fix here" coding. Your debugging a problem, stepping through a method and see that the issue is because numberOfDoohickies is 3 when it should be 4. Simple fix add numberOfDoohickies++ to the method, bug fixed, check in the code, off we go. Next person comes along they have only 2 doohickies, and the software breaks, developer looks at the issue and find that the method is called with numberOfDoohickies equal 3, closer examination shows that it is always set to 3 no matter how many doohickies the user actually has. The original developer saw that the state was wrong at a particular point and decided it needed to be fixed up at that point, rather than search for the root cause.

I wish I could say this doesn't happen, and it's easy to accuse the first developer of being "unprofessional", but when there's a critical issue you've been debugging for 4 days with a customer threatening to take his money elsewhere, or a deadline looming, it's easy to miss the wood for the trees.

(By far the most common of these must be checking for null to remove a null reference exception that is being thrown, when the variable really should never be null. I've got an entire post planned to talk about that)

Of course this kind of issue causes code to degrade, become overly complex and difficult to understand. These issues tend to become required behaviour of the code as other classes gain Insert Fix Here behaviour in order to work around earlier Insert Here Fixes.

Understanding the code

Bugs are undesired and unexpected behaviours in code, unless your very unlucky the bug is most likely in your code and not the frameworks/libraries you are using. This means there is some behaviour of your code that you don't understand, some nuance or interaction that isn't quite how you thought it was.

This is not good. It's important that you understand your code, adding new features to code you don't understand is difficult. You cannot be certain how your changes will affect other features, this leads to delays and missed deadlines. So how do you understand you code? By reading it! Run it through in your head. When you have a bug to find, identify a point in the code where you know that the state is bad and run the code backwards, very few debuggers can go backwards and fewer still do it well. Speculate over what could be the cause of the bug then look for code that verifies your theory.

Examining the code like this will give you an understanding that you won't get from stepping in a debugger. This will in turn lead to less bugs in your code, and less need for the debugger.

If you've read this far, (thankyou) there's a good chance you've thought "but you wouldn't have this issue if you ...". Well I probably agree with you and I've been collating some my favourite ways to avoid using the debugger. They were originally going to be a single post, but it was getting too difficult to make it small enough, so I now l hope to write them up into a series of blog posts, giving each subject the attention it deserves. Some of the tips are technology specific (most likely C# and .Net), others more general. I'd also love to hear your suggestions, feel free to leave them in the comments and I may write some of them up as later posts.