Book Review


Designing and Coding Reusable C++

reviewed by Marc Briand


Title: Designing and Coding Reusable C++
Authors: Martin D. Carroll and Margaret A. Ellis
Publisher: Addison-Wesley, 1995
Pages: 317
Price: $49.50
ISBN: 0-201-51284-X

This month we begin a three-part series on code reuse, by Martin D. Carroll and by Margaret A. Ellis. Many of you will recognize Margaret Ellis as coauthor of the ARM [1] . The series is excerpted in part from their book Designing and Coding Reusable C++. Programmers probably started dreaming of reuse back when they had to enter their code with toggle switches. Today the dream seems almost within reach, and the intense desire for reuse expresses itself as corporate policy, not just as swear words from the next cubicle. The stakes are higher, the potential rewards are bigger, and the disappointments all the more frustrating. (My favorite seminar title from the Software Development West '96 conference was Why Can't Johnny Reuse?). Since this topic is so important to programmers and corporations, we felt it would be a good one to explore in CUJ.

Although the series presented here provides a good sample of the authors' writing style and method of presentation, it is not totally representative of the book's contents. The main difference is one of focus. These articles focus on issues of class design; the book is much broader in scope. In this review I hope to give you an idea what the rest of the book is like.

Overall Impressions

First, the book is refreshingly sober. It does not promise that using C++, or OOP, will magically result in reusable code. I do not detect here an agenda that pushes C++, or OOP, or even code reuse. In fact, if this book errs, it's probably on the side of caution. At the outset it warns that trying to produce reusable code is not always worth the effort. It can be costly in development time and it can result in inefficient code. And since developing reusable code takes extra effort, with the payoff not realized for months or years, those who develop for reuse risk being viewed as unproductive.

In this first article, and in their book, the authors give a definition for nice classes. So here I take the liberty to define a term of my own: the nice problem. A nice problem is one that is solvable in a few concrete, well-defined steps, and has a predictable outcome. The steps may be enormously costly and painful, but they get the job done. Thus, gangrene is a nasty condition, but a "nice" problem — just sever the offending limb and that's that. By contrast, balancing the federal budget is not a nice problem, because it requires thousands of measures and the outcome is uncertain.

Creating reusable code is not a nice problem. It's messy; it takes lots of little steps. This is partly because the obstacles to reuse occur on so many different levels: at an environmental level (operating systems), at global and class scope, at syntactic and semantic levels, and possibly even at political levels (management).

Human language can complicate things. Does everyone at your workplace agree on the definition of "reuse?" Under one interpretation, even scavenging (cutting and pasting source code) is a primitive, albeit cumbersome form of reuse. That's probably not what most programmers have in mind, however. Martin and Ellis's four properties of reusable code are probably closer:

1) It is easy to find and understand.
2) There is reasonable assurance that it is correct.
3) It requires no separation from any containing code (well, so much for scavenging).
4) It requires no changes to be used in a program.

These four simple properties belie the painful decisions involved in achieving reuse. If your company makes you their Reuse Guru, you may find yourself trading off different kinds of reusability — such as extensibility vs. portability. Efficiency is another issue sure to rear its ugly head. As the authors point out, increased efficiency comes at the cost of practically every other desirable software property, including reusability. But to make matters worse, some efficiency is also necessary for reusability. For example, really slow code won't get used, much less reused. The trick, as in so many things, is in finding the right balance.

I formed this overview of current software development by reading this book. The book also showed me that reusability is a highly fragmented topic. Problems and solutions seem to be scattered like so much wreckage across the programming landscape. That makes it hard to produce a cohesive book. Given the difficulty of saying one big thing on this topic, I would expect a book on reuse to be one of the following:

I would place this book in the second category. It is a survey. The book's approach is to identify the major areas where reuseability issues crop up. It then presents some of the most common and/or difficult problems and solutions within those areas.

Major Sections of the Book

The book contains twelve chapters. It is not divided into sections, but one way to divide it is as follows:

The level of technical detail, or what I like to refer to as intensity, varies considerably from chapter to chapter. Thus, the chapters on class design and inheritance hierarchies are pretty intense. The chapter on documentation is like a walk in the park.

Intense or not, the authors kind of remind me of shrewd therapists. They do not tell us what to do so much as suggest what we might want to look at. The authors generally discourage adherence to hard-and-fast rules, and even go about uprooting some that have sprouted in recent years. For example, In Chapter 8, Inheritance Hierarchies, the they present four "unfounded rules" for class hierarchy design, which have apparently been making the rounds in the C++ community:

1. Singly rooted hierarchies are best.
2. Multiply rooted hierarchies are best.
3. The depth of a hierarchy should be no more than 7 +- 2.
4. The fanout of a hierarchy should be no more than 7 +- 2.

Obviously, these rules were not all formulated by one person. For each of these rules the authors present convincing counterexamples.

Getting Technical

The chapters I found most interesting were the ones on Class Design, Extensibility, Compatibility, and Inheritance Hierarchies. (By strange coincidence, it is material from these chapters that makes its way into CUJ.) These chapters are dense with technical details, code snippets, and figures, just as I would expect from a programming book. That doesn't mean the rest of the book is fluff — it is not. But these chapters seem to reveal more hard-won information than the others — the kind of things you may stumble upon only by chance in your day-to-day programming. Some of this stuff is mildly disconcerting.

