Lecture Notes

for

CSCI 1301: Great Ideas in Computer Science

(c) 1993 - 2007 John E. Howland, All Rights Reserved

Department of Computer Science

Trinity University

One Trinity Place

San Antonio, Texas 78212-7200

Internet: jhowland@ariel.cs.trinity.edu

8 Software Engineering

8.1 Software Prototypes

Software engineering involves use of some of the traditional engineering methods in the analysis, design, and implementation of computer software. One important engineering design methodology is prototyping. Engineering always builds one or more prototype machines before committing a new design for a product to manufacturing.

However, the production of software is so labor intensive (that is, expensive) that it is often considered too expensive to prototype a design. Hence the prototyping phase is often omitted. Therefore, the customer may be forced to use a program on a production basis which is really not much more than a prototype.

Design engineers often say that they don't fully know how to solve the problem until after they have produced a prototype. A corollary to this saying is that the first prototype design should be scrapped and the second prototype or final design should be built from the ground up using the experience gained from the first prototype.

If software design is often so expensive as to preclude prototyping as a standard practice, how then can one afford to do one or more prototype designs before building the production design? This is a challenging question and much research is being done on this question inorder to make software prototyping a more common practice.

What is needed are special prototyping languages or systems which are perhaps interactive systems containing a rich and powerful set of design and construction tools. Such programming environments should allow easy development of software layers from the top down and should facilitate the design of abstraction barriers between the software layers. These barriers are the interfaces which allow isolation of one design layer from another so that changes to a given layer can be made without requiring changes to the layers above and below. This is accomplished by making sure that communication with a layer above or below is always done using the interface to the layer, rather than being done in terms of the actual implementation of the layer.

Here is a diagram illustrating the idea of layering.

The interface between layers usually consists of a set of operations to construct, access and otherwise manipulate objects in the layer below.

In general, it is difficult to achieve a perfectly layered software design. The reasons for this are many.

Programming languages often don't support the degree of abstraction required to fully support layering. The interfaces between layers introduce an inherent inefficiency which may affect the overall efficiency of the software system being designed. In practice, the designer has to trade off efficiency for a properly layered design. In the final analysis efficiency often wins.

8.2 Object Technology

Object programming is a technology which allows binding both data structure and programs which manipulate said data structures into a single entity called an object.

An object's enclosure tends to protect its contents (data items and functions to manipulate those data) from inappropriate use and helps insure that software is built in a modular fashion. If objects are defined in terms of other objects it may be possible (depending on the object programming language used) to allow objects to inherit data and operational behavior from other objects. This technique allows systematic reuse of data structure and programs which is useful when engineering software. These are some of the reasons why there is a lot of interest in object programming currently.

8.3 An Example of Layered Design

In what follows we will try to illustrate some of these ideas by example programs where we deal with two layers of software.

We wish to design a system which will perform exact rational number arithmetic. Recall that a rational number may be represented as a fraction or ratio of two integers a / b where, of course, b != 0. There are infinitely many fractions which represent the same rational number. For example, 4 / 8 and 5 / 10 are two different representations of the rational number one half which we usually write as 1 / 2.

If we introduce an abstract data type of rational-number then we have to define a set of objects, i.e. pairs of integers and a set of operations for rational numbers such as addition, subtraction, multiplication, division, etc.

A layered design for rational numbers might provide an interface which separates the layer where the representation of rational numbers is defined from the layer where the rational number operations are defined. This interface should specify a rational number constructor and accessors for the various parts of a rational number such as the numerator and denominator.

8.3.1 Rational number Constructor


numerator make-rat denominator ==> rational-representation

8.3.2 Rational number accessors


num rational-number ==> numerator
den rational-number ==> denominator
Notice at this point we have not given actual definitions of how rational numbers are to be represented. We have specified the interface operations only. Next we can define the rational number operations of addition, subtraction, multiplication and division. Recall that when adding or subtracting fractions one must have a common denominator and when multiplying or dividing one multiplies or divides numberators and denominators. Hence we have the following definitions for the rational number operations.

8.3.3 Rational Number Addition


plus_rat =: dyad def '(((num x.) * den y.) + (num y.) * den x.) make_rat (den x.) * den y.'

8.3.4 Rational Number Subtraction


minus_rat =: dyad def '(((num x.) * den y.) - (num y.) * den x.) make_rat (den x.) * den y.'
8.3.5 Rational Number Multiplication

times_rat =: dyad def '((num x.) * num y.) make_rat (den x.) * den y.'

8.3.6 Rational Number Division


