How do you debug a program? What approaches and qualities of mind are needed to get the job done? In many ways debugging a program is quite different from the act of programming and we need to be clear about it so that we can both do it better and teach it better.
Programmers "think different"
There is a real and distinct change in the mind when moving from non-programming to programming. Non-programmers read a text, by which I include the code of a program, in a linear fashion, it has a beginning a middle and an end. To a non-programmer a text is a static thing that provides you with some fixed information that doesn't change much no matter how often you read it.
Programmers, on the other hand, see a text as something very different. If you can program then for you a text can be a dynamic thing which describes behavior. It consists of conditionals, loops and self reference, and more than anything else it describes an exact process.
Becoming a programmer is all about making this step of seeing how a static text can give rise to a dynamic process. The code of a program is an activity and we write the code to produce the activity we want. This is why, once you have learned to program in one language, learning to program in another is much easier.
There really is something to be learned more than just the details of a programming language.
One of the problems is that not all programmers are very sure what it is that is special about the skill needed to program, and often think that it is just a matter of remembering the commas or semicolons.
There is of course more than this needed to be a successful programmer and one of the extras is the skill of debugging.
As is the case with programming in general, some programmers aren't very clear about what skills are important in debugging a programing. It is a common misconception that debugging is just programming, but getting it right rather than wrong.
In fact debugging is quite a different skill.
You can tell that this is true because if your are an experienced programmer you will be familiar with the way your mind changes the way that it works when you switch from programming to debugging. It is like a transition in a stick-shift gearbox.
When you are programming you are taking an algorithm, a process, and converting it into the static text of a program. You are performing the dynamic-to-static transcription.
When you are confronted by a program that isn't doing what it is supposed to, even if you have just written it, your understanding of the program has to change.
It isn't the program you thought it was.
The text expresses a process that isn't the one you were trying to render.
You could just start over and attempt to render the process into static text a second, or third or ... time. This sometimes works and it is the first level of the debugging skill.
Notice that you don't have to actually start over from scratch. In most case what happens is that you mentally try to recreate the program while reading what is already there. By comparing your recreation with the existing text you can often spot simple errors. These are the easiest of bugs to find. They correspond to the first level of bugs which are found using the first level debugging skill.
It is at the next level that things shift into a new gear.
Some programmers do this automatically without realizing that they have done it. Other programmers just don't know how to move up to the next level and so waste a lot of time uselessly rewriting the code in their heads to find a discrepancy.
If rewriting the code doesn't find the the bug then the problem most likely is as much in your head as it is in the code.
You may know what the process is you are trying to capture and you may be happy about the transcription from process to text but what you are doing is wrong in some way. Either the process you have in your head is flawed or you are repeatedly making the same error in rendering it into text.
So what to do next?
The answer is that you have to bring the "experimental method" to the problem.
Yes, the experimental method as used in all of science. This is the reason that ex-scientists often make good debuggers.
What you have to do next is to use the code to make a prediction of what should happen when you run the program. Then you run the program and see if your prediction is correct.
If it is correct you have achieved nothing and need to repeat the process.
If it is incorrect then you have found the bug.
You may not understand the bug or be able to fix it yet but you now have its location and nature pinned down and with some extra exploration of the same kind you should be able to understand where you are going wrong.
Notice that this is the scientific method. You take your existing code and use it to make a prediction. This is like taking a theory that you believe to be true and making a prediction. You then construct an experiment in the hope of invalidating the prediction.
In other words, you are trying to prove your theory wrong.
This is standard scientific method and it is really important. You are not trying to create an experiment to prove your prediction correct. In debugging proving that you know what the program is doing doesn't help one little bit - apart from to make you feel unreasonably good about it. What matters is finding a discrepancy between your understanding and what actually happens. Once you have this you can build a new theory of what is going on and test this in the same way.
A good debugger will select a part of the program and construct a test that is most likely to find a difference between predicted and actual behavior.
A good debugger will also construct tests that are specific in their rejection of the current theory. If you build a test that shows that your theory is flawed in a number of possible ways then this is good, but a test that pins that flaw down to just one aspect is so much better.
It is the ability to see what tests and results narrow down the cause of the problem that makes a good debugger truly great.
So debugging is an activity that is different to programming. It takes your current understanding of a program as a theory that you have to disprove. From your understanding you have to construct an experiment and predict its outcome with the objective of being proved wrong.
This is classical experimental method applied to software.