Functions are named consistantly, variables use Hungarian Notation or some other standard.
I’d add that functions should be not only named but also grouped consistently. Matched pairs of functions (initialize/terminate, hold/release, get/put) should be close together so that when one is changed it’s easy to go and change the other in complementary ways.
As for Hungarian notation, I disagree. I’ve worked on lots of code that used it, and lots that didn’t, and I haven’t noticed any difference in readability. There’s even a possibibility when using HN that the name will not match the actual type after someone changes the code, and that can be worse than having to find the declaration. Type information belongs in declarations; names should contain information that can’t be expressed as a type – usually usage.
In the context of that last statement, I should also point out that typedefs and enums should always be used to maximize the amount of information in a declaration. Some of my code uses virtual block numbers, physical block numbers, and buffer numbers, for example. These all equate to integers of the same size, but they’re visibly different types with different expectations. Opaque pointers (“typedef struct foo * bar”) are also very useful to define usage.
Lots of comments, clearly written and explanatory…The best comment I heard was from a friend about a former coworkers code: “It’s English with some C++ thrown inbetween the comments.”
Yuk. Verbose comments are a waste of time. Yes, if you have to write a paragraph to describe why a tricky piece of code works the way it does or how a complex data structure is linked together, by all means do so. However, “English with some C++ thrown in” is likely to lead to information overload. IMO, comments are like warning flags; they alert the reader to something that they might miss otherwise. I’d say 80-90% of code is usually pretty self-explanatory as long as you have the proper context from design docs and module/function header comments, and further verbosity is undesirable.
As Joel points out, code embodies the experience of the people who wrote and debugged it. The most useful comments are those that share that experience in the form of pointing out things like why seemingly-extraneous code is really necessary. My personal favorite type of comment is one that anticipates my objections and says “we tried xxx, but it didn’t work because…”; that kind of comment can save hours or days of frustration.
Documentation is good. Write it.
Absolutely. I’m sometimes dismayed by the apparent drop-off in my productivity as measured by lines of code compared to the days when code was all I wrote, but I know deep down that writing the spec first is worth it, which brings me to the single most important point that I think you forgot to mention:
Fix things sooner, not later
In a lot of ways, this is like the Zeroth Law of Thermodynamics; it really underlies all of the other suggestions. Anticipating a problem or future enhancement in the design phase is cheaper than having to go back and rewrite thousands of lines. Anticipating and avoiding type confusion, or code rewritten into an inferior form due to misunderstanding, is also better than suffering the effects after the fact. Finding problems with thorough asserts or unit tests is better than finding them in the field is good. Finding problems in the field with internal integrity checks that warn you when a data structure first gets corrupted rather than when the corrupted data structure gets used is good. Always, in everything you do as a programmer, plan ahead and try to think of ways you can save yourself grief later by acting now. These “defensive” skills are the ones that set the programming elite apart from green recruits, and it’s unfortunate that most people don’t start developing them until they’ve lost a few months of their lives under the gun desperately searching through post-mortem dumps to figure out bugs that should have been caught in unit tests.