Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Part One.  Fundamentals


Chapter 1. Enforcing Design: Constraints, Contracts, and Assertions

When we design software, we want it to be used in accordance with our designs. This is not vanity. In most cases it is all too easy to use software in a way for which it was not intended, and the results of doing so are invariably disappointing.

As I'm sure you already know from personal experience, the documentation for most software is incomplete and/or out of date. That's when it's not plain wrong or missing: "if there is any situation worse than having no documentation, it must be having wrong documentation" [Meye1997]. Documentation is not needed when the components being used are simple, well used, and either standard or ubiquitous. For example, I'd be surprised if many programmers need to look up the usage for the C library function malloc() more than a couple of times. However, the number of such cases is very small. I've encountered many very experienced programmers who were not so familiar with the nuances of malloc()'s ostensibly simple brethren, realloc() and free().

There are several answers to these problems. One option is to make all software components more error resistant by increased parameter validation, but this is generally unattractive since it damages performance and tends to breed bad habits. Making, and keeping updated, better documentation is certainly an important part of the answer, but, in and of itself, it is insufficient because it is optional. Furthermore, it is very difficult to write good documentation [Hunt2000], and the more complex software is the more difficult it is for its original authors to put themselves in the position of not understanding it or for independent technical writers to cover all the nuances. A better way of ensuring correct use of code is required in nontrivial cases.

It's preferable if the compiler can find our errors for us. In fact, a large part of the effort described in this book has been about encouraging and facilitating the compiler to choke on bad code. Hopefully you realize that a couple of minutes spent placating the compiler is better than a few hours spent inside the debugger. As Kernighan and Pike say in The Practice of Programming [Kern1999] "[W]hether we like it or not, debugging is an art we will practice regularly....It would be nice if bugs didn't happen, so we try to avoid them by writing code well in the first place." Since I am no less lazy than any other engineer, I try to make the compiler do as much of my work as is possible. Programming by hairshirt is the easier option in the long run. But many causes of error cannot be policed at compile time. In such cases we need to look for runtime mechanisms. Some languages—for example, D, Eiffel—provide in-built mechanisms by which software can be ensured to be used according to its design via Design by Contract (DbC), a technique pioneered by Bertrand Meyer [Meye1997], which has its roots in the formal verification of programs. DbC specifies contracts for software components, and these contracts are enforced at particular points during process execution. Contracts in many ways substitute for documentation, since they cannot be ignored, and are verified automatically. Moreover, by following a specific syntax contracts are amenable to automated documentation tools. We discuss these in section 1.3.

One mechanism for enforcement is assertions, both the commonly known runtime assertions, and the less well-known, but arguably even more useful, compile-time assertions. Both are used liberally throughout the book, so we will take a detailed look at this important tool in section 1.4.


      Previous section   Next section