Sponsored Link •
Bjarne Stroustrup talks with Bill Venners about many aspects of software design, including growing small applications into larger ones, avoiding class distinctions between designers and users, the dangers of premature generalization, and the essence of elegance.
Bjarne Stroustrup is the designer and original implementer of C++. He is the author of numerous papers and several books, including The C++ Programming Language (Addison-Wesley, 1985-2000) and The Design and Evolution of C++ (Addison-Wesley, 1994). He took an active role in the creation of the ANSI/ISO standard for C++ and continues to work on the maintenance and revision of that standard. He is currently the College of Engineering Chair in Computer Science Professor at Texas A&M University.
On September 22, 2003, Bill Venners met with Bjarne Stroustrup at the JAOO conference in Aarhus, Denmark. In this interview, which is being published in multiple installments on Artima.com, Stroustrup gives insights into C++ best practice.
Bill Venners: In an interview with Biltek, you said, "I'm not a great believer in elaborate design methods with supporting tools. I am, however, a strong supporter of systematic use of data abstraction, OOP, and generic programming. Anyone who just sits down and writes page after page of code without an overall design and without supporting classes and templates is simply wasting time and creating unnecessary maintenance problems." How much up-front design would you recommend. How much should we think before we code?
Bjarne Stroustrup: That depends so much on the size of the problem. If you have to write a little program that you need this afternoon, the design would probably would fit on the back of an envelope. If you are building a system that has to live for years and takes years to build, then clearly, you have to do more up-front design. If many people are involved in the project, you have to do more up-front design. However, the larger the system, the harder the up-front design becomes. Design tools don't give you much feedback, so therefore I tend towards the view that you should build a smaller system and grow it into a bigger one. There is a rule of thumb that says that every successful large system is a development of a slightly smaller working system. You apply that rule recursively.
Some people might think I'm talking about building prototypes. I am to some extent, but that's not all I'm talking about, because prototypes can also be a trap. If you build a prototype using a different class of developer, a different class of tools, and a totally different workload than the real application, you can get into really difficult problems. For example, you can afford much more hardware per user, much more software overhead, and much more experienced developers in the development of a prototype supporting ten users than if you have to support ten thousand users. You can usually also afford to ignore inconvenient standards issues and compatibility problems in a prototype. And then, of course, some solutions just don't scale.
Sometimes you can build part of the functionality of a system to try it out. I'm rather keen on doing very early integration tests. You decide what tools you want to use, and you write the logical equivalent of "Hello, world!" For instance, in a distributed system, you start off with the tiniest example you can of the application on the different parts of the system, and let them try and talk to each other. That's when you find that your Java ORB won't talk to your C++ ORB, or your Java virtual machine on the HP will not talk to the Java virtual machine on the Sun, or whatever. Obviously, such things that should never happen, but they do; maybe not the particular problems I mentioned here, but something unexpected always happens. I'm not just talking about prototypes. I'm talking about experiments and trying out all the things that eventually would have to work. Try them out early.
Bill Venners: I've often heard you state that C++ supports library design. To what extent to you think programmers who are just building applications should think of themselves as library designers? Should we partition our application into this library and that library? Or should we only think in terms of library design when we're creating a library for others to use?
Bjarne Stroustrup: I think people should mentally separate programming into building libraries and using them. Today you write a supporting library, and tomorrow you use it. Far too many times people try and build their systems as one big conglomerate. Even when I do individual work for only a short time, I tend to build some supporting functions and classes, and I design them to be supporting. I don't dive straight into building the whole thing that can do the whole job. So, whether you are by title a library designer or application designer, you should think about how you can separate logically the different parts of your system.
One of the problems with C++ is that there are too many libraries, and not any common marketplace or meeting place where people can find libraries and information about libraries. The problem with C++ and GUI is not that there's no C++ GUI library, for instance. The problem is that there are about 25 reasonable C++ GUI libraries, of which a few are rather good. But people say, "Well, there's no C++ GUI. It's not in the standard." C++ has no common meeting place like there is for Python, for example, where you know you can find libraries. It's a problem of riches, plurality, and a lack of marketing. Nobody in the C++ world seems to have any money to provide such a common meeting place.
One way you shouldn't write libraries is by taking the view that you are the great library designer. You provide the library for me. I use it, and you go away and do something else. You have to think of a library as something you yourself are using, and something that you have to live with as a support person for a longish while. You have to talk a lot to users to get the right abstractions and the right practical details. A library has to evolve. The best libraries are by people who are also users of the libraries. You can't have a class distinction between designers and users.
Bill Venners: C++ has a lot of stuff in it. The more features that a designer puts into a language or library, the more power the users get, but also the more surface area they get. There are more features users have to know about, even if it is knowing they just want to ignore certain features. As a designer of a language or library, how do you decide what features to leave in what to leave out?
Bjarne Stroustrup: It's very hard. I don't have a good enough philosophical base for that, and neither has just about anybody else. In the C++ language, I try to put in what will help everybody as opposed to what will help a particular group. I try to put in very general facilities. For example, I don't put in specific features for GUIs, because lots of people don't put GUIs in in their programs. I don't put in specific features for databases, because lots of people don't use databases. I don't put in specific features for concurrency, because you can put them in a library. So I go for generality, for features that in principle will be used by everybody, directly or indirectly. And I try not to force people to do lots of repetitive, tedious stuff. My work with templates, inlines, and overloading to eliminate dangerous casts and repetition is probably the best example.
Of course, you can say that everything I just said could be applied either to the design of a language itself or to the design of a standard library that everybody's using, or should be using. And it is very hard to define exactly where you should draw the line between a language and its libraries. In C++, I have tended to build into in the language facilities that are abstraction mechanisms. So contrary to some languages, I have not put in an advanced vector, because I could do that in a library with the facilities provided by the language. I didn't provide a string with special facilities for string manipulation. Instead, I put in general mechanisms that allow you to write a string class. I didn't put in properties because you can write a properties class. So in that sense, I go against some of the currently fashionable trends in other languages to put in very specific and specialized facilities. I guess that's a kind of basic philosophy of mine.
If you want more detail, read The Design and Evolution of C++, which is where I document a lot about why C++ is designed the way it is. Among other things, reading that book will help you avoid things like people—in this case, me—being wise after the event and trying to change history retrospectively. That book is a fairly solid record of what I thought at the time. There is unfortunately a lot of revisionist history in the computing world. Interestingly enough, the C++ language hasn't changed since I described in The Design and Evolution of C++. What has changed is the standard library and some of the styles of usage.
Bill Venners: Language and library designers often think about protecting users from themselves, but in C++, you seem to operate more from the philosophy that designers should give people enough rope to shoot themselves in the foot. In your C++ FAQ, you write, "As you protect people from simple dangers, they get themselves into new and less obvious problems." In our designs, how much should we try to shield users from their own potential mistakes?
Bjarne Stroustrup: I'm not as paternalistic as some language designers. I may have romantic notions of how competent users—in this case programmers—are, but I don't consider programmers on average stupid, and I would like to see them as more respected professionals. I wish programmers would act professional more often and that people would be willing to pay for more competence.
In addition, I don't want to disenfranchise users by giving them facilities that allow them to only do so much. I often see systems that require their users, if they want to do more than the system directly provides, to go back to the system vendor and ask the vendor to provide new facilities. The best examples are some of the fourth generation languages and tools for generating code from models or diagrams. If some facility you require was not thought of by the designer and is not built into the tool, you basically have to go back to the tool vendor and ask for a change in the tool. I have seen an example where a customer couldn't easily define a new sort operation. A related example is languages where you can't write the standard libraries, let alone the advanced libraries, in the language itself. That means you separate the user population into the untrusted developer group and the trusted and privileged group that tends to work for the supplier.
I prefer to see everybody on a reasonably even keel. My ideal is that if you don't like the libraries your vendor ships, you can build your own, because you have the same tools available. That idea doesn't seem to be fashionable among tools and platform vendors either.
C was designed with the principle that you should be able to write its standard library in the language itself. That same principle guided the design of C++. I don't think it's a principle in just about any other language. What other languages do is write their implementation in C or C++ instead. That approach works, but it runs the risk of having the great unwashed masses writing in some more protective language, and the specialists, working for the vendor, writing in the language where the actual work can be done. Once you have generality, you can get safety though coding standards or libraries; preferably through domain-specific coding standards supported by libraries.
Bill Venners: In an interview with Rogue Wave, you said, "I wouldn't like to build a tool that could only do what I had been able to imagine for it."
Bjarne Stroustrup: Yes.
Bill Venners: How do we do that? How do we design for situations we can't imagine?
Bjarne Stroustrup: You listen to a lot of people and then you aim for generality.
Bill Venners: By generality do you mean more abstract solutions to specific problems? Or do you perhaps mean facilities that a lot of people will need to use? Define generality.
Bjarne Stroustrup: Let's take an example. A class can do just about anything. C++ does not have separate language constructs to specify classes for big objects, for small objects, for object-oriented classes, for value types, for GUI events, for properties, for threads, and so on. The C++ class concept is general enough to allow a user to specify all of those, and to do so efficiently. One example of generality in the design of C++, then, is that I didn't decide that classes should be only for big expensive operations because it's expensive to call them.
In addition to generality in the sense that you're not restricting users, you can also aim for generality in the mathematical sense. You make make sure all the cases work, that a whole logical design space is covered, as opposed to saying there are these five features you should be able to do and no more. Why five? When you design in terms of lists of features or lists of alternatives, you never get the design quite right. There always turns out to be just one more case, or one less. For example, in C++ there are no built-in facilities for doing exactly the events system for a particular operating system. There are general mechanisms with which you can write a class that does an events system for any operating system.
Bill Venners: Going back to the topic of the application designer who is thinking in terms of libraries, when should they put in general facilities versus specifically solving the problem that they have before them?
Bjarne Stroustrup: I really don't know. That's such a general question, and an honest answer must include "sometimes" and "it depends." I think I have a tendency to build the specific case first if I haven't tried something several times before, because it is pretty fundamental that we understand the concrete solution better than the abstract. When I see the same problem again and I get half way into the solution, I say, "Wait a minute, I'm doing the same thing twice." I did it once. I sort of understand it. I know what I mistakes I made in the previous solution. Now is the time to step back and see if I can have a general solution. That approach contrasts with the approach taken by people who start generalizing before they've tried it once—they sit trying to design a general solution to a problem that they have no experience with.
Bill Venners: That sounds like premature generalization.
Bjarne Stroustrup: Yes, that's right. You can have premature generalization as well as premature optimization.
Bill Venners: In your C++ Style and Technique FAQ, you say, "An ugly operation should have an ugly syntactic form." I thought that was an interesting approach to design. You want to discourage users from doing something, but you don't want it to be impossible for them to do it when they really need to, so you provide a way, but make it ugly.
Bjarne Stroustrup: Yes. That's the new cast syntax. Casts do provide an essential service. The new syntax is an improvement over the C-style casts because it allows the compiler to check for errors that it couldn't with the old syntax. But the new syntax was made deliberately ugly, because casting is still an ugly and often unsafe operation.
Bill Venners: What should our ideals and aims be when we are designing, and why? You talk about "elegance" a lot. What is the business return on investment for elegance, or whatever it is you think should be our goal in design?
Bjarne Stroustrup: "Elegant" and "simple" are very related words. "Understandable" is another word in that area. What do they mean? It comes to down to: you can apply tools to your program. You can optimize it. You can maintain it. You can port it. If you can logically identify something, you can deal with it. If it's just a bunch of code scattered throughout a large program, you can't do a thing with it.
One of the most elegant things, by the way, is something that's declarative. It's one of the reasons I like statically typed languages. You can say: this is a matrix of double precision floating point numbers. Now you know a lot. You know what it means to multiply. You have a whole theory for matrices. When you declare a class with a constructor and destructor and then make an object, it has been declaratively said that the resource will be released in the destructor. It's clear. It's right there. Humans gain understanding. Both humans and tools can do things.
When I use elegant,...
Bill Venners: Yes, what does elegant mean?
Bjarne Stroustrup: It's hard. If you see a proof in math, you can say it's elegant. It's as clear, as general, as short as you can make it. I was trained as a mathematician, among other things, and I think that's the kind of notion that shines through. There's an aesthetic here, but the aesthetic correlates very strongly to things people really want, like maintainability and reasonably fast development, because you're working at a higher level of abstraction and using higher level constructs. And there's efficiency if you want it.
I keep telling my students that they should be lazy. I reject programs that are too long, because when students get to do real work, they won't have time to write that much code. A lot of the things I call elegant you'll find are short compared to alternatives. One of the things that amazes people is when you compare good C++ code to good code in other languages, the C++ code tends to be shorter. C++ code is shorter when it's written in terms of things like the standard library or other good libraries, because you can express ideas very succinctly. That's one of my ideals. Say what you want. Say it clearly. Say it as generally as possible. You'll find the resulting code is relatively short and it will probably run fast too.
Come back Monday, March 1 for the next installment of a conversation with Bertrand Meyer. If you'd like to receive a brief weekly email announcing new articles at Artima.com, please subscribe to the Artima Newsletter.
is author of The C++ Programming Language, which is
available on Amazon.com at:
is author of The Design and Evolution of C++, which is
available on Amazon.com at:
Bjarne Stroustrup's home page:
Bjarne Stroustrup's page about the C++ Programming
Preface to Third Edition where Bjarne talks about Programming
Publications by Bjarne Stroustrup:
Interviews with Bjarne Stroustrup:
Bjarne Stroustrup's FAQ:
Bjarne Stroustrup's C++ Style and Technique FAQ:
Bjarne Stroustrup's C++ Glossary:
Libsigc++ Callback Framework for C++:
C++ Boost, peer-reviewed portable C++ source libraries:
Al Stevens' review of The C++ Programming Language,
by Bjarne Stroustrup:
Biltek interview with Bjarne Stroustrup in which he talks
about not being a "great believer in elaborate design methods
with supporting tools":
Rogue Wave interview with Bjarne Stroustrup in which he says,
"I wouldn't like to build a tool that could only do what I had
been able to imagine for it."
Enough Rope to Shoot Yourself in the Foot, a phrase
Bill Venners mentioned in this interview in the context of the
question about protecting users from themselves, is the title of
a book of C/C++ style guidelines by Alan Holub: