Duplication can occur unfortunately in several different ways, such as duplicated pieces of code, duplication of information between code and comments, between code and documentation, and so on. In the sections that follow, we'll explain how to avoid those pitfalls.
Normalization is enforced by the use of abstractions. Abstraction, in this context, means giving a meaningful name to a section of data definition or a section of code. The name should represent some useful entity that has meaning to the programmer.
In contrast, for instance, a macro that creates a copy of a big chunk of code whenever instantiated may create a huge executable program, but that macro is no problem with respect to maintainability, as the body of the macro exists in only one place. Thus, there is no unnecessary, unmaintainable repetition.
However, overuse of cut and paste in the text editor can easily create duplicated code in the sense that we're warning against. Not only does this kind of repetition tend to fill up the screen, making it harder to see the structure of the program, but this kind of repetition also makes it necessary to find all the instances whenever some modification is necessary.
We solve this problem of repeated chunks of source code by liberal use of functional abstraction; i.e., whenever the same piece of code appears twice with no or only slight modification, we try to create a function that contains the code. The two occurrences are replaced by calls to the function, possibly with arguments accounting for the slight difference. Of course, it is not a good idea to create a function from an arbitrary sequence of instructions that happen to be similar. Rather, we try to identify pieces of code that have a specific purpose. We then assign a descriptive name to the piece of code in the form of a function.
Sometimes it is impossible to use functions to describe common pieces of code. In that case, we may use a macro. For instance, if the code contains a goto statement the target of which depends on the code instance, a function can't be used to capture the common parts of the code. The same goes if the parameter is a type. Usually, these two cases are the only times it is really necessary to use macros.
In fact, there are a few situations in which it is a serious mistake to use macros. In spite of ``traditional wisdom,'' the use of macros for speed should be avoided. For one thing, modern RISC processors have very little procedure call overhead, so using macros in that case in hopes of gaining speed really doesn't buy you anything. Moreover, there is a tendency among programmers to be mistaken about which parts of a program have performance problems. When you don't really know where the performance bottlenecks are, creating macros for unnecessary optimizations is a waste of time and makes debugging harder. Finally, macros tend to increase the size of the executable program (not really the same problem as the size of the source code).
Because of limited size cache memories, unnecessarily repetitious code can actually make the program slower. In such situations, increased use of functions would actually be advantageous to cut down on repetition in the source code and in the executable.
i++; /* increment i */but there are many others. Indeed, even comments that provide additional information can be avoided by replacing them with a function that contains the code to be commented. The name of the function provides the additional information. For instance, instead of explaining that certain lines invert a matrix, one could create a function called invert_matrix that contains the code to be commented. This is an example of an abstraction that is not created for multiple use, but only for the purpose of giving it a name.