divide_rat =: dyad def '((num x.) * den y.) make_rat (den x.) * num y.'
Of course we could define many other rational number operations such as:

8.3.7 Rational Number Equality


equal_rat =: dyad def '((num x.) * den y.) = (num y.) * den x.'

8.3.8 Printing Rational Numbers


print_rat =: monad define
if. 1 = denum =. den y.
  do. format num y.
  else. (format num y.) , ' % ' , format denum
end.
)
Next we define the lower level representation of a rational number n/d to be the pair (n . d). Hence, we have:

8.3.9 Rational Number Construction


make_rat =: append
and rational number accessors

8.3.10 Rational Number Accessors


num =: 0 bond from
den =: 1 bond from
We now have enough software defined to begin testing. We can think of this as our first prototype for our rational number software package.

8.3.11 Testing the Prototype Software Design


   print_rat 3 make_rat 4 ==>
3%4
    print_rat(3 make_rat 4)plus_rat 1 make_rat 2 ==>
10%8
   print_rat (3 make_rat 4) divide_rat 1 make_rat 2 ==>
6%4

8.3.12 Exercise

Enter the above definitions into a J system and verify that each of the operations produces correct results.

8.3.13 Design of a Second Prototype

Notice that the result of adding 3%4 and 1%2 was 10%8. This answer is correct, but an answer of 5%4 would be preferable. Similarly, 3%4 % 1%2 should be 3%2, not 6%4.

We could make use of the greatest common divisor function, gcd =. +., and remove the greatest common divisor of the numerator and devisor from both the numerator and denominator. There are two possibilities; remove the common factors when the rational number is constructed or remove the common factors whenever a numerator or denominator is accessed.

8.3.14 Common Factors Removed at Construction Time


make_rat =: dyad def '(x. % g) append y. % g =. x. gcd y.'
Notice that none of the rational number operations needs to be changed to make this new prototype operational. This is one of the main benefits of the layered approach to engineering software.

8.3.15 Testing the Second Prototype


   print_rat 3 make_rat 4 ==>
3%4
   print_rat(3 make_rat 4)plus_rat 1 make_rat 2 ==>
5%4
   print_rat (3 make_rat 4) divide_rat 1 make_rat 2 ==>
3%2

8.3.16 Exercise

Verify that each of the other operations produces results which are properly reduced when using prototype 2.

8.3.17 Common Factors Removed at Access Time

The third prototype implements the strategy of removing comman factors whenever a numerator or denominator is accessed. In this prototype we utilize the original definition of make_rat:

make_rat =: append
and substitute the following definitions for num and den. Notice again that none of the rational number operations needs to be changed.

num =: monad def 'num % (num =. 0 from y.) gcd 1 from y.'

den =: monad def 'den % (0 from y.) gcd den =. 1 from y.'

8.3.18 Testing the Third Prototype


   print_rat 3 make_rat 4 ==>
3%4
   print_rat(3 make_rat 4)plus_rat 1 make_rat 2
5%4
   print_rat (3 make_rat 4) divide_rat 1 make_rat 2
3%2

8.3.19 Exercise

Verify that each of the other operations produces results which are properly reduced when using prototype 3.

The actual choice of reduction method to be used in a design for production software may depend on performance requirements of the rational number operations. It may, therefore, be necessary to make performance experiments for each design.

8.3.20 Exercise

Determine the relative performance of the three prototypes as measured by speed of arithmetic operations.

8.4 Phases of Software Development Activity

The life cycle of a software project as a number of distince phases.

8.4.1 Preliminaries

This phase covers the time and work done prior to beginning the actual system definition and construction. It includes such things as specific training, feasibility discussion, project management planning, hardware and software evaluation and selection, etc.

8.4.2 Specification

This phase covers gathering customer requirements, writing functional specifications, building and evaluating user-interface prototypes, building an essential data-flow model of the system and inspecting the specification documents.

8.4.3 Design

This phase includes data, process and user-interface modeling of the proposed system design, program architecture, computer screen layout, database design, module design and inspecting the design documents.

8.4.4 Implementation

This phase of the software development activity involves creating, documenting and inspecting the executable components of the system (code, screens, databases and help system). This may include a sequence of one or more prototype system implementations before development of the production system.

8.4.5 Testing

This phase includes writing, inspecting and executing module level and system test plans. Generation of test datasets and system load environments is required.

8.4.6 Maintenance

This phase includes all work done on the system after delivery of the software to the customer including fixing defects, adding enhancements and adapting the software to function properly in a changing environment. The maintenance phase also includes user support and training activities.

8.4.7 Writing

This activity includes writing user manuals, on-line help and system documentation.