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 novel ideas about building embedded systems (both hardware and firmware), join the 40,000+ engineers who subscribe to The Embedded Muse, a free biweekly newsletter. The Muse has no hype and no vendor PR. Click here to subscribe. |
By Jack Ganssle
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?