The Psychology of Quality and More
CHAPTER 10 : Programming Usage
10.1 Elegant programming
An elegant program is one of those things that is easy to detect, but is very hard to define. It is efficient in its use of language without recourse to obscurity. It is brief without using dense code. It codes complexity whilst remaining easy to read and understand, balancing the principles of simplicity and of explicitness. It is the ultimate goal of all programmers to write this coding equivalent of perfect prose.
There is no 'silver bullet', no single answer to this problem. Using coding standards will help, but they must be based on a firm foundation, where the essence of the problem is carried through the programming process and into the code.
10.1.1 Understand the problem
If the program does not match the problem that it is trying to solve, then it is unlikely to be clear or elegant. Understanding the problem usually means understanding the users and their real needs (which are seldom completely voiced), and deriving from this an agreeable model of the problem. Doing this will much improve the chances of ending up with a clear design and elegant code. The program specification is also likely to remain stable, resulting in less hacking and tweaking late in the process.
10.1.2 Use appropriate design method and model
There are a number of good design methods, each of which has an optimum application domain to which it is ideally suited. Thus, for example, Ward and Mellor's description of structured analysis and design is ideal for design of real time systems. It could also be used for object-oriented design, but is a poorer fit to this domain. Understanding the available design methods and using one which is appropriate to the problem will result in more natural, flowing code.
There are also a number of design models, such as state machines, which are appropriate to different problem types. It is one of the traps of programming to be introduced to one of these in a situation where it works well, and then, entranced by its elegance, attempt to use it in all kinds of other circumstances. Just as with the design method, the model used should be appropriate to the problem. If the design fits, then there is a better chance of a good code fit too.
In partitioning the program into subsystems, different functional areas should be clearly defined, and 'firewalls' built between them. The only items visible outside the subsystem should be the defined interface (no 'backdoor' access!).
A good general goal of partitioning is to minimize the number of interconnections between subsystems (and between the modules in a subsystem), as this will reduce the complexity of the design and consequently of the code.
Where a subsystem is intended for general use, it may also be a good idea to allow it to be extended by calling applications in a controlled manner. Thus a second level of interface may be defined where, for example, data structures are made directly accessible. With bad usage, this could result in unnecessary and hazardous bypassing of the main functions. However, giving this information could also avoid dangerous and uninformed hacks into uncharted territories in situations where, for example, performance improvements are essential.
10.1.4 Match process and data
Some problems are process oriented and some are data oriented. This should be reflected through the programming process and into the code.
In a data-driven application, the process should be moulded to the needs of the data. If the data has a natural sequence, then the program should process it in that sequence. If the data has a logical interrelationship, the equivalent logical relational statements will appear in the code.
In a process-oriented application the processes of the program should echo the processes of the problem, being supported, not dominated by the data.
And the big