Suppose, for example, you have implemented and distributed a class library. You then revise the library by adding a private member function to one of the classes. You might think there was no way that change could require a change in your users' source code. After all, your revision would seem to be doubly hidden, once by the class scoping mechanism and again by the private access specifier. However, if the user made some particularly unfortunate choices, your change could render the user's source code incompatible.

I will not attempt to demonstrate why. The authors do a plenty good job, and when all is said and done, this problem is highly unlikely to occur. But the problem demonstrates how difficult it is to design a language (or application) that is completely safe. In this particular example, the hole in the safety net appears because C++ does name lookups before it checks access privileges. I leave it to the experts to predict how many holes would open up if it were the other way around.

These chapters introduce some useful techniques for making your code extensible, among them "object factories," interface classes, and handle classes. The examples show just enough code to demonstrate how these things work. If you have used similar techniques already, you will probably catch on immediately. If not, the treatment given here may not be sufficiently powerful to switch on the lights. You may need to make a trip to the library.

Potholes and Black Holes

As C++ matures, its architects and users continue to identify some of the language's more notorious problems. It probably should come as no surprise that those problems turn up here. Some of them can be avoided with a bit of forethought. For example, the chapter on Class Design shows how to prevent unwanted implicit conversions. Implicit, or silent conversions are those things that spring up like weeds around function calls. If you pass a function an object of the wrong type, the compiler will make a valiant effort (some would say too valiant) to convert it to a type the function will accept.

We are used to implicit conversions from C. A mundane form is the conversion of an int to a long. What we want to avoid is having the compiler do something silly, like converting a Complex to an int. This is what the book calls a "nonsensible" conversion. This chapter shows an analysis of a user-created data type, sorting out sensible from nonsensible conversions. It also shows that in some cases, the distinction between sensible and nonsensible is not entirely clear.

Some C++ problems are more difficult to weasel out of. The Static Initialization Problem has received a lot of press lately, and it gets some exposure here as well. To quote from Chapter 12, Miscellaneous Topics: "If a C++ library defines and uses any nonsimple, nonlocal, static objects, it will be possible for a user of the library to build successfully a program that uses an object before it is constructed ..." This is not good. It's like driving onto a bridge that isn't there. In C++ lingo, the program's behavior is undefined.

You might think this problem is easy to fix, but it's got some of the best heads in the business scratching theirs. There are workarounds, and the book mentions a few, such as initalizer functions and double construction. All of these techniques have drawbacks, however. They either hurt performance, fail to completely eliminate the problem, or both.

The Static Initialization Problem is one you should heed especially if you are designing a library. This book will only get you started thinking about solutions. In this case its real value may be in the references it provides to more in-depth works.

Chapters to Relax By

If this book gets too technical, you can always turn to some of its less demanding chapters. There you may read about Myths of Reuse (myth: reuse will solve the software crisis), how to decrease template instantiation time and size, Myths of Inlining, error handling techniques (including exceptions), avoiding name conflicts, portability issues, using other libraries, and documentation. The advice given here is generally not trivial, but the English-to-snippet ratio is higher than in other chapters.

Exercises for the Energetic

Every chapter of this book concludes with a set of exercises. I did not work any of them. They looked too hard, for one thing. And besides, the book supplies no answers. For me, half the fun is checking the answers to see which ones I got right. It could be the authors are trying to cultivate us as Reuse Gurus. Like spiritual gurus, we must learn to savor questions as much as answers. Or maybe they didn't feel like working the exercises either. In fairness, the exercises do have value, because just looking through them will stimulate your imagination.

Target Audience

This book is written for programmers who know their way around C++ fairly well. While the code snippets and examples are typically not complex, they involve subtleties of the language that would confuse a beginning C++ programmer. In particular, before you curl up with this book you will want to have a thorough understanding of const, of the various uses and corruptions of the keyword static, an understanding of scope and nested classes, a basic understanding of what templates do, and some notion what happens inside a compiler (or linker) when a template is instantiated.

This book is clearly aimed at the library implementor. Of course, what other kind of C++ programmer would be interested in reusable code? Well, if OOP lives up to its promises, we can someday expect to see hoards of component integrators appear on the scene, object catalogs in hand. Presumably, these happy souls will still have to write a line or two of glue code now and then. This book is not for them. Nor does this book address those who engage in object modeling, that is, specifying complete object hierarchies and interactions in the abstract before writing a line of C++ code. I say this not as criticism, but to clarify the scope of the book. There is nary a peep here about COM or CORBA, Booch or Rumbaugh, or how object modeling contributes (or doesn't) to reusability in C++.

If you are a C++ programmer who is just starting out in a Reuse Program (or even thinking about it), you will probably want to read this book. If you are wearing rose-colored glasses, this book will turn them a shade darker. It will also give you a sense of what is possible and what is not. That's something we all wish our managers could know, but they might not be able to understand this book. So it's up to you. If you read this book, your next bid will probably be closer to reality than it's ever been before.o

Reference

[1] Bjarne Stroustrup and Margaret A. Ellis. The Annotated C++ Reference Manual (Addison-Wesley, 1990).

Marc Briand is managing editor of C/C++ Users Journal. He loves programming, writing, and too many other things for his own good. However, he hates to work, which is why he is an editor. He may be reached at mbriand@mfi.com.