HAYDEN BOOKS
046294
C with Excellence
ered a aco reiiy
Deemer eT
Pee UCC
with
EXCELLENCESf
HOWARD W. SAMS & COMPANY
HAYDEN BOOKS
Related Titles
Advanced C Primer ++
‘Stephen Prata, The Waite Group
C Programmer's Guide
to Serial Communications
Joe Campbell
MS-DOS® Bible
Steven Simrin, The Waite Group
Discovering MS-DOS?
Kate O'Day, The Waite Group
MS-DOS® Developer's Guide
John Angermeyer and Kevin Jaeger,
The Waite Group
Tricks of the MS-DOS® Masters
John Angermeyer, Rich Fahringer,
Kevin Jaeger, and Dan Shafer, The Waite Group
Inside XENIX®
Christopher L. Morgan, The Waite Group
UNIX® Primer Plus
Mitchell Waite, Donald Martin,
‘and Stephen Prata, The Waite Group
Advanced UNIX® —
‘A Programmer's Guide
Stephen Prata, The Waite Group
UNIX® Shell Programming Language
Rod Manis and Mare Meyer
UNIX® System V Bible
‘Stephen Prata and Donald Martin,
The Waite Group
UNIX® System V Primer,
Revised Edition
Mitchell Waite, Stephen Prata,
‘and Donald Martin, The Waite Group
C Primer Plus,
Revised Editi
Mitchell Waite, Stephen Prata,
‘and Donald Martin, The Waite Group
Turbo C Programming for the IBM
Robert Lafore, The Waite Group
(forthcoming)
Quick C Programming
Carl Townsend
Worthcoming)
UNIX® Communications
Bryan Costales, The Waite Group
YM/CMS Handbook
Howard Fosdick
Hayden Books
UNIX System Library
UNIX® Shell Programming
‘Stephen G. Kochan and Patrick H. Wood
UNIX® System Security
Patrick H. Wood and Stephen G. Kochan
UNIX® System Administration
David Fieldler and Bruce H. Hunter
Exploring the UNEX® System
‘Stephen G. Kochan and Patrick H. Wood
UNIX® Text Processing
Dale Dougherty and Tim O'Reilly
c
Programming
Stephen G. Kochan
Topics in C Programming
Stephen G. Kochan and Patrick H. Wood
For the retailer nearest you, or to order directly from the publisher,
call 800-428-SAMS, In Indiana, Alaska, and Hawaii call 317-298-5699.EXCELLENCE
Programming
Proverbs
HENRY FE LEDGARD
with JOHN TAUER
HAYDEN BOOKS
| Dinision of Howard W. Sams € Company
4300 West 62a Street
Indianapolis, tian 46268 US \© 1987 by Henry F Ledgard
FIRST EDITION
FIRST PRINTING— 1987
All rights reserved. No part of this book shall be reproduced, stored in a
retrieval system, or transmitted by any means, electronic, mechanical,
photocopying, recording, or otherwise, without written permission from the
publisher No patent liability is assumed with respect to the use of the
information contained herein, While every precaution has been taken in the
preparation of this book, the publisher assumes no responsibility for errors or
omissions. Neither is any liability assumed for damages resulting from the
use of the information contained herein.
International Standard Book Number: 0-672-46294-X
Library of Congress Catalog Card Number: 87-60648.
Acquisitions Editor: Michael Violano
Editor: Louis Keglovits
Interior Designer: T: R. Emrick
Cover Illustration: Keni Hill, Meridian Design Studio Inc,
Composition: Photo Comp Corp, Brownsburg, IN
Printed in the United States of America
LightspeedC is a trademark of THINK Technologies, Inc.CONTENTS
FOREWORD ix
PREFACE xi
TO THE READER xiv
PART ONE PROGRAMMING PROVERBS 1
Introduction 3
1 AGood Start Is Half the Race 7
Proverb1 Don't Panic
Proverb 2 Question the Problem 8
Proverb 3 Define the Problem 14
Proverb 4 Document First 16
Proverb 5 Think First, Code Later 18
Proverb 6 Proceed Top-Down 19
Proverb 7 — Beware of Other Approaches 22
2 Structure Is Logic 27
Proverb 8 Code it for the Other Guy 27
Proverb9 Make Files into Packages 30
Proverb 10 Black-Box Your Subroutines 35
Proverb 11 Don't Goto 40
3 Coding the Program a7
Proverb 12 All Words Mean Something 47
Proverb 13 Spaces Have Meaning, Too 51
Proverb 14 Comment for Content 61
Proverb 15 Make Constants Constant 65
Proverb 16 Name All Types 71
Proverb 17 _ Get the Syntax Correct Now 7C WITH EXCELLENCE: Programming Proverbs
Proverb 18 Remember Your Reader 75
Proverb 19 Your Output Is Not Only for You 82
Proverb 20 Review, Review, Review 85
Proverb 21 Careful Programmers Don't Have Accidents 88
And of Course. . . 93
Proverb 22 Have Someone Else Read the Work 93
Proverb 23 What Have You Read Lately? 95
Proverb 24 Don't Be Afraid to Start Over 97
Exercises for Part I 98
Part Two BEYOND THE PROVERBS 415
5 Program Standards 117
General Requirements 118
Declarations 123
Statements 127
Spacing 132
6 Potpourri 135
Global Variables 135
Selecting Names 145
Recu 154
Ficiency 159
2 Case Against Program Flowcharts 163
es for Part II 168
PartThree THE PROVERBS IN ACTION 173
Top-Down Programming 175
Programmer's Christmas Workshop 175
p-Down Programming Revisited 206
sercises for Part IIT 210
8 C Programmers—Wake Up 213CONTENTS
Appendix A Summary of Program Standards 217
Appendix B_ Program for Kriegspiel Checkers 224
Bibliography 249
Index 253FOREWORD
To the original edition of Programming Proverbs,
Hayden Books, 1975
By necessity, computer science, computer education, and com-
puter practice are all embryonic human activities, for they have
existed for only a single generation, From the beginning, pro-
gramming has been a frustrating black art, with individual abil-
ities ranging from the excellent to the ridiculous and often
exhibiting very little in the way of systematic mental procedure.
In a sense, the teaching of programming through mistakes and
debugging can hardly be regarded as legitimate university-level
course work. At the university level we teach such topics as the
notion of an algorithm, concepts in programming languages,
compiler design, operating systems, information storage and re-
trieval, artificial intelligence, and numerical computation; but in
order to implement ideas in any of these functional activities, we
need to write programs in a specific language.
Students and professionals alike tend to be overly optimistic
about their ability to write programs or to make programs work
according to preestablished design goals. However, we are begin-
ning to see a breakthrough in programming as a mental process.
This breakthrough is based more on considerations of style than
on detail. It involves taking style seriously, not only in how pro-
grams look when they are completed, but in the very mental pro-
cesses that create them. In programming, it is not enough to be
inventive and ingenious. One also needs to be disciplined and
controlled in order not to become entangled in one’s own
complexities.
In any new area of human activity, it is difficult to foresee la-
tent human capabilities. We have many examples of such ca-
pabilities: touch typing, speed writing, and 70-year-old
grandmothers who drive down our highways at 70 miles an hour.
Back in 1900, it was possible to foresee cars going 70 miles an
hour; but the drivers were imagined as daredevils rather than as.
grandmothers, The moral is that in any new human activity one
generation hardly scratches the surface of its capabilities. So it
will be in programming as well.
ixC WITH EXCELLENCE: Programming Proverbs
The next generation of programmers will be much more com-
petent than the first one. They will have to be. Just as it was easier
to get into college in the “good old days,” it was also easier to get
by as a programmer in the “good old days.” For this new genera-
tion, a programmer will need to be capable of a level of precision
and productivity never dreamed of before.
This new generation of programmers will need to acquire dis-
cipline and control, mainly by learning to write programs cor-
rectly from the start. The debugging process will take the new
form of verifying that no errors are present, rather than the old
form of finding and fixing errors over and over (otherwise known
as “acquiring confidence by exhaustion”), Programming is a se-
rious logical business that requires concentration and precision.
In this discipline, concentration is highly related to confidence.
In simple illustration, consider a child who knows how to play
a perfect game of tic-tac-toe but does not know that he knows. If,
you ask him to play for something important, like a candy bar, he
will say to himself, “I hope I can win.” And sometimes he will
win, and sometimes not. The only reason he does not always win
is that he drops his concentration. He does not realize this fact
because he regards winning as a chance event. Consider how
different the situation is when the child knows that he knows
how to play a perfect game of tic-tac-toe. Now he does not say, “I
hope I can win.” He says instead, “I know I can win; it’s up to me!”
And he recognizes the necessity for concentration in order to en-
sure that he wins.
In programming, as in tic-tac-toe, it is characteristic that con-
centration goes hand in hand with justified confidence in one’s
own ability. It is not enough simply to know how to write pro-
grams correctly. The programmer must know that he knows how
to write programs correctly, and then supply the concentration
to match.
This book of proverbs is well suited to getting members of the
next generation off to the right start. The elements of style dis-
cussed here can help provide the mental discipline to master
programming complexity. In essence, the book can help a pro-
grammer make a large first step on the road to a new generation
of programming.
HARLAN D. MILLS
Federal Systems Division, IBM
Gaithersburg, Maryland
xPREFACE
Cis a programming language that evokes strong reactions. I have
heard many passing remarks. They run rov:* * .< this:
Cis the language of choice.
Cis the alternative to assembler languag,
Cis for “real” software, i.e., systems programming,
The speed is it.
The syntax is dreadful
© It's a hacker's language.
© Cis wonderful.
While I have my own reservations about t G, the ian-
guage has achieved a notable status. The ittle doubt
that its ability to promote systems progra: id avoid as-
sembler language is a major achievement.
Many years ago the first version of th. Programming
Proverbs” appeared. This work was motivated by a small book
called The Elements of Style, written by William Strunk, Jr, and
revised by E. B. White. Originally conceived in 7918, Strunk's book
stressed the need for rigor, conciseness, and clarity in tie writing
of English prose. In like manner, this C version of the Proverbs is
intended for C programmers who want to write carefully con-
structed, readable programs.
Over the years much has been learned about programming.
Each successive version of the Programming Proverbs has incor-
porated this knowledge. Each version has been proved both in
form and content. Nevertheless, writing this ve. .on in C has not
been easy. Two factors stood out:
© Efficiency is a vital part of C, and this can get in the way of
readability.C WITH EXCELLENCE: Programming Proverbs
xii
© The syntax of the language, in several places, resists clarity.
It is fine to say that Construct A is clearer than Construct B, but if
Construct B is hardly efficient or so unusual in C, the justification
of Construct B is seriously weakened.
The dilemma, though, is even deeper. The need for clarity is
especially great in C. The C language is system oriented and thus
is used for “real” software, that is, software maintained for years.
C programmers should put craftsmanship and elegance as a pri-
mary design goal. Unfortunately, the power of C is often mis-
directed, excuses are made, and impenetrable code is often
written.
This book is designed not as an introduction to the details of
C, but as a guide to better programming. It should be of value to
all programmers who have some familiarity with C. As such, it
may be used as a supplement in courses where C programming
is a major concern, or as an informal guide to those who have an
interest in improving software quality.
I strongly believe that the ideas presented should go hand in
hand with learning the C language itself. The reader who dis-
misses the overall objective of this book with the comment, “I've
got to learn all about C first,” may be surprised to find that the
study of good programming practices in conjunction with the
basics of the language may reap quick and longstanding
rewards.
C with Excellence is organized in three major parts. After the
opening statement of the Introduction, Part One gives a collec-
tion of simple rules, called proverbs. The proverbs summarize in
terse form the major ideas of this book.
Part Two elaborates on several important and sometimes con-
troversial ideas discussed in the chapter on programming
proverbs. It includes a chapter on program standards, giving the
programming conventions used in this book. These rules have
been strictly followed here.
Part Three captures the point of the entire text with an exam-
ple of a strict top-down approach to a programming problem.
The approach is oriented toward the easy writing of complete,
correct, readable programs. The approach hinges on developing
the overall logical structure of the program first. Moreover, the
programming proverbs are brought into action during the pro-
gram’s development. This chapter concludes with a full-scalePREFACE
program, given in Appendix B. The program is intended as a
model of good programming.
As mentioned above, this work is based on several pre-
decessors, the so-called “Programming Proverbs” series: Pro-
gramming Proverbs, Programming Proverbs for FORTRAN
Programmers, Cobol with Style, BASIC with Style, FORTRAN with
Style, Pascal with Style, and, notably, Pascal with Excellence. Many
people assisted or inspired these earlier works, including
William Cave, Leslie Chaikin, Louis Chmura, Joseph Davison,
William Fastie, Jon Hueras, Michael Marcotty, Paul Nagin, and
Andrew Singer
Frank Lhota helped me start this C version. Mitch Gart de-
serves a special bow. He explained the inner logic of C to me, an-
swered my odd questions, and has a gift in explaining things
quietly and well. Stephen Kochan offered a thoughtful, profes-
sional review of this work.
The motivation for doing this current work owes much to my
experience in teaching professional programmers for full-time
seven-week courses at Philips Electronics in Europe. These
courses were under the brilliant direction of Allen Macro, as-
sisted by John Buxton. The participants in the courses, together
with my colleague Nat Macon, deepened my commitment to
excellence.
The programs given here follow the commonly accepted ver-
sion of C. For a definition of the common basis of C, see the ANSI
standard definition of C. The programs given here were run with
THINK Technologies LightspeedC on the Macintosh. For a good
tutorial on C with a good sense of craftsmanship, I recommend A
book on C [Kelley and Pohl, 1984].
C with excellence is bascically my own personal statement
about programming, There are likely to be things you disagree
with. But, above all else, I firmly believe that the study of
guidelines for good programming can be of great value to all pro-
grammers and that there are principles that transcend the tech-
niques of any individual practitioner
Henry Ledgard
RFD 3
Amherst, MA 01002TO THE READER
In a profession where change is the order of the day, program-
mers tend to be thrown off the foundations over which the pro-
fession was built. We rush for solutions often before we examine
the problem in depth. Sometimes we succeed. But we pay a
price. The price is software that is far from excellent, both in its
user interface and its underlying quality.
This book is a two-fold response to these observations. First,
the programming proverbs represent years of thinking about
some basic issues. This is a substantive work, about issues that
count.
And second, this work is lighthearted in many ways. The
approach is not directed away from the technical issues but
toward conveying key ideas with good spirit. The purpose is to
teach the fledgling and (I hope) remind the veteran that certain
principles need to be reviewed now and again.ONE
PROGRAMMING
PROVERBSINTRODUCTION
Thave seen the future, and it works.
Lincoln Steffens
Whenever anyone discusses the “state of the art” in a discipline
or profession, the purpose is to make a statement of what “is” to
what “can be” orwhat “ought to be.” This was an inherent objec-
tive of the earlier editions of the Programming Proverbs Series.
Now, many years later, it is somewhat disheartening for me to re-
port that what “was” still “is.”
Seldom are the results of a programming development project
concluded with a proud exclamation: “It worked right the first
time!” That programs seldom work right the first time is not the
only indication of the state of the art. Sometimes programs work
only part of the time, some never work at all. Of the ones that do,
their utility is lost in the arduous and often painful effort to
maintain them. Writing high-quality programs is possible, but
unusual. I ask the question: Why is the product program so poor
so often, ie., why don’t programmers succeed more often? The
reasons are simple. First, programming is difficult, especially as
programmers must keep pace with an expanding and demand-
ing technology—ours is a profession that is never static, even
from one project to the next.
Second, there are very few principles for developing and writ-
ing good programs. We tend to think that the principles of pro-
gramming lie in the perfection and execution of the algorithm
itself and, once we have learned the programming language, all
that needs to be known is understood. The result is that pro-
grammers conceive procedures to work around the difficulties
3(C WITH EXCELLENCE: Programming Proverbs
of programming—procedures that are often born in the heat of
development—but they have few principles to rely upon for real
success.
The concern of the computer industry about the quality of
software has persisted for many years. Surely the time has come
for programmers to write programs that work correctly the first
time—or at least the tenth or twentieth time—programs that do
the job even when the original problem was poorly conceived;
programs that are easy to read, understand, and maintain.
Some may think attempts to correct such practices are unre-
alistic, especially considering their past experiences of constant
revision, debugging, and difficulties in deciphering code. There
are, however, well-founded principles that can be utilized to
achieve these goals. Some of these principles I shall present are
obvious, even to novice programmers. Others, even experienced
programmers might debate. However, before any principle is re-
jected, it must be remembered that a program is not only a set of,
descriptions and instructions for a computer, but a document
that must be understood by human beings—for you, the pro-
grammer first of all, and for all others who will read your pro-
gram in the process from development to maintenance.
It is well known that the cost of program development and
maintenance is high and growing higher. In fact, I have heard it
said that the cost of program maintenance on some poorly con-
structed systems is a hundred times greater than the cost of ini-
tial development and testing. To attack these costs, methodology
and clarity must be early programming concerns.
Although the development of effective algorithms and data
structures is an important activity that is closely related to gen-
eral programming techniques, this is not the focus here. The is-
sue here is how to help the programmer write clear programs of
impeccable quality. With this in mind, the programming
proverbs are the motivation for this book.
As with most maxims or proverbs the rules are not absolute,
but neither are they arbitrary. Each proverb is a reservoir of
thought and experience. At first glance, some of them may seem
trivial or too time-consuming to follow. But they were written to
be succinct and useful, much (I hope) like Ben Franklin's simple
maxims that he collected as a guide to everyday living in his Poor
Richard’s Almanac. Like old saws, the proverbs overlook much
important detail in favor of easily remembered phrases. Indeed,
there are some cases where programs should not conform toINTRODUCTION
Figure 1. Matrix summary of The Programming Proverbs
DEV DOC CODE TEST DEBUG M&M
Chapter 1. A Good Start
Is Half the Race
1 Don't Panie +e
2 Question the Problem we
3 Define the Problem + o*e * +
4 Document First + tee * +
5. Think First, Code Later ee + *
6 Proceed Top-Down * * + tee
7 Beware of Other Approaches + * *
Chapter 2. Structure Is Logic
8 Code It for the Other Guy * +e eee
9 Don't Nest * *
10 _Black-Box Your Subroutines ee oeRR ee
11_ Don't Goto _* * *
Chapter 3. Coding the Program
12 All Words Mean Something = * +
13 Spaces Have Meaning, Too * * +
14 Comment for Content * *
15. Make Constants Constant + +
16_Name All Types * * *
17 Get the Syntax Con +
18 Remember Your Reacler +e
19 Your Output Is Not Only for You * +
20. Review, Review, Review * * *
21 Careful Programmers Don't
Have Accidents * *
Chapter 4. And of Course
22 Have Someone Else Read the Work +e Ree eee
23 What Have You Read Lately? te ke
24 Don't Be Afraid to Start Over +e +e
‘Total 10° 11-20 13 18 30
Note: The stars indica my subjective evaluation of the relative values ofthe processes indicated(C WITH EXCELLENCI
Figure 2
Programming Proverbs
standard rules—that is, there are exceptions to every proverb.
Nevertheless, I think experience will show that these exceptions
are rare and that a programmer should not violate these rules
without serious reasons to do so.
Alist of the proverbs is given in Figure 1. The proverbs are fur-
ther related to a relative timing process of development (DEV),
documentation (DOC), coding (CODE), testing (TEST), debugging
(DEBUG), and maintenance and modification (M&M). This figure
suggests their relative impact on programming.
The relative importance of one proverb over another depends
quite markedly on the problem at hand. By assigning a relative
value to each proverb based on observation and experience (as in
Figure 1), the graph in Figure 2 indicates the overall effect of the
proverbs during program development.
In closing this introduction, note that the word “proverb” is
used, rather than the most accurate word “maxim.” Proverbs
and maxims both refer to pithy sayings derived from practical
experience. Proverbs are usually well known, whereas maxims
are not. Admittedly, the programming proverbs are not in them-
selves popular sayings. However, the title was chosen with an
idea to the future. I hope that these proverbs will assist program-
mers in joining Lincoln Steffens in seeing the future with the
confidence that it will work.
Overall impact of the proverbs
Values 5 10 15 20 25 30
Deveeppet
Drei
Coding —
‘Testing —s
Debugging es
Maintenance and
Modification —CHAPTER
1
Proverb 1
A GOOD START IS
HALF THE RACE
First establish the main principles and the small
details cannot get away.
-Mencius
Cofounder of Confucianism
DON’T PANIC ‘The world belongs to the enthusiast
who keeps cool.
WILLIAM MCFEE
Casuals of the Sea, Book 1
Most programming projects are the result of a need for a new
piece of software to solve a new problem or the need to modify
and improve a functioning piece of software. There should be a
thoughtful prelude to the project, but at the moment of incep-
tion, panic begins to reign when reason is needed most. The
causes are as varied as the personalities that attend them. More
often than not, a programmer is loaded down with other work of
equal importance. Instructors or management may put pressure
on you by setting an unrealistic schedule. You may need to finish
before the semester ends, or there may be a bonus for complet-
ing by some deadline. There is a tendency for the enthusiastic
programmer to “get on with the job,” ie., code, code, code. Over-
looking this first programming proverb results in a tendency to
obtain speedy results—this is counter to all good programming
practices
Good sense must prevail. The same thoughtful approach that
defined the project must be carried into the initial phases of the
project before coding is begun. To rush in panic to the terminal
will surely result in a situation that is all too common today,
7C WITH EXCELLENCE: Programming Proverbs
Proverb 2
where existing code must constantly be reworked as new code
shows oversights, where bugs hide all over the finished code,
and maintenance takes longer than expected.
This advice applies to programmers and project managers
alike: if you find yourself plowing ahead with a new program-
ming assignment, don't panic.
1. Stop.
2. Calm down,
3. Return to methodical programming techniques that work.
Time is your ally. Sometimes, if the enthusiast takes the after-
noon off, he or she will discover what can be done in the cool of
the evening.
QUESTION THE
PROBLEM
Programmers must recognize the difference between solving a
puzzle and solving a programming problem. There is only one
solution to a puzzle, but a problem often comes begging its own
definition. A puzzle may be simply rearranging pieces on a card
table until the picture stands before us; it may be finding the elu-
sive definition that fills in the last square of a crossword. But a
programming problem asks the examiner to question the pre-
mise of the problem itself.
More often than not, programmers are handed a problem to
be solved—not a puzzle to be solved. Someone else invents the
problem. Most likely, there will be a written description of the
problem. The first impulse is to take the problem as stated.
Watch out! This is the difference between a puzzle and a
problem.
There are two almost universal difficulties with the statement
ofa problem or, as it is usually referred to, the “specification” of a
problem:
1. The problem is not fully specified. There may be gaping
omissions and inconsistencies.
The problem itself can be better defined.Figure 1-1.
GOOD START IS HALF THE RACE
This proverb addresses the second point.
Consider the small problem definition of Figure 1-1A. It is set
in type, looks unnegotiable, and may even seem reasonable. Stop.
Why do the inputs need to be in columns? As stated,
20000 8.00 22-1
is a fine input, but
20000 8.00 22 -1
is not. Thus, input is tedious and error prone. And why -1, not a
“2" or -92 How will the computer read -1 when (say, for the per-
centage) a fixed-point real number is requested? And who will
remember the order of inputs? How will the user know what to
input? For a small problem, this is a fast start to trouble.
Figure 1-1B gives a more inspired problem definition. The dif-
ference may not seem great, but it is certainly better for the hu-
man user, and probably easier to implement as well.
Proposed definitions of a mortgage problem
(A) Poor Problem Definition
We wish to devise a program to help potential homeowners as-
sess the finances of mortgaging a home. There are four basic fac-
tors to be considered: the principal, the interest rate, the number
of years for the mortgage, and the monthly payment. The pro-
gram must input values for any three of the above quantities,
output the fourth quantity, and also output a table indicating
how the amount of the first monthly payment of each year is di-
vided between principal and interest.
The input to this program is a line containing three of the
above four figures:
Columns Quantity
15 Principal
8-12 _ Interest Rate
15-16 Number of years
17-23 Monthly payments(WITH EXCELLENCE: Programming Proverbs
The principal and number of years are given as integers, the in-
terest rate and monthly payments are given as fixed-point real
numbers. The missing quantity is given as -1.
The output is to be a line indicating the value of the missing
quantity, and a table giving, for the first monthly payment of each
year, the amount contributed to decreasing the principal and the
amount paid as interest.
(B) Better Problem Definition
We wish to devise a program to help potential homeowners as-
sess the finances of mortgaging a home. There are four basic fac-
tors to be considered: the principal, the interest rate, the number
of years for the mortgage, and the monthly payment. The pro-
gram must obtain values for any three of the above quantities,
output the fourth quantity, and also output a table indicating
how the amount of the first monthly payment of each year is di-
vided between principal and interest.
The program will prompt the user for the following four
figures:
Quantity Prompt
Principal Enter Principal (in whole dollars):
Number ofyears Enter Loan Period (in whole years)
Interest rate Enter Interest Rate (to two decimal
places):
Monthly payment Enter Monthly Payment (to two decimal
places):
The principal and number of years are given as integers, the in-
terest rate and monthly payments are given as fixed-point real
numbers. The missing quantity is given as zero.
The output is to be a line indicating the value of the missing
quantity, and a table giving, for the first monthly payment of each
year, the amount contributed to decreasing the principal and the
amount paid as interest.
10
The miniature example above is not the odd case. Specifica-
tions written for programs will astound you! For instance:A GOOD START IS HALF THE RACE
1. Far too many features
2. Unreadable prose
3. Confusing inputs or outputs
4. Baroque syntax for user notations
5. Overly complex solutions
6. Lack of thought to new versions
7. Strange punctuations for user notations
8. Impossible scheduling requirements
9. Cryptic messages
10. Etc., etc,, etc
You cannot be discouraged. Your task is simply to question the
problem that was given to you—it is not a puzzle, it is a problem
it comes with the territory. I have seen all these “problems
with problems.” Don't let them happen to you.
You should be reminded of John Stuart Mill's classic remark
more than a hundred years ago:
‘To question all things; - never to turn away from any difficulty; to ac-
cept no doctrine either from ourselves or from other people without
a rigid scrutiny . . . these are the lessons we learn
Questioning a problem in programming is, indeed, the lesson
you must learn.
Proverb3 DEFINE THE Nothing is difficult to a man who
PROBLEM has persistence.
CHINESE PROVERB
Once satisfied with the rough specifications of a problem, you
must tackle the task of defining the problem completely. This
and the previous proverb are, perhaps, one: as you question the
problem, the definition of the problem will become clear to you.
Good problem definitions are vital to the construction of good
ilC WITH EXCELLENCE: Programming Proverbs
12
programs. An incomplete or ill-formed definition implies that
the problem is not fully understood. Missing information, igno-
rance of special cases, and an abundance of extraneous informa-
tion in a definition are good signs of poor programs or, at best, of
programs that will be a surprise for the ultimate user Any pro-
gram that processes large amounts of data is bound to encounter
some simple unnoticed condition, resulting in the all too com-
mon program crash.
We have often heard the claim that it is quite permissible to
start with an imperfect problem definition, for during later pro-
gram development a good programmer will readily pick up any
critical points missed in the initial definition. This is a dangerous
premise as it is counter to all the principles of professional pro-
gramming. Starting with solid, complete program definition is la-
borious and requires persistence. But it is often half the solution
to the entire problem. Moreover, good definitions will serve as
the basis for good program documentation, and make program-
ming itself easier
There are many reasons why precise problem definitions are
rare. First, there is no well-accepted idea of what comprises a
precise definition. Different programmers, instructors, and man-
agers usually employ different definition techniques. For exam-
ple, some project managers require only program narratives,
decision tables, or system flowcharts. Another common practice
is to have an experienced systems analyst draw up several sys-
tem flowcharts, some narrative descriptions, and some detailed
descriptions of some inputs and outputs. Of course, the quality
and completeness of these definitions will vary according to the
style of the individual analyst.
Second, as we noted in the first proverb, there is an almost ir-
resistible temptation to skirt over the issue of definition in order
to “get on with the job.” This temptation is especially acute when
the given problem is similar to previously solved problems or
when there is strong external pressure to produce some quick,
visible results (that is, programs). Furthermore, even if program-
mers could avoid the rush to get on with the job, management
and the “customer” often make it difficult to invest the time and
money in a good problem definition. The results of good defini-
tions often appear to be wasted, since working code is usually
delayed, especially when a programmer works hard to ensure
that no problem situations go unnoticed.Figure 1-2.
A GOOD START IS HALF THE RACE
Third, good problem definitions involve plain hard work.
There is an intense amount of persistence and discipline re-
quired to get any definition straight.
As an example, consider the earlier definition of Figure 1-1B,
which defined a program to aid a prospective homeowner in de-
termining the financial arrangements of a mortgage loan. The
definition might appear adequate, but on closer analysis many
points need to be resolved:
1, Howare the principal, number of years, interest rate, and
monthly payments related? Is there a formula?
2. Where are the prompts?
3. Are the ranges for each value adequate?
4. What input errors can arise?
5. Why can't we see a sample of the input/output?
6. What is the format of the table?
7. Does the table have headings?
8. Are numbers like "20,000" allowed?
These decisions will have to be made eventually. Now is the time.
The definition in Figure 1-2 resolves each of the above issues. Al-
though longer, it is more precise than that of Figure 1-1B.
One important point of Figure 1-2 is the inclusion of a sample
of the input and output. Often a simple dialogue or printout can
be of great value to a program in giving a quick synopsis of the
problem. In addition, a sample can prevent surprises in cases
where the program turns out to be quite different from the ex-
pectations of the person defining the program. If a programmer
is not given a sample of the input-output, he or she should try to
provide it before programming. It is a pity that samples are so
rarely done, as though we in our own trade are hiding from the
real problems we are committed to solve.
Clarified definition of a mortgage problem
(1) Problem Outline: We wish to devise a program to help poten-
tial homeowners assess the finances of mortgaging a home.
There are four basic quantities to be considered:
13C WITH EXCELLENCE: Programming Proverbs
14
The principal amount of the mortgage
The yearly interest rate for the mortgage
The number of years for the duration of the mortgage
The constant monthly payment required to pay back the
principal P over N years at the interest rate I.
gz7%
The above quantities are related by the equation:
Perini ti
Me GFie-1
where
i =1/12 =monthly interest rate
n =12+N =number of monthly periods in N years.
Briefly, the program is to input any three of the above quan-
tities, compute and print the fourth quantity, and also print a
table specifying how the first monthly payment of each year is
divided between interest and principal.
(2) Input: The program will prompt the user for the values of BN,
1, and M. These have the general form:
P: ddddd
N: dd
I: dd.dd
ddd.dd
5
where the d's represent decimal digits such that
P =the principal in dollars
N =the number of years in integer form
I =the percentage interest rate computed to two decimal
places
M = the monthly payment in dollars and cents.
The value of P I, N, or M to be computed is given as zero. Addi-
tional or fewer digits (than those shown above) are allowed.
However, each whole number must have at least one digit, and
each decimal point number must have at least one digit on each
side of the decimal point, Except to the right of a decimal point,
leading zeros may be replaced by blanks.A GOOD START IS HALF THE RACE
(3) Output: The output from the program is to consist of two
parts: (a) The value to be computed, using one of the formats:
PRINCIPAL $dddddd
NUMBER OF YEARS = dd
INTEREST RATE dddd
MONTHLY PAYMENT = $dddd.dd
(b) A table giving for the first monthly payment of each year the
amount paid to principal and the amount paid to interest. The
headings and formats for the table values are as follows:
YEAR AMT TO PRINCIPAL AMT TO INTEREST
dd $ddd.dd $ddd.dd
Except to the right of a decimal point, leading zeros for any value
are to be replaced by blanks.
(4) Exceptional Conditions: If any of the input values are not in the
prescribed format, the user is to be prompted for the value again.
If any output value is not in the range indicated, the program is
to print an appropriate message to the user
(5) Sample Input:
The principal, number of years, interest rate, and monthly
payment of a mortgage are required below. Enter three values,
and enter 0 for the value to be computed.
Enter principal (in whole dollars): 20000
Enter loan period (in whole years): 25
Enter interest rate (to two decimal places): 8.0%
** Value not recognized. Try again: 8.00
Enter monthly payment (to two decimal places): 0
(6) Sample Output for Above Input:
MONTHLY PAYMENT = $154.36
YEAR AMT TO PRINCIPAL AMT TO INTEREST
1 21.03 133.33,
2 22.77 131.59
25 142.53 11.83
15(C WITH EXCELLENCE: Programming Proverbs
Proverb 4
16
In Chapter 7, we will discuss several ideas for producing good
problem definitions in conjunction with a complete example.
However, there are a few points about good definitions that de-
serve to be mentioned here. First, in attempting to supply a com-
plete problem definition, the programmer probably cannot err
by devoting extra time and thought. While perfect definitions are
probably unattainable, with good technique and discipline, you
will end up “close” to one. Remember that all languages have
rigid rules for the execution of programs, and programmers
must be specific to the last detail. If something is left unspecified
in the original definition, the programmer will eventually have to
face the consequences. At best, the changes that must be made
are frustrating and distracting.
Once you believe that a definition is complete, put it aside fora
while. Pick it up later, reread and rethink it—this is genuine per-
sistence. Better still, have someone else read it (see Proverb 22).
The problem definitions thought to be “complete” may reveal
flaws by the light of a new day or the cool of another evening. You
will find that most of these proverbs apply simultaneously to any
problem you have. As a final word, make sure that you have a
complete written description of the problem before you do any-
thing else.
DOCUMENT FIRST
What can be said in one short proverb about a subject that has
been cussed and discussed for years? Many have tried to define
and analyze program documentation, to motivate programmers
to understand that the central purpose of documentation is to
provide effective communication of factual information among
people. The most important aspect of documentation is not just
to doit early, but to do it first. You will be in close touch with your
user's or instructor's requirements so that your understanding of
the required program will be greatly enhanced.
What is “documentation” in the sense that we define it here? It
could be:
a. Inaclass exercise, a summary of program design.
b. Ina class project, a manual or write-up of how to use the
program.A GOOD START IS HALF THE RACE
©. Inacommercial project, a reference manual or user's
guide.
d. Ina contracted piece of software, an installation manual
or user's guide.
Very often, it comes down to:
a. The comments at the beginning of a program.
b. The user manual.
Now, very few programmers do these things before coding. Why?
Because documentation takes effort. And because, more often
than not, it appears to be a drudgery. It is easy to hide from hard
work, to consider it premature if not impossible. Rubbish.
There is quite a difference between ‘I can’t do it’ and ‘I won’t do it’.
Usually it is the latter.
MENCIUS
Doing the documentation first is a powerful design tool. It fer-
rets out the nebulous ideas and clarifies them in the mind of the
programmer The user will never be able to read your thoughts or
sketchy notes as you begin a project, but he will surely read your
preliminary user manual. If you do the documentation first, you
will put the user first. In so doing, you will expose the details of
the program for yourself. Oddly enough, doing the documenta-
tion first is more fun because, as the project proceeds, delaying
documentation compounds the difficulty and it becomes a
chore.
On major programming projects, the better ones, good docu-
mentation is identified by several characteristic:
1. Readability is the major goal. Documentation is meant to
be read by other human beings. With proper
documentation, the reader does not have to stare at a
shelf of material with no idea of where to begin.
2. Documentation is considered important. The what,
when, and how of good documentation are recorded
somewhere (ie., standardized), and help is available
to understand the standards.
17Proverb 5
18
3. The required documentation is planned from the
beginning. Some documents are written long before
others and serve as guides for the later ones.
4. Documentation is part of the daily programming process.
Finger paralyzing treatises on long-forgotten topics are
not needed. The documentation system drives the
programming process!
5. There is no pressure to skimp. Someone asks for needed
documentation, someone else reads it, and the end
reward is the production of high-quality documentation.
In all honesty, documentation is rarely as good as all this. It is
possible to be involved in a programming project with a less than
perfect documentation system. The point is that, in this event, it
is incumbent upon you to develop your own ideas and pro-
cedures early.
We all should be able to recognize good documentation. The
only thing left to do is to begin providing it. While you may not
achieve good documentation right away, any step in that direc-
tion is to be preferred to the confusion that will result if you de-
lay documentation later in the project. Try it once. You will see
the point.
THINK FIRST, Man is obviously made to think.
CODE LATER PASCAL
There is as much literature on the mechanics of the golf swing as
there is on the mechanics of programming. Without exception,
professional golfers have mastered the mechanics of the golf
swing. But there is more. In a recent televised tournament, one
such professional golfer was leading by two strokes going into
the final hole. He placed his tee shot into a fairway bunker, tried
to reach the green and hit a greenside bunker, bladed the next
shot into a bunker on the other side of the green, pitched onto
the green, two-putted for a six that tied the tournament, and
then lost it on the first play-off hole. The television commentator
asked in effect: “Why didn't he think? All he had to do was lay up
out of the first bunker, pitch it on the green and two-putt for a
five. He just didn’t think!”Proverb 6
A GOOD START IS HALF THE RACE
In much the same way, the mechanics of programming, rela-
tive to thinking about the problems of programming, can be mas-
tered without undue difficulty. This proverb is intimately
connected with the previous proverbs; the difference is that
once the mechanical procedures of problem definition and doc-
umentation are finished, the essential task is to start thinking
about the solution as soon as possible.
The proverb is literal: Think first means think—do not code!
Start thinking while the problem is fresh in your mind and the
deadline is as far away as it will ever be. Consider at least two dif-
ferent ways to solve the problem. Examine the approaches to
discover possible trouble spots or areas where the solution ap-
pears difficult. Our professional golfer should have thought: the
only way I can lose this tournament is to hit the ball into that
greenside bunker. You should think that the only way this pro-
gram will crash is if there is a faulty algorithm.
Code later means giving yourself time to weed out the difficult
parts and polishing the algorithm before trying to formalize it in
actual code. It is easier to clarify thoughts now than to discard
poor programs later
This proverb is most often violated by a “linear” approach to
programming. This simply means that a programmer receives a
problem and immediately starts preparing the code to solve it.
‘Tempting as this approach may be, avoid it for it is full of hidden
costs and dangers. Without thinking first, you may be coding for
an ill-conceived program or—the ultimate foolishness —writing
a program that you will have to throw away later
In conclusion, remember Murphy's second law of program-
ming: “it always takes longer to write a program than you think.”
A corollary might be: the sooner you start coding the program
instead of thinking about it, the longer it will take to finish it.
PROCEED The last thing one settles in writing
TOP-DOWN a book is what one puts in first.
PASCAL
A major objective of this book is to advocate the “top-down” ap-
proach to programming problems. The top-down approach is it-
self subject to several interpretations, but many of these overlook
important issues. Top-down programming is discussed at length
19(C WITH EXCELLENCE: Programming Proverbs
in Chapter 5; however, following these rules of the top-down ap-
proach is the essence of this proverb:
1. Design the top levels first.
2. Use language-independent forms initially.
3. Postpone details to lower levels.
4. Formalize each level.
5. Verify each level.
6. Make successive refinements.
Consider the example in Figure 1-3. Here is the first pass at the
top level for the programming problem of Chapter 7. Examining
the definition of the problem, the programmer writes an infor-
mal program, P1 (first pass) in pseudocode. After a more detailed
look at the problem definition and considering the overall al-
gorithm, the programmer develops P1 into a more refined ver-
sion, P2 (second pass). The main program ultimately emerges
and can be verified by testing. The code to produce the modules
referenced in the final pass at the program must be developed in
P2 and further refined in successive levels.
Top-down programming has two distinct advantages. First, a
programmer is freed from the confines of a particular language
and can deal with the more natural data structure of the pro-
gram. Second, top-down programming leads to a modular ap-
proach that allows the programmer to write statements relevant
to that program structure. The details can be developed later in
separate modules. The main goal of top-down programming is
to aid the programmer in writing well-structured, modular
programs.
The substance of this proverb will be amplified in Chapter 7.
For now, Pascal's observation means this for the top-down ap-
proach: One does not write the first lines of the program first.
One designs and writes the main control routine, the “top level,
first.Figure 1-3.
GOOD START IS HALF THE RACE
Initial stages of program development
P1 (First pass)
initialize program variables
welcome players
do
get a proposed move
if move is legal then
process the move
if the game is not over then
change players
else
exit
while not game over
2 (Second pass)
initialize (PLAYER, BOARD)
write (INTRODUCTORY_MESSAGES)
do
get (MOVE) from PLAYER
if LEGAL_MOVE (PLAYER, BOARD, MOVE) then
UPDATE_BOARD (PLAYER, BOARD, MOVE)
if LEGAL_JUMP (PLAYER, BOARD, MOVE)
and JUMP_CAN_BE_CONTINUED (PLAYER, BOARD, MOVE) then
CONTINUE_THE_JUMP (PLAYER, BOARD, MOVE)
if NO_KING (BOARD, PLAYER)
and MOVES_LEFT (BOARD, OPPONENT) then
swap PLAYERS
prompt OPPONENT for next MOVE
else
write (WINNING MSG) for PLAYER
write (LOSING_MSG) for OPPONENT
GAME_OVER is true
else
write (ILLEGAL MOVE MSG) for PLAYER
while not GAME_OVER
21(C WITH EXCELLENCE: Programming Proverbs
Proverb 7
BEWARE OF OTHER beware of false prophets, which
APPROACHES come to you in sheep's clothing, but
inwardly are ravening wolves.
Matthew VI 12
Programmers have traditionally used many different ap-
proaches to programming. Consider the following:
1. Bottom-up approach.
2, Inside-out approa
3. Linear approach.
4. Typical systems analyst approach.
5. Imitation approach.
Bottom-Up Approach
In the “bottom-up” approach, the programmer writes the lower
modules first and the upper levels later This bottom-up ap-
proach is an inversion of the top-down approach. Its weakness
lies in enticing the programmer to make specific decisions about
the program before the overall problem and algorithm are
understood.
Inside-Out Approach
There are four other approaches to programming between the
bottom-up and top-down. The “inside-out” or “forest” approach
means, simply, starting in the middle of the program and work-
ing down and up at the same time. As such a method is conven-
ient, it is therefore popular and needs closer examination.
Roughly speaking, if we followed this approach, we would pro-
ceed as follows:
1. General idea. First we decide upon the general idea for
programming the problem.
2. Arough sketch of the program. Next we write any sections
of the program that we deem to be “important,” assumingA GOOD START IS HALF THE RACE
initialization in some form. In some sections we would
write portions of the actual code.
3. Coding the first version. After Step 2, we write specific
codes for the entire program. After one module has
been coded, we debug it, and immediately prepare a
description of what it does.
Rethinking and revising. As a result of Step 3, we should
be close to a working program, but it may be possible
to improve it. So we continue by making several
improvements until we obtain a complete working
program.
Itis fair to say that many programmers work inside-out. Usually,
they don't start very close to the top or the bottom of the pro-
gram. Instead, they begin in the middle and work outward until a
program finally emerges. This approach is obviously weak be-
cause as programs undergo patches and changes, there is no
clear, logical structure for a programmer to follow.
Linear Approach
The third method is the “linear” approach that we introduced in
Proverb 5. Here, one starts writing code as it will appear when
executed: first line first, second line second, and so forth. This
results in making specific detailed decisions with little as-
surance that they are appropriate to the problem at hand. As
poor as this technique may appear to be, there is a strong temp-
tation to employ it, especially on “easy” programs. But be par-
ticularly wary of this temptation: there is no such thing as an
“easy” program.
Systems-Analyst Approach
The fourth technique is the typical “systems-analyst” approach.
When used wisely, it can be an effective technique, and admit-
tedly it has been successfully used for many large programs. We
shall briefly compare it with the top-down approach, the tech-
nique advocated in this book.
The systems analyst often starts on a large programming
problem by dividing up the task on the basis of the flow of con-
trol he sees in the overall program. The program is broken into a
23(C WITH EXCELLENCE: Programming Proverbs
number of modules, which are then farmed out to the program-
mers. After these have been completed, the analyst will firm up
the interfaces and try to make things work right. The lower-level
modules receive special attention since their function and data
handling are used often.
With the top-down approach, on the other hand, the flow of
control is subservient to the logical structure. There does not
have to be an identifiable flow of control that is easy to flowchart
The flow of control is rather like traversing a tree. It starts at the
top level, goes down one or more levels, comes back, goes on to
another level, and so forth. The top-down approach thus has lit-
tle need for flowcharting
Imitation Approach
Asa final method, consider what I call the “imitation” approach,
a method superficially resembling the top-down approach. This
approach is discussed in detail because many programmers
think that the top-down approach is really the way they have al-
ways programmed. The imitation approach is described as
follows:
1. Thinking about the program. Having been given a
programming assignment, take the time to examine the
problem thoroughly before starting to program. Think
about the details of the program for a while, and then
decide on a general approach.
2. Deciding on submodules. After having thought about
the problem in detail, decide on what sections will be
sufficiently important to merit being made into
submodules.
3. Data representation. After compiling a list of the
submodules, decide on a data representation that will
enable them to be efficient, unless the representation
is already specified.
4. Coding of submodules. At this point, write each
submodule. After each is completed, write down what it
expects as input, what it returns as output, and what it
does. The submodules should be written in a hierarchical
manner: the most primitive first, calling routines second,A GOOD START IS HALF THE RACE
and so forth. Doing this will ensure that the submodules
are fully coded before the upper-level program structures
are finalized.
5. Coding the main program. After all submodules have been
written, write the main program. The purpose of the
main program will be sequencing and interfacing the
subroutines.
The imitation approach has some important resemblances to
the top-down approach:
1. The programmer must understand the problem
thoroughly before writing code.
2. The actual writing of the program is postponed until
after certain decisions have been made.
. The problem is broken up into logical units.
However, there are important different characteristi
two approaches.
in the
Top-Down Approach
1, In the top-down approach, a specific plan of attack is
developed in stages. Only the issues relevant to a given
level are considered, and these issues are formalized
completely.
2. Furthermore, whenever the programmer decides to use a
subprogram, the interfaces [i.e., arguments, returned
values, and effects) are decided first. The inputs and
outputs are formalized before developing the submodule;
that is, the submodules are made to fit the calling routine
instead of the other way around.
3. Most important, at every step in the top-down approach,
the programmer has a complete, correct “program.”
The major disadvantages of the imitation approach are that it
is more likely to produce errors, usually requires major program
modifications, or results in a somewhat ill-conceived program.
Choosing a partially specified attack may require serious
25(C WITH EXCELLENCE: Programming Proverbs
changes to the program. Coding submodules first may result in a
confusing program logic, if the submodules do not happen to in-
tegrate easily into the upper-level code designed later.
In summary, think carefully about programming technique.
The top-down approach, which is discussed at length in Chap-
ter 5, is the smart alternative.CHAPTER
2 STRUCTURE IS
LOGIC
Rose is a rose is a rose is a rose.
Gertrude Stein
Sacred Emily
An idealist is one who, on noticing that a rose
smells better than a cabbage, concludes that it will
also make better soup.
HL. Mencken
Sententiae
Proverb8 CODE IT FOR THE Our life is frittered away by detail
OTHER GUY Simplify, simplify.
HENRY DAVID THOREAU
Walden: Where I lived, and what I lived for
Have you ever put together one of those “easily assembled” toys
on Christmas eve? The instructions read:
Attach the tower(B) to the base(A) and the chute(C) to the tower
In the same manner, the next page of instructions directs:
The kit contains 16 3/4” bolts. Insert each one into the holes marked
“J; place base(A) snugly so edges(L) are aligned with arrows inside
base(A). In kit 2, take nuts(D) and. . .
and so on, etc. You wonder if the designer of the toy was ever in
communication with the author of the instructions on how to
put the toy together
In programming, we have the same problem. Somehow, we get
so caught up in design that we forget the user, we forget to code it
for the other guy. Eventually, programs work, but that is not the
test of a good program. The test is: can a program be easily un-
derstood, are there no unnecessary details, and is the logical
structure clear? Well-structured programs are the mark of a
27(C WITH EXCELLENCE: Programming Proverbs
careful development process and are usually characterized by
small, functionally specific modules. Generally, the statements of
aC module should not extend beyond one page.
Just like the instructions for assembling the toy, some pro-
grammers write calls to procedures like this:
READ_TABLE;
PROCESS_TRANSACTION;
UPDATE_STATUS;
and then you tum to this:
UPDATE_STATUS ()
/* Update data base file with new information */
{
fscanf (file_l, "Zc", &CONTROL)
TRANS . COUNT 0;
TRANS. ON 0;
TRANS. BUFFSIZE = 100;
if (FIELD. INDICATOR != TERM_VAL)
while (STATE_VAR = GO_VAL) {
HANDLE(NEW_TRANS, COUNTER, @RESULT_STATUS) ;
UPDATE_TRANSFILE;
++ACOUNT;;
}
Ifyou throw your hands up with this, it is understandable. Some-
one seems to be lying. Why? In this case:
1. The function UPDATE_STATUS has probably ten(!)
purposes.
2. There may be fifteen or so effective inputs or outputs,
each buried in the code.
We need to answer the question: What is a logical structure,
and what is a logical unit? It is a section of code, normally a func-
tion or procedure with:Figure 2-1.
1, asingle purpose, and
STRUCTURE IS LOGIC
2, clearly stated inputs and outputs.
Programmers usually violate these two points, and getting to the
bottom of such matters is, in a sense, the thrust of this book.
The most direct value of modular code is felt during program
maintenance, for time is not wasted trying to determine what is
being done over several sections of code. Consider Figure 2-14,
which outlines the logical structure of a hypothetical program.
The structure is difficult to follow. Figure 2-1B pictures the re-
medied situation where simple computations are isolated into
units,
Display of logical structure
(A) Poor logical structure (B) Better logical structure
INPUT DATA. INPUT DATA
‘ANALYZE ANALYZE
DATA DATA
COMPUTE
SUBTOTALS
COMPUTE Poe compute
ANALYZE ALE
RESULTS pea
PRINT EDIT
RESULTS RESULTS
ourput
RESULTS
PRINT
RESULTS
29C WITH EXCELLENCE: Programming Proverbs
Proverb 9
30
There are many advantages to modular code. Coding time is
shortened by the use of previously written modules. Implemen-
tation costs are lower because of easier changes, decreased re-
compilation costs, smaller tasks, and isolated code bottlenecks.
Testing is simpler because “simple” modules usually have not
more than, say, a half dozen looping and branching constructs
and thus a smaller number of total execution paths. In the end,
the dividend is paid in the maintenance of the program.
When the time comes to write the actual code, there are three
guidelines that will help you code in logical units:
1. First, and most obviously, make the best use subprograms.
2. Avoid confusing control structures.
3. Display the resulting logical structure with good spacing
and indentation.
The next three proverbs treat these issues. But if you think of
yourself as the other guy, and simplify, simplify, both the process
and the product will be improved.
MAKE FILES INTO When I was a ten-year-old girl, my
PACKAGES uncle gave me a little Russian doll
that looked like an Easter egg. If
you turned the head, the egg-doll
‘opened and inside there was
another doll until at last, opening
the smallest doll, I found a piece of
chocolate. But after I had put the
dolls back together again, there
was no chocolate at the end of my
search. I did not understand this,
although I tried again and again to
find the chocolate.
“ANONYMOUS:
AC program consists of:
* asequence of files, one of which contains the main
program routine.‘STRUCTURE IS LOGIC
The main program is a function called MAIN. So far, so good. A
file consists of:
# a sequence of declarative items.
A declarative item can be a function definition, variable declara-
tion, or any declaration for that matter: So far, still so good.
Now for the big point:
© The best model for the contents of a file is the concept of a
“package.”
What does this mean? Let's try.
We are familiar with the idea of a library package of mathe-
matical routines. This might be a file like
” File math.c
general declarations */
float sin(x)
definition
}
float cos(x)
definition
}
The file will probably include definitions of sin, cos, tan, arctan,
log, and so forth. This is a model package.
© A package is a group of items (functions, types, constants,
and variables) related to a single purpose.
In the simplest case, a package can be a collection of constants,
for instance
/* File plot.n */
float X.MIN, Y_MIN = 0
float X.MAX, YMAX = ll
float DELTA =0
float MISSING_VALUE = 1
31(WITH EXCELLENCE: Programming Proverbs
In amore complex example, a program will consist of a num-
ber offiles, each of which should capture knowledge of one con-
cept. Imagine a program to monitor a game of checkers. Two
users input moves for playing a game, and the program displays
the board and enforces the rules. Such a program can be
organized in endless ways, but one good way is shown in Figure
2-2.
Figure 2-2. Use of Packages
/* -- Main program file, checkers.c
General comments describing the overall program
and its design */
#include "general. h"
#include “board.h"
#include “user.h"
main (argc, argv)
int argo:
char *argv []:
{
color PLAYER, OPPONENT;
SET_UP (BOARD);
/* -- Header file board.h
-- Constants, types, and functions for the BOARD */
#define NUM_SQS 32
typedef enum {BLACK, RED} color:
typedef enum {BLACK_PIECE, RED_PIECE, VACANT} sq_status;
extern boolean JUMP_AVAILABLE (/* PLAYER, BOARD */);STRUCTURE IS LOGIC
/* -- Computation file board.c
-- Support functions for operating on the BOARD */
#include "general. h”
#include “board.h"
boolean JUMP_AVAILABLE (PLAYER, BOARD)
board_contents BOARD;
color PLAYER;
/* -- Header File user.h
-- User interface input and output */
#define MAX_LINE_SIZE 72;
typedef char input_buffer [MAX_LINE_SIZE];
extern SEND_MSG (/* PLAYER, MSG_ID */);
/* File user.c */
#include "general.h"
#include
#include “user.h"
SEND_MSG (PLAYER, MSG_ID)
color PLAYER;
msg_name MSG_ID;
GET_LINE (PLAYER, BUFFER)
color PLAYER;
input_buffer BUFFER;(C WITH EXCELLENCE: Programming Proverbs
/* -- Header file general.h
General language constants */
#define FALSE 0
#define TRUE 1
#define boolean short
Consider the file “board”. This file contains only declarations.
The declarations define the organization of the checker board,
the legal adjacent squares, and the headers for several external
functions. These functions check if a jump is available, if a move
is legal, and so on. This and the other files reflect these concepts:
checkers.c The main program.
board.h Definitions for using the board.
board.c Code for the functions specified in board.h.
user.h Definitions for user i/o.
user.c Code for functions specified in userh.
general.n Generally useful constants.
These support files are used by the main routines, given in the
file checkers.c.
T do not wish to imply that getting a good overall package de-
sign is easy. It is not. But the physical concept of a file is an ideal
match for the logical concept of a package.
Moral: A file is not just a place to hold some code. Rather, we
should think:
1 file <> 1 package «—> 1 purpose
The file is the cornerstone of good program structure.‘STRUCTURE IS LOGIC
Proverb 10 BLACK-BOX YOUR Years later; when I was older and
SUBROUTINES my uncle was in the evening of his
life, he came to visit and asked
about the egg-doll that he had given
me. When I brought it to him, I
‘found that the smallest doll had
‘been broken. I was sad. “Do not ery,
my child,” he said. “But I cannot
replace the smallest doll, only the
whole doll. Some times, one must
replace the whole to repair the
part.”
ANONYMOUS
The function facilities in C are a powerful tool for coding clear,
modular programs. Not only do these facilities allow the pro-
grammer to “factor out” sections of code, but more importantly,
they provide a basic unit for abstraction of program modules.
This abstraction can have a great effect on program readability
by exposing the program's logical structure, even if the function
is called only once.
Most languages (but not C) make a distinction between:
(a) a subprogram that returns a value and thus acts as an
expression.
(b) a subprogram that does not return a value and acts as a
statement,
In Pascal, Modula-2, and Ada, these two subprograms are called:
(a) a function.
(b) a procedure.
In G, the only ready analog is:
(a) a function.
(b) a “void” or “statement” function.
but this is more tongue-twisting than helpful. Hence, we will oc-
casionally use the term “procedure” when we refer to a void
function.
35(C WITH EXCELLENCE: Programming Proverbs
36
The basic idea behind functions is an old and familiar one. We
almost grow up with the little mapping
FX, Y) > Z
which can also be expressed as
x——|
\—>
This latter view suggests that F is some algorithm expressed in a
programming language. Fine, so far
But when the going gets rough, programmers lose this simple
idea. A procedure or function should have all its inputs and out-
puts identified. Even something like a procedure to update an
array should follow the rule. For instance, consider the array
BOARD in
PLAYER
‘sat
TT re | conn
|
BOARD ———»
This can be expressed by the procedure header
UPDATE_BOARD (/* for */ PLAYER,
/* using */ SQl, SQ2,
/* updating */ BOARD)
This is the full interface for this procedure, no matter how com-
plex its internal algorithm.
Consider the programs of Figure 2-3. Given the values for the
three-element arrays A, B, C, and D, the programs use the deter-
minant method (assuming DENOM is nonzero) to solve three in-
dependent equations of the following form for the unknowns x,
yand z:
AX+By+C,2=D,
A,X +B,y +C,2=D,
Ax + By + Dgz=Dy,Figure 2-3.
STRUCTURE IS LOGIC
The program of Figure 2-34 is a confusion of arithmetic calcula-
tions. It contains little hint of the determinant method or the al-
gorithm needed to solve the problem. In contrast, Figure 2-3B
uses a function subprogram to calculate the determinants. It is
explicitly clear that each unknown is the quotient of two deter-
minants and that the denominator is the determinant of the vari-
able coefficient matrix. Figure 2-3C shows an even greater
improvement when the arrays are passed as arguments.
There are two points of Figure 2-3C: first, by black-boxing sub-
routines, the entire structure of the program is more logical; sec-
ond, your program will be longer But in this fashion, you can use
functions and procedures often. The other guy can read it more
easily, and maintenance and debugging are easier when we can,
as with the doll, replace the entire black box rather than tamper
with the individual parts.
Solution of three independent equations
(@) Poor solution: functions not used
/* program EQUATION.1 */
#include
#define NUM_UNKNOWNS 3
main ()
{
typedef float coefficients [NUM_UNKNOWNS + 1];
coefficients A, B, C, D;
float DENOM;
float X, ¥, Z;
short I;
printf ("Enter coefficients: \n");
for (I = 1; I <= NUM_UNKNOWNS; ++I)
DENOM =
x=
scant ("Ef Zf ZF EE", @A[T], &B[I], KCI], &D[1]);
(A{L]*B(2]*e(3]) + (A(2]*B[3]*C(1]) + (A[3]*B[1]*C[2])
(a(3]*B(2]*e[2]) - (A[2]*B(1]}*C(3]) - (Al1]*B[3] *C[2]);
(D[1]*B[2]*C[3]) + (D[2]*B[3]*C(1]}) + (D[3]*Bl1]*c[2])
(D[3]*B2]*c[2]) - (D[2]*B(1]*C[3]) ~ (D[1]*B[3]*cl2]):
37C WITH EXCELLENCE: Programming Proverbs
Y= (A(1]}*D[2]*C(3]) + (Al2]*D[3]*C[1]) + (A[3]*D[1] *C[2])
~ (A[3]*D[2]*C[1]) ~ (al2]*D[1]*e[3]) - (Al1]*D[3]*c[2]);
Z= (A(1}*B(2]*D(3]) + (A(2]*B(3]*D[1]) + (A[S]*B[1]*D[2])
- (A[3]*B[2]*D[1]) ~ (Al2]*BE1]*D[3]}} ~ (AL1]*B[3] *Dl2]);
X =X / DENOM;
Y= ¥ / DENOM
Z=Z / DENOM;
printf ("Unknowns are: %g, %e, Ze\n", X, Y, Z);
(B) Better solution: a function used
/* program EQUATION 2 */
#include
#define NUM_UNKNOWNS 3
float DETERM (X1,X2,X3, Y1,¥2,¥3, Z1,22,23)
float X1, X2, X3,
Yl, ¥2, ¥3,
Zl, 22, 23;
{
float RESULT;
RESULT = (X1*¥2*Z3) + (X2*YS*Z1) + (XS*Y1*Z2)
~ (XB*Y2*Z1) - (X2*¥1*Z3) - (K1*¥3*Z2);
return (RESULT) ;
}
main ()
{
typedef float coefficients [NUM_UNKNOWNS + 1];
coefficients A, B, C, D;
float DENOM;
float X, ¥, Z;
short I;
printf ("Enter coefficients: \n");
for (I = 1; I <= NUM_UNKNOWNS; ++I)
scant ("Zf Bf Bf Bl", &ACT], &B(I], &C[I], &D{T]);}
STRUCTURE IS LOGIC
DENOM = DETERM (A(1], B[1], C[1], Al2], B2], cf2]. a(S], Bf3}, c{3})
X = DETERM (D[1], B{1], C{1], D(2], B(2], {2}, D{3], BIS], CI3}):
Y= DETERM (A[1], D{1], C[1], Al2], D{2], c{2], a3]. DIS], c[3]);
2 = DETERM (A(1], B(1], D1], Al2]. Bl2], D[2], ALS], BIS]. D[3]};
X =X / DENOM:
¥ = ¥ / DENOM;
Z=Z / DENOM;
printf ("Unknowns are: %g, $e. Ze\n", X, Y, 2);
(C) Still better solution: using a function and passing arrays
/* program EQUATION 3 */
#include
#define NUMUNKNOWNS 3
typedef float coefficients [NUM_UNKNOWNS + 1];
float DETERM(R, S, 7)
{
coefficients R, S, T;
float RESULT;
RESULT = (R(1]*S(2]*T(3]) + (R[2]*S(3]*T(1]) + (R[3]*S(1]*T(2])
- (R[3]*8[2]*T[1]) ~ (Rl2]*S[1]*T[3]) - (R[1]*S[3]*7[2});
return (RESULT);
oO
coefficients A, B, C, D;
float DENOM;
float X, ¥, Z:
short I;
printf ("Enter coefficients: \n");
for (I = 1; I <= NUM_UNKNOWNS; +41)
scanf ("Zt Bf Bf Zt", &AT], SBI], &C[I], eD{T]});
DENOM = DETERM(A, B, C);
3SC WITH EXCELLENCE: Programming Proverbs
X = DETERM(D, B, C) / DENOM;
Y = DETERM(A, D, C) / DENOM
Z = DETERM(A, B, D) / DENOM
printf ("Unknowns are: Zg, Zg. Ze\n", X, Y. Z);
Proverb 11 DON’T GOTO A little neglect may breed mischief
. . .for want of a nail, the shoe was
lost; for want of a shoe, the horse
was lost; for want of a horse, the
rider was lost.
BENJAMIN FRANKLIN
‘Maxims, prefixed to Poor Richards
‘Almanack
Earlier we observed that program documentation was a contro-
versial issue in programming. But even more controversy has
been generated over control structures. What is the best way to
ensure that at every point in a program the next action to be car-
ried out will be clearly specified? In many languages, the control
structure issue focuses on one small statement: the goto.
The unconditional transfer of control, which is the purpose of
the goto statement, has been associated with programming since
its inception. Its historical ties have left indelible marks on to-
day’s major programming languages. Until recently, virtually all
higher-level languages have had some form of an unrestricted
goto. My position is this: make a little sign and hang it where you
can see it every day:
DO NOT GOTO
YOU DON'T HAVE TO
Let me show you why. Consider the following little example.
SUM = 0;
counT = 0;
Ll: if (COUNT > MAX)
goto L2;
SUM += COUNT;
COUNT += 2;
40STRUCTURE IS LOGIC
goto Ll;
La: printf ("Za\n", SUM);
This is hardly complex, but does make the point. The goto’s force
us to develop a mental image of chasing about. This disappears
with the alternative rendering:
‘SUM 0;
COUNT = 0;
while (COUNT <= MAX) {
SUM += COUNT;
COUNT += 2;
}
printf ("d\n", SUM);
‘Moreover it is now really clear that the program contains a single
loop, and when the loop is complete, COUNT is greater than
MAX.
In another setting, consider the two programs of Figure 2-4.
Here we see simple subroutines for extracting of a substring
(designated by two character positions) in a given source string.
Under certain conditions, the subroutine sets an error flag.
In particular, both segments of code perform the following
computation.
Inputs:
SOURCE_STR: a string of characters
STR_LENGTH: the length of the string
POS1, POS2: _the start and end positions of a substring
Outputs:
SUB_STR: the substring of SOURCE_STR from POS1
to POS2
ERROR: FALSE if (POS1 <= POS2)
and (0 <= POS1) and (POS2 <= 100)
TRUE otherwise
Here the type
typedef char STRING[101];
is assumed. Also, if ERROR is true, no value is assigned to
SUB_STR.
41(C WITH EXCELLENCE: Programming Proverbs
In Figure 2-4A, we see a quite tightly nested series of intercon-
nected if statements. In Figure 2-4B, we have a program derived
from the use of alternative control structures. The second exam-
ple provides a much clearer description of the algorithm, mainly
because of the use of simple 1-in, 1-out control structures.
Figure 2-4. Control structures
(A) Poor
af (POS1 < 0)
goto Ll;
else
goto L2;
Ll: ERROR = TRUE;
goto L5;
L2: if ((POS2 < POS1) |} (POS2 > 100))
goto Ll;
else
goto L3;
L3: if ((POS2 - POS1) > LENGTH)
goto L
else
goto L4;
L4: ERROR = FALSE;
for (I = POS1; I <= PoS2; ++I) {
CHAR_POS = (I - POS1);
SUB_STR[CHAR_POS] = SOURCE_STR[1];
t
Ls: /* done */;
(B) Better
if ((POS1 <0) |! (POS2 < POS1) || (POS2 > 100)
(POS2 - POS1 > LENGTH))
ERROR = TRUE;
else {
ERROR = FALSE;
for (I = POS1; I <= Pos2; +41) {
CHAR_POS = (I - POS1);
SUB_STR[CHAR_POS] = SOURCE_STR[I];‘STRUCTURE IS LOGIC
The differences between these two programs are quite clearly
expressed in their corresponding flowcharts, shown in Figures
2-5 and 2-6. In the first illustration, the flowchart shows a profu-
sion of branching lines; in the second, a clearer structure is
evident.
Figure 2-5. Flowchart for Figure 2-44
POS2100
Pos2 Post
STRLENGTH
ERROR : = FALSE
t
FOR-LOOP
43C WITH EXCELLENCE: Programming Proverbs
In yet another setting, consider the program segments of Fig-
ure 2-7. These segments employ a variation of the bubble sort al-
gorithm to sort an array A, containing 10 entries. The array A is
declared of type
typedef float A[NUM_ELEMENTS];
Figure 2-6. Flowchart for Figure 2-4B
(Post <= 0)
oR
(Pos2=Post)
OR
(P0s2>100)
OR
(POS2 —POsi>
STRLENGTH)
ERROR := TRUE
ERROR : = FALSE
1
FOR-LOOP
The subroutine SWAP exchanges the values of the two variables
given as arguments. Basically, the programs scan the array A
once from the bottom (position 0) to the top (position 9). At each
examined position in the array, the elements at the top of the
position are already in order, and the program checks to see if
the element in the next position is itself in order If not, the ele-
ment is swapped with the previous element and then “bubbled”Figure 2-7.
‘STRUCTURE IS LOGIC
Avariant of the bubble sort algorithm
(#) Poor
#define NUM_ELEMENTS 10
int I, J;
float A[NUM_ELEMENTS] ;
{
I = NUM_ELEMENTS - 1;
Li:
La: if (J
goto L4
if (A[J] <= AlJ+1])
goto L3;
SWAP (&A[J], &A
L3: +43;
goto L2;
[y41]);
L4; T=1-1;
goto Ll;
L5: for (I = 0; I < NUM_ELEMENTS;
printf ("Zf\n", A[T]);
}
(B) Better
#define NUM_ELEMENTS 10
int I, J;
float A[NUM_ELEMENTS] ;
{
for (I = NUMELEMENTS; I >=
+41)
1; --1) {
for (J = 0; J < (I - 1); +4)
af (A[J] > A[J+1])
SWAP (&A[J], &A[J+1]);
}
for (I = 0; I < NUMELEMENTS; ++I)
printf ("Zf\n", A[T]);
45C WITH EXCELLENCE: Programming Proverbs
up until its proper place in the sorted part of the array is found.
Processing then continues at the position below the element
originally examined.
One point of this example is that the (possibly) small gain in
efficiency via the goto is not as important as the improvement in
clarity when the programmer uses alternative ways of construct-
ing his program.
The trap that programmers fall into is trying to “get rid” of
goto’. Ifyou play that game, the secret to winning is to start from
the problem. Someone says, “Hey, this goto is perfect.”
while (I< J) {
if (K<¥) {
goto L99;
}
L99: DOSOMETHING;
You are at a loss. But, just start over from the problem. Try to
undo the logic that led to this deeply embedded goto. The name
of the real game is to program without goto’s. The reason is to
force a clear, sensible logic to all your code.
In summary, the deeper issue here is not merely the elimina-
tion of goto's but the use ofa clear, logical program structure. The
trouble with goto’s is that when they are abused, they can lead a
programmer down the path of a confusing, almost spaghettilike,
logic. Chapter 5, which discusses program standards, provides
specific rules for the use of well-formed control structures. Ifyou
stick to these standards from the beginning, the goto problem
may go away.
As in all four proverbs of this section, the deeper issue here is
not the elimination of goto’s, nesting, or black-boxing—it is cod-
ing in logical structure so the other guy can read it.CHAPTER
3
Proverb 12
CODING THE
PROGRAM
Works done least rapidly, art most cherishes.
Robert Browning
Old pictures in Florence, Stanza 17
ALL WORDS MEAN “When tse a word,” Humpty
SOMETHIN Dumpty said, in a rather scornful
tone, “it means just what I choose it
to mean - neither more nor less.
“The question is," Alice said,
“whether you can make words
‘mean so many different things.
LEWIS CARROLL
Alice Through the Looking Glass
The adjective “mnemonic” is a useful modifier for programmers,
since precision in choosing words is necessary. It means " . .as-
sisting or intending to assist the memory.” As it is difficult to
overestimate the value of using mnemonic, user-defined names,
it is also easy to become careless using names that may later
complicate or confuse the intent of a program. Principles for se-
lecting good mnemonic names are discussed at length in Chap-
ter 6. Here it is sufficient to make one point: use names that
correctly reflect the objects they are intended to represent.
The disadvantage of poor names is easily seen in Figure 3-1
The programmer who writes code like the code shown in Figure
3-1A will probably need to keep a separate list specifying what
each variable name represents. Otherwise, the programmer may
lose track of what each variable does. Figure 3-1B significantly
47(C WITH EXCELLENCE: Programming Proverbs
clarifies the situation. The names themselves almost state the in-
tended calculation.
Figure 3-1B, however, is written using variable names with a
maximum length of six characters (the requirement in standard
FORTRAN). Figure 3-1C shows the possibilities in Pascal, and Fig-
ure 3-1D illustrates the possibilities in languages like C and Ada
that allow break characters within names. Certainly OVER-
TIMEWAGE and OVERTIME_WAGE are more informative than
OVTIME,
Figure 3-1. Use of mnemonic names
(A) Poor (BASIC revisited)
LET G = (W* H) + (0 * X)
LET T= R1*G
LETS =S1*G
LET P=G-T-S
(B) Better (FORTRAN revisited)
GROSS = (WAGE * HOURS) + (OVTIME * XHOURS)
TAX = TAXRATE * GROSS
SSEC = SSRATE * GROSS
PAY = GROSS - TAX - SSEC
(©) Best (good use of Pascal)
GROSSPAY (WAGE * HOURS) + (OVERTIMEWAGE * EXTRAHOURS) ;
TAX TAXRATE * GROSSPAY;
SOCSECURITY := SOCSECRATE * GROSSPAY;
NETPAY GROSSPAY - TAX - SOCSECURITY:
(D) Best (the C option)
GROSS_PAY (WAGE * HOURS) + (OVERTIME_WAGE * EXTRA_HOURS) ;
TAX TAX_RATE * GROSS_PAY;
SOC_SECURITY = SOC_SEC_RATE * GROSS_PAY;
NET_PAY GROSS_PAY - TAX - SOC_SECURITY;CODING THE PROGRAM
The challenge in picking names is somehow to balance the
following criteria:
a. accuracy
b. brevity
c. spurious effects.
For instance, consider Figure 3-2. The names here might ap-
pear fine at first glance. But look at these problems.
SUIT__NUM itis nota variable, as suggested by the name.
D too short for a major name.
INDEX too long for an innocent count? Yes or No?
COUNTS the plural is odd.
R_VAL cryptic.
These problems are rectified in Figure 3-2B.
Figure 3-2. Use of mnemonic names
(A) Paying token homage
#define SUIT_NUM 4
#define RANK_NUM 13
#define CARD_NUM 51
#define DECK_NUM 52
typedef struct {
int S_VAL;
int RVAL;
} card;
typedef char name[6];
typedef int rank[RANK_NUM+1];
typedef card deck[DECK_NUM] ;
PRINT_CARD (D)
deck D;
This procedure examines a deck of cards with one missing
card. It finds and prints the rank (e.g., "THREE" or
"KING") of the missing card. */
49(C WITH EXCELLENCE: Programming Proverbs
rank COUNTS;
name NAME_STR;
int RANK_VAL;
int INDEX;
for (RANK_VAL = 1; RANK_VAL <= RANK_NUM; ++RANK_VAL)
COUNTS[RANK_VAL] = 0;
for (INDEX = 0; INDEX < CARD_NUM; ++INDEX) {
RANK_VAL = D[INDEX].R_VAL;
++COUNTS[RANK_VAL] ;
for (RANK_VAL = 1; RANK_VAL <= RANK_NUM; ++RANK_VAL)
if (COUNTS[RANK_VAL] < SUIT_NUM)
NAME_STR = RANK_NAME[RANK_VAL] ;
printf ("Missing rank: %s\n", NAME_STR);
}
(B) More accurate names
#define NUMSUITS 4
#define NUMLRANKS 13
#define NUM_CARDS 51
#define DECK SIZE 52
typedef struct {
int SUIT_VAL;
int RANK_VAL;
} card_info;
typedef char rank_name[6];
typedef int rank_tally[NUM_RANKS + 1];
typedef card_info deck_contents[DECK_SIZE];
FIND_MISSING_CARD (DECK)
deck_contents DECK;
/* -- This procedure examines a deck of cards with one missing card
-- It finds and prints the rank (e.g., "THREE" or
-- "KING") of the missing card. */
50CODING THE PROGRAM
rank_tally COUNT;
rank_name NAME;
int I, RANK;
for (RANK = 1; RANK <= NUM_RANKS; ++RANK)
COUNT[RANK] = 0;
for (I = 0; I < NUMCARDS; ++I) {
RANK = DECK[I] .RANK_VAL:
+4COUNT[RANK] ;
t
for (RANK = 1; RANK <= NUMLRANKS; +4RANK)
if (COUNT[RANK] < NUM_SUITS)
NAME = RANK_NAME[RANK] ;
printf ("Missing rank: %s\n", NAME)
Proverb 13
These examples indicate that the thoughtful use of mnemonic
names improves the readability for the programmer and for
those who read the program later It is worth the time to devise
and use informative names. You may not fully appreciate the
benefits of your efforts now, but you will when a large program
has to be debugged or modified later Then you will appreciate
that you chose a word to mean exactly what you wanted it to
mean. Mnemonic assistance is then priceless.
SPACES HAVE Well timed silence hath more
MEANING, TOO eloquence than speech.
MARTIN panQuanian TUPPER
Of Discretion
Somewhere, in some graduate student class, a term was coined
that may seem trite but, as in the previous proverb, means some-
thing: “prettyprinting,” Briefly defined, it means the effective use
of “extra” spaces, blank lines, or special characters to delineate
the logical structure of a program.
Prettyprinting is especially useful in the verification and main-
tenance of programs. It helps to detect errors, such as im-
51(C WITH EXCELLENCE: Programming Proverbs
properly structured data description entries and incorrectly
nested if statements. Furthermore, a programmer trying to read
the program does not have to devote extra time to discovering its,
structure, an advantage that makes the program considerably
easier to understand.
At the “macro” level, the goal of prettyprinting is to show the
logical structure of major program units. For instance, suppose
we have a program of the form:
a. half page of comments
b. general #define statements
. half page of declarations
d. aone-page procedure ALPHA
e. a two-page procedure BETA
fa thirty-line main program.
If this is the structure, then prettyprinting means showing it, not
leaving it to the reader to figure it out. The key here is to use
breaks between major units. A break can be achieved by (1) a
group of 4 or 5 blank lines, (2) a dotted line, or (3) a new page. Fig-
ure 3-3 shows one possibility, using dotted lines.Figure 3-3.
CODING THE PROGRAM
Displaying overall structure
*
program SHOWME
== comments describing
== the program +)
/* ~~ Definitions */
#define NUMITEMS 100
#define MAX_LENGTH 72
#define FILENAME “entry.dat"
i
Declarations */
type declarations
variable declarations
ALPHA (parameters)
parameter declarations
{
local declarations
statements
BETA (parameters)
parameter declarations
{
local declarations
Statements(C WITH EXCELLENCE: Programming Proverbs
main ()
declarations
statements for
main program
Obvious? Sure, but take a peek at a professional program
sometime and see how often it is done.
At the “mini” level, the goal is to show the logical units of a sin-
gle module. For instance, consider:
A_COUNT = 0;
E_COUNT = 0;
T_COUNT = 0;
O_COUNT = 0;
U_COUNT = 0;
while (C != EOF) {
body of loop
}
There should be (it is not a matter of “taste”) one blank line here.
Where? Just before the while loop. Why? Because there are two
logical units: (a) initialization of counters, and (b) a processing
loop. It is impossible to give hard and fast rules where to put
blank lines, but the gist of it is
© between conceptual chunks
Some programmers stuff a few blank lines in just for looks, but
the purpose is to show meaning.
At the “micro” level, the goal is to show the individual parts of
statements. You would be horrified to see
ACCOUNT #1
but not
ALCOUNT += 1;CODING THE PROGRAM
The reason is that the correct spacing illuminates the correct
meaning.
There are all kinds of cases where a little white space is effec-
tive. Consider:
(a) ACOUNT=0
(b) THR*Q
(ce)
(d) for (I=2;I<-MAX;+4I)
(e) if(HEIGHT
#define EOL '\n'
main ()
{int ALCOUNT, E_COUNT, I_COUNT, O_COUNT, U_COUNT;
char CHARACTER; A_COUNT = 0;
E_COUNT = 0; I_COUNT = 0
O_COUNT = 0; U_COUNT
printf("Enter line of text:\n");
do {
scanf ("Zc", &CHARACTER);
switch (CHARACTER)
{
case 'A':++A COUNT;
break,
case 'E':++E_COUNT.
break;
case 'I':++4I_COUNT;
break;
case '0':++0_COUNT.
break;
case 'U':++U_COUNT;
break;
default: /* do nothing */;
}} while (CHARACTER != EOL);
printf(" A=Z%d E=%d I=%4 O-=%d U= Zain",
ALCOUNT, E_COUNT,
I_COUNT, O_COUNT, U_COUNT);
}CODING THE PROGRAM
(B) Giving token thought to prettyprinting
/* program VOWELS */
#include
#define EOL '\n'
main ()
{
char CHARACTER;
int ALCOUNT, E_COUNT, I_COUNT,
O_COUNT, U_coUNT;
‘A_COUNT-
E_COUNT=0;
I_count=0;
o_count=0;
‘U_COUNT:
printf ("Enter line of text: \n");
do {
scanf ("%o", &CHARACTER) ;
switch (CHARACTER)
{
case 'A':++A COUNT;
break;
case 'E!:++E COUNT;
break;
case '
:++I_COUNT;
+40_COUNT;
:++U_COUNT;
default: /* do nothing */;
} } while (CHARACTER != EOL);
printf(" A= %d E=%d I=%d O=%d U= Zd\n",
ALCOUNT, E_COUNT, I_COUNT, O_COUNT, U_COUNT) ;
}
57(C WITH EXCELLENCE: Programming Proverbs
(©) Going overboard
/* program VOWELS */
#include
#define EOL '\n!
main ()
{
char CHARACTER;
int A_COUNT,
E_COUNT,
T_COUNT,
o_couNT,
U_couNT;
‘A_COUNT
E_COUNT
T_CouNT
0_COUNT
U_COUNT
"
ecoco
printf ("Enter line of text: \n");CODING THE PROGRAM
scanf ("Zc", &CHARACTER);
switch (CHARACTER)
{
case 'A': ++A_COUNT;
break;
case 'E': ++£ COUNT;
break;
case ++I_COUNT;
break;
case '0': +40_COUNT;
break;
case 'U': +4U_COUNT;
break;
default: /* do nothing */;
}
} while (CHARACTER != EOL);
printf("A=%d E=%d I=%d O0=%d U-=Za\n",
A_COUNT, E_COUNT, I_COUNT, O_COUNT, U_COUNT); }
59(C WITH EXCELLENCE: Programming Proverbs
(D) Good prettyprinting
/* program VOWELS */
#include
#define EOL '\n'
main ()
{
char CHARACTER;
int A_COUNT, E_COUNT, I_COUNT, O_COUNT, U_COUNT;
A_COUNT
E_COUNT
T_CouNT
o_couNT
U_COUNT
escco
printf ("Enter line of text:\n");
do {
soanf ("Zo"", &CHARACTER) ;
switch (CHARACTER) {
case 'A': +4A COUNT;
break;
case 'E': +48 COUNT;
break;
case 'I': ++_COUNT;
break;
case '0': +40_COUNT;
break;
case
2 +4U_COUNT;
break;
default: /* do nothing */;
}
} while (CHARACTER != EOL);
printf("A=%d E=%d I=%d O=Zd U= d\n",
ALCOUNT, E_COUNT, I_COUNT, 0_COUNT, U_cOUNT);Proverb 14
CODING THE PROGRAM
In our examples, we have attempted to incorporate certain
prettyprinting examples. Appendix A itemizes some of these
standards. We encourage the reader to make use of these stan-
dards. But keep in mind that standards (and automatic pret-
typrinting programs) only provide a minimum guide. It's the
meaning that counts, both in the words and in the spaces. If the
program you are writing has a good logical structure, then show
it!
COMMENT FOR And none can read the text, not
CONTENT even I;
And none can read the comment
but myself
ALFRED, LORD TENNYSON
Pelleas and Ettarer
What the poet has to say above is, unfortunately, all too descrip-
tive of too many programs written today. The myths surrounding
the use of comments in programs is perhaps dispelled in this
simple test:
True or false: A readable program is one with copious
comments.
Answer: Usually, false
‘True or false: A good program may have no comments in the
statement part.
Answer: Possibly, true.
Comments are a form of internal documentation that allows
the programmer to describe the internal workings of a program.
One example will suffice to make the point. Consider the pro-
gram of Figure 3-5A. This program has no comments. The reader
is invited to examine the program and determine the meaning of
each statement
Next, consider the program of Figure 3-5B. The comments
convey the logical structure of the program. OX. But they go too
far They are distracting and tell the obvious.
Figure 3-5C shows a deeper concern for the reader The com-
ments are clearly separated from the code and give more precise
statements about the entire function, even telling us about Eu-
clid’s algorithms.
61C WITH EXCELLENCE: Programming Proverbs
Figure 3-5. Use of effective comments
(A) Poor solution: no comments
/* Program GCD_1 */
#include
main ()
{
int HILVALUE, LO_VALUE, REMAINDER, GCD, Nl, N2;
printf ("Enter two positive integers: ");
scanf ("Zd Za", &N1, &N2);
if (Nl <= N2) {
LO_VALUE = Nl;
HI_VALUE = N2;
} else {
LO_VALUE = N2;
HILVALUE = N1;
)
do {
REMAINDER = HI_VALUE % LO_VALUE;
HI_VALUE = LO_VALUE;
LO_VALUE = REMAINDER;
} while (REMAINDER != 0);
GOD = HI_VALUE;
printf ("GCD is Zd\n", GCD);CODING THE PROGRAM
(B) Overdoing it (but good for teaching)
/* ~~ Program GOD_2
-- Function to compute the greatest common divisor of
-- two numbers using Euclid's algorithm. Euclid's algorithm
-- is one of repeated division. */
#include
main ()
{
int HILVALUE, LO_VALUE, REMAINDER, GCD, Nl, N2;
/* Get initial values. */
printf ("Enter two positive integers: ")
scanf ("Zd %d", &N1, &N2);
/* Place in order. */
af (NL <= Na) {
LO_VALUE = NL;
HI_VALUE = N2;
} else {
LO_VALUE = N2;
HI_VALUE = NL;
}
/* Perform Euclid's algorithm until GCD is found. */
do {
REMAINDER = HI_VALUE % LO_VALUE;
HI_VALUE = LO_VALUE;
LO_VALUE = REMAINDER:
} while (REMAINDER != 0);
/* Print results. */
GCD = HI_VALUE;
printf ("GCD is Zd\n", GCD);(C WITH EXCELLENCE: Programming Proverbs
(©) Best solution: careful commenting
(* -- Program GCD_3
Euclid's algorithm for the greatest common divisor of:
two numbers:
(1) The higher number H is divided by the lower L
(2) If the remainder is zero, H is the GCD,
(3) otherwise, repeat with L divided by the remainder. */
#include
main ()
{
int HI_VALUE, LO_VALUE, REMAINDER, GCD, Nl, N2;
printf ("Enter two positive integers:
scant ("Zd %d", &N1, &N2);
if (NL <= N2) {
LO_VALUE = N1;
HI_VALUE = N2;
} else {
LO_VALUE = NZ:
HI_VALUE = Nl;
,
do {
REMAINDER = HI_VALUE % LO_VALUE;
HILVALUE = LO_VALUE
LO_VALUE = REMAINDER;
} while (REMAINDER != 0);
GoD = HT_VALU
printf ("GOD is Z4\n", GcD);
Although the value of using comments can be illustrated over
and over again, the programmer is often tempted not to use
them. After all, when a programmer is writing a piece of code,
comments may not be needed. But how many times during cod-
ing does the programmer go back and try to figure out what has
happened and what is left to do? And what about the next day?
Or the next week? Or the occasion when you are asked to change
someone else's program?Proverb 15
CODING THE PROGRAM
One additional proverb is useful here: Temperance is modera-
tion in all things. Comments can be overused as well as misused.
It is far better to use good prettyprinting and good mnemonic
names rather than to clutter up your code with copious com-
ments. Comments should convey useful information. Frequent
comments like
/* Empty buffer */
CLEAR_BUFF (B);
not only clutter up your program but may completely dis-
courage anyone from trying to wade through it.
Here are Henry's three rules for C comments:
1. Write header comments (for the main program or a
function).
2. Do header comments before coding.
3. Try not to use any other comments.
Don't let rule 3 confuse you. Good code speaks for itself: ifa piece
of code needs a comment, it is likely that the code itself could be
better
In short, comments can promote the design of maintainable
programs. They can make a difference. Use them, temperately.
MAKE CONSTANTS
CONSTANT
The result of this proverb is most valuable after a program is
written, during program testing or maintenance. The essential
idea is to make sure that all constant data items are recognized
and given a name. Furthermore, no statement should modify
these constant data items.
Consider the programming situation of Figure 3-6A. The pro-
grammer assumed that the table would always contain 50 items.
The integer 50, besides its use in constructing tables, was used
freely throughout the programs in computing averages and con-
trolling conditions. When an increase in data items resulted in
100 items, changing the number 50 to 100 was a searching chore,
65(C WITH EXCELLENCE: Programming Proverbs
Figure 3-6.
for it was all too easy to miss an occurrence of the integer 50.
This is not the case with Figure 3-6B, where a data name was cre-
ated and given a value in the constant declaration.
Integer constants
(A) Poor
main ()
{
float TOTAL, AVERAGE;
int I;
float LIST[50];
TOTAL = 0
for (I= 0; I < 50; +41)
TOTAL = TOTAL + LIST[I];
AVERAGE = TOTAL / 50;
printf ("\nAverage Value = Zf\n", AVERAGE);
(B) Better
#define NUM_ITEMS 50
main ()
{
float AVERAGE, TOTAL;
int I;
float LIST[NUM_ITEMS] ;
TOTAL = 0;
for (I = 0; I < NUMLITEMS; ++I)
TOTAL = TOTAL + LIST[I];
AVERAGE = TOTAL / NUM_ITEMS;
printf ("\n Average value = Zf\n", AVERAGE);Figure 3-7.
CODING THE PROGRAM
Not all numbers should be named. Consider the simple code
fragments of Figure 3-7. Here the gain is not clear Reason? The
conversion factor 9/5 and the base 32 stand for themselves.
Naming obvious constants
(4) Poor
float CENTIGRADE (FAHRENHEIT)
float FAHRENHEIT;
{
}
return ((9.0/5.0)*(FAHRENHEIT - 32.0));
(B) Better?
#define CONV_FACTOR 1.8
#define BASE_VALUE 32.0
float CENTIGRADE (FAHRENHEIT)
float FAHRENHEIT;
{
return (CONV_FACTOR*(FAHRENHEIT - BASE_VALUE));
}
The real issue involved here concerns the so-called “magic num-
bers,” numbers such as those used in Figure 3-8A, a problem de-
rived from [Marcotty, 1977], where
typedef enum {TOO_SHORT, TOO_TALL, OVER_WEIGHT,
UNDER_WEIGHT, NORMAL} weight_status;
How many times while reading code have you been stumped by
what 75 and 124.0 are all about? A preceding comment line
might help, but isn't the alternative of Figure 3-8B much better?
The principle is this:
Ifa number, character, or string has a meaning that is not
self-apparent, might be changed, or is confusing on its
own, name it.(C WITH EXCELLENCE: Programming Proverbs
Figure 3-8. Magic numbers
(A) Poor
weight_status WEIGHT_CHECK(HEIGHT, WEIGHT)
float HEIGHT, WEIGHT;
/* -- Function to determine whether a man's weight lies
- within normal limits. For heights in the range of
62 to 75 inches
~~ On exit, WEIGHT_CHECK returns one of:
= TOO_SHORT, TOO_TALL, OVERWEIGHT, UNDER_WEIGHT, NORMAL */
if (HEIGHT < 62.0)
return (TOO_SHORT) ;
else if (HEIGHT > 75.0)
return (TOO_TALL) ;
else if (WEIGHT > (133.0 + 4.3¥(HEIGHT - 62.0)))
return (OVER_NEIGHT) ;
else if (WEIGHT < (124.0 + 4.0*(HEIGHT - 62.0)))
return (UNDER_WEIGHT) ;
else
return (NORMAL);CODING THE PROGRAM
(B) Better
#define MIN HEIGHT 62.0
#define MAX_HEIGHT 75.0
#define LO_WEIGHT 124.0
#define HI_WEIGHT 133.0
#define CONV_FAC_1 4.3
#define CONV_FAC_2 4.0
weight_status WEIGHT CHECK (HEIGHT, WEIGHT)
float HEIGHT, WEIGHT;
/* ~- Function to determine whether a man's weight lies within
-- normal limits. For heights in the range of 62 to 75 inches.
On exit, WEIGHT_CHECK returns one of
TOO_SHORT, TOO_TALL, OVERWEIGHT, UNDER WEIGHT, NORMAL */
if (HEIGHT < MIN HEIGHT)
return (T00_SHORT) ;
else if (HEIGHT > MAX_HEIGHT)
return (TOO_TALL) ;
else if (WEIGHT > (HI_WEIGHT + CONV_FAC_1*(HEIGHT - MIN_HEIGHT)))
return (OVERWEIGHT) ;
else if (WEIGHT > (LO_WEIGHT + CONV_FAC_2*(HEIGHT - MIN HEIGHT) ))
return (UNDERWEIGHT) ;
else
return (NORMAL);
There are two good ways in C to name constants.
The first method is using initial values in declarations, for
instance
float MIN_HEIGHT = 62.0;
Such a value will hold within the definition of a function.
The second method is using #define, for example,(C WITH EXCELLENCE: Programming Proverbs
#define NUM_ITEMS 50
The #define option is most useful for naming quantities that
are used in numerous functions, for preprocessor lines apply to
the remainder of a file. Typically, we may have
#define SOC_SEC_RATE 6.45
#define NUM_ITEMS 50
#define MAX_LINE_LENGTH 80
#define STD_INDENT 10
#define CONTROL_CHAR '@'
#define BLANK ' '
#define FILLER " ”
#define MARKER "**"
If the constants are used in several files, the constants can be
defined in a separate file. When the constants are needed in an-
other file, the file of constants can be “included,” for example,
#include TABLE_DATA
where TABLE_DATA may contain #define’s as well as other de-
clarative items.
On the other hand, there will be a few (only a few) cases where
a constant can stand on its own, for instance,
52 the number of cards in a deck.
9/5 Fahrenheit to centigrade conversion factor
0 initial value of a sum.
Here, you have a choice, name it or not. (But don't name 0 as
“zero.”)
Perhaps Shakespeare's remark in one of his sonnets summar-
izes this proverb better: ‘A constant is a wondrous excellence.”
The moral is simple: a well-designed program isolates constant
data items in a constant declaration. There are two advantages.
First, program modification is easier But more important, read-
ing the program is not an exercise in detective work, i.e,, it is not
supposed to be a mystery for the reader You will need more con-
stant declarations, but this is a small, one-time price to pay for
the long-term dividend of a logically organized program.
70CODING THE PROGRAM
Proverb 16 NAME ALL TYPES There are ways, but the Way is
uncharted; There are names but
not nature in words: Nameless
indeed is the source of creation.
But things have a mother and she
has a name.
Lao TZU
‘The Way of Life, 1
What is an unnamed type? Look at the followin,
typedef struct {
int SUIT:
int RANK
} card_deck(52):
float A[NUM_TESTS] ;
card_deck D;
For the variable A, what is the name of its type? There is none. For
D{2}, what is its name of its type? There is none. Both have un-
named types.
Now consider the following:
typeder struct {
int SUIT;
int RANK;
} card_info;
typedef card_info card_deok(52];
typedef float test_results[NUM_TESTS] ;
test_results A;
card_deck —D;
What is the name of type of A? “test_results.” What is the name
of type of D{2}? “card_info.” Both now have named types
Why should we name all types?
. It is an easy thing to do.
71C WITH EXCELLENCE: Programming Proverbs
Proverb 17
Figure 3-9.
(A) Wrong
lL X+H=1;
2. INDEX int;
2. Procedure and function parameters will be clearer with
named types.
3. The name tells us what the type means.
Why is this proverb lost on most programmers? Maybe they
haven't thought about it, maybe it takes a little extra effort.
It's a good idea. Try it.
GET THE SYNTAX Experience is the name everyone
CORRECT NOW gives to their mistakes.
(OSCAR WILDE
Lady Windemere’s Fan, Act IIT
In G, there is a good deal of criticism about its syntax. There is
also criticism, which is often passed off to a C compiler; for not
helping with “obvious” program mistakes. But the wise program-
mer does not look for excuses.
Consider the program fragments of Figure 3-94, which con-
tain some simple syntactic errors. Figure 3-9B shows the corre-
sponding corrected version. Errors like the ones in the first
example should be screened out in advance by a careful pro-
grammer It is our contention that no errors, no matter how triv-
ial, should pass the attention of a good programmer, for it is
possible that some of them may not be detected by the compiler
and will appear only after a program is in full operation. More
subtly, some of those “tiny” errors may be keys to larger
problems.
‘Some simple syntactic errors
3. for (I= 1, I<=N, +4I)
4. if (x=
¥)
printf ("Unequal values\n");CODING THE PROGRAM
5. SORT(NAME[50})
string NAME;
6. SQUARE(A, Y)
float A, ¥;
{
v= Ata;
}
7 Y, Z: float;
XY;
8. if (A != 0.0) {
X1 = B + (sqrt((B*B) ~ (4*a*(C))) / (2*A)):
X2 = B- (sqrt((B*B) - (4*A(C))) / (2*A)):
else
XL = -C / B;
X2 = 0.0;
}
9. printf ("A poorly structured program is like a syntax, error\");
10. #define PI = 3.14159;
int I;
(B) Correct
LK
2. int INDEX;
3. for (I= 1; 1 <=N; +41)
4. if (x !=¥)
printf ("Unequal values\n");
5. SORT(NAME)
string NAME[50];
6. SQUARE(A, ¥)
float A, *Y
{
}
*Y = ATA;(C WITH EXCELLENCE: Programming Proverbs
7
10
float X, Y, Z;
if (¥
z
else
0.0)
xIY
printf (“Attempt to divide by zero! \n");
if (A != 0.0) {
xl=
x2
} else {
x1
x2 =
}
B + (sqrt((B*B) - (4*A*C) / (2*A));
B ~ (sqrt((B¥B) - (4*A*C) / (2*A)):
“CB;
0.0;
printf ("A poorly structured program is like a syntax error\n");
#define PI 3.14159
int I;
74
Sometimes we work away at a desk and the manual is out in
the car Rather than pause for a moment to ensure that a particu-
lar syntactic construction is correct, we press forward thinking
that any trivial error will be caught during verification. Not so.
The time to consider syntax is not while testing the completed
program, but while preparing it. Keep the manual handy as you
write the code. If you are not absolutely positive that the syntax
of the statement you are writing is perfect, look it up. It only takes
a few seconds, and your grasp of the language will improve with
constant references to the manual. This work habit is all the
more crucial if you are just learning C or if you have done consid-
erable programming in another language with similar, but nev-
ertheless, different syntactic constructs.
Why? Why not let the compiler find the errors? It is shocking
how much time programmers squander at the terminal. There is
always a reason. A short function here, ten lines there, a quick
test run, It doesn’t work. A quick fix. An integration error A new
run, On and on. .
You can and should write programs that are completely free of
syntactic errors on the first run. But to do so, you have to be con-
vinced that you can indeed do it. Have someone else read the
work you produce (Proverb 22). Just think of all the hours of ter-Proverb 18
CODING THE PROGRAM
minal time you can waste tracking down simple syntactic errors,
not to mention some severe run-time problems that can be
caused by such “trivial” errors.
REMEMBER YOUR ___ Whenever you teach, be brief that
READER your readers minds may readily
comprehend and faithfully retain
your words.
HORACE
Epistles, 1
Programmers have a desire to produce that one unique program
which will establish for themselves a lasting reputation in the
field. They do this by shortening the code, running the program
faster, and using fewer variables. The result is more often a tar-
nished image because the benefits seldom outweigh the costs in-
curred. Resist this temptation; a good programmer writes code
that is simple to read and focuses on the problem to be solved.
To avoid surprises for the reader, a programmer must be
aware that part of his job is to map real-world entities (e.g,
prices, temperatures, dollars, dates, and people's names) into the
constructs of the C language (e.g, numbers and strings). A pro-
grammer must not only choose a particular representation for
an entity but must make sure that an operation validly repre-
sented in C has meaning when applied to the original entity. For
example, you can perform all arithmetic operations on numeric
data. But while you can subtract two dollar amounts to get an-
other dollar amount, it does not make sense to multiply two dol-
lar amounts or to take the square root of a dollar amount
More generally (see Figure 3-10), the input to any program re-
presents some class of real-world entities: chess squares, wages,
row numbers, cards, colors, and the like. A computation is re-
quired to transform these entities into other entities; for exam-
ple, a chess move, an amount of money, a new row number, a
card played, another color, and the like. The computer, however,
can operate only in limited ways and on a limited set of entities
like strings or integers. Thus, it is necessary to transform the
real-world set of entities and operations into a program contain-
ing computer entities and operations. We shall say that a pro-
gram is “straightforward” if each step in the computer algorithm
75(C WITH EXCELLENCE: Programming Proverbs
Figure 3-10.
76
Model for a typical programming task
REAL WORLD
PROBLEM RESULT
REAL WORLD
ENTITIES AND
OPERATIONS
REAL WORLD ALGORITHM
REAL WORLD
ENTITIES
THE PROGRAMMER’S
REPRESENTATION
OF THE PROBLEM
INTERPRETATION
OF RESULTS
PROGRAM RESULT
PROGRAMMING
LANGUAGE
OBJECTS AND
OPERATIONS
COMPUTER ALGORITHM
‘OUTPUT DATA
has a simple correspondence to a step in a reai-world algorithm
that a person would use to solve the problem.
Straightforwardness and naturalness are closely connected to
the clarity and readability of programs. Any programmer soon
learns that understanding another programmer's code is the
“curse” of the profession. Often programs do not accurately re-
flect the real-world algorithm corresponding to the numerical,
array, logical, or string operations that are required for their com-
puter implementation.
Try looking at any program that you wrote a month ago with-
out peeking at the comments or the documentation. See if you
immediately understand all of its details. Now imagine what it
would be like for someone else who hadn't seen the program be-
fore. Clarity is a godsend to anyone who has to document, de-
bug, extend, use, grade, or otherwise handle a computer
program, Unless a program printout is being used only by the
original programmer, clarity is a double godsend to anyone hav-
ing to use the program.
For example, consider the following problem. Given a deck of
51 cards, we are asked to find the rank of the missing card (by
computer, of course!). The deck is stored in a 51-element array
called DECK. To make things simple, a function GET_RANK_
NAME is assumed to be defined. GET_RANK_NAME takes theCODING THE PROGRAM
rank of a card (1 to 13) as its argument and maps the rank into a
6-character string:
"ACE " for 1,
“TWO " for 2,
“KING " for 13
Figure 3-11 depicts two pieces of code, both of which claim to do
the job correctly. Your problem is to discover “why” each one
gives the correct result.
Figure 3-11B is obviously correct. In the real world, it corre-
sponds to keeping a checklist of each rank and checking off the
card ranks, one by one, until the deck is exhausted. Then the
checklist is scanned to find out which card has been checked
fewer than four times, and the selected rank is printed.
Figure 3-114 is also correct but far less straightforward. It has
little correspondence to typical card table operations. It runs
through the deck keeping a modulo 13 count of all the ranks. Af-
terwards it subtracts this count from 13 to get the rank of the
missing card. If you are not convinced, try it.(C WITH EXCELLENCE: Programming Proverbs
Figure 3-11.
78
‘Two card-counting algorithms to find the rank of a missing card
(A) Tricky card count
/* Program CARD_1 */
#include
#define NUMSUITS 4
#define NUM_RANKS 13
#define NUM_CARDS 51
#define DECK_SIZE 52
typedef struct {
int SUIT_VAL;
int RANK_VAL;
} card_info;
typedef char rank_name[6];
typedef card_info deck_contents[DECK SIZE];
main ()
{
int I, RANK, COUNT;
rank_name NAME;
deck_contents DECK;
COUNT = 0;
for (I = 0; I < NUMCARDS; +41) {
RANK = DECK[I].RANK_VAL;
COUNT = (COUNT + RANK) % NUM_RANKS;
}
NAME = GET_RANK_NAME(NUM_RANKS - COUNT) ;
printf ("Missing rank is: %s\n", NAME);CODING THE PROGRAM
(B) Natural card count
/* Program CARD_2 */
#include
#define NUM_SUITS 4
#define NUM_RANKS 13
#define NUM_CARDS 51
#define DECK_SIZE 52
typedef struct {
int SUIT_VAL;
int RANK_VAL;
} card_info;
typedef char rank_name(6];
typedef int rank_tally[NUM_RANKS + 1];
typedef card_info deck_contents[DECK_SIZE];
main ()
{
rank_tally COUNT;
int I, RANK;
rank_name NAME;
deck_contents DECK;
for (RANK = 1; RANK <= NUMLRANKS; ++RANK)
COUNT[RANK] = 0;
for (I = 0; I < NUMCARDS; ++I) {
RANK = DECK[I].RANK_VAL;
++COUNT[RANK] ;
}
for (RANK = 1; RANK <= NUMLRANKS; ++RANK)
if (COUNT[RANK] < NUM_SUITS)
NAME = GET_RANK_NAME(RANK);
printf ("Missing rank is: %s\n", NAME);(C WITH EXCELLENCE: Programming Proverbs
Another area where natural programs have an advantage is
that of extendability. Because a natural algorithm is analogous to
real-world operations, extensions using these operations can
often be made easily. Since a tricky algorithm usually depends on
specific properties of numbers or strings, it usually cannot be ap-
plied to cases other than the original problem.
The program of Figure 3-11 illustrates this point well. Say that
we now wish to extend the given programs to find the ranks of
missing cards from a deck containing fewer than 51 cards. The
algorithm of Figure 3-11B can be extended quite readily, as
shown in Figure 3-12. In the corresponding real world, the
sweep of the checklist is the same as before except that when we
find that a card is missing, we print it, and see if any others are
missing.
Figure 3-11A cannot be extended, even to cover the case of two
missing cards. The validity of the algorithm is based on the con-
dition that there is only one missing card. With only one missing
card, the difference between 13 and the count must be the rank
of the missing card. With two or more missing cards, the sum of
the ranks of the missing cards may be split in an arbitrary num-
ber of ways. In short, this algorithm fails because it is based on
the particular properties of numbers instead of the properties of
cards.
Before concluding the discussion of this proverb, remember
that when you depart from a clear analogy with the problem,
structure and clarity are frequently lost. Merging two or more
modules of code in order to wring out those “extra lines” or
adding a few lines in order to gain efficiency are both easy ways
to prevent anyone from following the program. Considering the
extra time needed to develop the special wrinkle and the extra
testing time needed to check the new and often subtle boundary
conditions, are you sure that fewer machine instructions or
faster machine execution is likely?
There are cases where unusual methods are, in fact, justified,
for example, to provide execution speed where it counts or econ-
omy of storage. However, before you resort to tricky program-
ming, you should have a clear reason for doing so. Moreover, you
should estimate the actual gain such programming will yield.
Otherwise, you should stick to operations and objects that have a
natural analogy in the real world.
Heed well, C programmers.CODING THE PROGRAM
Figure 3-12. _ Extension of the natural card count
/* Program CARD_3 */
#include
#define NUM_SUITS 4
#define NUMRANKS 13
#define DECK_SIZE 52
typedef struct {
int SUIT_VAL;
int RANK_VAL;
} card_info;
typedef char rank_name(6] ;
typedef int rank_tally[NUM_RANKS + 1];
typedef card_info deck_contents[DECK_SIZE];
main ()
{
rank_tally COUNT;
int I, RANK, NUMCARDS, NUM_MISSING;
deck_contents DECK;
rank_name NAME;
for (RANK = 1; RANK <= NUM_RANKS; ++RANK)
COUNT [RANK]
scanf ("Zd", &NUM_CARDS);
for (I = 0; I < NUMCARDS: ++I) {
RANK = DECK(T] .RANK_VAL;
+4COUNT[RANK] ;
}
for (RANK = 1; RANK <= NUYLRANKS; ++RANK)
if (COUNT[RANK] < NUMSUITS) {
NAME. = GET_RANK_NAME(RANK) ;
NUM_MISSING = NUM_SUITS - COUNT[RANK] ;
printf (" Zd missing with rank: %s\n", NUMLMISSING, NAME);
81(C WITH EXCELLENCE: Programming Proverbs
Proverb 19
Figure 3-13.
YOUR OUTPUT IS He has gained every point who has
NOT ONLY FOR YOU _ mixed practicality with pleasure, by
delighting the reader at the same
time as instructing him.
HORACE,
Epistles, 1
The best programmers that I have known have a bit of the poet in
them: their output sings alittle, rhymes a little. This quality often
goes undetected by those who read the program later, who do
not care how much time the programmer has spent in designing
and debugging a program. The programmer who gets locked
into a pattern of writing programs thinking only about results—
the product that most people look for—generally produces out-
put that pleases only the author and not the reader: Although the
programmer knows what his program means and how to inter-
pret the results, the same cannot be said for those who must
wrestle with his product six months later
This is such an obvious proverb. With all the work and effort
required to write a program, i.e., to solve a problem, why should
the output of a complex challenge simply look like a slipshod
effort because it is poorly spaced, messy, or skimpy? Look at Fig-
ure 3-13. The output of this simple program is almost in-
comprehensible without an exact knowledge of the problem
definition or the program itself. The output of Figure 3-13B, on
the other hand, can be clearly understood by even the fledgling
programmer.
Use of informative output
(A) Poor
/* program REPORT_1 */
#include
#define NUM_WEEKS 4
typedef int sales[NUM_NEEKS + 1];
sales WEEKLY SALES;
int SALESMAN, AVERAGE_SALE;
int NUM_SALES, I;CODING THE PROGRAM
int TOTAL SALES (WEEKLY_SALES)
sales WEEKLY_SALES;
{
int I, TOTAL;
TOTAL = 0;
for (I = 1; I <= NUM WEEKS; ++I)
TOTAL = TOTAL + WEEKLY_SALES[I];
return (TOTAL);
}
main ()
{
scanf ("%d", &SALESMAN) ;
for (I = 1; I <= NUMWEEKS; ++I)
scanf ("%d", &WEEKLY_SALES[T]);
AVERAGE_SALE = TOTAL_SALES(WEEKLY SALES) / NUM_WEEKS;
printf ("4d Zd\n", SALESMAN, AVERAGE_SALE) ;
for (I = 1; I <= NUMWEEKS; +41)
printf ("%4d\n", WEEKLY_SALES[T]);
}
-- Output from Figure 3-13A
4 1000
1030
980
1000
990
(B) Better
/* Program REPORT\2 */
#include
‘#define NUM_WEEKS 4
typedef int sales[NUM_WEEKS + 1];
sales WEEKLY_SALES;
int SALESMAN, AVERAGE_SALE;
int NUM_SALES, I;
int TOTAL_SALES (WEEKLY_SALES)
sales WEEKLY_SALES;
{
83C WITH EXCELLENCE: Programming Proverbs
int I, TOTAL;
TOTAL = 0
for (I = 1; I <= NUMLWEEKS; ++I)
TOTAL = TOTAL + WEEKLY_SALES(I];
return (TOTAL) ;
main ()
printf ("Enter salesman id number: ");
scanf ("%d", &SALESMAN) ;
printf ("Enter each weekly sales amount: "
for (I = 1; I <= NUM_WEEKS; ++I)
scanf ("Za", gWEEKLY_SALES[T]);
printf ("\nSalesman Z1d sold:\n", SALESMAN);
for (I = 1; I <= NUM_WEEKS; ++I)
printf (" $2Z5d in week %ld\n", WEEKLY_SALES[I], I);
AVERAGE_SALE = TOTAL_SALES(WEEKLY_SALES) / NUM_WEEKS;
printf ("\n");
printf ("Average weekly sales: $Z4d\n", AVERAGE_SALE) ;
-- Output from Figure 3-13B
Salesman 4 sold:
$ 1030 in week 1
$ 980 in week 2
$ 1000 in week 3
$ 990 in week 4
Average weekly sales: $1000
The most pleasing poems and the most striking works of art
have an organizing simplicity about them. They delight the
reader and viewer as they instruct him. The moral of this proverb
is that your output must be annotated so that it will stand on its
own, not just for you but, if your effort is truly worthwhile, for all
the future readers who will appreciate it.CODING THE PROGRAM
Proverb 20 REVIEW, REVIEW, When I get an ancient edition, I
REVIEW have it copied; after it is copied, 1
have it checked; after checking,
have it set up, after setup, have it
checked again; after checking, have
it printed; after printing, have it
checked again. Even with such care,
there are two or three percent
typographical errors. In this matter,
where the eyes face something
directly and closely, there are
mistakes.
CHEN CHIJU, (1558 - 1639)
Yentse Yushih
The plight of this early Chinese writer is much the same as
ours—and we have to solve our problems much the way he did,
by reviewing again and again or, in our terms, by simply “hand-
checking” the program. It is not easy to convince programmers
that a program should be hand-checked before it is run. Yet run-
time errors are the hardest to detect, and even if the system
provides excellent debugging facilities, using the computer
alone for this necessary review can be hazardous. The program-
mer may well be surprised at discovering errors like incorrect
signs, infinite loops, and unusual conditions that lead to pro-
gram crashes. More important, the benefit is that the program-
mer is likely to improve the program when it is seen in its “final”
form.
The technique is simple. Choose a sample input, then calcu-
late the output as if you were the computer, assuming nothing
and using exactly what is written. See that each logical unit per-
forms correctly and that the control sequence through the units
is correct. If the program is too long or too complex to check in
its entirety, then check each major section first, and later check
the smaller units, assuming that the major units are correct.
When choosing sample input, take special care to include the
boundary conditions and other unusual cases. Failure to ac-
count for them is one of the most common programming errors.
For example, suppose you are writing a program that takes
two nonnegative integers as input, a dividend and a divisor, and
prints out two numbers—the integer part of the quotient and the
integer remainder
8S(C WITH EXCELLENCE: Programming Proverbs
Figure 3-14.
(A) First attempt
Assume that C does not have integer division and modulus op-
erators. That is, given the integer variables DIVIDEND, DIVISOR,
QUOTIENT, and REMAINDER, assume that you cannot just say:
QUOTIENT
REMAINDER
DIVIDEND / DIVISOR;
DIVIDEND % DIVISOR:
As a first pass, consider the program segment in Figure 3-14A.
Does this work? No. Why not? Checking by hand, we find that the
program runs into trouble at the while statement. REMAINDER
is initially undefined. (Remember, never assume that the compu-
ter assumes anything.) So we change the program as shown in
Figure 3-14B.
Does the program work now? Obviously not. Checking the
boundary conditions by hand, we find that when the divisor is
zero, the algorithm doesn't terminate. Since division by zero is
undefined, we should process this case separately. It wouldn't be
wise to leave the program in an infinite loop if we only consid-
ered the cost of computer time. So we change the program as
shown in Figure 3-14C.
It still doesn't work. Checking another boundary condition,
we find that if the divisor exactly divides the dividend, we always
get a quotient of 1 less than the correct value. For example, 10 di-
vided by 5 is always 2 with a remainder of 0 not 1 with a re-
mainder of 5. Correcting this error is easy, as shown in Figure
3-14D.
Hand-checking a program
/* program DIVIDE */
#include DIVISOR) {
REMAINDER -= DIVISOR;
+4QUOTIENT;
}
printf ("Quotient = Zd, Remainder = d\n", QUOTIENT, REMAINDER):
}
(B) Second attempt
/* program DIVIDE */
#include
main ()
{
int DIVIDEND, DIVISOR, QUOTIENT, REMAINDER;
printf ("Enter dividend and divisor: ");
seanf ("Zd %d", &DIVIDEND, &DIVISOR);
QUOTIENT = 0
REMAINDER = DIVIDEND;
while (REMAINDER > DIVISOR) {
REMAINDER -= DIVISOR;
+4QUOTIENT;
}
printf ("Quotient = Zd, Remainder = %d\n", QUOTIENT, REMAINDER);
}
(©) Third attempt
/* program DIVIDE */
#include
main ()
{
int DIVIDEND, DIVISOR, QUOTIENT, REMAINDER;
printf ("Enter dividend and divisor: ");
scanf ("Zd %d", &DIVIDEND, &DIVISOR);
if (DIVISOR == 0)
printf ("Attempt to divide by 0.\n");
else {
QUOTIENT = 0;
REMAINDER = DIVIDEND;(C WITH EXCELLENCE: Programming Proverbs
while (REMAINDER > DIVISOR) {
++QUOTIENT;
REMAINDER -~ DIVISOR;
)
printf ("Quotient = 4, Remainder = d\n", QUOTIENT, REMAINDER) ;
}
(D) Fourth attempt
/* program DIVIDE */
#include
main ()
{
int DIVIDEND, DIVISOR, QUOTIENT, REMAINDER;
printf ("Enter dividend and divisor: "
scanf ("Zd Za", &DIVIDEND, &DIVISOR) ;
if (DIVISO! 0)
printf ("Attempt to divide by 0.\n");
else {
quorrenr = 0
REMAINDER = DIVIDEND;
while (REMAINDER >= DIVISOR) {
+4QUOTIENT;
REMAINDER
DIVISOR;
}
printf ("Quotient = Zd, Remainder %d\n", QUOTIENT, REMAINDER);
Proverb 21 CAREFUL What in hell have I done to deserve
PROGRAMMERS all these kittens.
DON’T HAVE DON MARQUIS
‘Mehetibel and her kittens
ACCIDENTS
The top-down approach to programming is the answer to the
question: “What have I done?” —a question that may not need to
be asked at all if the approach is followed conscientiously. The
top-down approach is not only an effective tool in program de-
velopment, but it is just as valuable in program verification. ThisCODING THE PROGRAM
simply means that the main program and upper levels are ver-
ified first and the most primitive modules last.
Top-down testing is a strategy which says
© Test a given module before testing any modules that it
calls.
Figure 3-15. __ Picture of a program designed top-down
My FCM,
OO
The Entire Program FCM—First Called Submodules
M—Main Modules ‘SCM—Second Called Submodules(C WITH EXCELLENCE: Programming Proverbs
90
Verifying from the top down should seem obvious, especially
if a program is being written top-down. Programs of this type are
usually well modularized, and it is unwise to verify lower levels if
the upper levels may be incorrect. Since the sections of the pro-
gram are integral units that can stand on their own, the most im-
portant ones should be verified first. A schematic illustration of a
program is presented in Figure 3-15. Encircled sections indicate
that the set of enclosed modules is to be considered as a unit.
The program has five main modules, each of which can be ver-
ified separately. The verification process starts with the main
program. As an upper module is verified, the process continues
through lower levels until the entire program has been verified.
Verifying the entire program in one lump is to be avoided.
Consider Figure 3-16, a program for summing the first N posi-
tive integers for differing values of N. Clearly, the answers gener-
ated by the program are incorrect. The programmer decides to
check the main program loop and adds the verification lines
shown, which print out the inner loop control variable and the
current value of the sum. When the revised program is run, we
can easily see that on iteration 1 for the second value of N, the
sum is already 22; that is, SUM is not properly initialized. Moving
the SUM initialization statement to just before the inner for loop
solves the problem.
One popular verification aid available in several implementa-
tions is the “trace.” Basically, a trace is a facility which will moni-
tor the behavior of prespecified variables or functions. The trace
ofa variable can monitor any use or change of its value. The trace
of a function can monitor each call, its arguments, and its re-
turned value. The programmer can use a trace to verify that func-
tions are being called with proper arguments and correct
returned values. In function-oriented programs or programs
using recursion, traces can be invaluable.
Systems that implement C ordinarily offer other verification fa-
cilities. Does your system support any program design, develop-
ment, or documentation aids? Does your system support a
cross-reference lister, program librarian, test data generator, test
supervisor, module testing, execution monitor, or output ana-
lyzer package? Be sure to find out what features your systemCODING THE PROGRAM
Figure 3-16. _Use of the print statement for verification
/* Program SUM */
/* Program to sum the first N positive integers. */
#include
main ()
{
int SUM, I, J, N;
int NUM SEQUENCES;
printf ("Enter number of sequences: ");
scanf ("%d", &NUM_SEQUENCES) ;
SUM = 0
for (I = 0; I < NUM.SEQUENCES; ++I) {
printf ("Enter number of terms: ");
soanf ("Zd", &N);
for (J = 1; J <=N; +H) {
SUM += J;
+
printf ("Sum of first $d integers is Zd\n", N, SUM)
}
-- Output from Figure 3-16
Sum of first 6 integers is 21
Sum of first 3 integers is 27
-- Verification line inserted within inner loop:
printf ("Iteration Zd, Sum %d\n", J, SUM);
-- Output from Figure 28 with Verification Line
Iteration 1 Sum 1
Iteration 2 Sum 3
Iteration 3 Sum 6
Iteration 4 Sum 10
Iteration 5 Sum 15
Iteration 6 Sum 21
Sum of first 6 integers is 21
Iteration 1 Sum 22
Iteration 2 Sum 24
Iteration 3 Sum 27
Sum of first 3 integers is 27
a1(C WITH EXCELLENCE: Programming Proverbs
92
provides, and incorporate them into programs you write. Some
implementations of C have excellent run-time aids. Although it
may not be wise to use implementation-dependent features as
part of a final program, it would be foolish not to utilize them for
verification. |
There is no doubt that you will ask yourself “What have I
done?” many times in program verification. But if you make the
best use of the verification facilities in your computer system,
you will know what you have done. One who expects the worst is
doubly blessed; even when the worst happens, you will be pre-
pared. If the best is your lot, you will be quietly proud that nei-
ther result was an accident.CHAPTER
4
Proverb 22
AND OF COURSE.
Let us hear the conclusion of the whole matter.
Ecclesiastes 12:13
HAVE SOMEONE An old man has crossed more
ELSE READ THE bridges than a young man has
WORK crossed streets.
CHINESE PROVERB
The sequence of these proverbs is a matter of choice, but if there
is one “proverb among proverbs” for programmers, it should be
this one. Few programmers can, or should, enjoy any kind of
“poetic license” in their work—the parameters of the discipline
demand that one’s effort be open for inspection and criticism. In
fact, this proverb should be understood as a requirement of good
practice as much as any proverb that has gone before.
Having someone else read the work does not mean just having
someone else read the final code. Even a developing definition
should be read by someone else, as well as the program specifi-
cations, the levels of the top-down design, and the test plans.
The benefits of a second reading are legion: identifying un-
warranted assumptions, unintentional omissions, unnecessary
complexities, or just plain mistakes. But more important, both
you and your reader will improve your problem-solving and pro-
gramming techniques by this kind of mutual involvement. This is
the principle of programming teams that is so important in fos-
tering cooperative communication, maintaining team standards,
promoting quality documentation, and generally keeping
abreast of the total work effort.
93(C WITH EXCELLENCE: Programming Proverbs
Consider Figure 4-1, which contains two simple errors—both
easy to make and both difficult to detect. The programmer who
wrote the code would find the errors almost impossible to detect
by rereading the code even one more time. Try to find them
yourself.
Figure 4-1. The compiler catches one error, but who catches the other?
/* -- Program ROOTS
- Finds the real roots of the equation
= A*(X*X) + (BX) +0 = 0 4/
#include
#include
main ()
{
float A, B, C, Z;
float X1, x2;
float DISCRIMINANT;
printf ("Enter A, B, and C:
scanf ("Bf Zf Bf", BA, &B, &C);
if (A == 0.0)
it (B == 0.0)
printf ("This is the degenerate case");
else {
X= -C/B;
printf ("One real root. . . X = Bd", X);
}
if ((A 1= 0.0) && (B != 0.0)) {
DISCRIMINANT = (B*B) - (4*A*C);
if (DISCRIMINANT < 0.0)
printf ("No real roots. \n");
else if (DISCRIMINANT == 0.0) {
M1 = -B / 2*A;
x2 = Xl;
printf ("Equal real roots: Xl = f X2= 1", X1, x2);
} else if (DISCRIMINANT > 0.0) {
X1 = (-B + sqrt (DISCRIMINANT) ) / 2*A:
X2 = (-B - sqrt (DISCRIMINANT) ) / 2*A;
printf ("Distinct real roots: Xl = Bf X2 = Zf", Xl, x2);
}
}
printf ("\n");Proverb 23
AND OF COURSE... .
Here are a few suggestions to make your work-reading more
effective:
a. Make sure you have a piece of work in writing.
b. Don't give someone else 20 pages at a time; 3 or 4 will do.
©. Focus on the work itself, not on the persons or
personalities that produced it.
d. Do it on a regular basis: put it in your plan.
e. Don't confuse work-reading with a “meeting” —it is not a
structural function, it is a good programming habit.
‘Take special care in looking for missing details, fuzzy
statements, and boundary conditions
If the piece of work is too long or too complex to check in its en-
tirety, check each major section first—or better, just remember
the last proverb.
Work-reading requires extra time and demands extra care, but
it is a small price to pay for the benefits listed above. At first, the
practice may seem time consuming, annoying, and even embar-
rassing at times. But given time, abiding with this proverb will
have a marked impact on your work. You should practice as a
reader for others, too. You will find, as I have, that all aspects of
good programming practice are improved by work-reading. In no
time at all, you will be crossing-more bridges than streets—and
you don't have to be an old man to do it.
WHAT HAVE YOU No book is worth anything until it is
READ LATELY? worth much; not is it serviceable
until it has been read, and reread,
and loved, and loved again; and
marked, so that you can refer to the
passages you want in it.
JOHN RUSKIN
‘Sesame and Lilies,
Of Kings’ Treasuries, Sect. 32
A friend of a friend was taking a year off and was asked, in an off-
handed manner, what was he looking forward to the most? He
said, “Oh, of course, to read! Teaching does not give one the
95C WITH EXCELLENCE: Programming Proverbs
chance to read. I'm always afraid I may have missed something.”
This is what this proverb is all about—reading. There are two
facets to readit
a. Technical: the reading associated with your system —
language reference manuals, system manuals, utilities
manuals, operating and environment guides.
b. Literary: the reading associated with your field—
magazines, journals, and books.
The objectives are:
a. To become a master of the system.
b. To develop better perspectives.
The common failures are:
a. Clinging to a timid subset of a system.
b. Becoming a “hacker” of the trade.
Nothing can be as boring as system manuals. Why go back to
read them again? Because somewhere in all those poorly written
manuals, the same kind that you are dedicated to improve, there
is a kernel of knowledge that you may have overlooked. After pro-
gramming for a while, we tend to settle into our own kind of con-
venient language for our host computer: Periodically rereading
the manuals helps to remind us that there are many useful fea-
tures that, even though we don't use them every day, may be just
the ones we need later
For example, do you know how C deals with control charac-
ters? Do you know how integer division works? Do you know all
the predefined functions and the file manipulation capabilities?
How does your implementation allow setting up of program li-
braries? How about the number of significant figures kept by
your particular machine? How do enumeration types really
work on your implementation? Can input or output files be
structured or segmented? What are the default specifications for
output of standard data types? Do you know what the maximum
integer is?AND OF COURSE... .
As you survey the literature, you will encounter the writings of
programmers of many stripes: hackers, boring, introverted, ma-
chinelike, uncultured, and so on. These impressions need not be
true as you read more deeply into their writings. Programmers
who publish do so to advance their profession because they un-
derstand that their experience is as beneficial to their peers as
the writings of their mentors was to them. While there is no sub-
stitute for project experience, our own skills will be improved by
reading. Consider:
a, anarticle on professional ethics;
b. an experiment on the philosophies of command
languages;
©. areport ona new printer;
d._a book on software engineering;
e. data on which features of System X are little used;
f. areport on typical C enhancements;
8. acurriculum for computer science;
hh. a product review.
‘These can be worth reading, even when they may be only margi-
nal to your current project.
The question is: What is the state of the art? As you make your
contribution, you must read to see what other contributions are
being made. Rereading your own manuals and the current liter-
ature goes with the territory. You may be pleased at what you
find.
Proverb 24 DON’T BE AFRAID To dry one’s eyes and laugh at a fall,
TO START OVER and baffled, get up and begin again.
OBERT BROWNING
Life in a Love, Stanza 2
Odd as it may seem, the first and last of these proverbs are en-
couraging! Don't panic and don’t be afraid to start over! I say this
because I hope that you should never have to lean on either
proverb—all the ones that come between should be enough
support.
97(C WITH EXCELLENCE: Programming Proverbs
Exercise 1
Sometimes during program development, testing, or modi-
fication, it may seem that the program you are working on is be-
coming unusually cumbersome. So many user-requirements
changes may have been thrown at you that the problem itself is
different from the original proposal. Other than the first and last,
maybe none of these proverbs seemed to apply in developing the
program and the result is that the program produces error after
error
No matter At some point, pruning and restoring a blighted
tree is an impossible task—better to replace it with a seedling.
The same is true of blighted computer programs. Restoring a
structure that has been so distorted by patches and deletions or
trying to find a program with a seriously weak algorithm simply
isn’t worth the time. The best that can result is a long, inefficient,
unintelligible program that defies even more maintenance.
You have to develop a heartless sense of recognizing that
when the program is hopelessly in trouble, chuck it out and start
over again. And don't let your ego stand in the way. The lessons
that you learned on the old program can be applied to the new
one and yield the desired result in far less time with far less
trouble.
Use these two proverbs to advantage. Don't panic, and start
over Above all, don't try to convert a bad program into a good
one—bring out a new model.
EXERCISES FOR PART 1
Problem Definitions
Consider the following program specification:
“Write a program that computes the weight in kilograms of a
weight expressed in pounds and ounces.”
Upon careful thought, the above specification reveals many
points that would be unclear to a programmer Rewrite the above
specification so that any two programs written to solve the prob-
lem would have exactly the same input-output characteristics,
for example, the same input format, the same number of signifi-
cant digits, the same headings, and so forth. (Note: You are to
write a program specification, not a program; a good definition
may require a full page.)Exercise 2
AND OF COURSE. . .
Problem Definitions
Each of the following program specifications is either missing
one or more critical definitions or is unclear, misleading, or con-
fusing. (a) How are the program specifications deficient? (b) Re-
write all or part of one program specification to make it as clear
and explicit as possible, that is, so that there can be no doubt as
to what the program should do. (c) In general, what does any
program specification require to make it as clear and explicit as
possible?
Program specification 1: Given the following rates of payment,
write a program that will compute the monthly commission for a
car salesman.
Grade of Salesman Commission Rate
1 $5.00 + 0.50% for first ten sales
$7.50 + 0.75% every subsequent sale
2 $7.50 + 0.75% for first ten sales
$10.00 + 1.00% every subsequent sale
3 $10.00 + 1.00% for first ten sales
$12.50 + 1.25% every subsequent sale
4 $12.50 + 1.25% for first ten sales
$15.00 + 1.50% every subsequent sale
5 $15.00 + 1.50% for first ten sales
$17.50 + 1.75% every subsequent sale
The input should be the grade of the salesman and the number
of sales he has made for the month. The output should read
“THE SALESMAN’S COMMISSION Is § C,” where C is the sales-
man's commission.
Program specification 2: The Programmer's Equity Life Insur-
ance Company offers policies of $25,000, $50,000, and $100,000.
The cost of a policyholder’s annual premium is determined as
follows. There is a basic fee that depends upon the amount of
coverage carried. This is $25 for a $25,000 policy, $50 for a $50,000
policy, and $100 for a $100,000 policy.
In addition to the basic fee, there are two additional charges de-
99(C WITH EXCELLENCE: Programming Proverbs
Exercise 3
Exercise 4
100
pending on the age and lifestyle of the policyholder The first ad-
ditional charge is determined by multiplying the basic fee by
either 1%, 2, or3 if the policy is at the $25,000, $50,000, or $10 0,000
level, respectively. The second additional charge is determined
by the policyholder’ lifestyle, which is a rating of the danger of
harm resulting from his occupation and hobbies. This rating is
determined by company experts from a questionnaire returned
by the policy applicant. They return a rating from 1 to 5 in steps
of 1, with 1 being the safest rating. The charge is then determined
by multiplying this rating by 5 and then further multiplying by
1%, 2, or 3 if the policy is either at the $25, 000, $50,000, or $100,000
level, respectively. The total premium is found by adding to-
gether these separately determined charges.
Write a program to output tables of yearly premium costs for
persons of ages from 21 to 75 for all amounts of policy value and
safety ratings.
Perfect Problem Specifications
Upon extremely careful reading, the problem definition of Fig-
ure 1-2 shows several deficiencies. The exact input-output
characteristics are not really fully specified. Describe these
deficiencies.
Functions and Procedures
Consider a program with the following specifications:
Input: a positive integer N
Output: the values SUM(N), SUM?(N), SUM9(N), and
SUM*N)
where SUMIN) denotes the sum of the first N integers, SUM?(N)
denotes SUM(SUM(N)), and so forth.
1, Write the program without the use of functions.
2. Write the program using functions.
The differences can be quite striking.Exercise 5
Exercise 6
AND OF COURSE... .
Goto's
Restructure the following statements to eliminate all goto’s and
statement labels and to make the sequence as short and clear as
possible. (Note: Can it be done with only two assignment
statements?)
goto L3;
Li: if (x == 0)
goto L9;
else
goto L5;
L5: if (x > MAX_VALUE)
goto L6;
else
goto L4;
L9: printf ("Ze", X);
goto LT;
L3: scanf ("%e", &X);
goto Ll;
Le: X = sart(X);
L8: X = X*X +X;
goto L9;
La: X = X*X;
goto L8;
L7: /*do nothing*/;
Goto's
Consider the conventional 8 by 8 array representation of a
chessboard whose squares are denoted by (1,1), (1,2), .. . (8,8)
and the problem of placing eight queens on the board so that no
queen can capture any other queen. In chess, a queen can cap-
ture an other piece if the queen lies on the same row, column, or
diagonal as the other piece.
101(C WITH EXCELLENCE: Programming Proverbs
Exercise 7
Exercise 8
102
1. Write a program to read in the coordinates of eight
queens and print “TRUE” if no queen can capture any
other, and “FALSE” otherwise.
2. Drawa flow diagram for the program.
3. Score the program using the formula
SCORE = 100 - 10*(number-of-crossing-lines)
as applied to the diagram.
Syntax
It is important that a programmer be able to detect simple syn-
tactic errors. Consider the following program to compute PI
using the series,
PI4/96 = 1/14 + 1/34 + 1/54 +
How many syntactic errors are there? Correct the program frag-
ment so that there are no errors.
N=0;
while (ABS(TEMP) < EPSILON) {
HN;
TEMP = 1 / (2N - 1)**4;
SUM += TEMP;
}
PI = sart(sart(96*SUM) );
Syntax
Which of the following examples are syntactically correct in-
stances of the given C categories? Correct the erroneous exam-
ples. If possible, you should assume any suitable specification
statements needed to make a construct correct. That is, the
question is “Is there any conceivable program where the con-
struct could be correct?”
1. Arithmetic Expression
F(F)AND OF COURSE... .
2. Arithmetic Expression
P[Q] + X**X**X - 1/2
3. If Statement
if (X && ¥ > 4)
printf ("%f", X);
4. For Statement
for (I
x(1]
I= N; +41)
X(I] + X[1];
5. ‘Type Declaration
typedef enum {TRUE, FALSE} boolean;
6. Switch Statement
switch (COLOR) {
case RED: GREEN;
case GREEN: printf ("GO");
case YELLOW: printf ("SLOW DOWN");
case RED: printf ("STOP");
break;
}
7. Function
float F(X, ¥, Z);
float X, Y,
{
return (X*¥ + Z);
}
8. Function
float F(X, Y, Z)
float X, Y, 2:
{
float SUM;
103C WITH EXCELLENCE: Programming Proverbs
Exercise 9
104
for (I = 0; I< Z; +41)
SUM += X[I] + ¥[T];
return (SUM);
}
9, Assignment Statement
I = F{t).B[1]
10. Compound Statement
{
typedef enum {RED, WHITE, BLUE color;
color C;
if (C < RED)
Mnemonic Names
The following statement sequence performs a well-known, sim-
ple arithmetic computation. By basically changing all identifiers,
rewrite the program so that it clearly illuminates the intended
computation.
/* program STRANGE */
#include
#include
#define FOUR 4
#define FIVE 3
#define AND "and"
main ()
{
float LEFT, RIGHT, MIDDLE, ONE, ALL, LEFT_1,
LEFT_2, RIGHT_1;
printf ("Enter three values: ");
scant ("Ze Ze Ze", &LEFT, aMIDDLE, BRIGHT);
RIGHT_1 = LEFT*RIGHT*FOUR;
LEFT_1 = MIDDLE*MIDDLE - RIGHT_1;
LEFT_2 = sqrt(LEFT_1);AND OF COURSE. ..
ONE -(MIDDLE - LEFT_2) /(FIVE*LEFT) ;
ALL = -(MIDDLE + LEFT_2)/(2.0e0*LEFT);
printf ("Rg Zs Ze", ONE, AND, ALL):
Exercise 10 Change
Exercise 11
Consider a program to compute the gravitational force F exerted
between two planets M1 and M2 located (over time) at different
orbital distances from each other In particular, let
M1 = mass of planet 1 =6* 10%
M2 = mass of planet 2 8* 10%
G =gravitational constant = 6.7 * 10"
F =G*M1*M2/(R*)
Write a program to output F for values of R varying from 100°105
to 110°10® in increments of 0.01°108 such that all constants are
named and no constant terms are recomputed.
Output
The following input/output was generated by the use of a work-
ing interactive program:
Input: What is N1 and N2?
5, 10
Output: 25
36
49
64
81
100
Seariosa
Rewrite the input/output so that the average “man on the street”
would be able to understand what the input numbers 5 and 10
represent and what the output means.
105(C WITH EXCELLENCE: Programming Proverbs
Exercise 12
Exercise 13
Exercise 14
Exercise 15
106
Work Reading
Describe two more advantages to having someone else read the
work you produce during the various phases of a programming
project. Describe two disadvantages or bottlenecks.
Reading the Manuals
Reread your C manual and find three features that you had for-
gotten or did not know existed. For each feature, give an example
of how it would be useful.
Reading the Manuals
Answer precisely the following questions with reference to your
C implementatior
1. What type names are predefined?
2, How many dimensions can an array have?
3. What is a legal subscript?
4. Are matrix commands available?
5. How many significant digits are kept for real numbers?
Code Rewrite
The following program performs a simple, well-known com-
putation. Rewrite the program so that it clearly illuminates the
intended computation. In the process, eliminate goto's, use
mnemonic names, and produce good output.
/* Program GOTOS */
#include
main ()
{Exercise 16
Exercise 17
AND OF COURSE... .
float A, B, C, D, F, G:
goto Ll;
L2: scanf ("Zf", &A);
F=A*A;
goto Li
Ll: scant ("$f", &B);
G=B*B;
goto L2;
L3: if ((A <= 0) || (B<=0))
goto LS;
else {
C=F+G;
D = sqrt(C);
printf ("The answer is Zg", D);
}
LS: /*exit*/;
It Worked the First Time
Write a program that determines whether an input string is a
palindrome. A palindrome is a string that reads the same for-
ward and backward. For example, LEVEL is a palindrome but
PALINDROME is not. When the program is running correctly,
score it using the following formula:
SCORE
100 - (S*times-resubmitted)
= (2tnumber-of lines-changed)
Intermediate Variables
Usually a complex mathematical expression can be coded as a
single arithmetic expression. However, for the sake of clarity it is
often advantageous to split up a lengthy arithmetic expression
and use intermediate variables. Give an example where clarity is
gained with the use of intermediate variables. Give an example
where the obvious result is program efficiency. Give an example
107(C WITH EXCELLENCE: Programming Proverbs
where the use of inappropriate or excessive intermediate vari-
ables causes confusion.
Exercise 18 Guess the Proverb
The following program is supposed to have the following
characteristics:
Input: a sequence of n nonzero integers terminated by
the integer zero
Output: the MEAN (the sum of all values di
SUMIx) /n
and standard deviation,
sqrt(SUM(x2) /n - MEAN?)
of the integers in the input sequence.
/* program STATISTICS */
#include
#include
main()
{
int A, B, C;
float D, E, F, G;
scanf (Zf, &G);
while (G != 0) {
A=A+G;
C= C+ GG;
scant ("Zi", &G);
HB:
}
} while (G >= F);
108Exercise 19
AND OF COURSE... .
ALB;
sqrt(C/B - sar)(A));
I= F - D/F;
printf ("Mean is %g Deviation is %g", D, E)
}
Of all the programming proverbs, which single proverb, if prop-
erly followed, would be most valuable in converting the above
program to a good program? (Note: There really is a best answer)
The C Quiz
A user who has a very thorough knowledge of any language
should be able to detect erroneous programs that prematur
terminate because of a syntactic (compile-time) or semantic
(run-time) error In the absence of a formal definition of a lan-
guage, the definition of constructs that produce fatal syntactic or
semantic errors must ultimately be based on a particular imple-
mentation. To treat this issue precisely, consider the insertion of
print statements as the first and last executable statements in a
program, and let us adopt the following definitions:
1. A program is “syntactically invalid’ if the first print
statement is not executed, that is, if the implementation
finds an unacceptable error.
2. A program is “syntactically valid” if the first print
statement is executed, that is, if the implementation
detects no severe errors, translates the program into
machine language, and begins execution.
3. A program is “semantically invalid” (but syntactically
valid) if the first but not the last print statement is
executed, that is, the program is started but not
completed.
4. A program is “semantically valid” (and syntactically valid)
if the first and last print statements are executed, that is, if
the program is executed without abnormal termination.
To test your knowledge of the above issues in C, you are asked to
answer two simple questions about the programs given in the C
quiz below.
109C WITH EXCELLENCE: Programming Proverbs
Is the first printf statement (which prints “Start”) executed?
Is the last printf statement (which prints “Finish”) executed?
Check the appropriate spaces or put your answers on a separate
sheet. Note: It is quite difficult to answer all questions correctly.
1. Start: YES. NO.
Finish: YES__ NO.
/* program ONE */
#include
main ()
{
int A, B, C;
printf ("Start \n");
A=1;
L6: B
goto L6;
L6: C = 3;
printf ("Finish \n");
}
2, YES__ NO__
Finis! YES__ NO__
/* program TWO */
#include
float X;
float MAX_VALUE (X, Y)
float X, Y;
{
if (k<¥)
return (X);
else
return (Y¥);
}
float P(F)
float (*F)();
{
110AND OF COURSE... .
return ((*F)(1.4, 2.6));
}
main ()
{
printf ("Start\n");
X = P(MAX_VALUE) ;
printf ("Finish\n");
}
Start: YES. NO.
Finish: YES. NO.
/* program THREE */
#include
P(X, ¥)
float X, *Y:
{
44x;
ry t= (X41);
}
main ()
{
float A;
printf ("Start\n");
A=2;
P(A, 1);
printf ("Finish\n");
}
Start: YES__ NO__
Finish: YES___—_—NO.
/* program FOUR */
#include
#define FALSE 0
#define TRUE 1
P(X, Y)
float X, *Y;
{
111(C WITH EXCELLENCE: Programming Proverbs
112
44K:
ey tS (X41);
main ()
float A;
printf ("Start\n");
A=
P(A, &TRUE);
printf ("Finish\n");
/* program FIVE */
#include
int G(N)
int N;
{
if (N == 0)
return (1);
else
return (N*G(N-1));
}
int FACT(N)
int N;
{
return (G(N));
}
main ()
{
int J;
printf ("'Start\n");
J = FACT(5);
printf ("Finish\n");
}AND OF COURSE... .
6. Start: YES. NO.
Finish: YES. NO.
/* program SIX */
#include
main ()
{
typedef float boolean;
boolean B;
printf ("Start\n");
B= 14.2;
printf ("Finish\n");
113TWO
BEYOND THE
PROVERBSCHAPTER
5
PROGRAM
STANDARDS
We come from a world where we have known
incredibie standards of excellence.
Thornton Wilder
The Bridge of San Luis Res Chapter 4
The C language has been with us for many years, yet the writing
of high-quality C programs has remained a matter of personal
style. The thrust of this chapter is to go beyond the “proverbs”
and present some rigorous standards for the writing of C pro-
grams. Developing rigorous program standards is not easy, for
the rules must be unambiguous. They must also be of sufficient
merit that a programmer will not be unduly stifled by their adop-
tion, We have followed this chapter's standards throughout this
book.
The importance of developing such standards is clear For
managers, instructors, and programmers, there is a need to de-
velop uniform rules so that everyone may more easily under-
stand programs, a need to develop coding techniques that
reduce the complexity of programs, and a need to control the
entire software development effort.
I make no attempt here to encompass the numerous features
of the C language. No standard can cover every aspect of a given
programming problem. Nevertheless, the standards presented
here should go part way to promote quality programs.
The general issue of programming standards is indeed com-
plex. When the initial boundaries of a language are restricted,
the programmer resistance can be great. In addition, any at-
tempt to promote or enforce such a set of standards must re-
solve a number of difficult issues.
117(C WITH EXCELLENCE: Programming Proverbs
(Gen-1}
118
First, exceptional cases must be avoided when writing a stan-
dard. It is critical that the standard be as solid as possible, for
otherwise, the credibility of the entire activity can be under-
mined. Possible exceptional cases must thus be carefully
screened before the standard is adopted.
Second, there is the issue of enforcement. What mechanism
should be set up to enforce the standards? Can the standards
really be enforced without some kind of automatic aids? What
about the professional programmer who does not see any rea-
son for a particular standard or its use in a particular case?
These questions are difficult to answer
Third, given that some method of enforcement has been
adopted and given that some useful exception does occur, how is
the exception to be handled? While an ideal standard has no ex-
ceptions, when exceptions do arise, they have to be handled
without undermining the credibility of the entire effort.
Fourth, there are a number of human factors that must be
faced, especially when standards are being developed. For one,
choices may be a matter of taste, and there are bound to be some
arbitrary decisions. Further, there may be some initial overhead
in following the standards. The human tendency is to get on
with the job, ie,, code. This tendency must be resisted, for if the
standards are correct, breaking the standards is shortsighted.
Finally, one must be very careful to avoid expecting too much
from the standards, for eventually the problem-dependent fea-
tures of a program will demand special attention. While the
adoption of good standards may help in producing good solu-
tions to the problem at hand, ultimately the style and expertise
of the practicing programmer becomes paramount,
GENERAL REQUIREMENTS
Someone Should Be Appointed To Maintain and
Enforce the Standards.
The rationale here is to refine the standards, monitor them, and
allow exceptions, but only in a controlled manner: It is critical
that all programming standards, unless revoked, be followed to
the very last detail. The method for enforcing the standards is
left to the particular instructor, team leader, or manager[Gen-2]
([Gen-3]
PROGRAM STANDARDS
All Full Programs Shall Include Header
Comments.
For example:
/* == ** PROGRAM TITLE: Brief title
WRITTEN BY: Name of author(s)
DATE WRITTEN: Date
PROGRAM SUMMARY: Brief program summary
INPUT AND OUTPUT FILES
file-name: Brief description of use
”
The rationale here is to give at least a minimum synopsis of the
program's intent, input and output files, and title information, so
that a program has some minimal internal documentation.
For Each Application There Should Be an
Adopted Set of Standard User-Defined Names
and Abbreviations.
The standard words should be chosen before coding. The fol-
lowing set of names is a very small sample of those that might be
adopted for an application.
svM Abbreviation for symbol
usc Abbreviation for message
Num Abbreviation for number
POS Abbreviation for position
GET_SYM For a subroutine to input symbols
SYM_LENGTH —_For lengths of symbols
PUSH For a subroutine to put symbols on a stack
POP For a subroutine to remove symbols from a
stack
STACK For a stack of attributes
KEY_VALUE For an array of keyword values
119