Stages in The Program Development Life Cycle
Stages in The Program Development Life Cycle
Developing a program involves different stages. You solve a problem by designing the solution using Structured
English, a flowchart and / or pseudocode. You write the program code and test it.
When large software systems are required to solve big problems, these stages are more formal, especially when
more people are involved in the development. Before a solution can be designed, the problem needs to be
analysed. When the program works and is being used, issues might arise that require changes. This is known as
maintenance.
Analysis - The first step in solving a problem is to investigate the issues and the current system if there is one. The
problem needs to be defined clearly and precisely. A ‘requirements specification’ is drawn up. The next step is
planning a solution. Sometimes there is more than one solution. You need to decide which is the most appropriate.
Design - You have a solution in mind. How do you design the solution in detail? an identifier table is a good starting
point. This leads you to thinking about data structures: do you need a 1D array or a 2D array to store data while it is
processed? Do you need a file to store data long-term? Plan your algorithm by drawing a flowchart or writing
pseudocode.
Coding - When you have designed your solution, you might need to choose a suitable high-level programming
language. If you know more than one programming language, you have to weigh up the pros and cons of each one.
You implement your algorithm by converting your pseudocode into program code. When you start writing programs
you might find it takes several attempts before the program compiles. When it finally does, you can execute it. It
might ‘crash’, meaning that it stops working. In this case, you need to debug the code. The program might run and
give you some output. This is the Eureka moment: ‘It works!!!!’. But does the program do what it was meant to do?
Testing - Only thorough testing can ensure the program really works under all circumstances.
There are several different development methodologies. These include the waterfall, the iterative and the rapid
application development model.
The program development life cycle - follows the defined stages of analysis, design, coding (implementation),
testing and maintenance. When maintenance no longer results in a program fit for purpose, the development starts
again, therefore creating a cycle.
The arrows going down represent the fact that the results from one stage are input into the next stage. The arrows
leading back up to an earlier stage reflect the fact that often more work is required at an earlier stage to complete
the current stage.
The iterative model - does not attempt to start with a full specification of requirements. Instead, development
starts with the implementation of a small subset of the program requirements. Repeated (iterative) reviews to
identify further requirements eventually result in the complete system.
The analysis, design, code and test phases are incorporated into a series of short, iterative development cycles.
Figure 15.03 shows a structure chart for a module that calculates the average of two numbers. The top- level box is
the name of the module, which is refined into the three sub-tasks of Level 1. The input numbers (parameters
Number1 and Number2) are passed into the ‘Calculate Average’ sub-task and then the Average parameter is
passed into the ‘OUTPUT Average’ sub-task. The arrows show how the parameters are passed between the
modules. This parameter passing is known as the ‘interface’.
Deriving pseudocode from a structure chart
Let’s look at the pyramid problem again. In a modular solution was created without using a structure chart and all
variables were global. Now we are going to use local variables and parameters. The reason for using local
variables and parameters is that modules are then self-contained and any changes to variables do not have
accidental effects on a variable value elsewhere.
The top-level module, Pyramid, calls four modules. When a module is called, we supply the parameters in
parentheses after the module identifier. This gives the following pseudocode:
MODULE Pyramid
CALL SetValues(NumberOfSymbols, NumberOfSpaces, Symbol, MaxNumberOfSymbols) REPEAT
CALL OutputSpaces(NumberOfSpaces)
CALL OutputSymbols(NumberOfSymbols, Symbol)
CALL AdjustValuesForNextRow(NumberOfSpaces, NumberOfSymbols)
UNTIL NumberOfSymbols > MaxNumberOfSymbols ENDMODULE
PROCEDURE SetValues(NumberOfSymbols, NumberOfSpaces, Symbol, MaxNumberOfSymbols) INPUT Symbol
CALL InputMaxNumberOfSymbols
NumberOfSpaces ← (MaxNumberOfSymbols – 1) / 2 NumberOfSymbols ← 1
ENDPROCEDURE
PROCEDURE InputMaxNumberOfSymbols(MaxNumberOfSymbols) REPEAT
INPUT MaxNumberOfSymbols
UNTIL MaxNumberOfSymbols MOD 2 = 1
ENDPROCEDURE
PROCEDURE OutputSpaces(NumberOfSpaces) FOR Count ← 1 TO NumberOfSpaces
OUTPUT Space // without moving to next line NEXT Count
ENDPROCEDURE
PROCEDURE OutputSymbols(NumberOfSymbols, Symbol) FOR Count← 1 TO NumberOfSymbols
OUTPUT Symbol // without moving to next line NEXT Count
OUTPUT Newline // move to the next line ENDPROCEDURE
PROCEDURE AdjustValuesForNextRow(NumberOfSpaces, NumberOfSymbols) NumberOfSpaces ←
NumberOfSpaces – 1
NumberOfSymbols ← NumberOfSymbols + 2
ENDPROCEDURE
Note that a structure chart does not give details about how parameters are passed: by reference or by value.
The information about the states of an FSM can be presented in a state-transition table. Table 15.01 shows an
example FSM represented as a state-transition table.
A state-transition diagram can be used to describe the behaviour of an FSM. Figure 15.07 shows the start state
as S1 (denoted by). If the FSM has a final state (also known as the halting state), this is shown by a double-circled
state (S1 in the example).
Types of error
Why errors occur and how to find them - Software may not perform as expected for a number of reasons, such
as:
How are errors found? The end user might report an error. This is not good for the reputation of the software
developer. Testing software before it is released for general use is essential. Research has shown that the earlier
an error can be found, the cheaper it is to fix it. It is very important that software is tested throughout its
development.
The purpose of testing is to discover errors. Edsger Dijkstra, a famous Dutch computer scientist, said ‘Program
testing can be used to show the presence of bugs, but never to show their absence!’. Finding syntax errors is easy.
The compiler/interpreter will find them for you and usually gives you a hint as to what is wrong.
Depending on your development environment editor, some syntax errors may be flagged up by your editor, so you
can correct these as you go along. A syntax error is a ‘grammatical’ error, in which a program statement does not
follow the rules of the high-level language constructs.
Some syntax errors might only become apparent when you are using an interpreter or compiler to translate your
program. Interpreters and compilers work differently. When a program compiles successfully, you know there will
be no syntax errors remaining. This is not the case with interpreted programs. Only statements that are about to be
executed will be syntax checked. So, if your program has not been thoroughly tested, it might even have syntax
errors remaining.
Figure 15.11 gives an example of how a compiler flags a syntax error. The compiler stops when it first notices a
syntax error. The error is often on the previous line. The compiler can’t tell until it gets to the next line of code and
finds an unexpected keyword.
Much more difficult to find are logic errors and run-time errors. A run-time error occurs when program execution
comes to an unexpected halt or ‘crash’ or it goes into an infinite loop and ‘freezes’.
Both of these types of error can only be found by careful testing. The danger of such errors is that they may only
show up under certain circumstances. If a program crashes every time it is executed, it is obvious there is an error.
If the program is used frequently and appears to work until a certain set of data causes a malfunction, that is much
more difficult to discover without perhaps serious consequences.
Testing methods
Stub testing - When you develop a user interface, you might wish to test it before you have implemented all the
facilities. You can write a ‘stub’ for each procedure (see Figure 15.12). The procedure body only contains an output
statement to acknowledge that the call was made. Each option the user chooses in the main program will call the
relevant procedure.
Black-box testing - As the programmer, you can see your program code and your testing will involve knowledge of
the code (see white-box testing).
As part of thorough testing, a program should also be tested by other people, who do not see the program code
and don’t know how the solution was coded.
Such program testers will look at the program specification to see what the program is meant to do, devise test
data and work out expected results. Test data usually consists of normal data values, extreme/boundary data
values and erroneous/abnormal data values. The tester then runs the program with the test data and records their
results. This method of testing is called black-box testing because the tester can’t see inside the program code:
the program is a ‘black box’.
Where the actual results don’t match the expected results, a problem exists. The programmer needs to find the
reason for this discrepancy before correcting the program. Once black-box testing has established that there is an
error, debugging software or dry-running have to be used to find the lines of code that need correcting.
White-box testing - How can we check that code works correctly? We choose suitable test data that checks every
path through the code. This is called white-box testing.
Dry-running an algorithm - A good way of checking that an algorithm works as intended is to dry-run the
algorithm using a trace table and different test data. This is also known as a walk through.
The idea is to write down the current contents of all variables and conditional values at each step of the algorithm.
Software often consists of many modules, sometimes written by different programmers. Each individual module
might have passed all the tests, but when modules are joined together into one program, it is vital that the whole
program is tested. This is known as integration testing. Integration testing is usually done incrementally. This
means that a module at a time is added and further testing is carried out before the next module is added. Software
will be tested in-house by software testers before being released to customers. This type of testing is called alpha
testing.
Bespoke software (written for a specific customer) will then be released to the customer. The customer will check
that it meets their requirements and works as expected. This stage is referred to as acceptance testing. It is
generally part of the hand-over process. On successful acceptance testing, the customer will sign off the software.
When software is not bespoke but produced for general sale, there is no specific customer to perform acceptance
testing and sign off the software. So, after alpha testing, a version is released to a limited audience of potential
users, known as ‘beta testers’. These beta testers will use the software and test it in their own environments. This
early release version is called a beta version and the chosen users perform beta testing. During beta testing, the
users will feed back to the software house any problems they have found, so that the software house can correct
any reported faults.
It is important to recognise that large programs cannot be exhaustively tested but it is important that systematic
testing finds as many errors as possible. We therefore need a test plan. In the first instance, an outline plan is
designed, for example:
- flow of control: does the user get appropriate choices and does the chosen option go to the correct module?
- validation of input: has all data been entered into the system correctly? do loops and decisions perform
correctly?
- is data saved into the correct files?
- does the system produce the correct results?
This outline test plan needs to be made into a detailed test plan.
How can we carry out these tests? We need to select data that will allow us to see whether it is handled correctly.
This type of data is called ‘test data’. It differs from real, live data because it is specifically chosen with a view of
testing different possibilities. We distinguish between different types of test data, listed in Table 15.07.
How to prevent errors - The best way to write a program that works correctly is to prevent errors in the first place.
How can we minimise the errors in a program? A major cause of errors is poor requirements analysis. When
designing a solution it is very important that we understand the problem and what the user of the system wants or
needs. We should use:
tried and tested design techniques such as structured programming or object-oriented design conventions such as
identifier tables, data structures and standard algorithms tried and tested modules or objects from program
libraries.
When a problem is reported, the programmer needs to find out what is causing the bug. To find a bug, a
programmer either uses program debugging software or a trace table (see Section 15.06).
For example, the Connect 4 game allows two players, O and X, to play against each other. An amended version
would be for one player to be the computer. This would mean a single player could try and win against the
computer.
Adaptive maintenance is the action of making amendments to a program to enhance functionality or in response
to specification changes.