Saturday, 29 November 2014

Effective Debugging with Divide and Conquer

I frequently get support requests for NAudio from people who complain that their audio doesn’t sound right. Can I have a look at their code and see what they are doing wrong?

Frequently the code they post contains multiple steps. Audio is recorded, processed, encoded with a codec, sent over the network, received over the network, decoded with a codec, processed some more, and then played.

Now if the audio doesn’t sound right, there’s clearly a problem somewhere, but how can we pinpoint where exactly? It is possible that a code review might reveal the problem, but often you’ll actually need to debug to get to the bottom of a problem like this.

The Number One Debugging Principle

Perhaps the most basic and foundational skill you need to learn if you are ever to debug effectively is to “divide and conquer”. If you have thousands of lines of code in which the bug might be hiding, going through each one line by line would take too long.

What you need to do is divide your code in half. Is the bug in the first half or the second? Once you’ve identified which half contains the problem, do the same again, and before long you’ll have narrowed down exactly where the problem lies. It’s a very simple and effective technique, and yet all too often overlooked.

Divide and Conquer Debugging in Practice

To illustrate this technique, let’s take the audio example I mentioned earlier. Where’s the problem? Well lets start by eliminating the network code. Instead of sending audio out over the network, write it to a WAV file. Then play that WAV file in Windows Media Player. If it sounds fine, then the problem isn’t in the first half of our system. With one quick test, we’ve narrowed down the problem to the decode and playback side of things.

Now we could test half of the remainder of the code by playing back from a pre-recorded file instead of from the network. If that sounds OK then its something in the code that receives over the network and decodes audio. So we can very quickly zero in on the problem area.

The point is simple, you don’t need to restrict yourself to looking at the output of the entire system to troubleshoot a problem. Look at the state at intermediate points to find out where things are going wrong. And often you don’t need to run through all the code. Can you pass data just through a small part of your logic, to see if the problem resides there?

Learn it and Use it

If you learn the art of divide and conquer, you’ll not only be great at debugging, but it will improve the way you write your code in the first place. Because as I’ve argued before on this blog, divide and conquer is perhaps the most essential skill for a programmer to have.

No comments: