Defensive Programming
Programming style is just as important as any other parameter
that goes into a coding task. Here are a few ideas, rants, and
raves.
Published in Embedded Systems Programming, January 1993
 |
For hints, tricks and ideas about better ways to build embedded systems, subscribe to The Embedded Muse, a free biweekly e-newsletter. No hype, just down to earth embedded talk. 23,000 other engineers subscribe. It takes just a few seconds (all we need is your email address, which is shared with absolutely no one) to subscribe to the Embedded Muse. |
My New Year's wishes are for world peace, environmental cleanup,
a balanced budget, and an end to crummy embedded code. There's
not a lot I can do about the first 3 items (hey - I voted for
Perot!), but each of us can and should resolve to improve our
coding styles.
Industrial strength programming, while a technical activity, is
done solely to satisfy a business need. Why does the big boss
pay your salary? Her goals are to get a product to market quickly,
keep it viable, and (if she is truly enlightened) re-use what
is learned and written on the next generation of products.
Those of us writing firmware or managing engineering teams are
really the company's technical custodians. Most high level managers
don't know or care enough to translate those overriding goals
into objectives that the software team can work towards. Personally,
I feel a software group's coding-style objectives should be to
find a style leading to:
- Fast coding and debugging
- Few bugs over life cycle
- Easy maintenance
- Generate reusable libraries
- Prolong the code's life
The first three items have been beaten to death in the trade press,
though far too many programmers don't live and breathe these needs.
My objective of "prolonging the code's life" is meant
to avoid the all-too-familiar catastrophe of giving up on the
code in a project, and re-writing it from scratch because it is
too hacked to be understandable or maintainable. God knows I've
made the mistake of permitting code to evolve to chaos over a
development or maintenance cycle. How many times have you heard
someone critique another's code, only to say "I'll just have
to re-code that". This is truly unacceptable. Sometimes this
might be the Not Invented Here syndrome, but sometimes the code
is indeed so bad that recoding is required. Code that cannot be
maintained is worse than no code.
Managers: commit to reading your people's code. Simply delete
that which is unreadable or poorly structured. The employees will
get the message or move on, solving the problem either way. The
cost is initially high and schedules might collapse, but unless
you take strong, consistent action the situation will never improve.
With OOP being the buzz-acronym of the 90's, generating reusable
libraries is a hot item, but so far not too many companies are
really benefiting from it. I'm really impressed by Microsoft and
Borland's reusability strategies. I have no idea how they manage
projects internally, or how well they do at preserving individual
functions. Both outfits sure do a great job of reusing major software
components. Look at Borland's TurboVision and Object Windows,
that preserve their editors and user interface across a wide range
of products. Microsoft goes one better: their Basic is available
customized as a word-processor Basic, a spreadsheet Basic, etc.
The Windows DRAW is built in to a lot of applications. Microsoft's
Word interface looks a lot like Microsoft Works or Microsoft Powerpoint.
It's clear some clever folks are snipping off (at least) the
major parts of code and applying them to the entire product line.
This is the direction of the future.
Variables and Functions
During the last Ice Age Basic and Fortran limited us to 2 to 6
character variable names. Read about it in the history books.
Yet today people still code using classic integers like "I"
or "II" or (my personal favorite) "III". If
your compiler or assembler forces you into this straitjacket,
then buy some decent tools!
Of course, the other extreme is the undisciplined use of long
variable names - 60 to 70 characters built up like some Germanic
compound word, that takes a lot of head scratching to differentiate
from the other 100 similarly constructed variables in that module.
There are two reasons to give a variable a name. 1) to make the
damn thing work, and 2) to convey information about how the program
works. We can satisfy (1) by using a random number generator to
create identifiers. (2) requires lots of common sense.
ReadFromBuffer() seems like a pretty descriptive function name.
But from what buffer? Read with a wait? What form does the returned
data take?
Maybe ReadByteFromUARTBuffer is better. The names can start to
get awkward if you get too descriptive.
Use acronyms and abbreviations, being careful to apply them to
common situations only. Generate a glossary of the definitions
of every abbreviation and acronym you use. I like PTR for pointer,
BUF for buffer, REG for register, etc. You want to convey information
quickly.
Consider using Hungarian Notation. Hungarian notation uses lower
case characters that indicate the type of a variable as the name's
prefix. Then, you can look at any variable and instantly know
it's type, crucial in a language like C that lets you get away
with murder in parameter passing and creating expressions. Form
the rest of the name by capitalizing the first letter of each
word.
For example, iGetUartStatus() returns an integer. cOutLcd puts
a character to an LCD. sOutLcd outputs a string to the same device.
Success lies in defining a set of standard types and the abbreviation
for each. Note that Hungarian even supports structures and other
complex types. Just be sure to add the abbreviation for each (like
"dow" for DayOfWeek) to the glossary.
Underscores are not really needed with Hungarian Notation.
Global Variables
Don't use globals. Tears come to my eyes looking at the average
embedded program, which is invariably riddled with hundreds or
thousands of globals. Every little function seems to access this
vast hoard of data, changing a bit here or complementing a word
there. None of us are smart enough to effectively deal with code
written like that.
Globals are a problem because any routine, at any time, can alter
the state of any global. There are just too many chances for a
careless reference to a variable creating havoc in some remote
part of the code.
Perhaps a few globals might be needed, to maintain major state
parameters of the system. Limit the number of these.
Use OOP-like packaging (even in C or assembly), keeping the method
and data in a module with a bulletproof shell around it. Access
to the function's local variables (which can be static) is though
a call to the function. Where a major data structure underlies
the operation of the entire system, protect that structure by
surrounding it with GET and PUT routines. Does it need to be sorted?
Protect it with a SORT function.
Files
I'm a DOS person. UNIX is great and all that, but I just don't
use it. Therefore, I'm saddled with DOS's crummy file structure.
8 character file names are simply not adequate, but that's all
we have to work with. Some friends swear by Norton's cute tricks
that associate more information with files, extending names considerably.
I consider this a toy, because few applications support the extended
naming convention. Who cares if you can get a nice directory listing,
but are still stuck with 8 character names in your editor, compiler,
and debugger?
Some people come up with elegant naming conventions that use an
acronym for the project as the first few characters of each filename
- like COM_MAIN, COM_IO, etc. I don't care for this; it unnecessarily
wastes a good chunk of the limited name asset.
Still, 8 characters is all we have. Use directories as name extensions.
Store all of the file for the COMM-BUFFER project in directory
COMM-BUF.
Don't use linked directories. That is, store all of the components
of a project in one directory, not in a dozen scattered all over
the disk. Using multiple directories seems to create multiple
problems. No one is really sure what file belongs to what project,
and backing the project up becomes a nightmare.
However, on a large project it's just not realistic to store hundreds
of possibly unrelated files in a single directory. Use the project
as a root directory, and store your code in sub directories off
of this project node. This concentrates all of the project's assets
in one, central location. A single XCOPY transfers the entire
project to a backup.
Sharing code is a noble and important goal, but works well only
if a certain discipline is enforced. If you plan to use a file
of code in several applications, then compile it to a library,
strip out all debugging information, and lock the source code
in a safe. Get it off the system. Either you plan to share this
code or you don't; if shared, any alterations made to the code
might have deleterious effects on many projects.
If you cannot bear the thought of turning a function into a true
library that no one may modify, realize that you have made the
implicit decision to use many versions of the code. You might
as well, then, just copy the source to each project directory,
and live in peace with the sure knowledge that each copy will
evolve in a different direction.
If you write good code, than a goal should be to get rid of the
source. Write it, test it, and then recompile it without debugging
information. Save the objects in a working directory, and isolate
the C code in a source-only directory or on tape. Tested, compiled,
and archived objects are useful. Source in the process of being
modified, or that is kept around "in case we need to make
some changes" invites chaos.
Commenting
I think the oft expressed notion "I don't need comments -
I use long variable names" is total hogwash. Comments serve
two functions: they describe what is going on in the code and
how it works, and (more importantly) they indicate what a function
does, and what its parameters are.
Everyone complains about comments, so I'll mostly leave this subject
alone. However, the most important programming manual on your
desk should be The Elements of Style, by William Strunk and revised
by E. B. White.
Make your comments clear, concise, and well organized
. Commenting is a literary experience - you are telling readers
a story about your code. Unlike prose from a novel, your comments
must also be correct.
Avoid long paragraphs. Use simple sentences: noun, verb, object.
Use active voice: "iStartMotor actuates the induction relay
after a 4 second pause". Be complete.
If writing the code is harder than writing the comments, you are
doing something wrong. I generally write all of the comments first
and then fill in the code. Even better, write the comments first
and hire a low-paid entry-level programmer to pound out some code.
Programming is really two jobs: figuring out how to do (ITALICS
ON do) something, which is represented in the comments, and converting
the comments/method to code.
Backups
12 years of Jesuits and nuns beating "correct-think"
into my brain left me with only a single religious belief: back
up your work constantly. If backup isn't the first thing you think
of in the morning and the last thing before leaving, then get
out of this field. Try sheep farming in New Zealand.
Backing up is perhaps not part of a programming style, but is
equally a part of the culture and philosophy of being in this
business.
Anything can destroy your carefully written code. A wild pointer
might wipe 100 MB from your disk; a computer crash can leave you
with no way to recover valuable data. Windows 3.0 had a nasty
habit of crashing - I found that it often lost a bunch of clusters
on my computer, some containing crucial code.
Unfortunately it is rather a pain to back up code, so come up
with a procedure that makes it easy. On DOS machines the backup
tools are pretty good.
I keep a 3.5" disk in my B: drive all of the time. Being
in B: it doesn't bother booting. Several times a day I copy the
stuff I'm working on to it. If you change a lot of things, use
a utility that flags changed files and does the copy automatically.
Once a week I roll the entire disk to tape.
Invest in a lot of tapes and disks. You may not even know that
a file is corrupt for months or years, so save the backups for
long periods of time.
Get the backups out of your building. Fires, malicious magnets,
or other problems can destroy the data. I keep copies at home
and in a safe deposit box at the local bank.
Some people back up to a nice big server. This is great if someone
copies the server itself to tape frequently, and copies of the
tapes are stored off-site.
Conclusion
"If architects designed buildings the way programmers write
code, the first woodpecker that came along would destroy civilization".
This is an old joke, but there may be a bit of truth in it. Strive
towards perfection; accept only that which is first class. Ross
Perot said in one of his interviews that he couldn't tolerate
non-performance. I wonder what working for him at EDS was like?
|