How we change what others think, feel, believe and do
CHAPTER 8 : Language Usage
8.1 General principles of language usage
Before looking at specific items, there are several general principles that may be applied when using the language. These are outlined below and are expanded in later sections of the chapter.
8.1.1 Break down complexity
In C, it is possible to write very dense and complex code, building a lot of processing into a few lines, or even a single statement:
/* insert NewBlock between FirstBlock and SecondBlock */
Dense items can usually be broken down into a number of less dense items. This is trading one form of simplicity and complexity for another: the simplicity of a single statement which is complex is being replaced with several items which are individually less complex, but which together are less simple:
NewBlock->Next = SecondBlock;
The critical skill in this type of situation is deciding where (if any) the breakpoints should be. This definition will vary greatly between programmers, depending on their experience and preferred style. It can be likened to eating: should you take large mouthfuls and spend longer chewing them, or should you take smaller mouthfuls which can be swallowed almost immediately? The answer, as usual, is defined by the consumers, the people who will have to read and understand the code, now and in the future. The result will be a compromise, breaking down reasonably complex items into reasonably small chunks.
This principle can be applied equally to expressions, declarations, and even to complete functions and programs.
8.1.2 Use appropriate constructs
The C programmer is continually faced with choices of how to code given requirements. For example, to code a loop do you use while, for or do? In a given set of circumstances one construct, one way of doing things, is usually more appropriate than others.
This choice, though, is not always that clear, forcing a value judgement to be made. These decisions can be made more easily by having a basic set of rules on usage of constructs. When exceptions occur, such as where a case is not clearly covered by one rule, the rules in combination or the spirit that they convey may help a sensible decision to be made.
8.1.3 Avoid obscure, implicit and hazardous features
C, as many languages, has its dark and arcane corners. Even some of the more brightly lit pathways have traps and pitfalls. These should be recognized and appropriate warning signs given to programmers and readers. It is not necessary to use features of the language, "Because they are there." Clever usage, even of common features, is likely to lead to reader misunderstanding and problems with portability, and should generally be avoided.
Note that 'avoid' does not mean 'do not use'. There will be circumstances where a given usage is clearly best. Such usages should be minimal and clearly commented. If they threaten portability, they should be segregated.
A common form of obscurity lies in the use of implicit features, such as comparisons being 'true' if they are non-zero. These should also be avoided, so that you always 'Say What You Mean' (see 3.4).
8.1.4 Avoid deep levels of nesting
Like any other powerful concept, nesting has its dangers. The problem with nesting statements is that each time you go down a level, you have to remember the entire context of the previous code, 'pushing' it onto the 'memory stack' (see 2.10). It also geometrically increases the number of paths through the code, making testing more difficult. McCabe's code complexity measure is based on the premise that each level of nesting increases the complexity of the code.
So how deep do you go? One natural limitation which helps is the width of the screen. When lines start wrapping a lot it is probably a good point at which to stop nesting. Other considerations (see 2.10) suggest that three levels of nesting is reasonable, that deeper nesting should be the exception rather than the rule, and should be for small blocks only.
So what do you do when your code really must go deep and long? Simple: break the code out into another function.
Nesting also occurs in parenthesized expressions and in structures and pointers, where the above considerations may equally be applied.
And the big