1
CODING
Programming Principles
Verification & Validation
Monitoring and Control
Unit 5
Coding
Learning Objectives
After reading this unit, you should appreciate the following:
- Programming Principles
- Verification & Validation
- Monitoring & Control
Top
Programming Principles
The main activity of the coding phase is to translate design into code. We have tried up to now how to structure our work products so that they facilitate understanding and we have tried to blueprint a well-thought out solution with good inherent structure. If we translate this structured design properly, we will have a structured program. Structured programming has been a buzzword for over a decade and many articles and books have described “structured code.” It is surely more then the absence of GOTOs. A structured program doesn’t just “happen.” It is the end product of series of efforts that try to understand the problem and develop a structured, understandable solution plan, i.e., the design. It is, all but impossible, to write a good structured program based on an unstructured, poor design. So, the minimum premises for a well-structured program are a well-structured design that was developed through the structured techniques.
The coding phase affects both testing and maintenance, profoundly. As we saw earlier, the time spent in coding is a small percentage of the total software cost, while testing and maintenance consume the major percentage. Thus, it should be clear that the goal during coding should notbe to reduce the implementation cost but the goal should be to reduce the cost of later phases, even if it means that the cost of this phase has to increase. In other words, the goal during this phase is notto simplify the job of the programmer. Rather, the goal should be to simplify the job of the tester and the maintainer.
This distinction is important, as most programmers are individualistic, and mostly concerned about how to finish their job quickly, without keeping the later phases in mind. During implementation, it should be kept in mind that the programs should not be constructed so that they are easy to write, but so that they are easy to read and understand. A program is read, a lot more often, and, by a lot more people, during the later phases. Often, making a program more readable will require extra work by the programmers. For example, sometimes there are "quick fixes" to modify a given code easily, which result in a code that is more difficult to understand. In such cases, in the interest of simplifying the later phases, the easy "quick fixes" should not be adopted.
There are many different criteria for judging a program, including readability, size of the program, execution time, and required memory. Having readability and understandability as a clear objective of the coding activity can itself help in producing software that is more maintainable. A famous experiment by Weinberg showed that if programmers are specified a clear objective for the program, they usually satisfy it. In the experiment, five different teams were given the same problem for which they had to develop programs. However, each of the teams was specified a different objective, which it had to satisfy. The different objectives given were: minimize the effort required to complete the program, minimize the number of statements, minimize the memory required, maximize the program clarity, and maximize the output clarity. It was found that, in most cases, each team did the best for the objective that was specified to it. The rank of the different teams for the different objectives is shown in Figure 5.1.
Resulting Rank ( 1 = Best)01 / 02 / 03 / 04 / 05
Minimize effort to complete (01) / 1 / 4 / 4 / 5 / 3
Minimize number of statements (02) / 2–3 / 1 / 2 / 3 / 5
Minimize memory required (03) / 5 / 2 / 1 / 4 / 4
Maximize program clarity (04) / 4 / 3 / 3 / 2 / 2
Maximize output clarity (05) / 2–3 / 5 / 5 / 1 / 1
Figure 5.1: The Weinberg Experiment
The experiment clearly shows that if objectives are clear, programmers tend to achieve that objective. Hence, if readability is an objective of the coding activity, then it is likely that programmers will develop easily understandable programs. For our purposes, ease of understanding and modification should be the basic goals of the programming activity. This means that simplicity and clarity are desirable, while cleverness and complexity are not.
Programming Practice
The primary goal of the coding phase is to translate the given design into source code, in a given programming language, so that code is simple, easy to test, and easy to understand and modify. Simplicity and clarity are the properties that a programmer should strive for.
Good programming is a skill that can only be acquired by practice. However, much can be learned from the experience of others, and some general rules and guidelines can be laid for the programmer. Good programming (producing correct and simple programs) is a practice independent of the target programming language, although some well-structured languages like Pascal, Ada, and Modula make the programmer's job simpler. In this section, we will discuss some concepts related to coding in a language-independent manner.
Top-Down and Bottom-Up
All designs contain hierarchies, as creating a hierarchy is a natural way to manage complexity. Most design methodologies for software also produce hierarchies. The hierarchy may be of functional modules, as is the case with the structured design methodology where the hierarchy of modules is represented by the structure chart. Or, the hierarchy may be an object hierarchy as is produced by object-oriented design methods and, frequently, represented by object diagrams. The question at coding time is: given the hierarchy of modules produced by design, in what order should the modules be built-starting from the top level or starting from the bottom level?
In a top-down implementation, the implementation starts from the top of the hierarchy and proceeds to the lower levels. First, the main module is implemented, then its subordinates are implemented, and their subordinates, and so on. In a bottom-up implementation, the process is the reverse. The development starts with implementing the modules at the bottom of the hierarchy and proceeds through the higher levels until it reaches the top.
Top-down and bottom-up implementation should not be confused with top-down and bottom-up design. Here, the design is being implemented, and if the design is fairly detailed and complete, its implementation can proceed in either the top-down or the bottom-up manner, even if the design was produced in a top-down manner. Which of the two is used, mostly affects testing.
If there is a complete design, why is the order in which the modules are built, an issue? The main reason is that we want to incrementally build the system. That is, we want to build the system in parts, even though the design of the entire system has been done. This is necessitated by the fact that for large systems it is simply not feasible or desirable to build the whole system and then test it. All large systems must be built by assembling validated pieces together. The case with software systems is the same. Parts of the system have to be first built and tested, before putting them together to form the system. Because parts have to be built and tested separately, the issue of top-down versus bottom-up arises.
The real issue in which order the modules are coded comes in testing. If all the modules are to be developed and then put together to form a system for testing purposes, as is done for small systems, it is immaterial which module is coded first. However, when modules have to be tested separately, top-down and bottom-up lead to top-down and bottom-up approaches to testing. And these two approaches have different consequences. Essentially, when we proceed top-down, for testing a set of modules at the top of the hierarchy, stubs will have to be written for the lower- level modules that the set of modules under testing invoke. On the other hand, when we proceed bottom-up, all modules that are lower in the hierarchy have been developed and driver modules are needed to invoke these modules under testing.
Top-down versus bottom-up is also a pertinent issue when the design is not detailed enough. In such cases, some of the design decisions have to be made during development. This may be true, for example, when building a prototype. In such cases, top-down development may be preferable to aid the design while the implementation is progressing. On the other hand, many complex systems, like operating systems or networking software systems, are naturally organized as layers. In a layered architecture, a layer provides some services to the layers above, which use these services to implement the services it provides. For a layered architecture, it is generally best for the implementation to proceed in a bottom-up manner.
In practice, in large systems, a combination of the two approaches is used during coding. The top modules of the system generally contain the overall view of the system and may even contain the user interfaces. Starting with these modules and testing them gives some feedback regarding the functionality of the system and whether the "look and feel” of the system is OK. For this, it is best if development proceeds top-down. On the other hand, the bottom-level modules typically form the "service routines" that provide the basic operations used by higher-level modules. It is, therefore, important to make sure that these service modules are working correctly before they are used by other modules. This suggests that the development should proceed in a bottom-up manner. Asboth issues are important in a large project, it may be best to follow a combination approach for such systems.
Finally, it should be pointed out that incremental building of code is a different issue from the one, addressed in the incremental enhancement process model. In the latter, the whole software is built in increments. Hence, even the SRS and the design for an increment, focus on that increment only. However, in incremental building, which we are discussing here, the design itself is complete for the system we are building. The issue is, in which order the modules specified in the design, should be coded.
Structured Programming
Structured coding practices translate a structured design into well-structured code. PDL statements come in four different categories: sequence, selection (IF-THEN-ELSE, CASE), iteration (WHITE, REPEAT-UNTIL, FOR), and parallelism. Data statements included structure definitions and monitor. Programming languages may have special purpose statements: patter matching in SNOBOL; process creation and generation of variates for some probability distributions in simulation languages such as SIMULA67, and creating appending, or querying a database file in DBase (Reg. Trademark). Even special purpose languages have at least the first three types of statements.
The goal of the coding effort is to translate the design into a set of Single-Entry-Single-Exit (SESE) modules. We can explain this by representing a program as a directed graph where every statement is a node and, possible transfers of control between statements is indicated through arcs between nodes. Such a control flow graph shows one input arc, one output arc and for all nodes in the graph a path starts at the input arc, goes to the output arc, and passes through that node.
Clearly, no meaningful program can be written as a sequence of simple statements without any branching or repetition (which also involves branching). So, how is the objective of linearizing the control flow to be achieved? By making use of structured constructs. In structured programming, a statement is not a simple assignment statement, it is a structured statement. The key property of a structured statement is that it has a single-entry and a single-exit. That is, during execution, the execution of the (structured) statement starts from one defined point and the execution terminates at one defined point. With single-entry and single-exit statements, we can view a program as a sequence of (structured) statements. And, if all statements are structured statements, then during execution, the sequence of execution of these statements will be the same as the sequence in the program text. Hence, by using single-entry and single-exit statements, the correspondence between the static and dynamic structures can be obtained. The most commonly used single-entry and single-exit statements are:
Selection: if B then S 1 else S2
if B then Sl
Iteration: While B do S
I repeat S until B
Sequencing: Sl; S2; S3;
It can be shown that these three basic constructs are sufficient to program any conceivable algorithm. Modern languages have other such constructs that help linearize the control flow of a program, which, generally speaking, makes it easier to understand a program. Hence, programs should be written so that, as far as possible, single-entry, single-exit control constructs are used. The basic goal, as we have tried to emphasize, is to make the logic of the program simple to understand. No hard and fast rule can be formulated that will be applicable under all circumstances.
It should be pointed out that the main reason that structured programming was promulgated is formal verification of programs. As we will see later in this chapter, during verification, a program is considered a sequence of executable statements, and verification proceeds step by step, considering one statement in the statement list (the program) at a time. Implied in these verification methods is the assumption that during execution, the statements will be executed in the sequence in which they are organized in the program text. If this assumption is satisfied, the task of verification becomes easier. Hence, even from the point of view of verification, it is important that the sequence of execution of statements is the same as the sequence of statements in the text.
Any piece of code with a single-entry and single-exit cannot be considered a structured construct. If that is the case, one could always define appropriate units in any program to make it appear as a sequence of these units (in the worst case, the whole program could be defined to be a unit). The basic objective of using structured constructs is to linearize the control flow so that the execution behavior is easier to understand and argue about. In liberalized control flow, if we understand the behavior of each of the basic constructs properly, the behavior of the program can be considered a composition of the behaviors of the different statements. For this basic approach to work, it is implied that we can clearly understand the behavior of each construct. This requires that we be able to succinctly capture or describe the behavior of each construct. Unless we can do this, it will not be possible to compose them. Clearly, for an arbitrary structure, we cannot do this merely because it has a single-entry and single-exit. It is from this viewpoint that the structures mentioned earlier are chosen as structured statements. There are well-defined rules that specify how these statements behave during execution, which allows us to argue about larger programs.
Overall, it can be said that structured programming, in general, leads to programs that are easier to understand than unstructured programs, and that such programs are easier (relatively speaking) to formally prove. However, it should be kept in mind that structured programming is not an end in itself. Our basic objective is that the program be easy to understand. And structured programming is a safe approach for achieving this objective. Still, there are some common programming practices that are now well understood that make use of unstructured constructs (e.g., break statement, continue statement). Although efforts should be made to avoid using statements that effectively violate the single-entry and single-exit property, if the use of such statements is the simplest way to organize the program, then from the point of view of readability, the constructs should be used. The main point is that any unstructured construct should be used only if the structured alternative is harder to understand. This view can be taken only because we are focusing on readability. If the objective was formal verifiability, structured programming will probably be necessary.
Information Hiding
A software solution to a problem always contains data structures that are meant to represent information in the problem domain. That is, when software is developed to solve a problem, the software uses some data structures to capture the information in the problem domain. With the problem information represented internally as data structures, the required functionality of the problem domain, which is in terms of information in that domain, can be implemented as software operations on the data structures. Hence, any software solution to a problem contains data structures that represent information in the problem domain.
In the problem domain, in general, only certain operations are performed on some information. That is, a piece of information in the problem domain is used only in a limited number of ways in the problem domain. For example, a ledger in an accountant's office has some very defined uses: debit, credit, check the current balance, etc. An operation where all debits are multiplied together and then divided by the sum of all credits is, typically, not performed. So, any information in the problem domain, typically, has a small number of defined operations performed on it.