cs 504
cs 504
1. Kinds of Stores:
A store sells things. There might be different types of stores in the future, but for now, it’s just a
general "store."
2. Kinds of Sales:
Sales: Normal sales where a customer buys an item.
Returns: When a customer brings back an item and gets money back. The difference between
sales and returns is that sales are positive, and returns are negative.
3. Prices:
Regular Price: The normal price of an item.
Promotional Price: The discounted price or sale price for an item.
4. Payments:
Payments can be made in three ways: cash, check, or charge (credit card).
Whole-Part Structures:
This talks about how things in the store are connected.
A store has cashiers, registers, and items.
A register has a cash drawer.
A sale consists of sale line items (the things that were bought).
Rules About Who Knows What:
This is about how things are connected or related to each other:
A person knows about the cashier.
A transaction (like a sale) knows about its participants (like the cashier and register).
A sale knows about its payments (how it was paid for).
A store knows about its sessions (time periods when the store is open).
A store knows about its cashiers, items, and registers.
Things and Their Roles:
1. Actors (people and systems that do something):
Person: Has information like name, address, and phone number.
Cashier: Works with sales and knows their number, password, and authorization.
Store: Knows things like item prices, cashiers, and registers.
2. Things You Can Touch (Objects):
Item: Things for sale in the store, like products.
Register: Where the cashier checks out customers.
Cash Drawer: Part of the register that holds money.
Sales Process:
A sale has a sale line item (an individual item bought) and knows its payments (how it was
paid for).
A sale line item knows which item it refers to and which sale it belongs to.
A return is when an item bought is brought back. It has a return price and reason for the
return.
General-Specific (Gen-Spec) Concept:
Some objects (like payments) can be general or specific. For example, a payment could be
a cash payment or an authorized payment (like credit or check). These share some things in
common, like the amount paid, but also have different details (like check number or card type).
This is a simplified way to think about how a store system works, with objects like sales,
payments, and items all connected to each other.
Purpose of Interaction Diagrams:
1. Model interactions between objects.
2. Understand how a system works (like how a user interacts with it).
3. Verify use cases, ensuring they can be supported by the existing classes.
4. Identify responsibilities and operations, then assign them to the right classes.
Two Types of Interaction Diagrams:
1. Sequence Diagrams (focus on time order of messages).
2. Collaboration Diagrams (focus on the organization of objects and how they communicate).
Sequence Diagram:
A Sequence Diagram shows how objects interact in a system and the order in which messages
are exchanged. It focuses on the timing and sequence of events.
Objects are shown horizontally (on the X-axis).
Time is shown vertically (on the Y-axis).
Messages are represented by arrows, showing which object sends a message to another.
Lifelines are vertical dashed lines that show an object's existence during the interaction.
Activation boxes (rectangles) show when an object is active during the interaction.
Notation in Sequence Diagrams:
Objects are shown as boxes.
Messages are shown as arrows going from one object to another.
Lifeline is a dotted line that represents the object's "life" during the interaction.
Activation boxes show when an object is active in processing a message.
Example:
Let's say we have two objects: LibraryMember and Book.
1. LibraryMember sends a message borrow(book) to the Book object.
2. The LibraryMember checks if it is allowed to borrow the book by sending the
message mayBorrow(). If ok is true, it proceeds with borrowing the book.
3. The Book object marks the book as "taken" by sending a message setTaken(member).
Important Points:
Self-calls: An object can send messages to itself, called self-calls. For example,
the LibraryMember object might check if it can borrow the book by calling
the mayBorrow() method on itself.
Message Arrows: The direction of the arrows shows which object is sending the message to
which other object.
Conditions: Sometimes, messages only happen if certain conditions are met. These conditions
can be shown in the diagram.
The goal of Sequence Diagrams is to help visualize how objects interact over time to achieve a
specific behavior, like processing a sale or borrowing a book.
1. Synchronous Messages:
What it is: These are call events where one object sends a message to another and waits for a
response before continuing.
How it's shown: Synchronous messages are shown with a full arrow.
Example: If an object A calls method B() on object B, it waits for B() to finish before
continuing its own work.
Return Values: Sometimes, the message may have a return value, shown with a dashed arrow.
Guidelines:
Don’t model return values if it’s obvious what is returned (e.g., getTotal()).
Model return values when needed for further messages (e.g., ok = isValid()).
2. Asynchronous Messages:
What it is: These are signals where the caller sends a message but does not wait for a response.
How it's shown: Asynchronous messages are shown with a half arrow.
Example: In multi-threading or multi-processing, objects send messages to each other without
waiting, such as notifying a new thread or creating a new object.
3. Create and Destroy Messages:
Create: An object can create another object with a <<create>> message.
Destroy: An object can destroy another object with a <<destroy>> message.
How it affects lifelines: Creation or destruction affects the object's life during the interaction.
4. Sequence Diagrams and Complexity:
Keep diagrams simple. Complex logic should be shown in separate diagrams, like flow charts.
5. Collaboration Diagrams:
These diagrams also show dynamic behavior but focus on organization and
relationships between objects. They use numbering to show message order.
Comparison:
Sequence Diagrams are great for showing the flow of time.
Collaboration Diagrams are better for showing static object connections and are harder to
follow when compared to sequence diagrams.
6. Evaluating Object-Oriented Design:
Good design is cohesive and loosely coupled (objects should work together with minimal
dependencies).
Example: A Heat Flow Regulator system can be more efficient by grouping related objects into
a Room object. This reduces coupling and simplifies communication between objects.
Example with Functions:
Instead of having multiple functions like SetMinimumValue(int
aValue) and SetMaximumValue(int aValue), you could combine them into one
function SetLimits(int minValue, int maxValue), reducing the number of messages sent
and making the system simpler.
The key takeaway is that designing a system to reduce the number of messages exchanged and
keeping interfaces small makes it easier to manage and understand.
In Lecture 22 of Software Engineering, we dive into the concept of Software and System
Architecture. Let's break it down in the simplest way:
What is Software Architecture?
Think of software architecture like the blueprint of a house. Just like different professionals
(architects, electricians, plumbers) work together to build a house, in software, various designers
and engineers collaborate to create a software system. The architecture of software is the
overall structure that defines how different parts (called components or subsystems) fit together
and communicate.
History
Software architecture became an important idea in the late 1960s. Edsger Dijkstra, a famous
computer scientist, suggested that software should be organized into layers. These layers can
only communicate with adjacent ones, which makes the system easier to manage. Later, David
Parnas further developed this idea by focusing on information hiding and program families,
which means software can be thought of as a group of related programs.
Definitions of Software Architecture
Different experts have provided definitions, but they all emphasize the structure of the software.
Some key definitions:
UML 1.3: Software architecture is the organization of a system’s components, how they interact,
and the constraints that guide their connection.
Bass, Clements, and Kazman: It’s the structure of a system, including the components, their
properties, and the relationships between them.
IEEE Glossary: Architectural design is the process of defining hardware and software
components and their interactions to build a system.
Why is Architecture Important?
Barry Boehm says that if a system doesn’t have an architecture, it shouldn’t proceed to full
development. Here’s why architecture is so important:
1. Communication: It helps different people involved in the project (like customers, developers,
testers) to understand the system and agree on its design.
2. Early Decisions: Architecture makes the early design decisions, which will affect the system's
development, maintenance, and quality. These decisions must be made early because they guide
the entire process.
3. Reusability: Once a software architecture is defined, it can be reused in future systems that have
similar requirements.
Architectural Attributes
Good software architecture needs to handle both functional (what the software does) and non-
functional (how well it works) requirements. These attributes include:
Performance: Make the system faster by reducing communication between components.
Security: Use layers to protect critical parts of the system.
Safety: Keep critical components isolated to prevent accidents.
Availability: Ensure the system keeps running by adding backup components.
Maintainability: Make the system easier to maintain by using simple, well-defined components.
Architectural Design Process
Architectural design is creative and iterative. It involves several steps:
1. System Structuring: Break down the system into smaller subsystems and define how they will
communicate with each other.
2. Control Modelling: Establish how control (decisions, processes) will flow between different
parts of the system.
3. Modular Decomposition: Break subsystems into smaller modules that handle specific tasks.
In summary, software architecture is a blueprint that shapes the software system's structure and
behavior. It helps ensure the system is efficient, secure, and maintainable.
Key Points:
1. Software Architecture: It is a high-level structure of a software system, consisting of elements
like data, processing, and connecting elements. These elements must meet both functional and
non-functional requirements. Perry and Wolfe define software architecture as {Elements,
Forms, Rationale} while Boehm's version adds {Constraints}.
2. Krutchen's 4+1 Architectural View Model:
Logical View: Represents the object model of the system.
Process View: Focuses on concurrency and synchronization.
Physical View: Describes how software is mapped to hardware.
Development View: Shows the static organization of the software in its development
environment.
Use Case View: Uses use cases to ensure that the architecture meets the system’s requirements.
3. Clements’ Modified Version: This version extends Krutchen’s model with the addition of
the Code View.
Functional View: Focuses on system functions and data flow.
Concurrency View: Deals with parallel processes and synchronization.
Physical View: Focuses on hardware and deployment.
Code View: Describes the system’s classes, objects, and modules.
Development View: Describes the file and directory structure.
4. Purpose of Views: Views are used to achieve desired system qualities and support analysis.
They are essential for documenting both current and future developments.
5. Hierarchical Views: Views can be hierarchical. For example, the Functional View can have
sub-functions, and the Development View can include files and directories.
6. Architectural Styles: These are generic models that describe the organization of components,
connectors, constraints, and semantic models in a system. Most large systems are heterogeneous
and do not strictly follow a single style.
Summary:
Software architecture uses various views to represent different aspects of a system, such as its
functionality, concurrency, physical deployment, and development structure. Krutchen’s and
Clements’ models provide different perspectives to analyze and design software systems,
ensuring that all necessary system qualities are met. Views help organize and understand
complex systems and make them easier to manage and extend.
In software architecture, there are different ways to organize and design systems. Here's a simple
breakdown of the concepts you've provided:
8.7 Architectural Models
Static Structural Model: Shows the main components of the system.
Dynamic Process Model: Shows how the system's processes work together.
Interface Models: Focus on how subsystems communicate with each other.
8.8 Architectural Styles
Architectural styles are patterns that can be followed during system design. Some common styles
include:
1. Data-Centered Architecture (Repository Model):
In this model, data is stored in one central place (a repository). Subsystems can access the data
from here. It is used when large amounts of data need to be shared.
Advantages: Data management (like backup) is easier, and there’s a global view of the system.
Disadvantages: Data evolution can be difficult, and it is hard to distribute efficiently.
2. Client-Server Architecture:
This model divides tasks between servers and clients. Servers provide services (like data
management), and clients use those services.
Advantages: It’s more efficient because data and processing are distributed across different
machines, making it more scalable.
Disadvantages: Different subsystems might organize data differently, making it inefficient to
share.
8.9 Client-Server Model
Client-Server: In this model, there are two types of components:
Clients: These are devices or applications that request services (like data) from servers.
Servers: These provide services like data or computing power to clients.
This model helps distribute processing tasks, so not all tasks are done by one central machine.
8.10 Client-Server Software Components
There are different components in a typical client-server system:
1. User Interaction/Presentation Subsystem: Handles how the user interacts with the system.
2. Application Subsystem: Implements the core functionality of the system.
3. Database Management Subsystem: Manages the data.
4. Middleware: Acts as a bridge to connect clients and servers.
8.11 Client-Server Configurations
There are different configurations of client-server systems, depending on how tasks are divided:
1. Thin Client Model:
The client does not do much processing, and most of the work is done by the server.
Disadvantages: Puts heavy load on both the server and network.
2. Fat Client Model:
The client does more processing on its own, reducing the load on the server.
Disadvantages: It’s more complex to manage because updates must be done on each client.
In short, architectural styles and models help in deciding how to design and organize a system,
based on needs like data sharing, performance, and management efficiency.
You said:
Zero Install Architecture:
Problem: In fat-client architecture, keeping the client machines up to date with the application is
tough and costly.
Solution: Zero Install eliminates the need for installing anything on the client side. It’s like using
a thin-client setup but with a twist—multiple servers share the workload. For example, web-
based applications where updates are done on the server, so users always get the latest version
without any updates on their side.
N-Tier Architecture:
What it is: N-tier architecture breaks the application into multiple layers, each running on
separate servers (for example, one for the user interface, one for the application logic, and one
for the database).
Benefit: It improves scalability and performance by distributing the workload. The more servers
you add, the better the system can handle increased demands.
Example: A 3-tier architecture has three main layers: the presentation layer (user interface), the
application layer (logic and functionality), and the database layer (data storage).
Data Flow (Pipes and Filters) Architecture:
What it is: This architecture focuses on passing data through a series of processes (called
filters). Each filter processes data independently, and the data flows through pipes connecting the
filters.
Benefit: It's simple and efficient, as each filter works independently of others.
Example: A data processing pipeline where each stage refines or transforms the data until it
reaches the final output.
Layered Architecture:
What it is: In this architecture, different layers are used for different purposes (for example, a
user interface layer, a business logic layer, and a data access layer).
Benefit: Each layer interacts only with the layer directly below it. This makes the system easier
to maintain because changes to inner layers don't affect outer layers as long as the interface
between them stays the same.
Example: The layers in an operating system, with the hardware layer at the bottom and the user
interface at the top.
Reference Architecture:
What it is: A reference architecture is a blueprint or standard for building systems. It isn’t a
physical system but a guideline for implementing or comparing systems.
Example: The OSI model for network communications, which defines how different network
systems should interact.
Partitioning the Architecture:
Horizontal Partitioning: This divides the architecture into separate modules based on major
functions, with a control module coordinating communication between them.
Vertical Partitioning: This divides the system from a decision-making perspective, where
higher layers make decisions, and lower layers do the work (also known as factoring).
Analyzing Architecture Design:
Goal: To find the best balance between system properties, such as performance, cost, and
maintainability.
Process:
1. Collect scenarios (use cases).
2. Gather requirements, constraints, and environment descriptions.
3. Describe the architectural styles/patterns chosen to meet the needs.
4. Evaluate quality attributes, like performance and reliability.
5. Analyze how sensitive the architecture is to various changes.
6. Critique the architecture based on the analysis.
This explanation simplifies the key concepts for you, making them easier to understand.
Design Patterns (Explained Simply)
What is a Design Pattern? A design pattern is a general solution to a common problem in
software design. It describes how to structure objects and classes in a way that solves a problem
in a particular situation. These patterns are reusable, meaning you can apply them to similar
problems without redoing the same work each time.
Key Parts of a Design Pattern
1. Motivation/Context: Describes the problem the pattern solves and where it applies.
2. Prerequisites: What needs to be in place before using the pattern.
3. Program Structure: How the pattern is structured in terms of code and components.
4. Participants: The classes or objects that participate in the pattern.
5. Consequences: The advantages and disadvantages of using the pattern.
6. Examples: Real-world examples of where the pattern is used.
Historical Perspective
The idea of design patterns was first introduced by Christopher Alexander in the 1970s in his
books about architecture. In the 1980s, software engineers started applying this idea to software
design, and by 1995, the book "Design Patterns: Elements of Reusable Object-Oriented
Software" was published, popularizing the concept.
GOF (Gang of Four) Design Pattern Format
This format is used to describe design patterns:
1. Name: A meaningful name for the pattern.
2. Problem: The problem the pattern addresses.
3. Context: The conditions under which the pattern applies.
4. Forces: The challenges and conflicts involved in the problem.
5. Solution: How the pattern provides a solution to the problem.
6. Examples: Real-world examples of the pattern.
7. Resulting Context: The state after the pattern is applied.
8. Rationale: Why the pattern works and how it resolves the challenges.
9. Related Patterns: Connections to other patterns.
10. Known Uses: Examples where the pattern has been successfully used.
Types of Design Patterns
1. Creational Patterns:
Focus on object creation.
Examples: Factory Method, Abstract Factory.
These patterns abstract the process of creating objects, making the system independent of how
they are created.
2. Structural Patterns:
Deal with the structure of objects.
Examples: Adapter, Decorator.
These patterns define relationships between objects and help compose large structures.
3. Behavioral Patterns:
Focus on communication between objects.
Examples: Observer, Strategy.
These patterns describe how objects interact and share responsibilities.
In Short:
Design patterns are like reusable solutions for common problems in software design. They help
developers structure their code in a way that is flexible, scalable, and easy to maintain. Each
pattern has a defined structure, which can be adapted to solve various similar problems.
Observer Pattern (Explained Simply)
Intent:
The Observer pattern is used to define a one-to-many relationship between objects. When one
object changes its state, all its dependent objects (observers) are automatically notified and
updated. It’s like a “publisher-subscriber” system, where one object (the "publisher") notifies
others (the "subscribers") when something changes.
Example: In the Model-View-Controller (MVC) design pattern, the "model" object (which
holds the data) notifies the "view" object (which displays the data) whenever the data changes, so
the view can update automatically.
Motivation:
It’s useful when a system needs to keep multiple objects in sync with each other. For example,
when multiple displays of data are needed and should update when the data changes.
Structure:
The structure of the observer pattern involves:
1. Subject: The object being observed. It maintains a list of observers and notifies them when it
changes.
2. Observer: The object that listens for changes and updates itself when the subject changes.
3. ConcreteSubject: A specific implementation of the subject that holds state and sends
notifications to observers.
4. ConcreteObserver: A specific implementation of an observer that reacts to changes in the
subject.
Example Implementation:
Subject has methods to attach, detach, and notify observers.
Observer has an update method to be called when the subject changes.
Consequences:
The Observer pattern can cause performance issues if too many observers are updating
frequently.
It helps decouple objects, so they don’t have to directly reference each other.
In Java, organize class and interface declarations with clear documentation and a consistent order
for variables and methods.
Coding Best Practices
Types and Type Conversion
Always perform explicit type conversions in both Java and C++. Avoid implicit type casting as
it can lead to errors.
Classes and Interfaces
Organize class/interface declarations in the following order:
1. Documentation
2. Class/interface statement
3. Static variables (public, protected, private order)
4. Instance variables
5. Constructors
6. Methods (no specific order)
Variables
Initialization: Declare and initialize variables in the smallest scope possible.
Encapsulation: Avoid public variables. Use private variables with getter/setter methods to
ensure encapsulation.
Global Variables: Avoid them. Keep variables within the scope of their use.
Variable Declarations: Group related variables of the same type in a single statement for
clarity.
Loop Structures
Keep loop control statements clean. Avoid mixing assignments or operations in the loop
statement.
Avoid do ... while loops because they complicate readability.
Prefer the form for(;;) for empty loops instead of while(true).
Conditionals
Avoid complex conditional expressions. Break them down into temporary boolean variables for
clarity.
Place the nominal case in the if block and exceptions in the else block to improve readability.
Avoid executable statements inside conditionals, as they can lead to unintended behaviors.
Miscellaneous
Magic Numbers: Avoid using raw numbers (except 0 and 1). Declare constants instead.
Floating Point Numbers: Always use a decimal point and at least one digit after it to improve
clarity.
C++ Return Type: Always specify the return type of a function. Implicit return types should be
avoided.
These guidelines aim to make code cleaner, more readable, and maintainable, which is crucial
when working in teams or on large projects.
You said:
Comments in Code:
1. Issues with Comments: Comments are not syntax-checked, and they often become outdated as
the code changes. Comments should not be used to "mask" poor code but should only clarify the
purpose of code that cannot be simplified.
2. Guidelines for Comments:
Language: Always write comments in English for international compatibility.
Use of // for Comments: For both single-line and multi-line comments. It is advised to
use // instead of /* */ to avoid nested comments.
Indentation: Comments should follow the indentation of the code they describe to maintain
consistency.
Layout in Code:
1. Indentation: A basic indentation of 2 spaces is recommended for readability. This helps prevent
overly deep nested code, which can become difficult to manage.
2. Natural Expression Formatting: Write expressions in a way that reads naturally and clearly,
for example, avoiding complex negations like if (! (block < activeBlock)) and rewriting
it as if ((block >= activeBlock)) for better clarity.
3. Parentheses: Always use parentheses to avoid ambiguity and logical errors. For instance:
Incorrect: if (x & MASK == BITS)
Correct: if ((x & MASK) == BITS)
4. Breaking Down Complex Expressions: Complex expressions should be split into simpler
statements to improve readability. For example, instead of:
c++
Copy code
*x += (*xp = ( 2 *k < (n-m) ? c[k+ 1 ] : d[k--]));
Break it into:
c++
Copy code
if ( 2 *k < n-m) *xp = c[k+ 1 ]; else *xp = d[k--]; *x = *x + *xp;
Cryptic Code:
1. Concise Code May Be Confusing: Using shortcuts can make code hard to understand. For
example:
x *= a + b; might be ambiguous. It could mean x = x * a + b or x = x * (a + b). The
latter is correct, but the intent is unclear without parentheses.
2. Avoiding Overuse of Bitwise Operations: A cryptic line like:
c++
Copy code
subkey = subkey >> (bitoff - (bitoff >> 3 ) << 3 );
is difficult to understand. A clearer approach would be:
c++
Copy code
subkey = subkey >> (bitoff & 0x7 );
3. Intent vs. Syntax: Right-shifting for division (e.g., a = a >> 2) is technically correct but
misleading, as the intent is to divide by 4. It would be clearer to use a = a / 4;.
Conclusion:
Clear and maintainable code is achieved by keeping it simple and readable. Avoid over-
complicating expressions or using shortcuts that make the code harder to understand.
Coding Style Guidelines (Continued)
1. Switch Statements:
Avoid Fall-through Logic: In C/C++, fall-through behavior in switch statements (when one
case flows into the next without a break) can be confusing and lead to errors. It’s better to
explicitly include break statements to ensure that only one case is executed.
Example of Bad Practice:
c++
Copy code
switch (c) { case '-' : sign = -1 ; case '+' : c = getchar (); case '.' : break ; default : if (!
isdigit (c)) return 0 ; }
This code is unclear because the fall-through makes it difficult to understand which case is being
executed.
Better Approach: Explicitly end each case with a break, even if it means repeating some code:
c++
Copy code
switch (c) { case '-' : sign = -1 ; c = getchar (); break ; case '+' : c = getchar (); break ;
case '.' : break ; default : if (! isdigit (c)) return 0 ; break ; }
Even Better (Use if-else Statements): If the switch is getting too complicated, an if-
else structure can make the logic clearer:
c++
Copy code
if (c == '-' ) { sign = -1 ; c = getchar (); } else if (c == '+' ) { c = getchar (); } else if (c !
= '.' && ! isdigit (c)) { return 0 ; }
2. Magic Numbers:
What Are Magic Numbers? Magic numbers are constant values (like 20, 27, 3, etc.) used in the
code without any explanation, making it difficult to understand their significance. They are like
"magic" because their meaning isn't clear.
Example of Magic Numbers:
c++
Copy code
fac = lim / 20 ; if (fac < 1 ) fac = 1 ; for (i = 0 , col = 0 ; i < 27 ; i++, j++) { col += 3 ; k =
21 - (let[i] / fac); star = (let[i] == 0 ) ? ' ' : '*' ; for (j = k; j < 22 ; j++) draw (j, col, star); }
draw ( 23 , 1 , ' ' ); for (i = 'A' ; i <= 'Z' ; i++) cout << i;
It's hard to understand what the numbers 20, 27, 3, etc., represent. They are just arbitrary
constants, making the code less readable.
Better Approach (Using Named Constants): By replacing magic numbers with named
constants (using enum or const variables), the code becomes self-documenting:
c++
Copy code
enum { MINROW = 1 , MINCOL = 1 , MAXROW = 24 , MAXCOL = 80 , LABELROW = 1 , NLET
= 26 , HEIGHT = MAXROW - 4 , WIDTH = (MAXCOL - 1 ) / NLET }; fac = (lim + HEIGHT - 1 ) /
HEIGHT; if (fac < 1 ) fac = 1 ; for (i = 0 ; i < NLET; i++) { if (let[i] == 0 ) continue ; for (j
= HEIGHT - let[i] / fac; j < HEIGHT; j++) draw (j - 1 + LABELROW, (i + 1 ) * WIDTH, '*' ); }
draw (MAXROW - 1 , MINCOL + 1 , ' ' ); for (i = 'A' ; i <= 'Z' ; i++) cout << i;
By naming the constants, it is clear what each value represents (e.g., MINROW, MAXROW, NLET,
etc.), improving code readability and making modifications easier.
3. Use (or Abuse) of Zero:
The Problem with Using Zero (0) in C/C++: In older C-style programming, 0 is commonly
used in ways that are ambiguous. For example:
flag = 0; (used as a boolean flag)
str = 0; (used as a string)
name[i] = 0; (used as a null terminator)
x = 0; (used as a floating-point value)
This can be confusing because it’s not clear from the variable's name what 0 is representing in
that context.
Better Approach (Use Meaningful Symbols): Instead of using 0 for everything, it’s better to
use explicit, self-documenting values:
c++
Copy code
flag = false ; // Boolean flag str = NULL ; // Pointer to a null value name[i] = '\0' ; // Null
terminator for string x = 0.0 ; // Floating-point zero i = 0 ; // Integer zero (clear in context)
This approach aligns with the philosophy of making code self-documenting, where variable
names and values reflect their actual purpose.
Conclusion:
Avoid Cryptic Code: Using clear, understandable constructs like if-else and avoiding fall-
through in switch statements make the code easier to follow.
Avoid Magic Numbers: Use named constants to improve readability and explain the purpose of
specific values.
Use Explicit Values: Instead of overusing 0, use more descriptive terms like false, NULL,
and \0 to make the intent of the code clearer.
You said:
Lecture No. 329 10.9 Clarity through modularity As mentioned earlier, abstraction and. We also
discussed that the size of individual functions plays a significant role in making the program easy
or difficult to understand. In general, as the function becomes longer in size, it becomes more
difficult to understand. Modularity is a tool that can help us in reducing the size of individual
functions, making them more readable. As an example, consider the following selection sort
function. void selectionSort(int a[], int size) { int i, j; int temp; int min; for (i = 0; i < size-1; i++)
{ min = i; for (j = i+1; j < size; j++){ if (a[j] < a[min]) min = j; } temp = a[i]; a[i] = a[min];
a[min] = temp; } } Although it is not very long but we can still improve its readability by
breaking it into small functions to perform the logical steps. The modified code is written below:
CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 171 void swap(int &x, int &y) { int temp; temp = x; x =
y; y = temp; } int minimum(int a[], int from, int to) { int i; int min; min = a[from]; for (i = from; i
<= to; i++){ if (a[i] < a[min]) min = i; } return min; } void selectionSort(int a[], int size) { int
min; int i; for (i = 0; i < size; i++){ min = minimum(a, i, size –1); swap(a[i], a[min]); } } It is
easy to see that the new selectionSort function is much more readable. The logical steps have
been abstracted out into the two functions namely, minimum and swap. This code is not only
shorter but also as a by product we now have two functions (minimum and swap) that can be
reused. Reusability is one of the prime reasons to make functions but is not the only reason.
Modularity is of equal concern (if not more) and a function should be broken into smaller pieces,
even if those pieces are not reused. As an example, let us consider the quickSort algorithm
below. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 172 void quickSort(int a[], int left, int right) { int i, j;
int pivot; int temp; int mid = (left + right)/2; if (left < right){ i = left - 1; j = right + 1; pivot =
a[mid]; do { do i++; while (a[i] < pivot); do j--; while (a[i] < pivot); if (i<j){ temp = a[i]; a[i] =
a[j]; a[j] = temp; } } while (i < j); temp = a[left]; a[left] = a[j]; a[j] = temp; quickSort(a, left, j);
quickSort(a, j+1, right); } This is actually a very simple algorithm but students find it very
difficult to remember. If is broken in logical steps as shown below, it becomes trivial. void
quickSort(int a[], int left, int right) { int p; if (left < right){ p = partition(a, left, right);
quickSort(a, left, p-1); quickSort(a, p+1, right); } } int partition(int a[], int left, int right) { int i; j;
int pivot; i = left + 1; j = right; pivot = a[left]; while(i < right && a[i] < pivot) i++; while(j > left
&& a[j] >= pivot) j++; if(i < j) swap(a[i], a[j]); swap(a[left], a[j]); return j; } CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 173 10.10 Short circuiting || and && The logical and
operator, &&, and logical or operators, ||, are special due to the C/C++ short circuiting rule, i.e. a
|| b and a && b are short circuit evaluated. That is, logical expressions are evaluated left to right
and evaluation stops as soon as the final truth value can be determined. Short-circuiting is a very
useful tool. It can be used where one boolean expression can be placed first to “guard” a
potentially unsafe operation in a second boolean expression. Also, time is saved in evaluation of
complex expressions using operators || and &&. However, a number of issues arise if proper
attention is not paid. Let us look at the following code segment taken from a commercially
developed software for a large international bank: struct Node { int data; Node * next; }; Node
*ptr; ... while (ptr->data < myData && ptr != NULL){ // do something here } What’s wrong
with this code? The second part of condition, ptr != NULL, is supposed to be the guard. That is,
if the value of the pointer is NULL, then the control should not enter the body of the while loop
otherwise, it should check whether ptr->data < myData or not and then proceed accordingly.
When the guard is misplaced, if the pointer is NULL then the program will crash because it is
illegal to access a component of a non-existent object. This code is rewritten as follows. This
time the short-circuiting helps in achieving the desired objective which would have been a little
difficult to code without such help. while (ptr != NULL && ptr->data < myData){ // do
something here } CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 174 10.11 Operand Evaluation Order and Side Effects
A side effect of a function occurs when the function, besides returning a value, changes either
one of its parameters or a variable declared outside the function but is accessible to it. That is, a
side effect is caused by an operation that may return an explicit result but it may also modify the
values stored in other data objects. Side effects are a major source of programming errors and
they make things difficult during maintenance or debugging activities. Many languages do not
specify the function evaluation order in a single statement. This combined with side effects
causes major problems. As an example, consider the following statement: c = f1(a) + f2(b); The
question is, which function (f1 or f2) will be evaluated first as the C/C++ language does not
specify the evaluation order and the implementer (compiler writer) is free to choose one order or
the other. The question is: does it matter? To understand this, let’s look at the definition of f1 and
f2. int f1(int &x) { x = x * 2; return x + 1; } int f2(int &y) { y = y / 2; return y - 1; } In this case
both f1 and f2 have side effects as they both are doing two things - changing the value of the
parameter and changing the value at the caller side. Now if we have the following code segment,
a = 3; b = 4; c = f1(a) + f2(b); then the value of a, b, and c would be as follows: a = 6 b = 2 c = 8
CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan
ChatGPT said:
ChatGPT
10.9 Clarity through Modularity
Modularity helps break complex tasks into smaller, more manageable functions, improving
clarity and readability. For example, consider the selection sort function:
Original Code:
cpp
Copy code
void selectionSort(int a[], int size) { int i, j, min, temp; for (i = 0 ; i < size - 1 ; i++) { min =
i; for (j = i + 1 ; j < size; j++) { if (a[j] < a[min]) min = j; } temp = a[i]; a[i] = a[min];
a[min] = temp; } }
Refactored Code for Modularity:
cpp
Copy code
void swap(int &x, int &y) { int temp = x; x = y; y = temp; } int minimum(int a[], int from,
int to) { int min = a[from]; for ( int i = from; i <= to; i++) { if (a[i] < a[min]) min = i; }
return min; } void selectionSort(int a[], int size) { for ( int i = 0 ; i < size; i++) { int min =
minimum (a, i, size - 1 ); swap (a[i], a[min]); } }
In the refactored version, the selection sort function is broken into smaller, reusable functions
(swap and minimum), making the code easier to understand and manage.
QuickSort Example (Further Modularity):
The original quickSort function can also be broken down for clarity:
cpp
Copy code
void quickSort(int a[], int left, int right) { if (left < right) { int p = partition (a, left, right);
quickSort (a, left, p - 1 ); quickSort (a, p + 1 , right); } } int partition(int a[], int left, int right)
{ int i = left + 1 , j = right, pivot = a[left]; while (i < right && a[i] < pivot) i++; while (j >
left && a[j] >= pivot) j--; if (i < j) swap (a[i], a[j]); swap (a[left], a[j]); return j; }
Breaking down complex functions into smaller pieces improves readability, reusability, and
maintainability, which are all key elements in software engineering.
10.10 Short Circuiting with && and ||
In C/C++, logical operators && (AND) and || (OR) are short-circuit operators, meaning they
stop evaluating as soon as the result is determined:
a || b: If a is true, b is not evaluated.
a && b: If a is false, b is not evaluated.
This behavior can prevent errors and improve efficiency. For example, consider this problematic
code:
cpp
Copy code
while (ptr->data < myData && ptr != NULL ) { // ptr might be NULL before accessing ptr-
>data // Do something }
If ptr is NULL, accessing ptr->data would cause a crash. The correct approach is:
cpp
Copy code
while (ptr != NULL && ptr->data < myData) { // Do something }
Here, short-circuiting ensures that ptr is checked before attempting to access its data, preventing
a potential crash.
Open sidebar
ChatGPT
You said:
Lecture No. 339 Common Coding mistakes Following is short list of common mistakes made
due to side-effects. 1. array[i++] = i; If i is initially 3, array[3] might be set to 3 or 4. 2. array[i+
+] = array[i++] = x; Due to side effects, multiple assignments become very dangerous. In this
example, a whole depends upon when i is incremented. 3. “,” is very dangerous as it causes side
effects. Let’s look at the following statement: int i, j = 0; Because of the syntax, many people
would assume that i is also being initialized to 0, while it is not. Combination of , and = -- is
fatal. Look at the following statement: a = b, c = 0; A majority of the programmers would
assume that all a, b, and c are being initialized to 0 while only c is initialized and a and b have
garbage values in them. This kind of overlook causes major programming errors which are not
caught easily and are caused only because there are side effects. Guidelines If the following
guidelines are observed, one can avoid hazards caused by side effects. 1. never use “,” except for
declaration 2. if you are initializing a variable at the time of declaration, do not declare another
variable in the same statement 3. never use multiple assignments in the same statement 4. Be
very careful when you use functions with side effects – functions that change the values of the
parameters. 5. Try to avoid functions that change the value of some parameters and return some
value at the same time. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 177 10.12 Performance In many cases, performance
and maintainability are at odds with one another. When planning for performance, one should
always remember the 80/20 rule - you spend 80 percent of your time in 20 percent of the code.
That is, we should not try to optimize everything. The proper approach is to profile the program
and then identify bottlenecks to be optimized. This is similar to what we do in databases – we
usually normalize the database to remove redundancies but then partially de-normalize if there
are performance issues. As an example, consider the following. In this example a function
isspam is profiled by calling 10000 times. The results are shown in the following table: sec %
Cum% cycles instructions calls function 11314990000 9440110000 4835000 strchr 1520280000
156646000 4618000 strncmp 648080000 854500000 2170000 strstr 456225559 344882213
2170435 strlen 21950000 28510000 isspam 11 other functions with insignificant performance
overhead The profiling revealed that most of time was spent in strchr and strncmp and both of
these were called from strstr. When a small set (a couple of functions) of functions which use
each other is so overwhelmingly the bottleneck, there are two alternatives: 1. use a better
algorithm 2. rewrite the whole set In this particular case strstr was rewritten and profiled again. It
was and found out that although it was much faster but now 99.8% of the time was spent in
strstr. The algorithm was rewritten and restructured again by eliminating strstr, strchr, and
strncmp and used memcmp. Now memcmp was much more complex than strstr but it gained
efficiency by eliminating a number of loops and the new results are as shown: sec % Cum%
cycles instructions calls function 880890000 1027590000 46180000 memcmp 665550000
902920000 isspam 140304 106043 strlen strlen now went from over two million calls to 652.
CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 178 Many details of the execution can be discovered by
examining the numbers. The trick is to concentrate on hot spots by first identifying them and
then cooling them. As mentioned earlier, most of the time is spent in loops. Therefore we need to
concentrate on loops. As an example, consider the following: for (j = i; j < MAX_FIELD; j++)
clear(j); This loop clears field before each new input is read. It was observed that it was taking
almost 50% of the total time. On further investigation it was found out that MAX_FIELD was
200 but the actual fields that needed to be cleared were 2 or 3 in most cases. The code was
subsequently modified as shown below: for (j = i; j < maxField; j++) clear(j); This reduced the
overall execution time by half. explain it easy also make it so easy by adding skyrim fun so i
explain it easy
ChatGPT said:
ChatGPT
Let’s make it super simple and fun by relating it to Skyrim so you can explain it easily! Imagine
you’re the Dragonborn, and the code is like planning your battles and adventures. Mistakes in
coding are like making bad decisions in the game that can lead to trouble. Let’s dive into the
details with a Skyrim twist:
4. Order of Evaluation
Example: You’re making a sandwich. If you butter the bread before cutting it, it’s fine. But if
you cut first, butter might spill everywhere. The order of actions matters!
In Code: Some systems process code in different orders. Avoid writing unclear code
like array[i++] = i; which behaves differently depending on the system.
8. Alignment
Example: Imagine parking cars in a garage. If you always park in designated spots, it's
organized. But if the spots vary in size, chaos ensues.
In Code: The position of data in structures may vary. Accessing data through pointers can lead
to errors.
9. Bit Fields
Example: Packing a suitcase tightly saves space. However, if someone else packs it differently,
your organization might get ruined.
In Code: Bit fields (packing small data pieces tightly) can cause issues on different systems
since their behavior isn’t consistent.
Final Thought:
Writing portable code is like creating instructions that work for everyone, no matter their tools or
habits. Stick to standard practices and avoid clever tricks that only work in specific situations.
It’s better to be simple and clear than to risk confusion later.
You said:
Lecture No. 359 10.14 Exception handling Exception handling is a powerful technique that
separates error-handling code from normal code. It also provides a consistent error handling
mechanism. The greatest advantage of exception handling is its ability to handle asynchronous
errors. The idea is to raise some error flag every time something goes wrong. There is a system
that is always on the lookout for this error flag. Third, the previous system calls the error
handling code if the error flag has been spotted. The raising of the imaginary error flag is simply
called raising or throwing an error. When an error is thrown the overall system responds by
catching the error. Surrounding a block of error-sensitive code with exception handling is called
trying to execute a block. The following code segment illustrates the general exception handling
mechanism. try { ___... ___... ___throw Exception() ___... ___... } catch( Exception e ) { ___...
___... } One of the most powerful features of exception handling is that an error can be thrown
over function boundaries. This allows programmers to put the error handling code in one place,
such as the main-function of your program. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 185 Exceptions and code complexity A number of
invisible execution paths can exist in simple code in a language that allows exceptions. The
complexity of a program may increase significantly if there are exceptional paths in it. Consider
the following code: String EvaluateSalaryAnadReturnName( Employee e) { if (e.Title() ==
“CEO” || e.Salary() > 10000) { cout << e.First() << “ “ << e.Last() << “ is overpaid” << endl; }
return e.First() + “ “ + e.Last(); } Before moving any further, let’s take the following
assumptions: 1. Different order of evaluating function parameters are ignored. 2. Failed
destructors are ignored 3. Called functions are considered atomic a. for example: e.Title() could
throw for several reasons but all that matters for this function is whether e.Title() results in an
exception or not. 4. To count as different execution paths, an execution path must be made-up of
a unique sequence of function calls performed and exited in the same way. Question: How many
more execution paths are there? Ans: 23. There are 3 non-exceptional paths and 20 exceptional
paths. The non-exceptional paths: The three non-exceptional paths are enumerated as below: 1. if
(e.Title() == “CEO” || e.Salary() > 10000) if e.Title() == “CEO” is true then the second part is
not evaluated and e.Salary() will not be called. cout will be performed 2. if e.Title() != “CEO”
and e.Salary() > 10000 both parts of the condition will be evaluated cout will be performed.
3. if e.Title() != “CEO” and e.Salary() <= 10000 both parts of the condition will be evaluated
cout will not be performed. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 186 Exceptional Code Paths The 20 exceptional code
path are listed below. 1. String EvaluateSalaryAnadReturnName( Employee e) The argument is
passed by value, which invokes the copy constructor. This copy operation might throw an
exception. 2. if (e.Title() == “CEO” || e.Salary() > 10000) e.Title() might itself throw, or it might
return an object of class type by value, and that copy operation might throw. 3. if (e.Title() ==
“CEO” || e.Salary() > 10000) Same as above. 4. if (e.Title() == “CEO” || e.Salary() > 10000) To
match a valid ==() operator, the string literal may need to be converted to a temporary object of
class type and that construction of the temporary might throw. 5. if (e.Title() == “CEO” ||
e.Salary() > 10000) Same as above. 6. if (e.Title() == “CEO” || e.Salary() > 10000) operator ==()
might throw. 7. if (e.Title() == “CEO” || e.Salary() > 10000) Same as above. CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 187 8. if (e.Title() == “CEO” || e.Salary() > 10000)
Same as above. 9-13 cout << e.First() << “ “ << e.Last() << “ is overpaid” << endl; As per C++
standard, any of the five calls to << operator might throw. 14-15 cout << e.First() << “ “ <<
e.Last() << “ is overpaid” << endl similar to 2 and 3. 16-17 return e.First() + “ “ + e.Last();
similar to 14-15. 18-19 return e.First() + “ “ + e.Last(); similar to 6,7, and 8. 20. return e.First() +
“ “ + e.Last(); similar to 4. Summary: A number of invisible execution paths can exist in simple
code in a language that allows exceptions. Always be exception-aware. Know what code might
emit exceptions. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 188 The Challenge: Can we make this code exception
safe and exception neutral? That is, rewrite it (if needed) so that it works properly in the presence
of an exception and propagates all exceptions to the caller? Exception-Safety: A function is
exception safe if it might throw but do not have any side effects if it does throw and any objects
being used, including temporaries, are exception safe and clean-up there resources when
destroyed. Exception Neutral: A function is said to be exception neutral if it propagates all
exceptions to the caller. Levels of Exception Safety Basic Guarantee: Ensures that temporaries
are destroyed properly and there are no memory leaks. Strong Guarantee: Ensures basic
guarantee as well as there is full-commit or rollback. No-throw Guarantee: Ensure that a
function will not throw. Exception-safety requires either no-throw guarantee or basic and strong
guarantee. Does the function satisfy basic guarantee? Yes. Since the function does not create any
objects, in the presence of an exception, it does not leak any resources. Does the function satisfy
strong guarantee? No. The strong guarantee says that if the function fails because of an
exception, the program state must not be change. This function has two distinct side-effects: an
overpaid message is emitted to cout. A name strings is returned. As far as the second side-
effect is concerned, the function meets the strong guarantee because if an exception occurs the
value will never be returned. As far as the first side-effect is concerned, the function is not
exception safe for two reasons: if exception is thrown after the first part of the message has
been emitted to cout but before the message has been completed (for example if the fourth <<
operator throws), then a partial message was emitted to cout. If the message emitted
successfully but an exception occurs in later in the function (for example during the assembly of
the return value), then a CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 189 message was emitted to cout even though the
function failed because of an exception. It should be complete commit or complete roll-back.
Does the function satisfy no-throw guarantee? No. This is clearly not true as lots of operations in
the function might throw. Strong Guarantee To meet strong guarantee, either both side-effects
are completed or an exception is thrown and neither effect is performed. // First attempt: String
EvaluateSalaryAnadReturnName( Employee e) { String result = e.First() + “ “ + e.Last(); if
(e.Title() == “CEO” || e.Salary() > 10000) { String message = result + “ is overpaid\n”; cout <<
message; } return result; } What happens if the function is called as follows: String theName;
theName = evaluateSalarayAndReturnName(someEmplyee); 1. string copy constructor is
invoked because the result is returned by value. 2. The copy assignment operator is invoked to
copy the result into theName 3. If either copy fails then the function has completed its side-
effects (since the message was completely emitted and the return value was completely
constructed) but the result has been irretrievable lost. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 190 Can we do better and perhaps avoid the problem by
avoiding the copy? // Second attempt: String EvaluateSalaryAnadReturnName( Employee e,
String &r) { String result = e.First() + “ “ + e.Last(); if (e.Title() == “CEO” || e.Salary() > 10000)
{ String message = result + “ is overpaid\n”; cout << message; } r = result; } Looks better but
assignment to r might still fail which leaves us with one side-effect completed and other
incomplete. // Third attempt: auto_ptr<String> EvaluateSalaryAnadReturnName( Employee e)
{ auto_ptr<String> result = new String(e.First() + “ “ + e.Last()); if (e.Title() == “CEO” ||
e.Salary() > 10000) { String message = (*result) + “ is overpaid\n”; cout << message; } return
result;(); // rely on transfer of ownership // this can’t throw } We have effectively hidden all the
work to construct the second side-effect (the return value), while we ensured that it can be safely
returned to the caller using only nonthrowing operation after the first side-effect has completed
the printing of the message. In this case we know that once the function is complete, the return
value will make successfully into the hands of the caller and be correctly cleaned-up in all cases.
This is because the aut_ptr semantics guarantee that If the caller accepts the returned value, the
act of accepting a copy of the auto_ptr causes the caller to take the ownership and if the caller
does not accept the returned value, say by ignoring the CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan explin it very easy from real world examples
ChatGPT said:
ChatGPT
Let's break down this explanation into simpler language using a real-world analogy to
understand exception handling and how to make code exception-safe and exception-neutral.
Ensure partial results (like messages) don’t leave the system in a bad state.
By keeping these principles in mind, you make the program robust, like having a well-prepared
kitchen that can handle any cooking mishap! Let me know if you'd like an even simpler
explanation or further clarification. 😊
You said:
Software Verification and Validation 11.1 Software Testing To understand the concept of
software testing correctly, we need to understand a few related concepts. Software verification
and validation Verification and validation are the processes in which we check a product against
its specifications and the expectations of the users who will be using it. According to a known
software engineering expert Berry Boehm, verification and validation are Verification Does the
product meet system specifications? Have you built the product right? Validation Does the
product meet user expectations? Have you built the right product? It is possible that a software
application may fulfill its specifications but it may deviate from users expectations or their
desired behavior. That means, software is verified but not validated. How is it possible? It is
possible because during the requirements engineering phase, user needs might not have been
captured precisely or the analyst might have missed a major stakeholder in the analysis.
Therefore, it is important to verify as well as validate the software product. 11.2 Defect The
second major and a very important concept is Defect. A defect is a variance from a desired
product attribute. These attributes may involve system specifications well as user expectation.
Anything that may cause customer dissatisfaction, is a defect. Whether these defects are in
system specifications or in the software products, it is essential to point these out and fix.
Therefore software defect is that phenomenon in which software deviates from its expected
behavior. This is non-compliance from the expected behavior with respect to written
specifications or the stakeholder needs. Software and Defect Software and defects go side by
side in the software development life cycle. According to a famous saying by Haliburton, Death
and taxes are inevitable. According to Kernighan: Death, taxes, and bugs are the only certainties
in the life of a programmer. Software and defects cannot be separated, however, it is important to
learn how discovering defects at an appropriate stage improves the software quality. Therefore,
software application needs to be verified as well as validated for a successful deployment.
Software Testing With these concepts, we are in a position to define software testing. Software
testing is the process of examining the software product against its requirements. Thus it is a
process that involves verification of product with respect to its written requirements and
conformance of requirements with user needs. From another perspective, software testing
CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 193 is the process of executing software product on test
data and examining its output vis-àvis the documented behavior. Software testing objective The
correct approach to testing a scientific theory is not to try to verify it, but to seek to refute the
theory. That is to prove that it has errors. (Popper 1965) The goal of testing is to expose latent
defects in a software system before it is put to use. A software tester tries to break the system.
The objective is to show the presence of a defect not the absence of it. Testing cannot show the
absence of a defect. It only increases your confidence in the software. This is because
exhaustive testing of software is not possible – it is simply too expansive and needs virtually
infinite resources. Successful Test From the following sayings, a successful test can be defined
“If you think your task is to find problems then you will look harder for them than if you think
your task is to verify that the program has none” – Myers 1979. “A test is said to be successful if
it discovers an error” – doctor’s analogy. The success of a test depends upon the ability to
discover a bug not in the ability to prove that the software does not have one. As, it is impossible
to check all the different scenarios of a software application, however, we can apply techniques
that can discover potential bugs from the application. Thus a test that helps in discovering a bug
is a successful test. In software testing phase, our emphasis is on discovering all the major bugs
that can be identified by running certain test scenarios. However it is important to keep in mind
that testing activity has certain limitations. Limitations of testing With the help of the following
example, we shall see how difficult it may become to discover a defect from a software
application. Example (adapted from Backhouse) This is a function that compares two strings of
characters stored in an array for equality. The tester who has to test this function has devised the
following test cases in which different combinations of inputs are tried and their expected
behavior is documented. From the look of this table, it seems that almost all major combinations
of inputs have been documented. Inputs and Expected Outputs A b Expected result “cat” “dog”
False “” “” True “hen” “hen” True “hen” “heN” False “ ” “” False CS504-Software Engineering
– I VU _____________________________________________________________________ ©
Copyright Virtual University of Pakistan 194 “” “ball” False “cat” “” False “HEN” “hen” False
“rat” “door” False “ ” “ ” True Results of testing The tester runs all the above-mentioned test
cases and function returns the same results as expected. But it is still not correct. What can be a
problem To analyze the problem, lets look at the code of this string equal routine Code of the
Function bool isStringsEqual(char a[], char b[]) { bool result; if (strlen(a) != strlen(b)) result =
false; else { for (int i =0; i < strlen(a); i++) if (a[i] == b[i]) result = true; else result = false; }
return result; } Analysis of the code It passes all the designated tests but fails for two different
strings of same length ending with the same character. For example, “cut” and “rat” would
results in true which is not correct. The above-mentioned defect signifies a clear limitation of the
testing process in discovering a defect which is not very frequent. However, it should be noted
from this example that a tester cannot generate all possible combinations of test cases to test an
application as the number of scenarios may become exhaustive. Testing limitations In order to
prove that a formula or hypothesis is incorrect all you have to do to show only one example in
which you prove that the formula or theorem is not working. On the other hand, million of
examples can be developed to support the hypothesis but this will not prove that it is correct.
These examples only help you in coming up with a hypothesis but they are not proves by
themselves and they only enhance your comfort level in that particular hypothesis or in this
particular case, in your piece of software. You cannot test a program completely because: o the
domain of the possible inputs is too large to test. o there are too many possible paths through the
program to test. According to Discrete Mathematics CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 195 o To prove that a formula or hypothesis is incorrect
you have to show only one example. o To prove that it is correct any numbers of examples are
insufficient. You have to give a formal proof of its correctness. 11.3 Test Cases and Test Data In
order to test a software application, it is necessary to generate test cases and test data which is
used in the application. Test cases correspond to application functionality such that the tester
writes down steps which should be followed to achieve certain functionality. Thus a test case
involves Input and output specification plus a statement of the function under test. Steps to
perform the function Expected results that the software application produces However, test data
includes inputs that have been devised to test the system
ChatGPT said:
ChatGPT
Summary: Software Verification and Validation & Software Testing
1. Software Verification and Validation
Verification: Checks if the product meets system specifications. Ensures the product is built
right.
Example: Checking code against the design document.
Validation: Ensures the product meets user expectations. Checks if the right product was built.
Example: Ensuring users are satisfied with the final application.
Key Point: A product can be verified but not validated if user needs were misunderstood or
missed during requirements gathering.
2. Defect
Definition: A defect is any variance from the desired behavior or expectations (system
specifications or user needs).
Key Insight: Defects are inevitable in software, but discovering them early improves quality.
3. Software Testing
Definition: The process of checking a software product against its requirements through test data
execution and output examination.
Objectives of Software Testing:
To find defects, not prove their absence.
Increase confidence in software reliability.
Testing is limited because exhaustive testing is impractical.
Successful Test: A test is successful if it discovers a defect. It is more about uncovering errors
than proving perfection.
4. Limitations of Testing
Exhaustive testing is impossible due to:
Large input domains.
Numerous possible paths in the program.
Testing can only show errors but cannot conclusively prove correctness.
Example (String Comparison Bug):
The function isStringsEqual failed for inputs like "cut" and "rat" because it didn’t
consider certain scenarios, even though it passed predefined test cases.
This highlights the limitations of testing in covering all edge cases.
Key Takeaways:
Verification ensures alignment with specs; validation ensures alignment with user needs.
Testing aims to find defects early but cannot guarantee defect-free software.
Test cases and data are essential for systematic testing, but exhaustive testing is impractical.
You said:
Lecture No. 379 11.3 Testing vs. development Testing is an intellectually demanding activity
and has a lifecycle parallel to software development. A common misperception about testing is
that it is not a challenging activity. It should be noted here that the testing demands grip over the
domain and application functionality as is required from an analyst or a designer who has to
develop the application from requirements. As without having an in-depth knowledge about the
system and the requirements from users, a tester cannot write test cases that can verify and
validate software application with respect to documented specifications and user needs. Writing
test cases and generating test data are processes that demand scenariobuilding capabilities. These
activities essentially require destructive instincts in a tester for the purpose of breaking system to
discover loopholes into its functionality. At the time when these two activities are being
performed, merely initial design of the application is completed. Therefore, tester uses his/her
imagination to come up with use patterns of the application that can help him/her in describing
exact steps that should be executed in order to test a particular functionality. Moreover, tester
needs to figure out loose points in the system from where he/she can discover defects. All these
activities are highly imaginative and a tester is supposed to possess above average (if not
excellent) analytical skills. We shall explain the testing activities parallel to development
activities with the help of the following diagram CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 196 Description Functional specification document is
the starting point, base document for both testing and the development Right side boxes
describe the development, whereas, left side boxes explain the testing process Development
team is involved into the analysis, design and coding activities. Whereas, testing team too is
busy in analysis of requirements, for test planning, test cases and test data generation. System
comes into testing after development is completed. Test cases are executed with test data and
actual results (application behavior) are compared with the expected results, Upon discovering
defects, tester generates the bug report and sends it to the development team for fixing.
Development team runs the scenario as described in the bug report and try to reproduce the
defect. If the defect is reproduced in the development environment, the development team
identifies the root cause, fixes it and sends the patch to the testing team along with a bug
resolution report. Testing team incorporates the fix (checking in), runs the same test
case/scenario again and verifies the fix. If problem does not appear again testing team closes
down the defect, otherwise, it is reported again. 11.5 The Developer and Tester Development
Testing Development is a creative activity Testing is a destructive activity Objective of
development is to show that the program works Objective of testing is to show that the program
does not work FS Analysis, Test Planning, Test Case and Test Data Preparation Analysis,
Design, Coding Test Case Execution and Defect Reporting Bug Fixing Activities carried out by
the development team Activities carried out by the testing team CS504-Software Engineering – I
VU _____________________________________________________________________ ©
Copyright Virtual University of Pakistan 197 Scenarios missed or misunderstood during
development analysis would never be tested correctly because the corresponding test cases
would either be missing or would be incorrect. The left side column is related to the
development, and the right side describes the testing. Development is a creative process as
developers have to build the system, whereas, testing is a destructive activity as the goal of a
tester is to break the system to discover the defects. Objective of development is to show that the
program works, objective of testing is to show that program does not work. However, FS is the
base document for both of these activities. Tester analyzes FS with respect to testing the system
whereas; developer analyzes FS with respect to designing and coding the system. If developer
does not understand the FS correctly then he cannot implement and test it right. Thus if the same
person who has developed a system, tests it, chances of carrying the same misunderstanding in
testing will be very high. Therefore, an independent testing can only prove his understanding
wrong. Therefore, it is highly recommended that developer should not try to test his/her own
work. 11.6 Usefulness of testing Objective of testing is to discover and fix as many errors as
possible before the software is put to use. That is before it is shipped to the client and the client
runs it for acceptance. In software development organizations, a rift exists between the
development and the testing teams. Often developers are found questioning about the
significance or even need to have the testing resources in the project teams. Whoever doubts on
the usefulness of the testing team should understand what could happen if the application is
delivered to client without testing? At the best, the client may ask to fix all the defects (free of
cost) he would discover during the acceptance testing. At the worst, probably he would sue the
development firm for damages. However, in practice, clients are often seen complaining about
the deliverables and a couple of defected deliverables are sufficient for breaking the relations
next to the cancellation of contract. Therefore, it should be well preserved among the community
of developers that testers are essential rather inevitable. A good tester has a knack of smelling
errors – just like auditors and it is for the good of the organization not to harm it. 11.7 Testing
and software phases With the help of the following diagram we shall explain different phases of
testing Requirement Specification System Specification System Design Detailed Design Code
and unit test Acceptance Test Plan System Integration Sub-system Integration System Sub-
system Integration Acceptance Production Test Test Plan Test Plan Integration Test Test CS504-
Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 198 Software development process diagram
Description of testing phases • Unit testing – testing individual components independent of other
components. • Module testing – testing a collection of dependent components – a module
encapsulates related components so it can be tested independently. • Subsystem testing – testing
of collection of modules to discover interfacing problems among interacting modules. • System
testing – after integrating subsystems into a system – testing this system as a whole. •
Acceptance test – validation against user expectations. Usually it is done at the client premises. •
Alpha testing – acceptance testing for customized projects, in-house testing for products. • Beta
testing – field testing of product with potential customers who agree to use it and report problem
before system is released for general use In the following two types of testing activities are
discussed. 11.8 Black box testing In this type of testing, a component or system is treated as a
black box and it is tested for the required behavior. This type of testing is not concerned with
how the inputs are transformed into outputs. As the system’s internal implementation details are
not visible to the tester. He gives inputs using an interface that the system provides and tests the
output. If the outputs match with the expected results, system is fine otherwise a defect is found.
11.9 Structural testing (white box) As opposed to black box testing, in structural or white box
testing we look inside the system and evaluate what it consists of and how is it implemented. The
inner of a system consists of design, structure of code and its documentation etc. Therefore, in
white box testing we analyze these internal structures of the program and devise test cases that
can test these structures. Effective testing The objective of testing is to discover the maximum
number of defects with a minimum number of resources before the system is delivered to the
next stage. Now the question arises here how to increase the probability of finding a defect? As,
good testing involves much more than just running the program a few times to see whether it
works or not. A good tester carries out a thorough analysis of the program to devise test cases
that can be used to test the system systematically and effectively. Problem here is how to develop
a representative set of test cases that could test a complete program. That is, selection of a few
test cases from a huge set of possibilities. What should be the sets of inputs that should be used
to test the system effectively and efficiently? String Equal Example For how many equal
strings do I have to test to be in the comfortable zone? For how many unequal strings do I have
to test to be in the comfortable zone? CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 199 When should I say that further testing is unlikely
to discover another error? Testing types To answer these questions, we divide a problem domain
in different classes. These are called Equivalence Classes.
ChatGPT said:
ChatGPT
The provided lecture details focus on the importance of testing in software development and its
role in ensuring the quality of the software. Here’s a concise summary:
Testing Techniques
1. Black Box Testing:
Focuses on input/output without knowing internal structure.
Verifies functionality based on user-facing interfaces.
2. White Box Testing (Structural Testing):
Evaluates internal design, code structure, and documentation.
Creates test cases to analyze system internals.
Effective Testing
Objective: Find as many defects as possible using minimal resources.
Strategies:
Perform thorough program analysis.
Develop a representative set of test cases.
Use Equivalence Classes to identify input sets for efficient testing.
This breakdown helps understand the critical role testing plays in delivering high-quality
software, ensuring that systems meet both technical and user requirements.
You said:
Lecture No. 38 Equivalence Classes or Equivalence Partitioning Two tests are considered to be
equivalent if it is believed that: if one discovers a defect, the other probably will too, and if
one does not discover a defect, the other probably won’t either. Equivalence classes help you in
designing test cases to test the system effectively and efficiently. One should have reasons to
believe that the test cases are equivalent. As for this purpose, one would need to understand the
system and see in how many partitions it can be divided. These partitions should be devised such
that a clear distinction should be marked. Test cases written for one partition should not yield the
same results when run for the second partition as otherwise these two partitions should become
one. However, finding equivalence classes is a subjective process, as two people analyzing a
program will probably come up with different sets of equivalence classes . Equivalence
partitioning guidelines Organize your equivalence classes. Write them in some order, use some
template, sequence, or group them based on their similarities or distinctions. These partitions can
be hierarchical or organized in any other manner. Boundary conditions: determine boundary
conditions. For example, adding in an empty linked list, adding after the last element, adding
before the first element, etc. You should not forget invalid inputs that a user can give to a
system. For example, widgets on a GUI, numeric instead of alphabets, etc. Equivalence
partitioning example In the following example, we shall see how equivalence partitions can be
developed for a string matching function. String matching Organization For equivalence
partitions, we divide the problem in two obvious categories of equal strings and the other one for
unequal strings. Within these equivalent partitions, further partitioning is done. Following is the
description of the equivalence partitions and their test cases Test cases for equivalence partitions
Equal Two equal strings of arbitrary length o All lower case “cat” “cat” o All upper case
“CAT” “CAT” CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 200 o Mixed case “Cat” “Cat” o Numeric values “123”
“123” o Two strings with blanks only “ ” “ ” o Numeric and character mixed “Cat1” “Cat1” o
Strings with special characters “Cat#1” “Cat#1” Two NULL strings “” “” Unequal Strings
Two different equal strings of arbitrary length o Two strings with different length “cat” “mouse”
o Two strings of same length “cat” “dog” Check for case sensitivity o Same strings with
different characters capitalized “Cat” “caT” One string is empty o First is NULL “” “cat” o
Second is NULL “cat” “” 11.10 Basis Code Structures For structural testing it is important to
know about basic coding structures. There are four basic coding structures sequence, if
statement, case statement, and while loop. These four basic structures can be used to express any
type of code. Flow graph notation In analysis and design, you have already seen the flow graph
notation. This is used to describe flow of data or control in an application. However, we do not
use flow graphs to describe decisions. That is, how a branch is taken is not shown in flow graphs.
In the following, we are using flow graph notation to describe different coding structures.
Sequence Sequence depicts programming instructions that do not have branching or any control
information. So we lump together several sequential instructions in one node of the graph. If
Second structural form is the If statement. In the following graph, the first node at the left depicts
the if statement and the two nodes next to the first node correspond to the successful case (if
condition is true) and unsuccessful case (if condition is false) consecutively. The control comes
to the same instruction from either of these intermediate instructions. CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 201 Case In Case statement, control can take either of
several branches (as opposed to only two in If statement.) First node represents the switch
statement (C/C++) and nodes in middle correspond to all different cases. Program can take one
branch and result into the same instruction. While A while loop structure consists of a loop guard
instruction through which the iteration in the loop is controlled. The control keeps iterating in the
loop as long as the loop guard condition is true. It branches to the last instruction when it
becomes false. Flow graph for bubble sort In the following example, code is given for a bubble
sort function. The diagram opposite to the code is the corresponding flow graph model. It
consists of six nodes. Node one is the while loop instruction that contains loop guard and node
six is the ending instruction of the while loop. Node two corresponds to the for loop instructions.
Node three corresponds to the swapping instruction. Nodes five and six correspond to the last
instructions of the if statement and the for loop consecutively. Point to note here is the
assignment of node numbers to program instructions. As, the corresponding flow graph model
would consist of nodes that correspond to instructions which are major decision points in the
code. For example, when this function will be invoked, control will certainly come to the while
loop instruction and at the minimum it will traverse from node one to node six. However, if
control enters into the while loop even for a single iteration, it will traverse through nodes two,
four and five for certain and node three too if it enters for loop and check in the if condition is
true. So all these combinations are to be tested during white box testing. CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 202 Paths Following are possible paths from starting to
the end of this code. Path1: 1-6 Path2: 1-2-3-4-5-1-6 Path3: 1-2-4-5-1-6 Path4: 1-2-4-2-3-4-5-6-
ChatGPT said:
ChatGPT
Here’s a simplified breakdown of Lecture 38, focusing on Equivalence Classes and Basis Code
Structures for your course CS504:
Flow Graphs
A visual representation of program flow, showing nodes (decisions) and edges (control flow).
Example: Bubble Sort
The flow graph has:
Node 1: The while loop guard.
Node 2: The for loop.
Node 3: Swapping elements.
Node 6: End of the while loop.
Paths for Testing:
1. Path1: 1 → 6 (minimum traversal).
2. Path2: 1 → 2 → 3 → 4 → 5 → 1 → 6.
3. Path3: 1 → 2 → 4 → 5 → 1 → 6.
Testing ensures all possible paths through the code are covered.
This overview simplifies the lecture content while covering the key ideas you’ll need for exams.
Let me know if you’d like further clarification!
You said:
Lecture No. 39 White box testing As described in the section above, in white box testing we test
the structure of the program. In this technique the test cases are written in a manner to cover
different possibilities in code. Below are described three coverage schemes. Coverage
Statement Coverage: In this scheme, statements of the code are tested for a successful test that
checks all the statements lying on the path of a successful scenario. Branch Coverage: In this
scheme, all the possible branches of decision structures are tested. Therefore, sequences of
statements following a decision are tested. Path Coverage: In path coverage, all possible paths
of a program from input instruction to the output instruction are tested. An exhaustive list of test
cases is generated and tested against the code. White Box Testing Example With the help of the
following example, we shall see how many test cases are generated for each type of coverage
schemes. sorted = false; while (!sorted) { //1 sorted = true; for (i=0; i < N-1; i++) { //2 if a[i] >
a[i+1] { swap(a[i], a[i+1]); //3 sorted = false; } //4 } //5 } //6 1 2 3 4 6 5 CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 203 Statement coverage: a=1,b=1 If a==b then
statement 2, statement 3 Branch coverage Statement 1: two braches 1-2, 1-3 Test case 1: if a
=1, b=1 then statement 2 Test case 2: if a=1,b=2: then statement 3 Path coverage Same as
branch testing Paths in a program containing loops Now we shall apply the path coverage
scheme on a piece of code that contains a loop statement and see how many test cases can
possibly be developed. Paths The following is an analysis of the above-mentioned code and the
flow diagram. It determines the number of paths against different iterations of the loop. N = 0:
If the control does not enter into the loop then only one path will be traversed. It is 1-5. N=1:
Two different paths can possibly be traversed (depending on condition). o 1-2-4-1-5 o 1-3-4-1-5
N=2: Four possible paths can be traversed. o 1-2-4-1-2-4-1-5 o 1-2-4-1-3-4-1-5 for (i = 0; i <
N; i++) { //1 if (condition1) // do something here //2 else // do something here //3 // something
here //4 } //5 1 2 4 3 5 if (a = = b) //1 c = d; //2 a++; //3 1 2 3 CS504-Software Engineering – I
VU _____________________________________________________________________ ©
Copyright Virtual University of Pakistan 204 o 1-3-4-1-2-4-1-5 o 1-3-4-1-3-4-1-5 Generalizing
the relation between loop variable N and the number of possible paths, for the value of N, 2N
paths are possible o Thus if N = 20 it means more then 1 million paths are possible. Flow graph
of a hypothetical program Following is a flow graph of a hypothetical program whose structure
is such that it consists of two loops, one embedded in the other. Left side of the diagram signifies
the loops where inner loop seem to be containing two if statements. Second loop has a branch
and then program finishes. Simple graph contains 1852 paths with each loop not iterated more
than twice Thus, the number of paths in a program that contains loops tends to infinity. It is
impossible to conduct exhaustive testing of a program that may consist of infinite number of test
cases. The question arises, how many test cases need to be executed in order to test all the major
scenarios in the code at least once? The answer is, calculate the cyclomatic CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 205 complexity of the code. This will give us a number
that corresponds to the total number of test cases that need to be generated in order to test all the
statements and branches in the code at least once. Cyclomatic complexity The concept of
cyclomatic complexity is extremely useful in white box testing when analyzing the relative
complexity of the program to be tested. It revolves around independent paths in a program which
is any path through the program (from start to end) that introduces at least one new set of
processing statements or a new condition. An independent path covers statements and branches
of the code. Cyclomatic complexity is a quantitative measure of the logical complexity of a
program. It defines number of independent paths in the basis set of a program. It provides an
upper bound for the number of tests that must be conducted to ensure that all statements and
branches have been executed at least once. Cyclomatic Complexity, V(G), for a flow graph G is
defined as: V(G) = E - N + 2 Where E is the number of edges and N is the number of nodes in
the flow graph G. Cyclomatic complexity provides us with an upper bound for the number of
independent paths that comprise the basis set. Cyclomatic Complexity of a Sort Procedure
Following is the same bubble sort program that we discussed above. This time we shall calculate
its cyclomatic complexity and see how many test cases are needed to test this function.
Cyclomatic complexity Number of edges = 8 Number of nodes = 6 C(G) = 8-6+2 = 4 Paths
to be tested Path1: 1-6 Sorted = FALSE; while (!sorted) { //1 sorted = TRUE; for (i=0; i < N-1;
i++) { //2 if a[i] > a[i+1] { swap(a[i], a[i+1]); //3 sorted = FALSE; } //4 } //5 } //6 5 1 2 3 4 6
CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 206 Path2: 1-2-3-4-5-1-6 Path3: 1-2-4-5-1-6
Path4: 1-2-4-2-3-4-5-6-1 Infeasible paths Infeasible path is a path through a program which is
never traversed for any input data. Example In the above-mentioned example, there are two
infeasible paths that will never be traversed. Path1: 1-2-3-4-5 Path2: 1-3-5 A good
programming practice is such that minimize infeasible paths to zero. It will reduce the number of
test cases that need to be generated in order to test the application. How can we minimize
infeasible paths, by simply using else part with the if statement and avoid program statements as
given above. Modified code segment Infeasible paths can be analyzed and fixed. There are no
infeasible paths now!
ChatGPT said:
ChatGPT
White box testing is a software testing technique where the internal structure and logic of the
program are tested. It involves creating test cases that focus on different paths, branches, and
logic within the code. Below are key concepts from the lecture along with explanations:
Key Concepts
1. Coverage Schemes in White Box Testing
Statement Coverage: Ensures that every line of code is executed at least once.
Example: If a code block has 3 statements, all three must be executed by test cases.
Branch Coverage: Tests all possible branches in decision-making structures (like if-else).
Example: If there's an if condition, the test must check both true and false outcomes.
Path Coverage: Covers all possible paths from the start to the end of the program.
Example: For loops or nested conditions, every path must be tested.
4. Paths in Loops
With loops, the number of paths increases exponentially.
Example:
For N=2N=2, 2N=42N=4 paths exist.
For N=20N=20, more than 1 million paths exist, making exhaustive testing impractical.
5. Infeasible Paths
Definition: Paths that are logically impossible to execute due to program structure.
Example: In the bubble sort, paths like 1-2-3-4-5 are infeasible.
Solution: Reduce infeasible paths by improving logic, e.g., using else blocks to cover all
conditions.
Summary
White box testing ensures thorough testing of a program's internal logic. Coverage schemes like
statement, branch, and path coverage help identify errors. Cyclomatic complexity quantifies the
testing effort needed. By minimizing infeasible paths, testing becomes more efficient and
effective.
You said:
Lecture No. 40 11.11 Unit testing A software program is made up of units that include
procedures, functions, classes etc. The unit testing process involves the developer in testing of
these units. Unit testing is roughly equivalent to chip-level testing for hardware in which each
chip is tested thoroughly after manufacturing. Similarly, unit testing is done to each module, in
isolation, to verify its behaviour. Typically the unit test will establish some sort of artificial
environment and then invoke routines in the module being tested. It then checks the results
returned against either some known value or against the results from previous runs of the same
test (regression testing). When the modules are assembled we can use the same tests to test the
system as a whole. Software should be tested more like hardware, with Built-in self testing:
such that each unit can be tested independently Internal diagnostics: diagnostics for program
units should be defined. Test harness The emphasis is on built in testability of the program
units from the very beginning where each piece should be tested thoroughly before trying to wire
them together. Unit Testing Principles In unit testing, developers test their own code units
(modules, classes, etc.) during implementation. Normal and boundary inputs against expected
results are tested. Thus unit testing is a great way to test an API. Quantitative Benefits
Repeatable: Unit test cases can be repeated to verify that no unintended side effects have
occurred due to some modification in the code. Bounded: Narrow focus simplifies finding and
fixing defects. Cheaper: Find and fix defects early Qualitative Benefits Assessment-oriented:
Writing the unit test forces us to deal with design issues - cohesion, coupling. Confidence-
building: We know what works at an early stage. Also easier to change when it’s easy to retest.
Testing against the contract (Example) When we write unit tests we want to write test cases that
ensure a given unit honors its contract. This will tell us whether the code meets the contract and
whether the contract means what we think the unit is supposed to do in the program. Contract for
square root routine result = squareRoot(argument); assert (abs (result * result – argument) <
epsilon); CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 208 The above contract tells us what to test: Pass in a
negative argument and ensure that it is rejected Pass in an argument of zero to ensure that it is
accepted (this is a boundary value) Pass in values between zero and the maximum expressible
argument and verify that the difference between the square of the result and the original
argument is less than some value epsilon. When you design a module or even a single routine,
you should design both its contract and the code to test that contract. By designing code to pass a
test that fulfill its contract, you might consider boundary conditions and other issues that you
wouldn't consider otherwise. The best way to fix errors is to avoid them in the first place. By
building the tests before you implement the code you get to try out the interface before you
commit to it. Unit Testing Tips Unit test should be conveniently located For small projects you
can imbed the unit test for a module in the module itself For larger projects you should keep the
tests in the package directory or a /test subdirectory of the package By making the code
accessible to developers you provide them with: Examples of how to use all the functionality of
your module A means to build regression tests to validate any future changes to the code You
can use the main routine with conditional compilation to run your unit tests. 11.12 Defect
removal efficiency Is the ability to remove defects from the application. We shall further
elaborate this idea with the help of the above-mentioned table. The above table depicts data that
was published after analyzing 1500 projects. In these projects, four different types of quality
assurance mechanisms were employed. It is evident from this table that testing alone can only
remove 53% of defects. However, testing and quality assurance mechanisms combine yield up to
65% efficiency. Whereas, if we combine code inspection and testing together, results are up to
75%. Similarly, design inspections and testing yield up to 80%. Moreover, combining design
inspections, quality assurance and testing results are up to 95%. If all four techniques are
combined, results are up to 99.9%. Design Inspection Code Inspection Quality Assurance
Testing Worst 30% 37% 50% 55% 65% 75% 77% 85% 95% Median 40% 53% 65% 70% 80%
87% 90% 97% 99% Best 50% 60% 75% 80% 87% 93% 95% 99% 99.9% CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 209 Inspection and chaotic zone In this diagram, a
chaotic zone has been defined. In fact, if defects are not discovered and fixed at the appropriate
stage, then at the testing and maintenance phases, these defects are piled up. Therefore, a chaotic
zone is formed for the testing and the development teams as the number of defects which are
piled up, destabilize the application and it becomes extremely hard to fix all these defects as
some of these bugs may involve changes in requirements and design. At testing or the
maintenance phases, fixing a defect in requirements or design becomes extremely expensive, as
underlying code will have to be changed as well. If we combine the results of the above two
diagrams, it is evident that testing alone does not suffice. We need to employ inspection
techniques and combine them with testing to increase the effectiveness of defect removal
efficiency. 11.13 Defect origination In inspections the emphasis is on early detection and fixing
of defects from the program. Following are the points in a development life cycle where defects
enter into the program. Requirements Design Coding User documentation Testing itself
can cause defects due to bad fixes Change requests at the maintenance or initial usage time
Chaotic Zone Defect Origination Defect Detection Requirement Design Coding Documentatio n
Testing Maintenance Requirement Design Coding Documentatio n Testing Maintenance CS504-
Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 210 It is important to identify defects and fix them as
near to their point of origination as possible
ChatGPT said:
ChatGPT
Unit Testing in Software Development (Lecture 40)
What is Unit Testing?
Unit testing involves testing individual parts (units) of a program like procedures, functions,
or classes.
It ensures each unit works as expected independently.
Example: Similar to testing a chip in hardware before integrating it into a circuit.
How Unit Testing Works:
1. Create an artificial environment for testing the unit.
2. Test the unit by calling its routines or methods.
3. Compare the results with:
Expected outcomes.
Results from previous tests (for regression testing).
Key Features of Unit Testing:
Built-in self-testing: Each unit is designed to be tested independently.
Internal diagnostics: Helps detect issues in specific program units.
Test harness: Provides the necessary tools for running unit tests.
Principles of Unit Testing:
Developers test their own code modules during implementation.
Test with normal and boundary inputs to verify results.
Effective for testing APIs.
Chaotic Zone:
A chaotic zone forms when defects pile up at later stages (testing/maintenance).
Fixing defects during these stages is:
Costly (affects code and requirements).
Difficult (can destabilize the application).
Inspection Preconditions
For successful inspections, the following preconditions must be met:
A precise specification should be available.
Inspectors must be familiar with organizational standards.
The code should be syntactically correct.
Inspectors should prepare a checklist to systematically find defects.
Inspection Checklists
Inspection checklists target common programming errors and help streamline the defect
identification process. Examples include:
Exception Management Faults:
Have all error conditions been addressed?
Data Faults:
Are all variables initialized before use?
Are array bounds correctly defined and handled?
Control Faults:
Do all loops terminate correctly?
Are conditional statements logically accurate?
Input/Output Faults:
Are all input variables utilized?
Are output variables assigned values before being output?
Interface Faults:
Do function calls have the correct number and types of parameters?
Are shared memory structures consistent across components?
Storage Management Faults:
Are dynamic storage spaces correctly allocated and deallocated?
Are linked structures properly modified?
Static Analyzers
Definition: Automated tools that analyze the source code without executing it.
Purpose: Detect potentially erroneous conditions, such as coding standard violations,
uninitialized variables, and unreachable code.
Usage:
Complements inspections.
Not a replacement for manual inspections but enhances their efficiency.
Summary
Inspections are a proactive approach to finding defects without running the software, focusing
on the structure and logic of the code or design.
Testing ensures the actual execution meets the intended behavior.
Combining both techniques increases the overall defect detection and quality of the software.
Static analyzers provide automated support to inspections, helping to identify issues early in the
development process.
You said:
Lecture No. 42 Debugging 12.1 Debugging As Benjamin Franklin said, “in this world, nothing is
certain but death and taxes.” If you are in the software development business, however, you can
amend that statement. Nothing in life is certain except death, taxes, and software bugs. If you
cryogenically freeze yourself, you can delay death indefinitely. If you move to a country with no
income tax, you can avoid paying taxes by not buying anything. If you develop software,
however, no remedy known to mankind can save you from the horror of software bugs. What is a
Bug? We call them by many names: software defects, software bugs, software problems, and
even software “features.” Whatever you want to call them, they are things the software does that
it is not supposed to do (or, alternatively, something the software doesn’t do that it is supposed
to). Software bugs range from program crashes to returning incorrect information to having
garbled displays. 12.2 A Brief History of Debugging It would appear that as long as there have
been computers, there have been computer bugs. However, this is not exactly true. Even though
the earliest known computer programs contained errors, they were not, at that time, referred to as
“bugs.” It took a lady named Admiral Grace Hopper to actually coin the term “bug.” After
graduating from Vassar in 1928, she went to Yale to receive her master’s degree in mathematics.
After graduating from Yale, she worked at the university as a mathematics professor. Leaving
Yale in 1943, with the onset of World War II, Mrs. Hopper decided to work for the Navy. Mrs.
Hopper’s first assignment was under Commander Howard Aiken at Howard University, working
at the Bureau of Ordinance Computation. She was a programmer on the Mark II, the world’s first
automatically sequenced digital computer. The Mark II was used to determine shooting angles
for the big guns in varying weather conditions during wartime. It was during her term with the
Mark II that Hopper was credited with coining the term “bug” for a computer problem. The first
“bug” was actually a moth, which flew through an open window and into one of the Mark II’s
relays. At that time, physical relays were used in computers, unlike digital components we use
today. The moth shorted out across two contacts, temporarily shutting down the system. The
moth would later be removed (de-bugged?) and pasted into the logbook of the project. From that
point on, if her team was not producing numbers or working on the code, they claimed to be
“debugging” the system. From that auspicious beginning, computer debugging developed into
something of a hitor-miss procedure for quite a few years. Early debugging efforts mostly
centered around either data dumps of the system or used output devices, such as printers and
display lights, to indicate when an error occurred. Programmers would then step through the
code line by line until they could determine the location of the problem. The next step in the
CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 214 evolution of debugging came with the advent of
command-line debuggers. These simple programs were an amazing step forward for the
programmers. Although difficult to use, even at the time, these programs represented the first
real attempt to turn debugging from a hit-or-miss proposition into a reproducible process. Once
the debugger became a part of the programmer’s arsenal, the software world began to consider
other ways that programs could be more easily debugged. Software projects starting getting
bigger, and the same techniques that worked well for small projects no longer worked when the
program reached a certain size. Importance of Debugging As we mentioned earlier in this course,
one of the prime objectives of software engineering is to develop cost effective software
applications. According to a survey, when a software application is in the maintenance phase,
20% of its lifecycle cost is attributed towards the defects which are found in the software
application after installation. Please bear in mind that the maintenance is the phase in which
2/3rd of the overall software cost incurs. Therefore, 20% of the 2/3rd cost is again a huge cost
and we need to understand why this much cost and effort is incurred. In fact, when a software
application is installed and being used, any peculiarity in it can cost a lot of direct and indirect
damages to the organization. A system downtime is the period in which tremendous pressure is
on developers end to fix the problem and make the system running again. In these moments,
every second costs hugs losses to the organization and it becomes vital to find out the bug in the
software application and fix it. Debugging techniques are the only mechanism to reach at the
code that is malfunctioning. In the following subsection, we shall discuss an incident that took
place in 1990 and see how much loss the company had to suffer due to a mere bug in the
software application. 12.4 Problem at AT&T In the telecommunications industry, loss of service
is known as an outage. For most of us, when as outage occurs, we lose our telephone service; we
cannot make calls and we cannot receive calls. Outages are accepted and expected hazards in the
industry. On January 15. 1990, AT&T had a US wide telephone system outage that lasted for
nine hours. The cause was due to a program error in the software that was meant to make the
system more efficient. Eight years later, on April 13, 1998, AT&T suffered another massive
failure in its frame-relay network, which affected ATM machines, credit card transactions and
other business data services. This failure lasted 26 hours. Again, the bug was introduced during a
software upgrade. Description of the Problem The code snippet that caused the outage is
illustrated as follows 1. do { 2. . . . 3. switch (expression) { 4. case 0: { 5. if (some_condition)
{ CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 215 6. . . . 7. break; 8. } else { 9. . . . 10. } 11. . . . 12.
break; 13. } 14. . . . 15. } 16. . . . 17. } while (some_other_condition); In this case the break
statement at line 7 was the culprit. As implemented, if the logical_test on line 5 was successful,
the program should have proceeded to line 6 to execute those statements. When the program
stepped to line 7, the break statement caused the program to exit the “switch” block between line
3 and line 15, and proceeded to execute the codes in line 16. However, this path of execution was
not the intention of the programmer. The programmer intended the break statement in line 7 to
break the if-then clause; so, after the program executed line 7, it was supposed to continue
execution in line 11. AT&T statement about the Problem “We believe that the software design,
development, and testing processes we use are based on solid, quality foundations. All future
releases of software will continue to be religiously tested. We will use the experience we've
gained through this problem to further improve our procedures.” We do not believe we can fault
AT&T’s software development process for the 1990 outage, and we have no reason to believe
that AT&T did not rigorously test its software update. In hindsight, it is easy to say that if the
developers had only tested the software, they would have seen the bug. Or that if they had
performed a code inspection, they would have found the defect. Code inspection might have
helped in this case. However, the only way the code inspection could have uncovered this bug is
if another engineer saw this particular line of code and asked the original programmer if that was
his or her intension. The only reason that the code reviewers might have asked this question is if
they were familiar with the specifications for this particular code block. 12.5Art and Science of
Debugging Debugging is taken as an art but in fact it is a scientific process. As people learn
about different defect types and come across situations in which they have to debug the code,
they develop certain heuristics. Next time they come across a similar situation, they apply those
heuristics and solve the problem in lesser time and with a lesser effort. While discussing the
debugging process we discuss the phenomenon of “you miss the obvious”. When a person writes
a code, he develops certain impression about that code. One can term this impression as a
personal bias that the developer builds towards his creation the CS504-Software Engineering – I
VU _____________________________________________________________________ ©
Copyright Virtual University of Pakistan 216 “code” and when he has to check this code, he can
potentially miss out obvious mistakes due to this impression or bias. Therefore, it is strongly
recommended that in order to reach to a defect in the code, one needs “another pair of eyes”.
That is, start discovering the defect by applying your own heuristics and if you could reach to the
problem, fine, otherwise ask a companion to help you in this process. We shall further elaborate
this idea based on the following example. Program at Bulletin Board Example Following piece
of code was once placed on a bulletin board making an invitation to people to discover the
problem in this code. Please look at this code and discover the problem 1. while (i =0; i < 10; i+
+) { 2. cout << i << newl; 3. } Well if you could not guess it by now, the problem lies with the
syntax of the while loop. This is so obvious that almost everyone forgot to ponder upon when
placed on the bulletin board of a university. The loop is “while” but the syntax used is a “for”
loop. In order to reach such defects, one needs a scientific approach to check and verify the code
methodically. Based on this discussion, we are now in a position to introduce a few classes of
bugs to the reader. This is not an exhaustive list since there could be a number of other classes of
bugs as well but the following classes will help the reader know about some well known bugs
that we usually find in the code
ChatGPT said:
ChatGPT
Debugging is the process of identifying, analyzing, and fixing errors (or "bugs") in a software
program. Lecture 42 on "Debugging" provides insights into the historical background,
importance, and techniques of debugging, as well as real-world examples of how software bugs
can have significant consequences.