0% found this document useful (0 votes)
273 views202 pages

Unix C Allen I. Holub Enough Rope To Shoot Yourself in The Foot Rules For C and C Programming Unix C Computing Mcgraw Hill 1995

Uploaded by

Andrei Maldar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
273 views202 pages

Unix C Allen I. Holub Enough Rope To Shoot Yourself in The Foot Rules For C and C Programming Unix C Computing Mcgraw Hill 1995

Uploaded by

Andrei Maldar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 202
ENOUGH ROPE TO SHOOT YOURSELF Enough Rope to Shoot Yourself in the Foot Rules for C and C++ Programming Allen |. Holub McGraw-Hill New York San Francisco Washington, D.C. Auckland Bogota Caracas Lisbon London Madrid Mexico City Milan Montreal New Delhi San Juan Singapore Sydney Tokyo Toronto Library of Congress Cataloging-in-Publication Data Holub, Allen I. Enough rope to shoot yourself in the foot : rules for C & C++ programming / by Allen I. Holub. Pp. cm. Includes index. ISBN 0-07-029689-8 (p) 1. C++ (Computer program language) 2.C (Computer program language) I. Title. QA76.73.C153H625 1995 005.13—dc20 95-35136 CIP McGraw-Hill A Division of The McGraw-Hill Companies Copyright © 1995 by The McGraw-Hill Companies, Inc. Printed in the United States of America. Except as permitted under the United States Copyright Act of 1976, no part of this publication may be reproduced or distributed in any form or by any means, or stored ina data base or retrieval system, without the prior written permission of the publisher. pb 123456789 FGR/FGR 90098765 ISBN 0-07-029689-8 The sponsoring editor of this book was Jennifer Holt DiGiovanna, the Executive Editor, was Robert E. Ostrander; and the book editor, John Baker. Printed and bound by Quebecor Printing, Fairfield, PA. McGraw-Hill books are available at special quantity discounts to use as premiums and sales promotions, or for use in corporate training programs. For more information, please write to the Director of Special Sales, McGraw-Hill, 11 West 19th Street, New York, NY 10011. Or contact your local bookstore. Product or brand names used in this book may be trade names or trademarks. Where we believe that there may be proprietary claims to such trade names or trademarks, the name has been used with an initial capital or it has been capitalized in the style used by the name claimant. Regardless of the capitalization used, all such names have been used in an editorial manner without any intent to convey endorsement of or other affiliation with the name claimant. Neither the authors nor the publisher intends to express any judgment as to the validity or legal status of any such proprietary claims. Information contained in this work has been obtained by McGraw-Hill, Inc. from sources believed to be reliable. However, neither McGraw-Hill nor its authors guarantee the accuracy or completeness of any information published herein and neither McGraw-Hill nor its authors shall be responsible for any errors, omissions, or damages arising out of use of this information. This work is published with the understanding that McGraw-Hill and its authors are supplying information but are Not attempting to render engineering or other professional services. If such services are required, the assistance of an appropriate professional should be sought. MH95 0296898 For Amanda Contents Acknowledgmentts............scscssssssssesssesees seesesessessscessssesssesacssessssssssessssssesesscsssssssseseors XTi Introduction .........ccsssccssccssssssscenssssssesscsssesssscussnsersnsscesecssssctsceseseessnsseseesseeseese Beeaseasensensrs xv Part 1 The Design Process 1 The essentials of programming: No surprises, minimize coupling, and maximize cohesion 2 Stamp out the demons of complexity (Part 1)3 2.1 Don’t solve problems that don’t exét ... . 2.2 Solve the specific problem, not the general case. A user interface should not look like a computer program (the transparency principle) Don't confuse ease of learning with ease of use. Productivity can be measured in the number of keystrokes. If you can't say it in English, you can’t say it in C/C++. 6.1 Do the comments firs! Read code. 7.1 There's no room for prima donnas in a contemporary programming shop. Decompose complex problems into smaller tasks. Use the whole language 9 Use the appropriate tool for the job. 10 Aproblem must be thought through before it can be solve 11 Computer programming is a service industry. 12 Involve users in the development process. 13 The customer is always right... 14 Small is Beautiful. (Big == slow) OnaRw oo vill Contents Part 2 15 16 17 18 19 20 Part 4 44 45 46 47 48 Part 5 50 51 52 General Development Issues First, do no harm... Edit your code.... A program must be written at least twice. You can’t measure productivity by volume You can't program in isolation Goof off... es Write code with maintenance in mind—the maintenance programmer is yor " 21.1 Efficiency is often & DUGADOO oo... ee eeesesecseseesesesneseecseseescsesucatesessaueaesnesesseensucnesuesesseateneseeneeneeees Formatting and Documentation. Uncommented code has no value... Put the code and the documentation in the same place. Comments should be sentences......... Run your code through a spelling checker. Acomment shouldn't restate the obvious.. Acomment should provide only information needed for maintenance. Comments should be in blocks... Comments should align vertically. Use neat columns as MUCH AS POSSIDIO...............scceecsseeeessecssneeessees Don't put comments between the function name and the open brace. Mark the ends of jong compound statements with something reasonable. Put only one statement per line.................. Put argument names in function prototypes.. Use a “predicate” form to split up long expressions.. A subroutine should fit on a screen. All code should be printable...... Use lines of dashes for visual separation between subroutine: White space is one of the most effective comments. Use four-space indents Indent statements associated with a flow-control statement. 41.1.Comments should be at the same indent level as the surrounding code. Align braces vertically at the outer level... eects renee eeeeeneneee Use braces when more than one line is present under a flow-control statement Names and Identifiers...............c00008 sacuseaseaseaceausaeeaescessesseaceaneaesserseeneateaesetsseens OO Names should be common English words, descriptive of what the function, argument, or variable does 44.1.Do not clutter names with gibberish. Macro names should be ENTIRELY_CAPITALIZED. 45.1 Do not capitalize members of anenun..... 45.2 Do not capitalize type names created with atypedef Avoid the ANSI C name space. Avoid the Microsoft name spac Avoid unnecessary symbols ..... Symbolic constants for Boolean values are rarely necessary. Rules for General Programming Don't confuse familiarity with readabili A function should do only one thing.... Too many levels of abstraction or encapsulation are as bad as too few. A function should be called more than once, but.......... 53.1 Code used more than once should be put into a function. 54 55 56 57 59 60 61 62 64 66 67 69 70 71 72 73 74 75 76 78 Part 6 Part 7 85 Contents Ix A function should have only one exit poin 54.1 Always put a return at the outer level Avoid duplication of effort. Don't corrupt the global name space 56.1 Avoid global symbols........ 56.2 Never require initidization of a global variable to call a functio 56.2.1 Make locals static in recursive functions if the value doesn’t span a recursive call 56.3 Use instance counts in place of initialization functions. 56.4 If anit ends in return, don’t useelse Put the shortest clause of anif/else on top... Try to move errors from run time to compile tim: Use C function pointers as selectors Avoid do/while loops... 60.1 Never use ado/while for a forever loo, Counting loops should count down if possible. Don't do the same thing in two ways at the same time. Use for if any two of an initialization, test, or increment are present. If it doesn’t appear in the test, it shouldn’t appear in the other parts ofor statement. Assume that things will go wrong... Computers do not know mathematic: 66.1 Expect the impossible............ 66.2 Always check error-return code Avoid explicit temporary variables... No magic numbers ............ Make no assumptions about sizes. Beware of casts (C issues) .. Handle special cases direct Don't try to make lint happy Put memory allocation and deallocation code in the same place. Heap memory is expensive............. Test routines should not be interactive. An error message should tell the user what's rig Don’t print error messages if an error is recoverable. Don’t use system-dependent functions for error messages The Preprocessor . Everything in a .h file should be used in at least two.c files Use nested #4ncludes........scccceccsccsseereereenneennee You should always be able to replace a macro with a functio 81.17: is not the same as if/else............. 81.2 Parenthesize macro bodies and argument: enum and const are better than a macro. A parameterized-macro argument should not appear more than once on the right-hand side 83.1 Never use macros for character constants When all else fails, use the preprocessor C-Related Rules.. Stamp out the demons of nr (Part 2). 85.1 Eliminate clutter... 85.2 Avoid bitwise mask: 85.3 Don’t use done flags use 9 bit fi elds x Contents 85.4 Assume that your reader knows C 85.5 Don't pretend that C supports a Boolean type ¢define TRUE) 86 1-bit bit fields should beunsigned.. 87 Pointers must be above the base address of an array. 88 Use pointers instead of array indexes. 89 Avoid goto except.. Part 8 Rules for C++ Programming. Part 8.A Design and Implementation Issues. 90 Object-oriented and “structured” designs don't mix. 90.1 If it's not object-oriented, use C........... 91 Expect to spend more time in design and less in development. 92 C++ class libraries usually can't be used in a naive way. 93 Use checklists a 94 Messages should exercise capabilities, not request informatior 95 You usually cannot convert an existing structured program to object-oriented. 96 A derived class object is a base-class object..................0 97 Derivation is the process of adding member data and method: 98 Design the objects first 99 Design the hierarchy next, from the bottom up. 99.1 Base classes should have more than one derived clas: 100 The capabilities defined in the base class should be used byall derived classes 101 C++ is not Smalltalk—avoid a common object class 102 Mix-ins shouldn't derive from anything, 103 Mix-ins should be virtual base classes 104 Initialize virtual base classes with the default constructor. 105 Derivation is not appropriate if you never send a base-class message to a derived-class object.. 106 107 Use private base classes only when you must provide virtual overrides. 108 Design the data structures last. 109 All data in a class definition must beprivat 110 Never provide public access to private data. 110.1 Do not use get/set functions. 111 Give up on C idioms when coding in C++. 112 Design with derivation in mind. 112.1 A member function should usually use theprivate data of a clas: 113 Use const 114 Use struct only if everything's public and there are no member functions. 115 Don’t put function bodies into class definitions. 116 Avoid function overloads and default argument Part 8.B Coupling Issues 117 Avoid friend classes 118 Inheritance is a form of coupling. 119 Don't corrupt the global name space: Part 8.C . 120 Reference arguments should always beconst 121 Never use references as outputs, use pointers. 122 Do not return references (or pointers) to local variables. 123 Do not return references to memory that came fromnew ................c cece cece ee cees tees eeseseeseseneeeeeeereseee 131 Part 8.D Constructors, Destructors, and operator=() 124 125 126 127 128 129 130 131 132 Part 8.E 136 137 138 139 140 141 142. 143 144 Part 8.F 145 146 147 148 149 150 151 Part 8.G 152 153 154 Part 8.H 155 156 157 158 Part 8.1 159 160 161 Contents xl 133 133 Operator=() should return aconst reference Assignment to self must work. Classes having pointer members should always define a copy congructor and operator If you can access an object, it has been initialized. Use member-initialization lists e Assume that members and base classes a are initialized in random order. Copy constructors must use member initialization lists. Derived classes should usually define a copy constructor andoperator = () Constructors not suitable for type conversion should have two or more arguments. Use instance counts for class-level initialization............ Avoid two-part initialization... C++ wrappers around existing interfaces rarely work well Virtual Functione...... Virtual functions are those functions that you can’t write at the base-class ‘evel A virtual function isn’t virtual when called from a constructor or destructor. Do not call pure virtual functions from constructors. Destructors should always be virtual : Base-class functions that have the same name as derived-class functions gengally should be virtual... 149 Don’t make a functionvirtual unless you want the derived class to get control of it protected functions should usually bevirtual Beware of casts: C++ issues............ Don’t call constructors from operator Operator Overloads An operator is an abbreviation (no surprises) Use operator overloads only to define operations for which there is a C analog (no surprises). Once you overload an operation, you must overload all similar operations. Operator overloads should work exactly like they would in C. It's best for a binary-operator overload to be aninline alias for a cast Don't go bonkers with type-conversion operators. Do all type conversions with constructors if possible. Memory Managemen Use new/delete rather thanmalloc()/free(). All memory allocated in a constructor should be freed in the destructor. Local overloads of new and delete are dangerous Templates... Use inline function templates instead of parameterized macros.. Always be aware of the size of the expanded template. Class templates should usually define derived classes. Templates do not replace derivation; they automate Exceptions........ Intend for exceptions not to be caught Throw error objects when possible ... Throwing exceptions from constructors is tricky COMCIUSION .........ssssssecceeeecseteetecesceeeseeserseesensenees sesceesceeaecescesceesceeaceseaescensececsesenseneneesees .179 Index .. Seve cesueteneeucesectecesuuetuserectduseunecusecstecetreusesctrecerttecnsscltssustecteecsetsnerttcetsteestscesteseetsettss 181 Acknowledgements This book was much too long in the making, and I’m indebted to three editors at McGraw-Hill who put up, in succession, with my constant delays: Neil Levine, Dan Gonneau, and Jennifer Holt- DiGiovanna. I’m particularly indebted to Bob DuCharme, who saved me from myself by making a very thorough pass through the initial draft of this book. His suggestions have considerably improved the current volume. Introduction The title of this book describes what I consider to be the main problem with both C++ and C: the languages give you so much flexibility that, unless you’re willing and able to discipline yourself, you can end up with a large body of unmaintainable gobblygook masquerading as a computer program. You can do virtually anything with these languages, even when you don’t want to. This book attempts to get a handle on this problem by presenting a collection of rules of thumb for program- ming in C and C++ — rules that will hopefully keep you out of trouble to begin with. Though most of the rules given here apply equally to C and C++ programming, I’ve included a lot of material that is relevant only in the C++ world, concentrated into the final section whenever possible. If you’re programming in C only, just ignore the C++ stuff that found its way into earlier sections. I’ve been programming professionally since about 1979, and the rules in this book are the ones that I use daily. I make no claims that these rules are definitive or that they’re even “correct.” I can say, however, that they’ve served me pretty well over time. Though this book is not a “traps-and- pitfalls” book, many of the rules will keep you out of the sort of trouble that “traps-and-pitfalls” books discuss. Rules of thumb are, by nature, flexible. They gradually change with experience, and no one rule is valid in every situation; I break my own rules all the time. Nonetheless, I’ll warn you at the outset that I’m pretty opinionated about this stuff, and I have little sympathy for sloppy thinking or sloppy programming. I make no apology for stating strongly things that I believe strongly. My opinions are always subject to change, of course, provided that you can convince me that I’m wrong, but bear in mind that this book is based on experience, not theory. I realize that a lot of this book treads dangerously close to religion for some and that many of the things I say are controversial, but I think that there’s always room for intelligent discourse between two people with the common goal of improving their craft. xvi Enough Rope to Shoot Yourself in the Foot I often teach C++ and object-oriented design classes, both inhouse for individual companies and for the University of California, Berkeley Extension. This book came about at the request of my stu- dents, most of whom are dedicated professionals with a real desire to learn the material. I look at a lot of code in the process of grading homework, and this code is pretty representative of the work produced by the professional programming community in the San Francisco Bay area. Unfor- tunately, every semester, I also see the same problems repeated over and over. This book, then, is in some ways a list of common problems that I find in real code produced by real programmers along with my advice on how to solve those problems. The programming and design problems discussed here are not limited to student code, unfor- tunately. Many of the examples of how not to do things are taken from a commercial product: the Microsoft Foundation Class (MFC) library. As far as I can tell, this library was designed without a thought to good maintenance, by people unaware of even rudimentary object-oriented-design princi- ples. I have not explicitly attributed most of these examples in the text because this book is not a “what’s wrong with MFC” book; users of the MFC library will recognize the code when they encounter it. I’ve taken examples from MFC simply because I work with it a lot, so I am very fami- liar with its foibles. Many other commercial class libraries have similar problems. Finally, this book is not an introduction to C++. The discussion accompanying the C++ -related rules assumes that you know the language. I don’t waste space describing how C++ works. There are lots of good books that teach you C++, including my own C+C++ (New York, McGraw-Hill, 1993). You should also familiarize yourself with object-oriented design principles. I recommend Grady Booch’s Object-Oriented Analysis and Design with Applications, 2nd ed. (Redwood City, Benjamin Cummings, 1994). About the rule numbering: Sometimes, I’ve grouped several rules together because it’s con- venient to describe them all at the same time. When I’ve done that, all the rules (which will have different rule numbers) are clumped together at the top of the section. I’ve used subrules (of the form “1.2”) when one rule is a special case of another. Part The Design Process This part and the following one on Development are the two most nebulous parts in the book. The rules here are quite general in nature—not concerning themselves with the mechanics of C or C++ programming at all, but rather discussing the more general process of program design and develop- ment. The rules in the current part deal with the overall design process. As I reread this part, I become worried that a lot of these rules will seem like platitudes. Nonetheless, some of the rules here are the most important ones in the book, because violating them can cause a lot of grief once development starts. In a way, many of the rules in this part are for managers; programmers often know them but aren’t given the freedom to user their knowledge. The Design Process 3 1. The essentials of programming: No surprises, minimize coupling, and maximize cohesion. Many (if not all) of the rules in this book can be summarized into the three meta-rules (if you will) in this section’s title. “No surprises” is self evident. A user interface should act the way it looks like it should act. A function or variable should do what its name implies. Coupling is a connection between two program or user-interface objects. When an object changes, everything that it’s coupled to might change as well. Coupling makes for surprises. (I change this thing over here, and suddenly that thing over there doesn’t work.) A C++ example: If an object of one class sends messages to objects of a second class, the sending class is coupled to the receiving class. If you change the interface to the receiving class, you'll also have to examine the code in the sending class to make sure it still works. This sort of light coupling is harmless. You do need to know about the coupling relationships to maintain the program, but without some coupling, a program couldn’t do anything. Nonetheless, you want to minimize coupling relationships as much as possible. This minimization is typically done in C using modules and in C++ using classes. The functions in the module (and member functions of the class) are coupled to each other, but with the exception of a few interface functions (or objects), they don’t communicate to the outside world at all. In C, you’d use the static storage class to restrict the use of a function to a single module. In C++, you use private member functions. Cohesion is the opposite of coupling, things that are grouped together (items in a dialog box, items on a menu, functions in a module, or members of a class) should be related functionally. A lack of cohesion is also “surprising.” The editor that I’m using has an “Options” menu, but it also scatters additional configuration options across four other pop-up menus. I expected a cohesive configuration, and when I couldn’t find the option I wanted under the “Options” menu, I assumed that the option wasn’t available. This bad design is still bothersome; after a year of use I still haven’t memorized where every option is located, and I often have to spend an annoying five minutes searching in five different places looking for the thing I want to change. On the source-code side, a lack of modular cohesion makes you do the same thing—spending your life looking for function declarations in 15 different files—an obvious maintenance problem. 2. Stamp out the demons of complexity (Part 1). Richard Rashid (the developer of Mach) gave a keynote address at a Microsoft Developer’s Confer- ence a few years ago. His main point was that too much complexity, both in user interfaces and within a program, was the single largest problem facing software designers and users today. Ironi- cally, this speech was given following two days of largely unsuccessful attempts to show several thousand very smart programmers how to program the Microsoft OLE 2.0 interface—one of the most complex application-programming interfaces I’ve ever seen. (OLE stands for “Object Linking and Embedding." The OLE 2.0 standard defines an interface that two programs can use to interact with each other in an orderly fashion. It’s really object orientation at the operating-system level.) An earlier speaker, who was urging us to use the Microsoft Foundation Class library, told us that MFC’s support for OLE “encapsulates 20,000 lines of code essential to every basic OLE 2.0 appli- cation.” The audience was stunned, not by the utility of MFC, but by the fact that it takes 20,000 lines of code to write a basic OLE 2.0 application. Any interface this complex is seriously flawed. 4 Enough Rope to Shoot Yourself in the Foot The next few rules use OLE to demonstrate specific problems, but don’t think that the complexity problem is Microsoft specific; it’s endemic. 2.1 Don’t solve problems that don’t exist. 2.2 Solve the specific problem, not the general case. It’s instructive to use OLE as an example of what goes wrong with many too-complex designs. There are two main reasons for OLE’s complexity. First, it unsuccessfully tries to be language independent. The idea of a C++ virtual-function table is central to OLE 2.0. The OLE specification even uses C++ class notation to document how various OLE interfaces have to work. To implement OLE in a language other than C++, you must simulate a C++ virtual-function table in that language, effectively limiting your choices to C++, C, or assembly language (unless you’re a compiler writer and can add features to your language of choice). Frankly, you’d be mad to program OLE in any language other than C++; it will take much less time to leam C++ than it will to write a C++ simula- tor. This idea of language independence then, is a failure. The interface could be simplified consid- erably by abandoning it. Looking back at the story in the previous section, the Microsoft Foundation Classes do actually solve the complexity problem with respect to OLE with a simple, easy-to-understand interface that implements all of the functionality needed by most OLE 2.0 applications. The fact that no one was willing to program OLE until the MFC wrapper layer became available is telling. Providing a good wrapper around a bad interface is no solution to the fundamental problem. If the MFC wrapper is so simple, then why is the underlying layer so complex? The answer to this question is a basic design issue. The designers of the OLE interface never asked themselves two basic questions: « What basic functionality does a real application have to support? « What is the simplest way to implement that functionality? In other words, they did not have a real application in mind when they designed the interface, but designed for some theoretical worst case. They implemented the most general interface they could without thinking about what was actually going to be done with the interface, resulting in a system that could do anything, but that was too complex to be usable. (They probably didn’t test the inter- face by implementing an application in it either, otherwise they would have found these problems.) In some ways, the object-oriented design process is an attempt to solve this problem. It’s rela- tively easy to add functionality to an object-oriented system, either with derivation or by adding new message handlers to existing classes. By hiding the data definitions from a class’s users, you give yourself the ability to completely change the interior organization of a class—including the data definitions—without affecting the users of that class, provided that you maintain the existing inter- face. In a structured design, you tend not to have that luxury. You usually design the data structures first, and modifying a data structure is a major undertaking because you have to examine every sub- routine that uses that data structure to make sure that it still works. As a consequence, “‘structured”’ programs tend to have a lot of code that doesn’t do anything. It’s there because someone might want to use some functionality in the future. In fact, many structured designers pride themselves on their ability to predict the direction in which the program might evolve. In all, this makes for a lot of needless work and for programs that are bigger than necessary. Rather than designing for every eventuality, design your code so that it is easily extended when a new functionality is actually needed. Object-oriented designs tend to work better here. The Design Process 5 3. A user interface should not look like a computer program (the trans- parency principle). I once heard someone say that the best user interface ever designed was the pencil. It’s function is immediately clear, it doesn’t need a user’s manual, it gets the job done with little fuss. The most important attribute, however, is transparency. The pencil, when viewed as an interface between you and the paper, is invisible. When you use a pencil, you are thinking about what you are writing, not about the pencil itself. Like a pencil, The best computer interfaces are the ones that obscure the fact that you’re even talking to a computer: the interface to the ignition system of your car is a great example. You turn on the ignition, put the car in gear, and step on the pedal, as if any of these interface objects (a key, an ignition switch, a pedal) are hooked up directly to the engine. They aren’t, though; they’re usu- ally just control inputs to a computer which is controlling the engine. Unfortunately, that level of clarity is often missing from user interfaces. Imagine a Windows GUI interface to an automobile. You start by selecting a main menu called “Move Car.” Clicking on it would pop up the “gear shift” menu, which would make you chose from “Forward,” “Reverse,” and “Neutral.” Click one of these to move a check mark next to the desired gear. Then move back up to the main menu and select the “Go” command. This item pops up the “Speed” dia- log, where you have to use a slider to enter a desired speed. It’s difficult to enter the correct speed, though, because the slider has such fine resolution (1 MPH is about half a millimeter of mouse movement), so you settle for 34.7 MPH rather than 35. You then click the “Go” button on the dialog box, and a message box with an exclamation point pops up saying “The parking break is still engaged—press F1 for help” (and the speaker emits a rude noise). Resigned, you click the “OK” button to get rid of the message box, then try the main menu again, but the machine just beeps at you. Finally realizing that the problem is that the modal “Speed”’ dialog is still displayed, you click the “CANCEL” button to get rid of it. You pull down the “Parking brake” menu and uncheck the “Engaged” box. You then pull down the “Go” menu again. You get another message box (and rude noise) telling you that you must first select a gear with the “Gear shift” menu. At this point, you decide that maybe you'll walk to work. Here’s another example: In the course of writing a review, I recently looked at several aviation- logbook programs. (A “logbook” is a very simple spreadsheet. Each line represents a particular flight, and the columns break out the total time for the flight into various categories [total time, time spent flying in the clouds, etc.] Other columns mark the flight as business related, and so forth.) By far, the best interface of the bunch was one that looked just like a familiar paper logbook, but it automated the drudgery. You put a time in the "total" column, and the same time appeared in other likely columns. Columns were added together automatically to form category totals. You could generate the necessary reports easily and export the data to a tab-delimited ASCII format that’s read- able by virtually every spreadsheet or word-processing program in the world. To an untrained eye, the whole interface seemed underwhelming, to say the least, but it was functional and intuitive, and the program was small and fast. The most important thing, though, is that this interface looked like a logbook, not like a Windows program. At the other extreme was a wowie-zowie Windows GUI, it had dialog boxes, it had 3-D graphics, you could generate pie charts showing the percentage of cloud time you’ ve had in Cessna 172s in the last 17 years, you could pull up a scanned photograph of an airplane ... you get the picture. The pro- gram looked beautiful, but it was almost impossible to use. There was no practical reason to create most of the charts and reports it could generate. Data entry was ungainly and slow; you had to pull down a dialog box with fields scattered all over the place. You actually had to read the whole thing 6 Enough Rope to Shoot Yourself in the Foot to find the category you were interested in, and some of the categories were hidden under buttons, necessitating an elaborate search. To add insult to injury, the program was built on top of a relational-database server (remember, all this to maintain a simple table with no relational links). It took up 30MB on my disk. It took almost 5 minutes for me to make an entry that takes about 10 seconds to make in the paper logbook or the simple GUI mentioned earlier. The thing was unusable, but it sure was impressive. One of the main problems was that the tools used to produce the second program drove the inter- face design. These programs were all developed in a very-high-level language: Visual Basic (which 1 like quite a bit, by the way). Applications created with application builders such as Visual Basic (or PowerBuilder, or Delphi, or ...) tend to have a particular look that immediately tells you what tool was used to build the application. An interface designer has no recourse if that particular look is inappropriate for a particular design. Users of application generators should have several to chose from, then use the one that best matches the needs of the interface. It’s been my experience that most realistic programs will eventually have to move at least some of the interface code into a lower-level language like C or C++, however, so it’s important that your application generator be able to use low-level code as well. 4. Don’t confuse ease of learning with ease of use. This problem was once confined almost entirely to the Macintosh, but Windows has been gaining lately. The Mac was designed primarily to be easy to learn. Your great aunt Mathilde MacGilicutty could walk into a computer store and be entering recipes in no time flat. So Mathilde takes the machine home and happily enters recipes for a few months. Now she wants to take her recipes, correlate them based on chemical composition, and write a journal article on the colloidal properties of albumin-based comestibles. Dr. MacGilicutty is a good typist, normally working at about 100 words a minute, but that darn mouse keeps slowing her down. Every time her hands leave the key- board, she loses a few seconds. She tries to find a word in her document and finds that she has to bring down a menu, enter text in a dialog box, and click several buttons. At the end of the file, she has to explicitly tell the search engine to wrap to the top. (Her 15-year-old version of the vi editor let her do all this with two keystrokes—without letting go of the keyboard.) In the end, she finds that it takes twice as long to perform a common task—writing a journal article—than it used to, mostly because of user-interface problems. She didn’t need a manual to use the program, but so what? Returning to the example of a pencil from the previous section. It’s very difficult to learn how to use a pencil. It takes most children several years. (You could argue that many doctors never learn how to use one.) On the other hand, once you learn how, a pencil is very easy to use. The main issue here is that expert users often require completely different interfaces than beginners. Band aids like hot keys don’t solve this problem; the old ungainly user interface is still in the way of productivity, and it makes little difference whether you open a menu with a hot key or the mouse. It’s the menu that’s the problem. 5. Productivity can be measured in the number of keystrokes. An interface that requires fewer keystrokes (or other user actions such as mouse clicks) is better than one that requires many keystrokes to perform the same operation, even though these sorts of inter- faces tend to be harder to learn. Similarly, user interfaces that hide information in menus or under buttons are usually harder to use because multiple operations (pulling down several menus) are needed to perform a single task. The Design Process 7 Program configuration is a good example. Many of the programs that I use daily scatter configuration options under several menu items. That is, I have to pull down one menu to bring up a dialog box that configures one aspect of what the program does (to choose a font, for example). Then I have to bring down another menu to do something related (to chose a color, for example). It’s better to put all configuration options on a single screen and use screen layout to group the options functionally. 6. If you can't say it in English, you can't say it in C/C++. This rule and the ones that follow are also user-interface rules, but the “user” is the programmer using the code that you’re writing—often yourself. The act of writing out an English description of what a program does, and what each function within the program does, is really a critical step in the thinking process. A well-constructed, gram- matically correct sentence is a mark of clear thinking. If you can’t write it down, odds are that you haven’t fully thought out the problem or the solution. Bad grammar and sentence construction is also an indication of sloppy thinking. The first step of writing any program, then, is to write out what the program does and how it does it. There is some discussion about whether it’s possible to think at all without language, but I’m convinced that analytical thinking of the sort needed in computer programming is closely tied to language skills. I don’t think that it’s an accident that many of the best programmers that I know have degrees in history, English literature, and the like. It’s also not an accident that some of the worst programs I’ve seen have been written by engineers, physicists, and mathematicians who had devoted a lot of energy in school to staying as far away from English-composition classes as possi- ble. As a matter of fact, skills in mathematics serve almost no purpose in computer programming. The sort of organizational skills and analytic ability needed for programming come entirely from the Humanities. Logic, for example, was taught by the philosophy department when I was in school. The process used to design and write a computer program is almost identical to the process used to compose and write a book. The process of programming has no connection at all to the process used for solving mathematical equations. I’m differentiating, here, between computer science—the mathematical analysis of computer programs—and programming or software engineering—a discipline concerned with the writing of computer programs. Programming requires organizational abilities and language skills but not the sort of mathematical abstraction needed to do calculus. (I was forced to take a year of calculus in school but never used any of it in a computer-science class, even though it was a prerequisite to most of them, or in the real world.) Scientists, mathematicians, and engineers who use computers in their work are not necessarily programmers. I once received a peer review of a book I’d written on the subject of compiler design in which the reviewer (who taught at an Ivy League school) said that he “‘saw absolutely no relevance in including the source code for a compiler in a book about compiler design.” To his mind, one should teach the “basic principles” —the underlying mathematics and language theory—and that the imple- mentation details were “trivial.” The first comment makes sense when you realize that it was written by a computer scientist, not a programmer. The reviewer was interested only in the analysis of a compiler, not in how to write one. The second comment just shows you how much the academic elite have isolated themselves from the real work of programming. It’s interesting that the original work in language theory that made compiler-writing possible was done by a linguist—Noam Chom- sky at M.I.T.—not by a mathematician. 8 Enough Rope to Shoot Yourself in the Foot The flip side of this coin is that, when you get stuck solving a problem, one of the best ways to unstick yourself is to explain the problem to a friend. More often than not, the solution springs into your head half way through the explanation. 6.1 Do the comments first. If you followed the advice in the previous rule, the comments for your program are already done. Just take the implementation description in the document you just wrote and add blocks of code after each paragraph implementing the functionality described in the paragraph. The excuse “I didn’t have time to add in the comments” is really saying “I didn’t design the code before I wrote it and don’t have time to reverse engineer it.” If the original programmer can’t do this reverse engineering, who can? 7. Read code. All writers are readers. That’s how you learn, by seeing what other writers are doing. Strangely, programmers—who are writers of C++ or C—don’t often read code. More’s the pity. I strongly sug- gest that, at minimum, the members of a programming group should read each other’s code. The reader can find bugs that you didn’t see and make suggestions that will improve the code. The idea here is not a formal “code critique” which has a judgmental air about it; nobody wants to step on a friend’s toes, so the odds of getting useful feedback in a formal situation are small. A better approach is for you to sit down with a friend and just go through the code, explaining what it’s doing and getting some feedback and advice. For this exercise to be useful, the code’s author shouldn’t do any explaining in advance. The reader should be able to understand the code by reading it. (We've all been subjected to textbooks that were so abstruse that we couldn’t understand anything until it was explained by a professor. Though this provides job security for the professor, it doesn’t reflect well on the book’s author.) If you find yourself having to explain something to your reader, that means that your explanation should have been in the code as a comment. Add the comment as you speak; don’t put it off until the review is over. 7.1 There's no room for prima donnas in a contemporary programming shop. This is a corollary to the reading rule. Programmers who think that their code is perfect, who resent criticism rather than taking it as helpful, and who insist that they must work in private, are probably writing unmaintainable gobblygook—even if it does seem to work. (The operative word, here, is seem). 8. Decompose complex problems into smaller tasks. This is really a rule of writing as well. If a concept is too difficult to explain all at once, break it up into smaller parts and explain each part in turn. That’s what chapters in a book and sections in a chapter are all about. To give an example in programming, a threaded binary tree is like a normal tree, except that the child pointers in the leaf nodes point back up into the tree itself. One real advantage of a threaded tree is that it’s easy to traverse the tree nonrecursively by following these extra pointers. The prob- lem is that it’s difficult to come up with traversal algorithms (postorder traversal in particular). On the other hand, given a pointer to a node, it’s easy to write an algorithm that finds its postorder The Design Process 9 successor. By changing the problem definition from “do a postorder traversal’’ to “starting at the deepest node, find postorder successors until there are no more,” the problem becomes tractable: tree t; node = postorder_first( t ); while( node ) node = postorder_successor( t ); 9. Use the whole language. 9.1 Use the appropriate tool for the job. This rule is a companion to “Don’t confuse familiarity with readability,” presented below, but is typically more of a management problem. I’ve often been told that a student isn’t permitted to use some part of C or C++ (typically, it’s pointers) because it’s “not readable.” Usually, this rule is imposed by managers who know FORTRAN, BASIC, or some other language that doesn’t support pointers and who can’t be bothered to learn C. Rather than admit their knowledge is deficient, these managers would rather hamstring their programmers. Pointers are perfectly readable to a C pro- grammer. Alternatively, I’ve seen situations where management required programmers to move from a language like COBOL to C, but weren’t willing to pay for the training needed to make the move. Even worse, management paid for the training but weren’t willing to budget the time necessary to actually lear the material. Training is a full-time occupation. You can’t get “useful” work done simultaneously, and you’re throwing away money if you try. Anyway, once the managers see that their staff hasn’t been turned into C++ gurus by taking a 3-day short course, they react by limiting the parts of the language that can be used. Effectively saying “You may not use any part of C++ that isn’t like the language we used before moving to C++.” It certainly won’t be possible to exploit any of the advanced features of a language—which are after all the reason for using the language in the first place—if you restrict yourself to the “‘simplest’’ feature subset. Given these limitations, | do wonder why the change from COBOL to C was made in the first place. It’s always struck me as lunacy to make COBOL programmers use C. COBOL is a great language for database work. It has built-in primitives that make simple work of tasks that are quite difficult in C. C, after all, was designed to build operating systems, not database application pro- grams. It’s easy enough to augment COBOL to support fancy graphical user interfaces if that’s the only reason for the move to C. 10. A problem must be thought through before it can be solved. This rule, and the two that follow, started out at the head of this part of the book. I’ve moved them here because, on reflection, 1 was afraid that you’d abandon the whole part after reading them. My intent is not to sermonize, however. These rules address very real problems and, in many ways, are the most important rules in the book. The current rule is such an obvious statement, when applied to life, that it seems strange that it’s almost heresy to apply it to programming. I’m often told that “it’s impossible to spend five months in design without writing a line of code—our productivity is measured in lines of code written per day.” The people who say this usually know how to do a good design, they just don’t have the “‘lux- ury.” 10 Enough Rope to Shoot Yourself in the Foot It’s been my experience that a well-designed program not only works better (or works at all) but can be written faster and maintained more easily than a poorly designed one. An extra four months in design can save you more than four months in implementation and could save you literally years of maintenance time. You haven’t been very productive if you have to toss out the last year’s work because of a fundamental design flaw. Moreover, poorly designed programs are harder to implement. The argument that you don’t have time for design because you “have to get the program market as fast as possible” just doesn’t hold water, since it takes much longer to implement a bad (or nonexistent) design. 11. Computer programming is a service industry. I’m sometimes shocked by the contempt that some programmers have for the users of their programs, as if the “user” (said with a sneer) is a lower life form incapable of cognitive reasoning. The fact is that the entire computer exists for only one purpose: to serve the end users of our products. If nobody used computer programs, there would be no programmers. The sorry fact is that well over half of the code written every year is discarded. The programs are either never put into service or are used for only a very short time, then discarded. This represents an incredible loss of productivity, dwarfing the day-to-day productivity concerns of most managers. Think of all the startup companies producing programs that will never sell—of all the inhouse development teams writing accounting packages that won’t be used. It’s simple to see how this sorry state comes about: programmers are building programs that peo- ple don’t want. The remedy is also simple, though surprisingly difficult to implement in some environments: ask people what they want, then do what they tell you. Unfortunately, many programmers seem to believe that end users don’t know what they want. Balderdash. More often than not, the users have been so intimidated by the buzzword-rattling “expert’’ that they clam up. I’ve often been told “I know what I need, but I can’t express it.” The best reply to which is, “Well, say it in English; I’ll do the translation to Computerish.” 12. Involve users in the development process. 13. The customer is always right. No program can be successful unless the designers talk directly to the end users. Often, however, the situation is more like the game many of us played in kindergarten where 20 kids sat in a circle. Somebody whispers a sentence to his or her neighbor, who relays it to his or her neighbor, and so on around the circle. The fun is in hearing what the message sounds like when it goes full circle— usually nothing like the original message. This same process often happens in program development. A user talks to a manager, who talks to another manager, who hires a consulting company. The president of the consulting company talks to a manager of development, who talks to a team leader, who talks to the programmers. The odds of even a simple requirements document making it through this process unscathed are nil. Even worse, there’s almost no way for the developers to ask questions of the end users. The only solution to this problem is to intimately involve users in the development process, ideally by making at least one end user a part of the development team. A related situation is really just arrogance on the part of the programmer, who says “I know that users say that they want to do it that way, but they don’t know enough about computers to make an informed decision; my way is better.” This attitude virtually guarantees that the program will never be used. The cure here is to make the end user officially the person who decides that the design is okay. No one can start coding until the user-member of the team signs it off. People who ignore the The Design Process 11 design in favor of their own concoction should be fired. There’s really no room for this sort of child- ish intransigence in the real world. This is not to say that an experienced designer can’t often come up with a better solution to a problem than the one invented by an end user, especially considering that end users often come up with interfaces modeled after programs that they use regularly. You have to convince the user that your way is better before implementing it, however. A “better” interface isn’t better if nobody but you can (or will) use it. 14. Small is Beautiful. (Big == slow) Program bloat is an enormous problem. The 350MB hard disk on my laptop can hold the operating system, a stripped-down version of my compiler, a stripped-down version of my word processor, and nothing else. Back in the dark ages, I could put CP/M versions of all of these on a single 1.2MB floppy. UNIX, at the time, ran quit happily on a 16-bit PDP 11 with 64K of core (internal memory). Nowadays, most operating systems require a 32-bit machine with a minimum of 16MB of core to run at a reasonable speed. I’m convinced that a lot of this memory bloat is the result of sloppy program- ming. Over and above the space issues, you also have execution-time issues. Virtual memory is not real memory. If your program is too big to fit into core or if it is running simultaneously with other programs, it will have to be swapped to disk periodically. These swaps are time consuming to say the least. The smaller the program, the less likely that a swap will occur, so the faster it tends to run. The third issue is modularity. One of the basic philosophies of UNIX is that “small is better.” Large tasks are best accomplished by a cooperating system of small modular programs, each of which does only one task well, but each of which can communicate with the other components. (Microsoft's Object Linking and Embedding [OLE] standard adds this capability to Windows as does OpenDoc to the Macintosh.) When your application is a modular collection of small programs that work together, it becomes very easy to customize your program by swapping modules. If you don’t like the editor, swap in a new one. Finally, programs tend to get smaller as they are refined. Large programs have probably never gone through the refinement process. Looking for a solution to this problem, I notice that poorly managed program groups often create unnecessarily large programs. That is, a band of cowboy programmers, each working alone in their office and not talking to one another, will write a lot of redundant code. Instead of one version of a simple workhorse function being used throughout the system, every programmer will create their own version of the same function. Part General Development Issues This part of the book contains rules for the general development process, without getting too much yet into the details of C or C++ themselves. I do that in subsequent parts. 14 Enough Rope to Shoot Yourself in the Foot 15. First, dono harm. This rule is for maintenance programming. Back when I was a kid, I read a Science Fiction story where a hapless time traveler accidentally steps on the prehistoric equivalent of a butterfly and returns to his own time to find the world altered in horrible ways. Large computer programs are like that, touch a seemingly insignificant thing over here, and all the code over there stops working. Object-oriented design techniques exist primarily to solve (or at least get a handle on) this problem in the future, but there are millions of lines of legacy code that has to be maintained today. I’ve seen programmers change parts of programs just because they don’t like the way that the code looks. This is a bad idea. Unless you are familiar with every part of the program that will be effected by a change (and that’s almost impossible), don’t touch the code. You can quite reasonably argue that virtually none of the rules in this book apply to maintenance programming. You just can’t change existing code to bring it into line with any style guide, no matter how much you would like to do so, without running the risk of irepairable harm. The rules presented here are useful only when you have the luxury of starting from scratch. 16. Edit your code. 17. A program must be written at least twice. 18. You can’t measure productivity by volume. Back when you were taking English in school, you would never have considered handing in the first draft of a writing assignment—at least not if you expected a grade better than aC. Many computer programs are just that, however—first drafts—and they have as many problems as your first-draft essays had. All good code is first written, then edited to make it better. (I mean “edit” in the English sense of “revise,” of course.) Bear in mind that the editing will have to be done eventually because unedited code is essen- tially unmaintainable (just as your unedited essay was essentially unreadable). The creators of the program are familiar with the code and can do the editing much more efficiently than a maintenance programmer who will first have to decipher the thing before any real work can be done. Unfortunately, it looks great on a performance review when someone codes quickly but without thought to maintenance or elegance. “Wow, she produces twice the code in half the time.” Consider that some poor maintenance programmer will then have to spend eight times as long, reducing the code to half its original size, to get it usable. Lines-of-code per day, a measure of volume, is not a measure of productivity. If you need motivation other than maintenance, bear in mind that editing can be seen as the pro- cess of making something smaller. Small programs run faster. 19. You can’t program in isolation. Gerald Weinberg’s classic The Psychology of Computer Programming (New York, Van Nostrand Reinhold, 1971) has a great story about soda-pop machines. The powers-that-be in a computing center decided that too much time was being spent hanging out around the pop machines goofing off. People were making too much noise, and getting nothing accomplished, so they removed the machines. Within days, the local consultants became so overloaded with work that people couldn’t schedule time with them. The lesson is that people weren’t goofing off at all; as they were making all that noise, they were solving each other’s problems. General Development Issues 15 Isolation can be a real problem in an object-oriented design team, which must by necessity be a mix of users, designers, programmers, documenters, and so forth, all working together. Because the number of programmers in the group is often smaller than in a more-traditional design environment, it becomes difficult to find someone with whom to discuss problems; productivity suffers. Think of a weekly company party as a productivity tool. 20. Goof off. If you can’t solve an intractable problem, do something else for a while. Programmers are often most productive when they’re staring out of windows, wandering the halls with blank expressions on their faces, sitting in coffee houses drinking Café Lattes, or otherwise “goofing off.” I was a student back in the dark ages when a personal computer was an Apple I, and serious pro- gramming geeks owned S-100 boxes that you programmed by entering binary instructions from the front-panel switches, one byte at a time. (If you were lucky, you had a BASIC interpreter and a ter- minal made out of an old TV set.) The undergraduates shared a PDP 11/70 running UNIX (which ran fine on a 16-bit machine with 64K of memory—My! How things have improved.). Using a PC for your homework was not an option. The average programming class had between 40 and 80 people in it, and there were six or so pro- gramming classes going on at any given moment. Consequently, when an assignment was handed out in class, you grabbed the paper and literally ran as fast as you could down to the terminal room, where you chained yourself to the computer and started coding furiously until you finished the assignment. This could take several days. If you got up to eat or sleep, somebody else got your ter- minal, and there was a very real possibility that you couldn’t get back on in time to get the assign- ment done. Some people still program this way. This environment, of course, did not lead to well-thought-out program designs, so most of these programs were four times larger than necessary and took two times longer to debug than was required. Also, the number of lines of code written per hour decreases proportionately with the number hours that you’ve been sitting staring at a screen. (It’s a fantasy to think that you can be more productive working 12 hours a day than working 8.) At some point during my senior year, I got so frustrated trying to solve a problem that I had been beating my head against for some four hours that I logged off in disgust and stomped out. About three minutes later, while walking down the hill for a knockwurst, the solution popped into my head unbidden. This was a real revelation: you have to relax to let your brain work. Unfortunately, I couldn’t get back onto the machine, so I never did fix my bug, but at least I understood how the pro- cess should work. 21. Write code with maintenance in mind—the maintenance programmer is you. Maintenance starts immediately after writing the code, and the maintenance programmer at this Stage is usually you. It’s a good idea to keep the maintenance programmer happy. Your first con- cern, then, is for the code to be easy to read. The structure and purpose of every line should be abun- dantly clear, and when it isn’t, you should add explanatory comments. One of the reasons that the quest for mathematical proofs of program correctness is so quixotic is that there are no correct programs. Not only does every program have bugs, but the requirements change as soon as the program is put into service and the user needs some new feature, thereby intro- ducing new-and-improved bugs. Since the bugs are always with us, we should write our code to 16 Enough Rope to Shoot Yourself in the Foot make the bugs easier to find. You could restate the current rule as: Don’t be clever. Clever code is hardly ever maintainable. 21.1 Efficiency is often a bugaboo. I’ve spent hours making a subroutine more “efficient’’ without stopping to consider how often that subroutine was called—a waste of time when the routine is called only once or twice. Your code should certainly be as efficient as possible, but your primary concern is maintenance, and you should not sacrifice readability on the altar of efficiency. Code first for maintenance, then run a profiler on your program and find out where the real bottlenecks are. Armed with real information, you now know where’s it’s worth giving up a little readability in exchange for speed and can go back and make the changes. You might consider putting the original code into a comment, however, so you don’t lose it. Always bear in mind that no amount of tweaking at the code level will improve efficiency as much as a better algorithm. A bubble sort is going to run slowly, no matter how well it’s coded. Part Formatting and Documentation Formatting is important; C and C++ are hard enough to read as it is without making matters worse with bad formatting. Imagine trying to read a book that had no formatting in it: no paragraph indent- ing, no blank lines, no capitalization, no spaces following punctuation, and so forth. Maintenance is impossible in poorly formatted code. I’ve combined formatting and documentation rules in one part because formatting is one of the best documentation tools at your disposal. The “documentation” discussed in the current part is pro- gram documentation (i.e., comments, not user-level documentation.) They don’t call it “code” for nothing, and a good comment is your Captain Crunch Secret Decoder Ring. I realize that debate on formatting and commenting issues approaches religious intensity at times. Bear in mind, then, that the rules I’ve given here are only the rules that I use. There are other perfectly reasonable ways to do things. On the other hand, some half-wit once told me that “It doesn’t matter what formatting style you use, as long as you use it consistently.” Code that’s format- ted consistently bad is worse than code that is occasionally readable. Accidental lucidity is better than none at all. 18 Enough Rope to Shoot Yourself in the Foot 22. Uncommented code has no value. A program that takes a year to write could be in use for 10 years. You’re going to spend a lot more money on maintenance than you will on the initial creation, and uncommented code is unmaintain- able. A “brilliant’’ programmer who, in one-third the time used by everybody else, writes short, elegant, but uncommented code is costing you money. Some less talented programmer is going to have to spend 10 times longer than necessary fixing the inevitable bugs. Programmers who can’t write English (or whatever language that’s spoken in the country where the maintenance is going to go on) are producing time bombs, not computer programs. Since good documentation is so critical to the maintenance process, it’s important that programmers be capable of writing it. For this reason, entry-level programmers with degrees in English, history, and other liberal-arts disciplines are often better bets than people with degrees in the hard sciences (math, phy- sics, and so forth.) The hard-science people rarely know how to write, and most of them don’t know how to program either; they’re taught how to code up an algorithm, not how to write a maintainable computer program. Fortunately, writing is a skill that is easily learned. Of course, if you follow the “Do the com- ments first” rule, you'll have all your comments written before you start coding. 23. Put the code and the documentation in the same place. Once the documentation is separated from the code, it’s very difficult to keep it up to date. Conse- quently, the bulk of your documentation should be in the comments, not in a separate document. If you really need fancy printed documentation, you can use something like Web (for Pascal) or CWeb (for C and C++) in conjunction with TeX.! I use a similar system called arachne that I developed in order to write my book Compiler Design in C. (Arachne documents C and C++, using troff as the typesetting engine.) All of these programs let you put the source code and documenta- tion in a single file. You can extract the source code to compile it, or you can submit the file to a word processor to print a combined source/documentation manual. The systems allow symbolic cross referencing between the code and document, allow you to reference pieces of code to other pieces of code (“‘this code is used over there”), and so forth. Because a normal text processor is used for the printed version, you can do things not easy to accomplish in comments—inserting pictures, for example. 24. Comments should be sentences. They should be properly spelled and punctuated, without abbreviations if possible. Don’t turn your comments into a secret code by using strange abbreviations and inventing your own grammatical structure. You shouldn’t need that Captain Crunch Secret Decoder Ring to decipher the comments, too. 1 Web is described in Donald Knuth, The WEB System of Structured Documentation (Palo Alto: Stanford University Dept. of Computer Science, Report No. STAN-CS-83-980, 1983). CWeb is described in Donald E. Knuth and Silvio Levy, The CWEB System of Structured Documentation (Reading: Addison Wesley, 1994). Both documents not only describe how the system works, but demonstrate it as well. The books document the actual source code using the system that the source code implements. TeX is Knuth’s typesetting system. It is available in several commercial versions. Formatting and Documentation 19 25. Run your code through a spelling checker. Not only will your comments be more readable, but this practice will encourage you to use variable names that are readable, because they’re actual words. For this reason I suggest not using the check-only-words-in-comments feature available in some editors. Common abbreviations (like i, j, p, str, buf, etc.) can be added to your spell checker’s exception dictionary. 26. A comment shouldn't restate the obvious. Again, beginning C programmers tend to fall into this trap. Avoid obviously ridiculous thing like: +4+x; // increment x but I also don’t like comments like: Your average programmer knows what a definition looks like. 27. A comment should provide only information needed for maintenance. A particularly obnoxious, and worthless, comment is the formulaic header block. A header in itself is not evil, quite the contrary. A comment block at the top of a file that describes what’s going on in the file can be quite useful. A good one tells you what functionality the file implements, provides a list of public (non static) functions, tells you what these functions do, etc. The headers that I don’t like are the ones whose contents are determined by fiat, typically by some sort of company-wide style guide. These sorts of headers usually look like Listing 1, increas- ing clutter with copious amounts of worthless information at the expense of readability. It is often the case, as in Listing 1, that the header is considerably larger than the actual code. It’s also com- mon, as is the case here, that the code is either perfectly self-documenting or needs only a line or two of comments to make it so. Though it might warm the cockles of an autocratic bureaucrat to require this nonsense, it does little to improve maintenance. Listing 1. A worthless header comment. ** DATE: 29 February, 2000 ee ** FUNCTION: ** equal +e ** AUTHOR: ** Joseph Andrews ae COIDNAWNH ** DESCRIPTION: ** The purpose of this function is to compare two strings ** for lexicographic equality. at ** EXCEPTIONS: 20 Enough Rope to Shoot Yourself in the Foot Listing 1. continued. .. 15 ** The function doesn’t work for unicode strings. a 16 ** +e ** SPECIAL REQUIREMENTS: te ** none. la +e ae ** ARGUMENTS: ae ** char *s1; A pointer to the first string to compare cae ** char *s2; A pointer to the second string to compare aa +e +e ** OUTPUTS: ae ** The function returns true if the argument strings are ** ** lexicographically identical. ae +e te ** COMMENTS: te ** none mae +e +e ** IMPLEMENTATION NOTES: +e ** None. we ae ae ** REVISION_HISTORY: te te +e ** AUTHOR: Andrews, Joseph ae ** DATE: 12, July, 1743 +e ** CHANGE: Initial ee +e ae ** AUTHOR: Jones, Tom aa ** DATE: 13, July, 1743 maa ** CHANGE: Changed names of arguments from strl, str2. a *e +e ** All code in this file is copyright (c) Fictitious Inc. *e ** All rights reserved. i ee +e ** No part of this subroutine may be reproduced in any form ** ** whatsoever without explicit permission in triplicate from** ** the department of redundancy department. Violators will ** ** be required to hand over their firstborn male child. ae *e */ inline equal( char *sl, char *s2 ) { return !strcmp(sl,s2); // Return true if strings are equal. } The real problem is that this sort of header violates many other rules: “don’t comment the obvious,” “eliminate clutter,” and so forth. What little real information that is supplied in the header belongs in a revision-control system, not in the source code. Comments in the code should tell you things that are useful for maintenance. Formatting and Documentation 21 28. Comments should be in blocks. Comments are generally best if placed in multiline blocks alternating with blocks of code. That is, the comment should be describing at a high level what the next several lines are doing. If every other line is a comment, it’s like reading two books at the same time, one line from the first, then one line from the second, and so on. If the code that you’re commenting is complex, you can use foot- notes: // Here is a block comment describing the block of code that follows. // After a general summary, I describe some specifics: // // 1. This comment describes what’s happening on the line // labeled with a 1 // 2. This comment describes what’s happening on the line // labeled with a 2 // At point 1, below, the algorithm is positioned to... here_is_the_code() ; while( some_condition ) { this_code_is_rather_obscure(); ae eI } more_stuff_here(); while( some_condition ) { this_code_is_also_obscure() ; ae ne } 29. Comments should align vertically. Align the /* and */ vertically in multiline comments. /* First line, * second line, * third line. */ If your compiler supports it, C++ style comments help here: // First line, // second line, // third line. There are two reasons for this rule, both demonstrated in the following code: 22 Enough Rope to Shoot Yourself in the Foot [EGO OOO OOOO mt void the_function( void ) Here is a multiline comment, doing all of the things that a comment should do. Unfortunately, the lack of a vertical line of stars to the left makes it difficult to visually separate the comment from the code FOI III IIIS SIO IIIDI IIIS ISIS I EE EEE ot void the_function( void ) { } // here is the actual function. code_goes_here(); OGIUISIUIGUIOIEIGIUISUOIIIIGISIISIUOUISEIOIIOSIG IOSD GE titi ttt / First, did you notice that I forgot to put a / at the far right of the second line of stars? It’s easy to lose entire functions this way. Second, it’s hard to see where the comment stops and the code begins. Fix both problems as follows: [RSIS IESE DIE tit mtt * 4 Oe OH HOH * void the_function( void ) Here is a multiline comment, doing all of the things that a comment should do. The vertical line of stars to the left makes it easy to visually separate the comment from the code Serer eee eee ee eee eee ee eee eee eee eee ee See eee eee eee ee eee eee eee eee ee eee ee */ void the_function( void ) { // here is the actual function. code_goes_here(); 30. Use neat columns as much as possible. Because formatting is really a kind of comment, the foregoing rule applies to the actual code as well. The following two blocks are functionally identical, but note how much easier it is to find variable names in the second block, not just because of the comment alignment, but because the names fall into a neat column as well: Formatting and Documentation 23 int x; // Describe what x does. unsigned long int (*pfi)(); // Describe what pfi does. const char *the_variable; // Describe what the_variable does. int z; // Describe what z does. xX = 10; // Comment goes here the_variable = x; // Another comment goes here 2 = x; // Here’s another. as compared to: int Xi // Describe what x does. unsigned long int ( *pfi )(); // Describe what pfi does. int 2; // Describe what z does. const char *the_variable; // Describe what the_variable does. x = 10; // Comment goes here the_variable = x; // Another comment goes here z =X // Here’s another. You can look at this sort of formatting as “tabular” in nature, as if I was creating a table with a “type” column, a “name” column, and a “description” column. Another good use of columns is in a C++ member-initialization list, which I format as follows: class derived : public base { string str; const int Xi public: derived( char *init_str, int init_x ){} } derived: :derived( char *init str, int init_x ) :base( str, xX ) ,str ( init_str ) 1X ( init_x ) {} 31. Don’t put comments between the function name and the open brace. The main problem with: foo( int x ) /* Don’t put * comments */ here. { Thea } is that the function body might end up on the next screen or page. Also, readers can’t easily tell whether they’re looking at a prototype or an actual definition. Move the comment either above the function name or into the function body beneath the open brace: 24 Enough Rope to Shoot Yourself in the Foot /* Either put ** it here. */ foo( int x ) { /* Or put it here, ** indented at the same level as the code. */ } 32. Mark the ends of Jong compound statements with something reasonable. First of all, end-of-block comments such as: while( a = 0; ) { f( i); } // for } // while do nothing but add clutter when the blocks are short. I use them only when the compound statement is too large to fit on a screen (in my editor about 40 lines) or when there is so much nesting that I can’t keep track of what’s going on. End-of-block comments are certainly worthwhile when the compound statements are large, but I’ve often seen code like this: On page 1: while( a = 0; ) ‘ for( j = 10; --j >= 0; ) // lots of code goes here On some subsequent page: } // for } // for } // while } // while These comments are too terse to be useful. End-of-block comments should identify the controlling statement completely. The end-of-block comments from the previous example should look like this: d// for( j = 10; --j >= 0; ) } // for( i = 10; --i >= 0; ) } Jf while( something_else() ) } // while(a

You might also like