SyntaxHighlighter

Showing posts with label debugging. Show all posts
Showing posts with label debugging. Show all posts

Wednesday, 27 March 2013

To find a bug.

So I thought as I've suggested that debuggers are bad I should describe my "process" for tracking down bugs. I'm not going to claim this is as perfect approach, but it seems to work for me.

What is the bug?

The first thing I try and do when receiving a bug report is get information on what the bug actually is. What the expected behaviour was and what the actual the behaviour was. Any experienced developer will at some point have misunderstood an issue and fixed the wrong problem. This step is vital, but not really what I want to talk about in this post.

Where is the bug?

Time to start theorising. What components are likely to be involved with the operation? This may be an easy question to answer if you know the code well, but if you don't, try doing a search for keywords in your code this is where good naming (and also good spelling) can really make things easier. (I think there's another post in that statement).

Now the most important step of all - read the code!

When I read code, knowing there is an issue with it, I gain a new perspective on it. I notice things I didn't see before and question the assumptions that it makes. I start to theorize about how this could possibly be doing what it is actually doing. Tests are important here as well, is there a test for the behaviour seen? If there is, then maybe the behaviour is by design, or maybe there are situations that the tests don't account for. The amount that can be learnt from this type of inspection goes far beyond that of a normal code review, where you see what you expect to see rather than what is actually there.

You can also learn a lot about how to write code from this. If a particular pattern is hard to understand this will become very obvious at this stage, and you can consider other patterns in the future. You can also spot what is a good name and what isn't. At this stage if the code is quite complicated I will often refactor it. This is particularly useful if there are no unit tests as refactoring will require you to write some.

I'd say the majority of bugs I look at will be found and understood by this point, usually much more quickly than it would take to fire up a debugger. Plus my understanding of the code will be greatly improved.

If I still haven't figured it out, now is the time to fire up the debugger. But this is made much easier due to my increased understanding of the code. I know where to put break points and have an idea of what I should be seeing.

Where else could this bug exist?

It always surprises me how often the same bug occurs more than once within a codebase. Patterns of usage tend to be copied and reused. The most obvious benefit of looking for other occurrences of a bug is that you may uncover another bug in your software, but it also gives you the opportunity to increase your understanding of the code and possibly improve it. Looking for these bugs you may find alternative approaches to the problem. If the bug is in business logic you may find DRY principle violations, or you may find ways of simplifying the problem so all places can be cleaned up and improved.

How do we reproduce it?

Write a test. This could be a unit test, an integration test or an acceptance test, which one really depends on the bug. Unit tests are light, and easy to implement, but acceptance test have the advantage that they are easier to show to the user and get them confirm this is the behaviour they expected.

Fix, review, push the changes.

Go home.

OK, now I've finally got that post out of the way, I can get on to the interesting stuff - how to make this process easy.

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.