|Code Complete, 2nd Edition / Steve McConnell|
|Reviewed by Tal Cohen||Thursday, 10 May 2007|
I’ve recently joined Google, and one of the books found on every programmer’s desk here is Code Complete’s 2nd edition. I could not help but wonder how does the update measure up — when read with the eyes of a more experienced software engineer, and compared to the legendary status achieved by the original?
The book still contains the same kind of solid, well-thought-out advice. I still believe that most novice programmers will benefit immensely from reading it. However, at the same time, I was somewhat disappointed by several shortcomings, and in particular McConnell’s disregard for language-specific issues.
The key philosophy of Code Complete is that good programs share certain traits, no matter what programming language is used. Information hiding, defensive programming, clear design, good naming conventions and so forth are all extremely relevant, regardless of the language. This is generally true. However, when language-specific issues are discussed, the book falls short, and the book greatly underplays the importance of the language used in those places where it does matter.
It all begins with the comparative review of key programming languages in use today. McConnell attempts to “rank” languages by comparing how “high-level” they are, relative to C. “A higher ratio,” the author explains, “means that each line of code in the language listed accomplishes more than does each line of code in C” (p.62). Yet somehow, Java and C++ ended up with the exact same rating. Considering the percentage of code in C++ that has to deal with memory management, I find that somewhat surprising. (Yes, you could argue that there are factors that tip the scales the other way, such as macros and template meta-programming. Still, being experienced in both, I believe that Java is at least slightly a “higher-level” language than C++ is.)
Lack of familiarity with Java riddles the rest of the book as well. The discussion of goto and labels in the code shows how in some cases, goto can be useful, in particular for breaking out of complex loop structures (Sec. 17.3); but it completely ignores the Java option of break/continue with labels. Or take the discussion about exceptions: table 8-1 (p.198) specifies six facts about exceptions regarding each of three languages. For Java, these six “facts” contain four errors. (Specifically: (1) in Java, you can throw instances of classes that inherit from Throwable, not from Exception; there’s a whole family of throwable objects that are not subclasses of Exception, namely Errors. (2) A “checked exception” need not terminate the thread of execution. (3) There is an effect to throwing an exception which is a “runtime exception”. And finally, (4) Exceptions caught need not be defined in the class interface.)
Not only Java suffers: the same goes for C#, which is discussed only briefly. For example, the discussion of multidimensional arrays (p.625-6) ignores C#’s support for matrices, as opposed to “jagged” multidimensional arrays (arrays-of-arrays). Using McConnell’s advise here will result in code that is significantly less readable, yet provides only a fraction of the promised performance benefit (or maybe no benefit at all). This is a classic example of where language-specific features do matter.
But not only language-specifics are lacking in this book. Recursion is presented as an “unusual” control structure, and the concept of tail recursion and its effect on performance is ignored. Mixins are explained nicely as “simple classes that are used to add a set of properties to an object” using multiple inheritance (p.149), but the explanation pays no regard to the fact that the term “mixins” is also used for a generic classes that extend their generic argument. But this is nothing compared to the fact that there’s simply no discussion of generic programming, including C++ templates; a glaring omission. The discussion of defensive programming (Chapter 8) never mentions defensive copies (see, for example, Item 24 in Effective Java). And the list goes on.
I think the mistake that bothered me most was the confusion of global data (p.335--343) with public class members. The discussion of global data in the first edition was precise and relevant; here, when moving to the world of object-oriented programming, McConnell botched things completely. Global variables are discussed on the same ground with public data members, even though each has a completely different set of issues, benefits and shortcomings; for example, public data members, unless they are also static, do not normally suffer from multithreading issues. And access routines, while they solve the problem of public members, are certainly not a solution for global variables. Conversely, the fact that static class members are actually global data is never mentioned.
To summarize: yes, the book is still a recommended reading, mainly because it presents well-thought-out reasoning for forging well-written code. I will still advise novice programmers to read it. But its many factual errors and confusions, and the overly broad attempt to be relevant to all programming languages, diminish its value noticeably.
|Has the Programming Language Universe Grown Too Large for One Pe [by Ken Magel] [2 replies in thread] [Unfold this thread]|