Electronic Review of Computer Books

[ ERCB Home | New | Feature | Brief | DDJ | Letters | Links ]

[an error occurred while processing this directive]

Vital Statistics

Title The Practice Of Programming
Authors Brian W. Kernighan and Rob Pike
Publisher Addison-Wesley Longman
Reading, Massachusetts
http://www.awl.com/
Copyright 1999
ISBN 0-201-61586-X
Pages 267
Price $24.95


Programmers Are More Than API Users

Most of the literature geared towards the category of professionals generically denominated "programmers" or "software developers" puts a strong emphasis on the importance of a few types of narrow-focused competencies. It is undeniable that the majority of the productive time of a modern computer programmer is in fact spent coding against some sort of API (short for Application Programming Interface), which abstracts his/her work from the low level pieces of hardware, or manipulates logical resources and objects faked by the operating system. As the technology progresses, new APIs and object models make their appearance, and the programming community needs manuals and reference guides to leverage them. That is the motivation for the abundance of texts covering very specific and relatively short-lived topics, which pair up with those covering programming languages in countless diverse flavors.

But as everyone with more than a few months of direct experience in this field knows well, a good software developer is lot more than an informed user of one programming language associated with a few APIs. There are several fundamental skills that cannot be overlooked in the preparation of a proficient software engineer (although more often than not they are), and they go well beyond the purely implementative level of the aforementioned manuals.

Subjects like debugging (in the original sense of producing measures to prevent and discover errors, not in the recent connotation of "using a debugger"), testing, spotting common mistakes, performance trade-offs, optimizations, and portability sit atop all the other quotidian problems. No matter what the hot technology of the day is, writing software ultimately passes through the phase in which the developer has a sufficiently clear idea of the needs, the constraints, and the solution to utilize, but needs to concretize his logical comprehension of the project into real, working, solid code. If you ask the average professional programmer, you will discover how the kind of background I outlined derives prominently from personally committed mistakes, not from much theoretical study -- countless try-fail-retry-learn cycles that do surely help, but many times could be simply avoided if a good source of "programming wisdom" existed.

To be honest, there are quite a few books around that teach algorithms and the fundamentals of computer programming. The problem is that those books are commonly designed to support academic classes in computer science, and consequently shine on the theoretical side but leave something to be desired on the pragmatic front.

The Practice of Programming is a great candidate to fill this widely perceived lack in the literature that I commonly refer to as "for the industry." Authored by two experienced researchers of the Computing Science Research Center at the well-known Bell Labs (the name Brian Kernighan will ring a bell to the millions of C programmers), this manageable text conveys a fantastic quantity of suggestions and guidelines that will come in useful to all the neophytes of programming, and at the same time provides some sound tips and principles to the more seasoned among us. The first chapter approaches the delicate topic of good coding style; while the opinions on this are always subjective, those expressed by the authors seem generally acceptable and worth following.

Subsequent chapters touch on design principles, testing practices, common algorithms (this chapter is too short to be of any real usefulness, I would have left it out), debugging (very good chapter), performance tuning, general portability hints, and a glance at notations. Nothing really new, but a remarkable collection of the rules typically taught by experience (the hard way). Most of the code samples are written in C, accompanied by a few others in C++ and Java, but by its very nature all the contents are absolutely language independent.

Another language that the two authors prove to be able to use very well is English: The style remains extremely clear throughout the entire book and greatly simplifies understanding the points made by the authors. Very solid and very educational, this manual is one I highly recommend to all professional programmers (and would-be programmers as well).

