2004.01.06
Writing better software without debuggers
by Karel Thönissen
Ask 10 programmers about their favorite tool for writing quality software, and 9 will mention a debugger. However, I have a disturbing message for them: debuggers actually decrease the quality of the code. I do not want to sound too dogmatic in claiming that we have never used a debugger at Garabit, but these occassions are few and far between, and it is getting fewer and farther. There are better ways to create high-quality code. Obviously, there is no harm in the tool itself. If not used, or if used sporadically and when appropriate, there is no problem at all. However, the debuggers are usually used in cases where vastly superior techniques exist.

The first problem with debuggers is that code that needs a debugger to understand needs refactoring. High-quality code is written in such a way that it is possible to understand the code by reading its source code. Names for objects and classes are clear and explain their intents, layout adds to readability, design patterns have been used where possible, explanations are added where necessary, subroutines are used for abstractions, etc. Use all this wisdom and your code reads like beautiful prose. When reading the code to hunt a bug, you may find other problems as well if the code is well-written.
My second objection is more fundamental. The problem with debuggers is that they allow a software development process that is not repeatable. During the debugging, the developer is mentally comparing the results as shown by the compiler with the results that he had expected. Even if his mental expectations are correct (we have no reason to assume this, because the same or a similar mind created the bug in the first place) we have the problem that this soll-wert is not documented. So if the programmer leaves the project, or when he is away from the code for more than a few weeks, as an organisation we have lost the ability to properly validate the code. Another objection is that we are using mind cycles instead of cpu-cycles which are several orders of magnitude cheaper. Comparing ist-wert and soll-wert takes at least a second per case for the human reader. Computers are much better at this and much, much cheaper.
So if we should not use debuggers, how do we catch the bugs that our code inevitably contains? The answer is assertions. Spice the code with assertions that test whether the assumptions of the coder are in fact true or not. With assertions in place, the expected state of the system at a particular point during the execution of the code is part of the source code. The test now has become repeatable and no longer depends on the knowledge of any particular developer doing the debugging. In fact, the assertion is tested all of the times, even after the bug has been found. Normally, assertions are even tested in deployed code. Only if performance requires so, assertions in production code are removed, but this should not be the norm. The purpose of assertions is to catch as many bugs as possible and as early as possible. This cannot be achieved when the assertions are only tested during bug chasing.
As a matter of fact, assertions should be placed in the code even if there are no known bugs in the code. The use of assertions does a lot for the quality of your code. It makes all the assumptions explicit and as such assertions are part of the documentation. Executable documentation! It forces you to think of which service is responsible for what. This cumulates in Design by Contract. But even in the context of debugging, assertions have another advantage: debuggers can only help to locate a known bug. Since code is usually not run in the debugger permanently, debuggers do nothing to signal the bug. Assertions not only help to find the location of the bug, they also signal that there is something wrong with the code, because assertions are in the code permanently.
Of course there are conditions that are very hard to capture with assertions. Correctness in the user interface and user friendliness come to mind. However, debuggers are problematic in the same area for the same reason. Pixels painted on a canvas are very hard to describe in an assertion expression or in a watch of a debugger.
The reason that debuggers are so popular may be because of their wide availability and because of the familiarity that developers have with these tools. The use of assertions requires the existence of a assertion framework, which is usually not readily available. However, our experience is that once this investment has been made, debuggers are no longer required. Sure there are cases where a debugger can help for a quick bug chasing session if the code contains insufficient assertions. But such code needs refactoring anyway.
|