Two important values that traditional engineering and software development share are their emphasis on defect prevention and learning from past failures. The software-development phases of design, coding, and testing correspond to the engineering stages of preliminary design, detail design, and development.
Engineers designing tangible objects apply mathematical analysis of physical laws to determine the reliability of an object. Software developers don’t have an analogous method. They both do peer reviews of their work.
Engineers designing tangible objects produce pictorial and textual descriptions of the object to be built or manufactured. Software developers also generate pictorial and textual descriptions of programs and the methods for building them.
Engineers designing tangible objects follow standards and codes when implementing a detailed design. Software developers should also follow standards when they are coding programs.
We can identify the three stages of coding a program as conception, expression, and transcription. In the conception stage, a program is couched in a language-independent notation. In the expression stage, the programmer encodes the algorithm in a specific programming language. In both of these stages, the activity may be entirely in the programmer’s mind, with no physical embodiment.
Expression-stage errors can occur in two ways: (1) the programmer can write a program that is invalid in the target programming language or (2) write a valid program that doesn’t say what he or she meant to write. Expression-stage errors that aren’t caught by the compiler are analogous to the subtle grammatical mistakes made by someone speaking in a second language. Languages that require the compiler to accept questionable source code mask many expression-stage errors. Languages that require the compiler to reject such code expose these errors at compile time.
In the transcription stage, the programmer embodies the program in a machine-readable medium. Transcription mistakes are trivial to correct, but they can result in valid programs that don’t express the intent of the programmer.
There are two main ways to avoid conception-stage errors: (1) you can write out the design in a textual or graphical format or (2) you can implement a prototype. Avoid errors in your code by writing out the design in some design medium. Without a design to compare to an implementation, there is really no objective way to assess whether a behavior is a conception-stage bug. An alternative to traditional design methods is building a prototype. A prototype can be thought of as an implementation with a set of simplifying assumptions or as a scale model of the implementation.
There are two main ways to avoid expression-stage errors: (1) you can exploit redundancy by adding information that will show errors at compile or execution time; and (2) you can also use nitpicking tools to identify potential problems that compilers must overlook.
There are two main ways to avoid transcription-stage errors: (1) you can exploit redundancy by adding information that will show errors at compile or execution time and (2) you can also compensate for human psychology by a variety of methods.
A programming convention is a set of choices made by an individual or group of programmers to code in a certain style to achieve their goals. There is rarely only one reasonable choice for any given coding style issue. Consistency in coding reduces the creation of errors and makes it easier for a programmer to diagnose and correct problems in a program.
The organizing principle for our approach to programming conventions is object-oriented programming. Rather than provide a set of specific conventions, we specify metarules from which a variety of consistent conventions may be generated. We divide our rules into the following categories: objects, source files, procedures, statements, names, and avoided constructs.
Data structures can be augmented for debugging with identification information that connects the data structure to the user input or the outside world. They can also be augmented with redundant structural information that makes it possible to detect and recover from memory corruption problems.
Procedures can be augmented for debugging with output that is generated when a preprocessor includes the code, or when a conditional statement evaluates to true at runtime. They can also be augmented by accumulating a log of actions performed by the program, which is stored in memory or written to disk.
Applications that have complex data structures can include a specialized debugger. This debugger is invoked from a standard interactive debugger and enables the programmer to navigate and display recursive data structures.
Assertions prevent programs from executing when the assumptions that they make aren’t valid. You can assert the assumptions a procedure makes before it starts to work and assert the conditions it guarantees to be true before it returns.