Artful Computing

I spent a lot of May 2021 acting as a Section Leader in Stanford University's "CodeInPlace" project, which aimed to teach Python remotely to a very large number of students world-wide (about 12,000),  staffed largely by volunteers. It was a great experience and I am posting here some of the general advice I gave to my students.

The word “Bug” suggest that we are suffering from little gremlins that crawl into our code and jam up the works - and that it really is not our fault. (Indeed, in the very early days of computing there are records of insects crawling into mechanical relays and causing breakdowns.) Let us be honest: we are the ones introducing the mistakes into the code.

A defect is a latent design error, sitting there in the code, waiting to be activated. (It may never happen - it requires the right conditions to occur.)

Unfortunately the cause of the failure and the actual failure are usually not in the same place in the code. In a "CodeInPlace Section Room"  I demonstrated what happens when you force Python to make an error and give a “traceback” which shows the code line where the error was detected. As it happens, this was a this was line that was unable to cope when we gave it a character string that could not be converted in to a number. (Perhaps not really an error at all: we wrote a program that was supposed to deal only with numbers.) In general, however, the actual root cause of a program failure may be in code that is a long way away from where the failure is reported because the error has set up a condition that will later cause a conflict. (This is why the course leaders keep going on about thinking in terms of pre- and post-conditions.) We leave a rake out on the lawn in the afternoon, but it is not until we step on it in the dark that it smacks us in the face.

So, every computer scientist and software engineer knows in his heart that the best way to eliminate bugs is not to make the design mistake in the first place. This is much easier said than done, so all programmers need to learn to diagnose the cause of program failures.

This is detective work: firstly we need to identify the condition that produced the failure, then we must deduce how that condition came about, working back to source of the original error. This often involves using our mind to try to trace in detail how the computer responds to every individual statement in our program.

The code designer, however, always finds it difficult to see faults in their own work. It takes a mental effort to make yourself accept that your code is probably full of errors.

Many of us find that asking a colleague for help breaks the blockage. We start explaining our program to the colleague and not infrequently see the answer to our problem before we have finished the explanation. Some software houses actually practice “pair-programming” with just one shared computer screen between two programmers: one typing, one doing continuous critical assessment.

Sometimes, unfortunately, we are working on our own, so what do we do to force a different mindset? I often find that it helps to take a break and do something else for a while (go for a walk?) returning with a fresh viewpoint.

A possibly apocryphal story once appeared on one of the American professional computing journals, of a Californian programmer who took his dog to work, because he found that explaining code to his dog was just as effective as talking to a human. (Cats, by the way, are useless. They are clearly not interested, at most they just sit on the keyboard until you supply food, whereas dogs pay attention and look as though they understand.) When the dog died he just pinned a photo of the dog beside his computer and claimed that worked almost as well. This story has appeared in various forms since - including “Rubber Duck Debugging”, as described in a well-known and excellent book called The Pragmatic Programmer. This version suggests that explaining your code to a rubber duck living by your screen is also highly effective. Make of that what you will: the point is that we can use verbalisation to force our mind to take a fresh viewpoint.

If I have a strong suspicion about where the error may be occurring, I may run a modified version of the code in which I have placed print() statements that show me the values held in the computer memory, or to indicate which way a conditional statement (if …) actually goes. A more sophisticated version of this is actively checking pre and post conditions around the area where you suspect an error. This works quite well for shortish programs, such as those answering teaching exercises, but with real-life codes you can end up with unmanageable amount of output in which you cannot find what you need.

Professionals therefore find that is worth becoming familiar with debugging tools that are part of “IDEs” (Interactive Development Environments). These allow one to step through the code line by line and actually watch what it does. We can also, for example, pause execution and look at the values being held in memory and see whether our pre- and post-conditions are actually holding. They are better than the “print()” method because we can tell the tool to run the code normally until it reaches a certain line, or enters a certain function, for example. It then pauses for us to take control.