-- Davide Marcato (marcato@programmers.net or http://www.DavideMarcato.com)


Excerpts from "The Practice Of Programming"

(From the Preface)

Have you ever...

wasted a lot of time coding the wrong algorithm?

used a data structure that was much too complicated?

tested a program but missed an obvious problem?

spent a day looking for a bug you should have found in five minutes?

needed to make a program run three times faster and use less memory?

struggled to move a program from a workstation to a PC or vice versa?

tried to make a modest change in someone else's program?

rewritten a program because you couldn't understand it?

Was it fun?

These things happen to programmers all the time. But dealing with such problems is often harder than it should be because topics like testing, debugging, portability, performance, design alternatives, and style --the practice of programming-- are not usually the focus of computer science or programming courses. Most programmers learn them haphazardly as their experience grows, and a few never learn them at all.

(From Chapter 4 - "Interfaces - Resource Management," page 107)

Free a resource in the same layer that allocated it.

One way to control resource allocation and reclamation is to have the same library, package, or interface that allocates a resource be responsible for freeing it. Another way of saying this is that the allocation state of a resource should not change across the interface. Our CSV libraries read data from files that have already been opened, so they leave them open when they are done. The caller of the library needs to close the files.

C++ constructors and destructors help enforce this rule. When a class instance goes out of scope or is explicitly destroyed, the destructor is called; it can flush buffers, recover memory, reset values, and do whatever else is necessary. Java does not provide an equivalent mechanism. Although it is possible to define a finalization method for a class, there is no assurance that it will run at all, let alone at a particular time, so cleanup actions cannot be guaranteed to occur, although it is often to assume they will.

Java does provide considerable help with memory management because it has built-in garbage collection. As a program runs, it allocates new objects. There is no way to deallocate them explicitly, but the run-time system keeps track of which objects are still in use and which are not, and periodically returns unused ones to the available memory pool.

There are a variety of techniques for garbage collection...

(From Chapter 8 - "Portability - Language," page 195)

Alignment of structure and class members.

The alignment of items within structures, classes, and unions is not defined, except that members are laid out in the order of declaration. For example, in this structure,

struct X {
char c;
int i;
}

the address of i could be 2, 4, or 8 bytes from the beginning of the structure. A few machines allow ints to be stored on odd boundaries, but most demand that an n-byte primitive data type be stored at an n-byte boundary, for example that doubles, which are usually 8 bytes long, are stored at addresses that are multiple of 8. On top of this, the compiler writer may make further adjustments, such as forcing alignment for performance reasons.

You should never assume that the elements of a structure occupy contiguous memory. Alignment restrictions introduce "holes"; struct X will have at least one byte of unused space. These holes imply that a structure may be bigger than the sum of its member sizes, and will vary from machine to machine. If you're allocating memory to hold one, you must ask for sizeof(struct X) bytes, not sizeof(char) + sizeof(int).

Bitfields.

Bitfields are so machine-dependent that no one should use them.

This long list of perils can be skirted by following a few rules. Don't use side effects except for a very few idiomatic constructions like

a[i++] = 0;
c = *p++;
*s++ = *t++;

Don't compare a char to EOF. Always use sizeof to compute the size of types and objects. Never right shift a signed value. Make sure the data type is bug enough for the range of values you are storing in it...


Table of Contents

Preface

Chapter 1: Style

1.1 Names

1.2 Expressions and Statements

1.3 Consistency and Idioms

1.4 Function Macros

1.5 Magic Numbers

1.6 Comments

1.7 Why Bother?

Chapter 2: Algorithms and Data Structures

2.1 Searching

2.2 Sorting

2.3 Libraries

2.4 A Java Quicksort

2.5 O-Notation

2.6 Growing Arrays

2.7 Lists

2.8 Trees

2.9 Hash Tables

2.10 Summary

Chapter 3: Design and Implementation

3.1 The Markov Chain Algorithm

3.2 Data Structure Alternatives

3.3 Building the Data Structure in C

3.4 Generating Output

3.5 Java

3.6 C++

3.7 Awk and Perl

3.8 Performance

3.9 Lessons

Chapter 4: Interfaces

4.1 Comma-Separated Values

4.2 A Prototype Library

4.3 A Library for Others

4.4 A C++ Implementation

4.5 Interface Principles

4.6 Resource Management

4.7 Abort, Retry, Fail?

4.8 User Interfaces

Chapter 5: Debugging

5.1 Debuggers

5.2 Good Clues, Easy Bugs

5.3 No Clues, Hard Bugs

5.4 Last Resorts

5.5 Non-reproducible Bugs

5.6 Debugging Tools

5.7 Other People's Bugs

5.8 Summary

Chapter 6: Testing

6.1 Test as You Write the Code

6.2 Systematic Testing

6.3 Test Automation

6.4 Test Scaffolds

6.5 Stress Tests

6.6 Tips for Testing

6.7 Who Does the Testing?

6.8 Testing the Markov Program

6.9 Summary

Chapter 7: Performance

7.1 A Bottleneck

7.2 Timing and Profiling

7.3 Strategies for Speed

7.4 Tuning the Code

7.5 Space Efficiency

7.6 Estimation

7.7 Summary

Chapter 8: Portability

8.1 Language

8.2 Headers and Libraries

8.3 Program Organization

8.4 Isolation

8.5 Data Exchange

8.6 Byte Order

8.7 Portability and Upgrade

8.8 Internationalization

8.9 Summary

Chapter 9: Notation

9.1 Formatting Data

9.2 Regular Expressions

9.3 Programmable Tools

9.4 Interpreters, Compilers, and Virtual Machines

9.5 Programs that Write Programs

9.6 Using Macros to Generate Code

9.7 Compiling on the Fly

Epilogue

Appendix: Collected Rules

Index


Quick Rating

Readability Star Star Star Star
Originality Star Star Star
Organization Star Star Star HalfStar
Accuracy Star Star Star HalfStar
Consistency Star Star Star
Depth Star Star Star
Timeliness Star Star Star
Editing Star Star Star HalfStar
Design Star Star Star HalfStar
Overall Value Star Star Star HalfStar

Explanation of ERCB rating scale: No stars = unacceptable, 1 Star = marginal, 2 Stars = average, 3 Stars = above average, 4 Stars = exceptional.


Copyright ©1999 Electronic Review of Computer Books
Created 5/2/1999 / Last modified 5/2/1999 / webmaster@ercb.com