CS211 Introduction
CS211 Introduction
SCHOOL OF ENGINEERING
@2021
1 Introduction
The term software engineering is composed of two words, software and engineering.
Software is more than just program code. A program is executable code, which serves
some computational purpose. Software is considered to be a collection of executable
programming code, associated libraries and documentations. Software, when made for a
specific requirement is called software product. Engineering on the other hand, is all about
developing products, using well-defined, scientific principles and methods. Thus, we can define
software engineering as an engineering branch associated with the development of software
product using well-defined scientific principles, methods and procedures. The outcome of
software engineering is an efficient and reliable software product. A small program can be written
without using software engineering principles. But if one wants to develop a large software
product, then software engineering principles are absolutely necessary to achieve a good quality
software product cost-effectively.
Small Computer Programs. Small computer programs are characterized by the data structures
and algorithms they employ, design of the program architecture, and the computational efficiency
of the program implementation. Small computer programs involve up to a few hundred lines of
source code. When you are learning to program in a new language, becoming familiar with its
syntax, data types, and control structures are the most important things because without them
you cannot transform a small-scale task into step-by-step programming instructions.
a. Large programs are most often written by programming teams. Team members must be
able to understand one another’s work. When the planning, design, and coding of a project
takes several years, communication of work among employees is of utmost importance.
b. Large programs are often developed within the constraints of short time-to-delivery
contract schedules. Programming teams may not have the luxury of starting again, even
after a key design flaw is identified.
c. Well-organized programs are easier to debug. A little extra time spent planning the layout
of a program may be saved many times over when it comes time to debug and upgrade
the software. Indeed, some estimates place maintenance at 60 to 80% of the overall cost
of a software project.
d. Large programs often evolve through a series of versions or updates. The programmer
making updates is likely to be a person other than the original developer.
a. Analysis phase
Often known as Software Requirements Specification (SRS) is a complete and
comprehensive description of the behavior of the software to be developed. It requires
system and business analysts to define both functional and non-functional requirements.
Usually, functional requirements are defined by means of use cases which describe the
users’ interactions with the software. They include such requirements as purpose, scope,
perspective, functions, software attributes, user characteristics, functionalities
specifications, interface requirements, and database requirements. In contrast, the non-
functional requirements refer to the various criteria, constraints, limitations, and
requirements imposed on the design and operation of the software rather than on
particular behaviors. It includes such properties as reliability, scalability, testability,
availability, maintainability, performance, and quality standards.
b. Design phase
It is the process of planning and problem solving for a software solution. It requires
software developers and designers to define the plan for a solution which includes
algorithm design, software architecture design, database conceptual schema and logical
diagram design, concept design, graphical user interface design, and data structure
definition.
c. Implementation phase
It refers to the realization of business requirements and design specifications into a
concrete executable program, database, website, or software component through
programming and deployment. This phase is where the real code is written and compiled
into an operational application, and where the database and text files are created. In other
words, it is the process of converting the whole requirements and blueprints into a
production environment.
d. Testing phase
It is also known as verification and validation which is a process for checking that a
software solution meets the original requirements and specifications and that it
accomplishes its intended purpose. In fact, verification is the process of evaluating
software to determine whether the products of a given development phase satisfy the
conditions imposed at the start of that phase; while validation is the process of evaluating
software during or at the end of the development process to determine whether it satisfies
specified requirements. Moreover, the testing phase is the outlet to perform debugging in
which bugs and system glitches are found, corrected, and refined accordingly.
e. Maintenance phase
It is the process of modifying a software solution after delivery and deployment to refine
output, correct errors, and improve performance and quality. Additional maintenance
activities can be performed in this phase including adapting software to its environment,
accommodating new user requirements, and increasing software reliability.
The radial direction of Figure 1.2 corresponds to the cumulative cost incurred in the
project, and the angular direction corresponds to progress made in completing each cycle
of the spiral. Each cycle of development has the following phases:-
a. Identify the design and development objectives for the cycle, as well as the
alternatives that are possible to achieve the goals.
b. Evaluate different alternatives based on objectives and constraints. Where
appropriate, identify uncertainties and risks. Risk means “something that can go
wrong” as the consequence of incomplete information, or perhaps, as the result of
human errors.
d. Plan the next stage, allowing for any of the possible lifecycle models to be used.
Requirements Specification. The objective of the requirements specification is to state the goals
of the program as carefully as possible. The goals could include, for example, expected input
(keyboard/files/IO board) and output. The requirements should also identify any known limitations,
possible errors, and accuracy issues.
Analysis. Carefully analyzing a problem and its possible solutions is of utmost importance. Either
you have to develop an algorithm yourself or find one that is already available. Do not hesitate to
look for established techniques; for example, an engineering problem might involve solving a set
of linear equations using a standard numerical algorithm such as Gaussian Elimination. To
develop algorithms from scratch when an already well-thought out and documented technique is
available is a waste of time.
Design. The next most important phase of program development is to develop a viable design
that you believe will work. Studies indicate that up to 50% of all errors in the software lifecycle are
made during the design phase of the software; the remaining errors tend to be programming and
syntax errors (as described previously). Unfortunately, design errors are also the most expensive
to repair since many appear only at run-time, and often after the software has been delivered to
the customer.
Test and Verification. In many engineering industries, such as airplane flight control, airport
traffic control, nuclear power plant control, medical instruments, and communications network
control, reliability of software is the most sought-after attribute. The software must execute as
expected and without errors. Obtaining software that performs with a high degree of confidence
requires a solid program design, a careful implementation, and a carefully designed suite of test
problems that may illuminate software bugs and weaknesses. The identification of bugs and
weaknesses is highly desirable, because this is the point at which fixing them is easy.
Maintenance. It is frequently stated that up to three quarters of a programmer’s time is spent on
maintaining existing software. Maintenance activities include (1) repair of coding (and design)
errors, (2) adapting the software to changes in the computing environment (e.g., an update in the
operating system), and (3) adapting the software to changes in the customers’ requirements.
a. Psychological studies indicate that the average human can only simultaneously
comprehend seven (plus or minus two) pieces of information. Hence, one module should
have no more than seven subordinate modules.
c. Every module must perform a task appropriate to its place in the hierarchy.
d. Every module should only receive as much information as it needs to perform its function,
and ideally, they should exchange as little information as possible.
Modules should enforce the principle of information hiding, namely, that all information about a
module should be private to the module unless it is explicitly declared public. Public data
should be avoided because it exposes implementation details that make reuse unlikely and
maintenance difficult. A key benefit of information hiding is the opportunity it affords for updating
the internal details of a module (here we assume that the details of the public interface will not
change) without affecting other modules in the system.
Coupling. Coupling measures the extent to which different modules in a system are
interconnected with one other. See Figure 1.3. In design we should keep the interfaces as minimal
and as simple as possible. Designing for minimal coupling among modules helps to ensure that
errors occurring in one module will not propagate across the whole system.
Cohesion. Cohesion is a concept that describes how strongly the attributes and functions of a
module are connected together. Ideally, a module should just perform one task. In design we
should keep related functions together and unrelated functions apart.
Fig.1.3: Highly coupled and loosely coupled modules
Abstraction
Programmers often apply techniques of abstraction to the development process, meaning that
they concentrate on the essential features of one part/module of the computer program, and
abstract from details of the computer program that are not immediately relevant to the current
module. In the development of computer software, two types of abstraction are common:
b. Data. Software development is based on the system data and the operations that can be
applied to the data. Implementations of data abstraction correspond to objects and the
operations that can be applied to the objects. This type of abstraction is common in object-
oriented programming languages such as C++ and Java.
1. The high-level design establishes the important subsystems of the design, the purpose and
intended behavior of each subsystem, and the relationship among subsystems. As already
mentioned, a good design corresponds to subsystems that are as uncoupled as possible.
2. The intermediate-level design breaks subsystems into modules. Each module should have
one well-defined purpose, hide its data from other modules, and have a minimal number of
connections to other modules in the program.
3. The low-level design involves detailed specification of algorithms and data structures.
As can be observed, the top-down design delays detailed decisions about the program flow and
data structures until they absolutely have to be made.
The strategy of bottom-up design starts with low-level procedures, modules, and subprogram
library routines, and tries to combine them into higher-level entities. A key benefit of bottom-up
design is its use of already implemented code. For example, numerical linear algebra packages
are one area where libraries are routinely linked to C programs for finite element and numerical
analysis, solution of differential equations, and solution of engineering control problems. No need
to reinvent the wheel.
Software designers never embark on the construction of a new system without first considering
available libraries/modules. Conversely, they never build software modules without a
preconceived vision of their future use. A balance of the above-mentioned criteria is usually
needed and desirable.
1.5 Debugging programs
Debugging is a methodical process of finding and reducing the number of bugs, or defects, in a
computer program. All bugs stem from a one basic premise: something thought to be right, was
in fact wrong. Due to this simple principle, truly bizarre bugs can defy logic, making debugging
software challenging. The typical behaviour of many inexperienced programmers is to freeze
when unexpected problems arise. Without a definite process to follow, solving problems seems
impossible to them. The most obvious reaction to such a situation is to make some random
changes to the code, hoping that it will start working again. The issue is simple: the programmers
have no idea of how to approach debugging.
The importance of a method of finding errors and fixing them during the life cycle of a software
product cannot be stressed enough. Testing and debugging are fundamental parts of
programmer’s everyday activity, but some people still consider it an annoying option. When not
carried out properly, consequences can be dreadful. In 1998, for example, a crew member of the
guided-missile cruiser USS Yorktown mistakenly entered a zero as data value, which resulted in
a division by zero. The error cascaded and eventually shut down the ship’s propulsion system.
The ship was dead in water for several hours because a program didn’t check for valid input.
Bugs can also be very expensive. In 1999, the 125 million dollars Mars Climate Orbiter was
assumed lost by officials at NASA. The failure responsible for loss of the orbiter was attributed to
a failure of NASA’s system engineering process. The process did not specify the system of
measurement to be used on the project. As a result, one of the development teams used Imperial
measurement while the other used the metric system. When parameters from one module were
passed to another, during orbit navigation correction, no conversion was performed, resulting in
the loss of the craft.
Despite being the realm of ingenuity and uncertainty, a debugging process can be divided into
four main steps:
1. localising a bug
2. classifying a bug
3. understanding a bug
4. repairing a bug
Localising a bug
A typical attitude of inexperienced programmers towards bugs is to consider their localisation an
easy task: they notice their code does not do what they expected, and they are led astray by their
confidence in knowing what their code should do. This confidence is completely deceptive
because spotting a bug can be very difficult. As it was explained in the introduction, all bugs stem
from the premise that something thought to be right, was in fact wrong. Noticing a bug implies
testing. Testing should be performed with discipline and, when possible, automatically, for
example after each build of the code. In case of a test failure, the programmer must be able to
see what went wrong easily, so tests must be prepared carefully.
Classifying a bug
Despite the appearance, bugs have often a common background. This allows to attempt a quite
coarse, but sometimes useful, classification. The list is arranged in order of increasing difficulty
(which fortunately means in order of decreasing frequency).
a. Syntactical Errors should be easily caught by your compiler. I say "should" because
compilers, beside being very complicated, can be buggy themselves. In any case, it is vital
to remember that quite often the problem might not be at the exact position indicated by
the compiler error message.
b. Build Errors derive from linking object files which were not rebuilt after a change in some
source files. These problems can easily be avoided by using tools to drive software
building, like GNU Make or Cmake, among others.
c. Basic Semantic Errors comprise using uninitialised variables, dead code (code that will
never be executed) and problems with variable types. A compiler can highlight them to
your attention, although it usually has to be explicitly asked through flags.
d. Semantic Errors include using wrong variables or operators (e.g., && instead of & in
MATLAB). No tool can catch these problems, because they are syntactically correct
statements, although logically wrong. A test case or a debugger is necessary to spot them.
Understanding a bug
A bug should be fully understood before attempting to fix it. Trying to fix a bug before
understanding it completely could end in provoking even more damage to the code, since the
problem could change form and manifest itself somewhere else, maybe randomly. A typical
example is memory corruption: if there is any suspect memory was corrupted during the execution
of some algorithm, all the data involved in the algorithm must be checked before trying to change
them.
The following check-list is useful to assure a correct approach to the investigation:
a. do not confuse observing symptoms with finding the real source of the problem;
b. check if similar mistakes (especially wrong assumptions) were made elsewhere in the
code;
c. verify that just a programming error, and not a more fundamental problem (e.g. an
incorrect algorithm), was found.
Repairing a bug
The final step in the debugging process is bug fixing. Repairing a bug is more than modifying
code. Any fixes must be documented in the code and tested properly. More important, learning
from mistakes is an effective attitude: it is good practice filling a small file with detailed
explanations about the way the bug was discovered and corrected.
A check-list can be a useful aid. Several points are worth recording:
a. how the bug was noticed, to help in writing a test case;
b. how it was tracked down, to give you a better insight on the approach to choose in similar
circumstances;
c. what type of bug was encountered?
d. if this bug was encountered often, in order to set up a strategy to prevent it from recurring;
e. if the initial assumptions were unjustified; this is often the main reason why tracking a bug
is some time consuming.
Exercise
1. Briefly explain the terms abstraction, modularity, coupling, cohesion, and information
hiding?
2. What are the goals of top-down and bottom-up design?
3. What is the difference between procedural abstraction and data abstraction?
4. What are the basic steps involved in the development of software?
5. What is the difference between syntax and semantic errors?
6. As an employ of Toyota, you are developing software for a computer vision system for
next generation self-driving cars, select an appropriate software development model for
the project and justify your choice.