Professional Documents
Culture Documents
February 2011 Master of Computer Application (MCA) - Semester 3 MC0071 - Software Engineering - 4 Credits
February 2011 Master of Computer Application (MCA) - Semester 3 MC0071 - Software Engineering - 4 Credits
Introduction
Over the last two decades, traditional top-down approaches to software engineering, such as the
Waterfall model, have gradually lost their dominance in favor of evolutionary and iterative methods. In
an evolutionary approach, such as the Unified Process, previously separate phases such as requirements
analysis, design and implementation are allowed to overlap, and the requirements themselves can change
or evolve while the system is being developed. In an iterative approach, such as extreme Programming,
the system is constructed through a series of versions, each of which delivers additional features, as
agreed at the start of each iteration. Iterative approaches naturally support and encourage the evolution of
requirements and design, since these typically change with each iteration; they are also therefore called
agile methods. These newer methods of software development were created in response to the failure, or
perceived failure, of top-down approaches to deliver the systems that meet or beat their specification,
deadline and budget. Indeed, the increasing use of the term “software development” rather than “software
engineering” is indicative of the move to evolutionary approaches.
The currently accepted approach in the formal methods community is top-down. Designers
should use a formal, mathematically-based notation such as B or Z to write a specification, which is then
refined, gradually adding detail until a level has been reached that can straightforwardly or automatically
be turned into program code. Associated with this process are proof obligations; these could be ignored,
checked informally, or else proved correct, as for example in the B method.
This top-down formal stepwise development method has not been widely adopted (except for
safety-critical systems where additional regulatory requirements exist). Part of the reason for this failure,
we speculate, is that programmers are forced to wait until designers have completed the specification. Of
course, for a small system, the programmers will also be the designers – but larger teams typically have
some specialization of development roles. Completing a specification can be a time-consuming activity,
since it requires designers to think through all special and error cases, and in fact these cases sometimes
come to light only after implementation has started. In addition, in many cases there is not the time, nor
the skills, required to discharge all proof obligations. Where proof is not being used, it is not immediately
clear that writing formal specifications and designs (formal models) offers any significant benefits over
less formal but more popular modeling notations such as the UML.
This paper considers two issues. Firstly how a formal model can be incorporated in an
evolutionary development, and secondly what benefits there may be in having a formal model in
situations where it is not necessary, or not desired, to carry out formal proofs. An example would be the
implementation of a business application, which is unlikely to be safety-critical, though it might be
mission-critical.
With regard to the first of these, in an evolutionary development, implementation work will begin
with a specification that is at most partially complete – which is why it is more helpful to use the phrase
formal model” rather than “formal specification”. During the development, both the implementation and
model will evolve. The question arises, how can these two evolving artifacts be kept consistent? Indeed,
what does consistency mean in this context?
This section considers potential benefits that can be obtained from having a formal, mathematical
model even in cases where it is not intended to prove (formally or informally) that the implementation
conforms to the model. One of the claimed benefits of formal methods is the extra insight gained from
giving a precise definition of system behavior. Similar claims are also made with respect to producing a
diagrammatic model using a notation such as the UML. Insight is hard to quantify, however, so it is
difficult to measure these benefits, or to compare formal and diagrammatic approaches. The clearest
benefits of having a formal model are those that follow from the availability of tool support, as described
below. The U2B tool, for example, can generate formal models from a diagrammatic UML model plus
annotations, allowing the benefits of each approach to be combined.
Animation
Model-Checking
Model-checking tools support exhaustive investigation of a system’s state space, which typically
this has to be finite. They can be used to detect states that do not satisfy an invariant, or to find execution
paths that do not satisfy properties expressed as a temporal logic formula. Note that the emphasis, as with
testing, is on finding errors, not proving their absence. This is because the model being checked is
usually a simplified version of the real system: absence of errors in the model does not guarantee the
system itself is correct. Conversely, errors found in the model usually indicate problems in the system
itself, though not always, because the model may be over-simplified or simply wrong. It is possible to
create a model, or series of models, to explore the consequences of design decisions, for example whether
to use synchronous or asynchronous message-passing. The model-checker can help the designer to
determine what properties are and are not satisfied. This technique is valuable in technology exploration
and risk-management, so it has been called “risk-driven” model-checking [3]; another appropriate name is
“throwaway modelling” as there is no obligation to keep the model once it has served its purpose.
Assertions
Formal models, particularly those based on pre- and post-conditions, are naturally associated
with the use of assertions in code. The AsmL [12] toolkit, for example, can automatically insert
assertions at appropriate points in the code to check that each operation conforms to its specification.
This approach is most helpful in debugging and error location, as assertion checking is usually disabled in
production software.
Model-Based Testing
It is possible to use a formal model to assist in testing. Classic techniques such as equivalence
partitioning and boundary testing can be applied to the definition of operations given in the model.
Mathematical formulas are converted into disjunctive normal form to discover cases of interest, and
constraint satisfaction to determine values that witness each such cases. This approach has been an area
of active research for a number of years [10], and is now starting to deliver tools of industrial relevance
[15]. Clearly, properties postulated during model-checking could be used to generate test cases, either
systematically or automatically. It is worth noting, however, that writing good test cases is essentially a
creative activity. Automated test case generation can at most supplement, not replace, the use of skilled
manual testing.
Another model-based approach to software testing involves the use of execution traces or event
logs. It is common for production software to support event logging or tracing of some kind. Typically,
traces can be rather large and it is appropriate to automatic analysis. It is possible to write special purpose
trace analysis tools, for example using Perl or a similar scripting language. Model-based trace-checking
[14] uses instead a model checker as the analysis tool; a trace and a model are supplied and the model-
checker determines whether or not the given trace conforms to the supplied model. An alternative name
for the technique is trace-driven model checking. ProB provides this facility directly. With a little
ingenuity, it is possible to use other popular model checkers such as Spin [13] in this fashion as well.
Trace-checking has the advantage relative to assertion checking that it can be run off-line and after the
fact. Global invariants can also be checked more easily. Trace checking only supplements other testing
techniques – there has to be a test-case or user driving the system in order for there to be a trace to check.
As with animation and model-checking, trace-checking is only applicable where the model is
“executable”, albeit that the requirement is weaker again, since here the input, output, pre- and post-
operation values are all available – it is only necessary to test these, not to generate them.
Answer:-
A) Software Reliability Metrics
Introduction
Computers and computer systems have become a significant part of our modern society. It
is virtually impossible to conduct many day-to-day activities without the aid of computer systems
controlled by software. As more reliance is placed on these software systems it is essential that
they operate in a reliable manner. Failure to do so can result in high monetary, property or human
loss.
This section will provide software practitioners with a basic overview of software
reliability, as well as tools and resources on software reliability.
Failure Rate: Is used to study the program failure rate per fault at the failure
intervals. As the number of remaining faults change, the failure rate of the program
changes accordingly.
Curve Fitting: Uses statistical regression analysis to study the relationship
between software complexity and the number of faults in a program, as well as the
number of changes, or failure rate.
Line Count:
Lines of Code (LOC)
o
Source Lines of Code (SLOC)
o
Complexity and Structure:
o Cyclamate Complexity (CC)
o Number of Modules
o Number of Go To Statements (GOTO)
Object-Oriented:
o Number of Classes
o Weighted Methods per Class (WMC)
o Coupling Between Objects (CBO)
o Response for a Class (RFC)
o Number of Child Classes (NOC)
o Depth of Inheritance Tree (DIT)
Dynamic Metrics
Failure Rate Data
Problem Reports
Unreliability F(t), a measure of failure, is defined as the probability that the system will fail by
time t.
F(t) = P(T =< t), t => 0.
R(t) = 1 - F(t).
Modern static analyzers have been used to find hundreds of bugs in the Linux kernel and many
large commercial applications without the false alarm rate seen in previous generation tools such as lint.
Static analyzers find bugs in source code without the need for execution or test cases. They parse the
source code and then perform dataflow analysis to find potential error cases on all possible paths through
each function. No technique is a silver bullet, and static analysis is no exception. Static analysis is usually
only practical for certain categories of errors and cannot replace functional testing. However, compared
with traditional QA and testing techniques, static analysis provides a way to achieve much higher
coverage of corner cases in the code. Compared with manual code review, static analysis is impartial,
thorough, and cheap to perform regularly.
Inconsistent Assumptions
Many types of bugs are caused by making inconsistent assumptions. Consider Listing One from line 188
of linux-2.6.4/drivers/scsi/aic7xxx/aic7770_osm.c. This seems like a strange error report. Why
wouldahc_alloc() free the argument name? Looking at the implementation, you see Listing Two. Clearly
if this function returns NULL, then the argument name is supposed to be freed, but the caller either didn't
know or forgot this facet of the interface. Why wasn't this found in testing? Probably because this is code
that runs when a device driver is initialized, and this is usually at boot time when malloc is unlikely to
fail. Unfortunately, this is not always the case, and it is possible that a device would be initialized later
(for example, with devices that can be hot-plugged).
Inconsistent assumptions are relatively easy to avoid when initially designing and writing code,
but they are difficult to avoid when maintaining code. Most code changes are usually localized to single
functions or files, but assumptions made about that code may be scattered throughout the code base.
It is especially common to see inconsistent assumptions related to conditions at the exit of a loop,
the join points at the end of if statements, the entry and exit points for functions, and error-handling code.
Static analyzers have an advantage over people in that they are not fazed by complex control flow, they
look for any path that has a contradiction of assumptions, and they have immediate access to information
across multiple source files and functions. Human auditors can be easily confused by irrelevant details of
the code. For example, inListing Three, an array-bounds error from a Linux MIDI device driver-
outEndpoint is incremented in a loop that is looking for a particular condition to hold. After the loop, the
code assumes that the condition held at some point in the loop. But this assumption might be false;
otherwise, the first part of the loop condition,outEndpoint < 15, is a redundant test. That is, if that test can
ever be false (why else would it be there?) then outEndPointmight be equal to 15 at the end of the loop,
which meansmouts[outEndpoint] will be accessing data past the end of the array allocated on the stack.
Error-Handling Code
Developers typically program with the primary goal of implementing core functionality, while handling
exceptional cases is often seen as a distraction. Furthermore, designing test cases that exercise error paths
is difficult because it requires artificially inducing errors. Despite the difficulty of handling errors, it is
critical to do. Unfortunately, debugging code often passes as error-handling code, such as in Listing
Four from a Linux SCSI device driver. The problem in this case is that the call to printk does not
terminate execution or signal an error to the caller. Either the error check on line 918 is unnecessary, or
the accesses on 920-922 will be out of bounds, leading to memory corruption. There are plenty of cases
where half-hearted error checking potentially leads to serious errors. Code auditors should determine
whether or not error checking is needed, and that it is done properly when required.
Even when error-handling code is in place, it is often riddled with bugs because it is difficult to make test
cases that trigger error conditions. In Listing Five, memory is being leaked inside the Linux code that
deals with IGMP, which is a protocol used for IP multicast. The function skb_alloc is a memory
allocation function that returns a buffer. In line 281, you see that the memory leak occurs when the
allocation succeeds (skb == NULL is false). In line 289, the function ip_route_output_key() is called. It
doesn't matter what this function does. What matters is that it can't free or save a pointer
to skb because skb is not passed to it. In the case where the function returns false, skb is still allocated. In
line 294, another condition is tested; if it is true, the function returns on line 296 without freeing the
memory allocated in line 280. If this code can be triggered by an external network packet, it becomes a
denial of service or remote crash attack on machines with IGMP enabled.
The error just described probably occurred because the programmer simply forgot to free the memory.
The widely used practice of having an error exit that performs cleanup would have prevented this error.
Alternatively, arenas or pools could have prevented this error by keeping memory associated with a
particular task together, then freeing all of the memory allocated by the pool once the task is finished. In
C++, the commonly used Resource Acquisition Is Initialization (RAII) idiom could prevent this sort of
error by automating the cleanup task in the destructors of scoped objects. However, take care even when
using RAII because resources that must survive past the end of the scope must have their cleanup actions
prevented. A common way of doing this is to provide acommit() method for the RAII object, but this
presents the potential for an error to occur if a commit() call is forgotten. In short, be careful because it is
ultimately up to you to decide how long objects should survive.
Ques. 3:- Suggest six reasons why software reliability is important. Using an example,
explain the difficulties of describing what software reliability means.
Answer:
The need for a means to objectively determine software reliability comes from the desire to apply the
techniques of contemporary engineering fields to the development of software. That desire is a result of
the common observation, by both lay-persons and specialists, that computer software does not work the
way it ought to. In other words, software is seen to exhibit undesirable behaviour, up to and including
outright failure, with consequences for the data which is processed, the machinery on which the software
runs, and by extension the people and materials which those machines might negatively affect. The more
critical the application of the software to economic and production processes, or to life-sustaining
systems, the more important is the need to assess the software's reliability.
Regardless of the criticality of any single software application, it is also more and more frequently
observed that software has penetrated deeply into most every aspect of modern life through the
technology we use. It is only expected that this infiltration will continue, along with an accompanying
dependency on the software by the systems which maintain our society. As software becomes more and
more crucial to the operation of the systems on which we depend, the argument goes, it only follows that
the software should offer a concomitant level of dependability. In other words, the software should
behave in the way it is intended, or even better, in the way it should.
A software quality factor is a non-functional requirement for a software program which is not called up
by the customer's contract, but nevertheless is a desirable requirement which enhances the quality of the
software program. Note that none of these factors are binary; that is, they are not “either you have it or
you don’t” traits. Rather, they are characteristics that one seeks to maximize in one’s software to optimize
its quality. So rather than asking whether a software product “has” factor x, ask instead the degree to
which it does (or does not).
Completeness
Presence of all constituent parts, with each part fully developed. This means that if the code calls
a subroutine from an external library, the software package must provide reference to that library and all
required parameters must be passed. All required input data must also be available.
Conciseness
Minimization of excessive or redundant information or processing. This is important where
memory capacity is limited, and it is generally considered good practice to keep lines of code to a
minimum. It can be improved by replacing repeated functionality by one subroutine or function which
achieves that functionality. It also applies to documents.
Portability
Ability to be run well and easily on multiple computer configurations. Portability can mean both
between different hardware—such as running on a PC as well as a smartphone—and between different
operating systems—such as running on both Mac OS X and GNU/Linux.
Consistency
Uniformity in notation, symbology, appearance, and terminology within itself.
Maintainability
Propensity to facilitate updates to satisfy new requirements. Thus the software product that is
maintainable should be well-documented, should not be complex, and should have spare capacity for
memory, storage and processor utilization and other resources.
Testability
Disposition to support acceptance criteria and evaluation of performance. Such a characteristic
must be built-in during the design phase if the product is to be easily testable; a complex design leads to
poor testability.
Usability
Convenience and practicality of use. This is affected by such things as the human-computer
interface. The component of the software that has most impact on this is the user interface (UI), which for
best usability is usually graphical (i.e. a GUI).
Reliability
Ability to be expected to perform its intended functions satisfactorily. This implies a time factor
in that a reliable product is expected to perform correctly over a period of time. It also encompasses
environmental considerations in that the product is required to perform correctly in whatever conditions it
finds itself (sometimes termed robustness).
Efficiency
Fulfillment of purpose without waste of resources, such as memory, space and processor
utilization, network bandwidth, time, etc.
Security
Ability to protect data against unauthorized access and to withstand malicious or inadvertent
interference with its operations. Besides the presence of appropriate security mechanisms such as
authentication, access control and encryption, security also implies resilience in the face of malicious,
intelligent and adaptive attackers.
Time Example
There are two major differences between hardware and software curves. One difference is that in the last
phase, software does not have an increasing failure rate as hardware does. In this phase, software is
approaching obsolescence; there are no motivation for any upgrades or changes to the software.
Therefore, the failure rate will not change. The second difference is that in the useful-life phase, software
will experience a drastic increase in failure rate each time an upgrade is made. The failure rate levels off
gradually, partly because of the defects found and fixed after the upgrades.
Ques. 4- What are the essential skills and traits necessary for effective project managers in
successfully handling projects?
Answer:-
Each CMM level has associated key process areas (KPA) that correspond to activities that must
be formalized to attain that level. For example, the KPAs at level 2 include configuration management,
quality assurance, project planning and tracking, and effective management of subcontracted software.
The KPAs at level 3 include intergroup communication, training, process definition, product engineering,
and integrated software management. Quantitative process management and development quality define
the required KPAs at level 4. Level 5 institutionalizes process and technology change management and
optimizes defect prevention.
Bamberger (1997), one of the authors of the Capability Maturity Model, addresses what she
believes are some misconceptions about the model. For example, she observes that the motivation for the
second level, in which the organization must have a “repeatable software process,” arises as a direct
response to the historical experience of developers when their software development is “out of control”
(Bamberger 1997). Often this is for reasons having to do with configuration management – or
mismanagement! Among the many symptoms of configuration mismanagement are: confusion over
which version of a file is the current official one; inadvertent side effects when repairs by one developer
obliterate the changes of another developer; inconsistencies among the efforts of different developers; etc.
A key appropriate response to such actual or potential disorder is to get control of the product
and the “product pieces under development” (configuration management) by (Bamberger 1997):
· Controlling the feature set of the product so that the “impact/s of changes are more fully understood”
(requirements management)
· Using the feature set to estimate the budget and schedule while “leveraging as much past knowledge as
possible” (project planning)
· Ensuring schedules and plans are visible to all the stakeholders (project tracking)
· Ensuring that the team follows its own plan and standards and “corrects discrepancies when they occur”
(quality assurance)
· Bamberger contends that this kind of process establishes the “basic stability and visibility” that are the
essence of the CMM repeatable level.