0% found this document useful (0 votes)
3 views

cs 504

The document outlines various aspects of store operations, including types of stores, sales processes, and payment methods, as well as the relationships between different components like cashiers and registers. It also discusses software architecture, emphasizing its importance in system design, and introduces various architectural models and styles, such as client-server and layered architecture. Key points include the significance of interaction diagrams in understanding system behavior and the necessity of a well-defined architecture for effective communication and system maintenance.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

cs 504

The document outlines various aspects of store operations, including types of stores, sales processes, and payment methods, as well as the relationships between different components like cashiers and registers. It also discusses software architecture, emphasizing its importance in system design, and introduces various architectural models and styles, such as client-server and layered architecture. Key points include the significance of interaction diagrams in understanding system behavior and the necessity of a well-defined architecture for effective communication and system maintenance.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 70

Types of Things (Structures):

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.

Singleton Pattern (Explained Simply)


Intent:
The Singleton pattern ensures that a class has only one instance and provides a global point of
access to it. It’s used when there should be only one object of a class, like a single connection to
a database or configuration manager.
Applicability:
Use the Singleton pattern when:
1. You need to ensure only one instance of a class.
2. The single instance should be easily accessible globally.
3. The class might need to be extended but should still ensure only one instance.
Structure:
 Singleton: This is the class with a static instance method that provides access to its unique
instance.
 Static uniqueInstance: A static variable that holds the one instance of the class.
Example Implementation:
cpp
Copy code
class Singleton { public : static Singleton* Instance() ; // Method to access the single
instance protected : Singleton (); // Constructor is protected to prevent external
instantiation private : static Singleton* _instance; // Pointer to the unique instance };
Singleton* Singleton::_instance = 0 ; Singleton* Singleton::Instance() { if (_instance == 0 )
{ _instance = new Singleton; // Create instance if not already created } return _instance; }

Façade Pattern (Explained Simply)


Intent:
The Façade pattern provides a simplified interface to a complex subsystem. It acts as a
"wrapper" that hides the complexity of the system and provides an easier way to interact with it.
Applicability:
Use the Façade pattern when:
1. You want to simplify a complex system with many components.
2. You need to minimize the dependencies between subsystems.
3. You want to decouple your system from the clients that use it.
Structure:
 Facade: Provides a simple interface for the client to interact with complex subsystems.
 Subsystem Classes: These are the actual classes that implement the complex functionality. The
façade delegates requests from the client to these subsystem classes.
Example:
In a complex system like a computer, instead of interacting with all the parts (CPU, Memory,
Disk, etc.), the façade could provide a simple interface like turnOnComputer() that takes care
of the detailed interactions.

These three patterns help in solving common design issues:


 Observer: Keeps objects in sync automatically.
 Singleton: Ensures only one instance of a class.
 Façade: Simplifies access to complex systems.
Lecture No. 28: Good Programming Practices and Guidelines
10.1 Maintainable Code
 Maintainability is crucial in software development. Code should not only work but also be easy
to understand and maintain.
 Fowler's Quote: "Any fool can write code that computers can understand, good programmers
write code that humans can understand."
 Three Basic Principles for Maintainability:
 Simplicity: Keep code simple and clear.
 Clarity: Code should be easily understandable.
 Generality/Flexibility: Code should be flexible for future changes.
 Self-Documenting Code:
 The code should explain itself without the need for extra comments or documentation.
 This is achieved by focusing on the following:
 Function Size: Functions should ideally not exceed 20 lines.
 Meaningful Identifier Names: Use descriptive variable names instead of cryptic ones






















 -+












 .
 Modularity: Code should be organized in manageable, reusable modules.
 Performance and Portability Considerations: Ensure that the code works efficiently on
different platforms.
Example of Self-Documenting Code:
 Instead of:
cpp
Copy code
if (x == 0 ) // Allocating a new number
Use:
cpp
Copy code
if (AllocFlag == NEW_NUMBER)
This makes the condition self-explanatory without the need for comments.

10.2 Coding Style Guide


 Consistency is key for making code readable and understandable, especially in large projects
with multiple developers.
 A Coding Style Guide ensures uniformity in the codebase, making it easier for everyone to
contribute.

10.3 Naming Conventions


 Hungarian Notation: A convention where the variable's name includes information about its
type (e.g., strName for a string).
 CamelCase: Used for compound words, with the first letter of each word capitalized, except the
first (e.g., totalAmount, getUserName).
General Naming Conventions:
1. Types: Capitalized (e.g., Line, FilePrefix).
2. Variables: Lowercase, camel case (e.g., line, filePrefix).
3. Constants: Uppercase with underscores (e.g., MAX_ITERATIONS).
4. Methods/Functions: Lowercase, verb-based (e.g., getName(), computeTotalWidth()).
5. Template Types (C++): Single uppercase letter (e.g., T).
6. Global Variables (C++): Use the :: operator for access (e.g., ::mainWindow.open()).
7. Private Class Variables: Suffix with an underscore (e.g., length_).
Specific Java and C++ Naming Guidelines:
1. Use get/set for accessing attributes.
2. Use is for boolean variables (e.g., isVisible, isFinished).
3. Use compute for methods performing intensive operations.
4. Use find for lookup methods.
5. Use initialize for setting up objects.
6. Use List for variables holding multiple items.
7. Use n for variables representing a number of objects (e.g., nPoints).
8. Avoid negated boolean names (e.g., isNotError).
9. Functions should be named after what they return, and procedures should be named after what
they do.
By adhering to these principles and guidelines, code becomes more understandable,
maintainable, and easier to work with in teams.
The lecture you've shared focuses on various best practices for file handling, class and variable
organization, and coding standards in Java and C++. Here's a summary of the key points
covered:
File Handling in C++ and Java
 File Extensions:
 C++ header files should have a .h extension, while source files can use .c++, .cpp, .cc, or .C.
 Class declarations should go in individual header files matching the class name. Implementations
should be in source files. Exception: inline functions, which go in header files.
 File Inclusion:
 Use a header guard in C++ to prevent multiple inclusions with the following structure:
cpp
Copy code
#ifndef MOD_FILENAME_H #define MOD_FILENAME_H // file content #endif

 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.

10.11 Operand Evaluation Order and Side Effects


In C/C++, the order of function evaluation is not guaranteed, which can lead to unexpected
results when functions have side effects (i.e., they modify variables outside their scope). For
example:
cpp
Copy code
int f1(int &x) {// x = x * 2 ; return x + 1 ; } int f2(int &y) { y = y / 2 ; return y - 1 ; } a = 3 ;
b = 4 ; c = f1 (a) + f2 (b);
Since the compiler is free to choose which function ( f1 or f2) gets evaluated first, it can lead to
different results depending on the order of evaluation. In this case:
 a = 6 after f1(a)
 b = 2 after f2(b)
 c = 8
The order in which functions are evaluated can lead to different outcomes, making it important to
be cautious with side effects.
In summary, clarity through modularity, careful use of short-circuit operators, and mindful
operand evaluation order can make your code easier to understand, maintain, and debug.
Messages beyond this point are only visible to you
ChatGPT can make mistakes. Check important info.

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:

Common Coding Mistakes


1. array[i++] = i
This is like trying to shoot two arrows with one hand while battling Alduin. If i starts at 3,
you’re not sure whether you’ll hit target 3 or 4 because you’re not focused. The result depends
on when the bowstring is pulled (i.e., when i increments).
2. array[i++] = array[i++] = x
This is like using two shouts (Fus Ro Dah!) at the same time. It’s unpredictable and dangerous!
You’re trying to set two values in one go, but you can’t tell how many dragons will fly in
because you’re changing i mid-battle.
3. The comma curse (,)
 Example: int i, j = 0;
You think both i and j are initialized to 0, but actually, only j is. It’s like thinking your Steel
Sword and Shield are both enchanted, but only the shield actually is.
 Example: a = b, c = 0;
Here, only c is set to 0, while a and b have random values (garbage). This is like wearing armor
you thought was fully upgraded but finding out only your helmet is!

Guidelines to Avoid Side Effects


1. Don’t use commas (`,”) outside declarations.**
Use them carefully, like managing your shouts properly.
2. Declare and initialize variables separately.
Don’t bundle them—focus on one enchantment at a time.
3. Avoid multiple assignments in one line.
Don’t try to craft all your potions in one go; do them step by step.
4. Be cautious with functions that change variables.
These are like sneaky NPCs who steal your loot while helping you.
5. Avoid functions that change values and return results together.
It’s like an alchemy table that uses up ingredients and gives you an unexpected potion.

Performance & Optimization: The 80/20 Rule


Think of Skyrim’s map. You spend most of your time in only a few locations—Whiterun,
Solitude, etc. The same applies to coding: 80% of the time is spent in 20% of the code. Focus on
optimizing these areas.
Example: Improving Efficiency in Loops
Imagine you’re clearing a dungeon. Your code (loop) is:
cpp
Copy code
for (j = i; j < MAX_FIELD; j++) clear (j);
Here, MAX_FIELD = 200, but usually, you only need to clear 2 or 3 fields. You’re wasting
time swinging your sword at empty barrels!
The improved code is:
cpp
Copy code
for (j = i; j < maxField; j++) clear (j);
Now, you only clear the necessary areas. It’s like knowing there are only 2 Draugrs left and not
clearing the whole dungeon unnecessarily. Much faster!

Skyrim Fun Summary


 Bad coding decisions are like bad battle strategies.
 Focus your efforts on what matters (hotspots in the code, like critical battles in Skyrim).
 Simplify your tasks, clear only what you need, and avoid over-complicating your journey.

Does this make it easier to explain? 😊


You said:
Lecture No. 349 10.13 Portability Many applications need to be ported on to many different
platforms. As we have seen, it is pretty hard to write error free, efficient, and maintainable
software. So, if a major rework is required to port a program written for one environment to
another, it will be probably not come at a low cost. So, we ought to find ways and means by
which we can port applications to other platforms with minimum effort. The key to this lies in
how we write our program. If we are careful during writing code, we can make it portable. On
the other hand if we write code without portability in mind, we may end-up with a code that is
extremely hard to port to other environment. Following is brief guideline that can help you in
writing portable code. Stick to the standard 1. Use ANSI/ISO standard C++ 2. Instead of using
vendor specific language extensions, use STL as much as possible Program in the mainstream
Although C++ standard does not require function prototypes, one should always write them.
double sqrt(); // old style acceptable by ANSI C double sqrt(double); // ANSI – the right
approach Size of data types Sizes of data types cause major portability issues as they vary from
one machine to the other so one should be careful with them. int i, j, k; … j = 20000; k = 30000;
i = j + k; CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 180 // works if int is 4 bytes // what will happen if int is
2 bytes? CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 181 Order of Evaluation As mentioned earlier during
the discussion of side effects, order of evaluation varies from one implementation to other. This
therefore also causes portability issues. We should therefore follow guidelines mentioned in the
side effect discussion. Signedness of char The language does not specify whether char is signed
or unsigned. char c; // between 0 and 255 if unsigned // -128 to 127 if signed c = getchar(); if (c
== EOF) ?? // will fail if it is unsigned It should therefore be written as follows: int c; c =
getchar(); if (c == EOF) Arithmetic or Logical Shift The C/C++ language has not specified
whether right shift >> is arithmetic or logical. In the arithmetic shift sign bit is copied while the
logical shift fills the vacated bits with 0. This obviously reduces portability. Interestingly, Java
has introduced a new operator to handle this issue. >> is used for for arithmetic shift and >>> for
logical shift. Byte Order and Data Exchange The order in which bytes of one word are stored is
hardware dependent. For example in Intel architecture the lowest byte is the most significant
byte while in Motorola architecture the highest byte of a word is the most significant one. This
causes problem when dealing with binary data and we need to be careful while exchanging data
between to heterogeneous machines. One should therefore only use text for data exchange. One
should also be aware of the internationalization issues and hence should not assume ASCII as
well as English. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 182 Alignment The C/C++ language does not define
the alignment of items within structures, classes, an unions. data may be aligned on word or byte
boundaries. For example: struct X { char c; int i; }; address of i could be 2, 4, or 8 from the
beginning of the structure. Therefore, using pointers and then typecasting them to access
individual components will cause all sorts of problems. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 183 Bit Fields Bit fields allow the packing of data in a
structure. This is especially useful when memory or data storage is at a premium. Typical
examples:  Packing several objects into a machine word. e.g. 1 bit flags can be compacted --
Symbol tables in compilers.  Reading external file formats -- non-standard file formats could be
read in. E.g. 9 bit integers. C lets us do this in a structure definition by putting :bit length after
the variable. i.e. struct packed_struct { unsigned int f1:1; unsigned int f2:1; unsigned int f3:1;
unsigned int f4:1; unsigned int type:4; unsigned int funny_int:9; } pack; Here the packed_struct
contains 6 members: Four 1 bit flags f1..f3, a 4 bit type and a 9 bit funny_int. C automatically
packs the above bit fields as compactly as possible, provided that the maximum length of the
field is less than or equal to the integer word length of the computer. If this is not the case then
some compilers may allow memory overlap for the fields whilst other would store the next field
in the next word. Bit fields are a convenient way to express many difficult operations. However,
bit fields do suffer from a lack of portability between platforms:  integers may be signed or
unsigned  Many compilers limit the maximum number of bits in the bit field to the size of an
integer which may be either 16-bit or 32-bit varieties.  Some bit field members are stored left to
right others are stored right to left in memory.  If bit fields too large, next bit field may be stored
consecutively in memory (overlapping the boundary between memory locations) or in the next
word of memory. Bit fields therefore should not be used. explin it very easy from real world
examples
ChatGPT said:
ChatGPT
Simplified Explanation of Portability with Real-Life Examples
Portability means writing software that can easily run on different computers or systems without
major changes. Think of it like designing a backpack that works for kids, teenagers, and adults—
no matter the size or shape. Let’s break it down with real-world examples.

1. Stick to the Standard


 Example: Imagine you’re baking cookies using a family recipe. If you only use common
ingredients (flour, sugar, eggs), anyone can bake the cookies, no matter where they live. But if
you use rare local spices, others may struggle to recreate it.
 In Code: Use standard C++ instead of special features provided by one compiler, so your code
can run on all computers.

2. Program in the Mainstream


 Example: When learning a language, it's better to stick to basic grammar and vocabulary that
everyone uses. If you add old or uncommon words, people may not understand you.
 In Code: Always use updated styles like double sqrt(double); instead of older ones
like double sqrt();.

3. Size of Data Types


 Example: Imagine you’re packing a lunchbox. If the lunchbox is small and you try to fit a giant
sandwich, it won't fit. But a medium or large box works fine.
 In Code: Data sizes like int (2 bytes or 4 bytes) differ between systems. Writing int i = j +
k; may work on one system but fail on another if it can’t handle large numbers.

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.

5. Signed vs. Unsigned Characters


 Example: Imagine storing weather data in a diary. If you only record sunny (+) or rainy (-) days
but forget to note the temperature, you’ll miss details.
 In Code: Use int instead of char for better accuracy, especially for values like EOF (End of
File).

6. Arithmetic vs. Logical Shift


 Example: You’re shifting books on a shelf. If you shift by moving one book at a time, it’s
precise (logical). But if you shift by guessing (arithmetic), some books may fall off.
 In Code: The >> operator can behave differently depending on the system. Java solves this by
having >> for arithmetic shifts and >>> for logical shifts.
7. Byte Order and Data Exchange
 Example: Writing addresses in different countries can confuse delivery services. Some write
"123 Street, City," while others say "City, 123 Street."
 In Code: Different systems store data in different orders. Use text instead of binary for
compatibility.

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.

What is Exception Handling?


Think of a computer program as a bus on a journey. During the journey, unexpected events (like
a flat tire or a traffic jam) can disrupt the trip. These events are like exceptions in programming
—they are problems that need to be handled so the journey (or program) can continue smoothly.
Exception handling is like having a mechanic and tools ready on the bus to fix these issues.
Here's how it works:
1. If something goes wrong (like a flat tire), the bus driver raises a flag (throws an exception) to
alert the mechanic.
2. The mechanic catches the flag (handles the exception) and fixes the issue.
3. Once fixed, the bus can continue its journey.
In programming:
 Try block: The bus is on the road (normal code execution).
 Throw: Something went wrong (raise an error).
 Catch block: The mechanic fixes the problem (handle the error).

Real-World Example of Exception Paths


Let’s say you're cooking a meal and following a recipe. The steps might include:
1. Cutting vegetables.
2. Boiling water.
3. Adding spices.
Non-exceptional paths are when everything goes smoothly:
1. You cut the vegetables, boil water, and add spices without any problem.
Exceptional paths are when unexpected problems arise, like:
1. The knife is blunt while cutting vegetables (exception).
2. There's no water for boiling (exception).
3. You accidentally add the wrong spices (exception).
For each exceptional path, you need to handle the problem:
 Sharpen the knife.
 Get water from another source.
 Replace or fix the spices.

Making Code Exception Safe


When making food, being exception safe means:
1. You don’t waste ingredients if something goes wrong.
2. You clean up any mess made while fixing the problem.
In programming:
 Basic Guarantee: Clean up any temporary mess (e.g., no memory leaks in code).
 Strong Guarantee: If something fails, the state of the kitchen (program) is restored to how it
was before the problem.
 No-Throw Guarantee: Ensure nothing can go wrong at all (very difficult in practice).

Improving the Code


Let’s apply the cooking analogy to the given function. The original code for checking employee
salaries can lead to exceptional paths, like:
 The function tries to calculate the employee’s name but fails halfway.
 It prints part of a message and then throws an error, leaving incomplete output.
The goal is to:
1. Avoid leaving a mess (like half-cooked food or half-printed messages).
2. Ensure everything either completes fully or rolls back to the original state.

Better Code Example


Here’s how you can make your code "exception-safe" using a real-world analogy:
1. Prepare everything before acting: Before cooking, ensure all tools and ingredients are ready.
2. Use safe containers: Use a cutting board to catch spills instead of cutting directly on the table.
3. Handle failures gracefully: If a knife breaks, use a spare knife instead of giving up.
In the code:
 Use objects or techniques that clean themselves up (like auto_ptr or smart pointers in C++).

 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.

5. Test Cases and Test Data


 Test Case: Specifies the steps to test a specific function, including inputs, outputs, and expected
results.
 Example: Testing a login function with valid and invalid credentials.
 Test Data: The actual inputs used to test the application.

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 vs. Development


 Testing is demanding: It requires domain knowledge, scenario-building skills, and a deep
understanding of requirements.
 Parallel lifecycle: Testing activities run alongside development.
 Tester’s tasks:
 Analyze requirements.
 Plan tests and write test cases.
 Execute test cases and report defects.
 Process:
 Development fixes defects found in testing.
 Testers verify the fixes until issues are resolved.
Key distinction:
 Development is creative: Aims to build a working program.
 Testing is destructive: Aims to find flaws in the program.
Phases of Testing
1. Unit Testing: Test individual components.
2. Module Testing: Test related components together.
3. Subsystem Testing: Test interactions among modules.
4. System Testing: Test the complete system as a whole.
5. Acceptance Testing: Validate the system against user expectations.
6. Alpha Testing: Internal testing before product release.
7. Beta Testing: Field testing with selected customers.

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.

Testing vs. Development Summary


 Developers build the system; testers break it.
 Developers aim to show functionality; testers aim to uncover failures.
 Independent testing avoids biases or misunderstandings by developers.

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:

Equivalence Classes / Equivalence Partitioning


Definition:
Equivalence Partitioning is a testing technique where test cases are grouped into partitions (or
equivalence classes). These groups are designed such that tests within the same group are
expected to yield similar results.
Key Points:
1. Purpose: To reduce the number of test cases while still covering as many scenarios as possible.
2. Criteria for Equivalence:
 If one test case finds a defect, the others in the same group likely will too.
 If one test case does not find a defect, the others likely won’t either.
Steps to Create Equivalence Classes:
 Analyze the System: Break it into partitions where each partition represents a unique case.
 Design Test Cases: Each partition must have distinct outputs. If two partitions behave the same,
combine them.
 Consider Invalid Inputs: Always include invalid cases (e.g., invalid data types or out-of-range
values).
Guidelines for Equivalence Partitioning:
1. Organize Classes: Group similar test cases hierarchically or sequentially.
2. Boundary Conditions: Test edge cases, such as empty lists or maximum values.
3. Invalid Inputs: Include test cases for unexpected inputs, like entering numbers in a text field.
Example: String Matching
For a function comparing two strings, equivalence partitions could be:
1. Equal Strings:
 Lowercase: "cat" vs. "cat"
 Uppercase: "CAT" vs. "CAT"
 Mixed: "Cat" vs. "Cat"
 Numeric: "123" vs. "123"
2. Unequal Strings:
 Different lengths: "cat" vs. "mouse"
 Same length, different values: "cat" vs. "dog"
 Case sensitivity: "Cat" vs. "caT"

Basis Code Structures


Understanding code flow and its control structures is crucial for white-box testing.
Basic Structures:
1. Sequence:
 A series of instructions executed one after another.
2. If Statement:
 Two branches: one for the true condition, one for the false condition.
3. Case Statement:
 Multiple branches depending on the value of a variable (like a switch statement in C++).
4. While Loop:
 Repeats execution as long as the loop condition is true.

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.

2. Example of White Box Testing


Consider a bubble sort code snippet:
cpp
Copy code
sorted = false ; while (!sorted) { sorted = true ; for (i = 0 ; i < N - 1 ; i++) { if (a[i] >
a[i+ 1 ]) { swap (a[i], a[i+ 1 ]); sorted = false ; } } }
 Statement Coverage: All lines are executed, including swaps and loop conditions.
 Branch Coverage:
 If a[i] > a[i+1] is true → perform swap.
 If a[i] > a[i+1] is false → no swap.
 Path Coverage:
 Multiple paths depend on how the loop and conditions execute.
 Example: With N iterations, paths like 1-2-4-5-1-6 or 1-2-3-4-5-1-6 occur.
3. Cyclomatic Complexity
 Definition: Measures the complexity of a program based on the control flow graph.
 Formula: V(G)=E−N+2V(G)=E−N+2
 EE: Number of edges (connections between nodes).
 NN: Number of nodes (decision points or statements).
 Example: For the bubble sort program:
 Edges (EE) = 8, Nodes (NN) = 6
 V(G)=8−6+2=4V(G)=8−6+2=4
 This means 4 independent test cases are required.

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.

Benefits of Unit Testing:


Quantitative:
 Repeatable: Can verify changes without introducing side effects.
 Bounded: Focused tests make defect detection and fixing easier.
 Cheaper: Early detection saves cost and effort.
Qualitative:
 Design improvement: Forces developers to address issues like cohesion and coupling.
 Confidence: Helps developers trust the functionality early.
Testing Against the Contract:
 A contract specifies what a unit should do.
 Example: For a square root routine:
cpp
Copy code
result = squareRoot (argument); assert ( abs (result * result - argument) < epsilon);

 Test for negative arguments (rejected).


 Test for boundary value (e.g., 0).
 Test valid arguments within range.

Unit Testing Tips:


1. Location:
 Small projects: Embed unit tests in the module.
 Large projects: Place tests in a /test directory.
2. Benefits:
 Provides examples for developers.
 Enables regression testing for future updates.
3. Use the main routine for unit test execution.

Defect Removal Efficiency


 Measures how effectively defects are identified and resolved.
 Insights from Data:
 Testing alone removes 53% of defects.
 Combining design inspections, code inspections, and testing achieves up to 99.9% defect
removal.
Technique Combination Efficiency (%)
Testing only 53%
Testing + QA 65%
Code Inspection + Testing 75%
Design Inspection + Testing 80%
All combined 99.9%

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).

Defect Origination and Detection


 Defects enter at stages like:
 Requirements, Design, Coding, Documentation, Testing, Maintenance.
 Early detection and fixing prevent the chaotic zone.
Key Insight:
 Combine inspection techniques (design/code) with testing for efficient defect removal.
You said:
Lecture No. 41 11.14 Inspection versus Testing Inspections and testing are complementary and
not opposing verification techniques. Both should be used during the verification and validation
process. Inspections can check conformance with a specification but not conformance with the
customer’s real requirements. Inspections cannot check non-functional characteristics such as
performance, usability, etc. Inspection does not require execution of program and they maybe
used before implementation. Many different defects may be discovered in a single inspection. In
testing, one defect may mask another so several executions are required. For inspections,
checklists are prepared that contain information regarding defects. Reuse domain and
programming knowledge of the viewers likely to help in preparing these checklists. Inspections
involve people examining the source representation with the aim of discovering anomalies and
defects. Inspections may be applied to any representation of the system (requirements, design,
test data, etc.) Thus inspections are a very effective technique for discovering errors in a software
program. Inspection pre-conditions A precise specification must be available before inspections.
Team members must be familiar with the organization standards. In addition to it, syntactically
correct code must be available to the inspectors. Inspectors should prepare a checklist that can
help them during the inspection process. Inspection checklists. Checklist of common errors in a
program should be developed and used to drive the inspection process. These error checklists are
programming language dependent such that the inspector has to analyze major constructs of the
programming language and develop checklists to verify code that is written using these
checklists. For example, in a language of weak type checking, one can expect a number of
peculiarities in code that should be verified. So the corresponding checklist can be larger. Other
example of programming language dependant defects are defects in variable initialization,
constant naming, loop termination, array bounds, etc. Inspection Checklist Following is an
example of an inspection checklist. Exception management faults  Have all possible error
conditions been taken into account? Fault Class  Inspection Check CS504-Software Engineering
– I VU _____________________________________________________________________ ©
Copyright Virtual University of Pakistan 211 Data faults  Are all program variables initialized
before their values are used?  Have all constants been named?  Should the lower bound of
arrays be 0, 1, or something else?  Should the upper bound of arrays be size or size -1?  If
character strings are used, is a delimiter explicitly assigned? Control faults  For each conditional
statement, is the condition correct?  Is each loop certain to terminate?  Are compound
statements correctly bracketed?  In case statements, are all possible cases accounted for?
Input/Output faults  Are all input variables used?  Are all output variables assigned a value
before they are output? Interface faults  Do all function and procedure calls have correct number
of parameters?  Do formal and actual parameters types match?  Are the parameters in right
order?  If components access shared memory, do they have the same model of shared memory
structure? Storage management faults  If a linked structure is modified, have all links been
correctly assigned?  If dynamic storage is used, has space been allocated correctly?  Is space
explicitly de-allocated after it is no longer required? In the checklist mentioned above, a number
of fault classes have been specified and their corresponding inspection checks are described in
the column at the right side. This type of checklist helps an inspector to look for specific defects
in the program. These inspection checks are the outcomes of experience that the inspector has
gained out of developing or testing similar programs. 11.15 Static analyzers Static analyzers are
software tools for source text processing. They parse the program text and try to discover
potentially erroneous conditions and bring these to the attention of the verification and validation
team. These tools are very effective as an aid to inspections. But these are supplement to but not
a replacement for inspections. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakista
ChatGPT said:
ChatGPT
Lecture No. 41: Key Points on Inspection versus Testing and Static
Analyzers
Inspection versus Testing
Inspections and testing are two distinct yet complementary methods used for software
verification and validation:
1. Inspection:
 Focuses on verifying the source representation of a program (e.g., requirements, design, test
data).
 Does not require program execution.
 Effective for detecting defects early in the software lifecycle.
 Can detect multiple defects in a single review.
 Inspections are guided by checklists prepared based on experience and domain knowledge.
 Useful for ensuring conformance with specifications but cannot validate real-world requirements
or non-functional aspects like usability or performance.
2. Testing:
 Requires program execution to verify that the software behaves as expected.
 Each defect might require multiple test executions because one defect can mask others.
 Focuses on ensuring the software meets both functional and non-functional requirements.

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.

Key Points from the Lecture


1. Definition of a Bug
A bug is any unintended behavior in a program:
 Something the software does that it shouldn't.
 Something the software doesn’t do that it should.
2. History of Debugging
 The term "bug" was popularized by Admiral Grace Hopper during her work on the Mark II
computer. The first recorded "bug" was an actual moth causing a malfunction.
 Early debugging methods included data dumps, output devices like printers, and manual code
inspection.
 The introduction of command-line debuggers revolutionized the process, making debugging
systematic rather than guesswork.
3. Importance of Debugging
 Debugging is crucial to minimize the cost of fixing bugs, which can account for a significant
portion of software maintenance costs.
 Bugs can lead to:
 Financial losses.
 Service outages.
 Damage to an organization’s reputation.
4. Real-World Example: AT&T Outages
 1990 Outage: A programming error in a break statement caused a 9-hour nationwide telephone
outage in the U.S.
 1998 Outage: A bug in a software upgrade disrupted AT&T's frame-relay network for 26 hours,
affecting business transactions like ATM services.
5. Debugging Techniques
 Heuristics and Experience: Debugging improves with practice and exposure to different bug
types.
 Code Inspection: A fresh pair of eyes can help identify errors missed due to developer bias.
 Scientific Approach: Debug systematically and verify the code step by step to identify potential
issues.
6. Example Bugs
 Syntax Error: A mismatched loop structure in the "while" loop example:
cpp
Copy code
while (i = 0 ; i < 10 ; i++) { // Incorrect syntax cout << i << newl; }
The problem: The syntax mixes elements of a "for" loop in a "while" loop.
Debugging as an Art and Science
 Debugging is both creative and methodical.
 Using tools and techniques like debuggers, logging, and peer reviews can make the process more
effective.
 Recognizing personal bias and involving others in the debugging process can help identify errors
more efficiently.
Understanding and improving debugging practices can significantly enhance software reliability
and reduce costs associated with fixing bugs.
You said:
Lecture No. 43 12.6 Bug Classes Memory and resource leak A memory leak bug is one in which
memory is somehow allocated from either the operating system or an internal memory "pool",
but never deallocated when the memory is finished being used. Symptoms  System slowdowns
 Crashes that occur "randomly" over a long period of time Example 1 Let’s take a look at a
simple memory leak error that can occur trivially in a C program. This type of code is found in
hundreds of programs across the programming spectrum. It illustrates the simplest possible case
of a memory leak, which is when memory is allocated and not deallocated in all cases. This
particular form of the bug is the most frustrating because the memory is usually deallocated, but
not always CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 217 char *buffer = new char[kMaxBufferSize+1];
memset(buffer,0,kMaxBufferSize+1); // Do some stuff to fill and work with the character buffer
if (IsError(nCondition)) // Did we get an error in the processing piece? { Message(("An error
occured. Skipping final stage")); return FailureCode; } // Final stage of code // Free up all
allocated memory delete buffer; return okCode; Note that in many cases, this code works
perfectly. If no error occurs in the processing stage (which is the norm) the memory is freed up
properly. If, however, the processing stage encounters an error, the memory is not freed up and a
leak occurs. Example 2 The following code snippet from some old C++ code contains a slightly
more insidious bug. It illustrates the point well. Class Foo { private: int nStringLength; char
*sString; public: Foo() { nStringLength = 20; //Default sString = new char[nStringLength + 1]; }
~Foo() { delete sString; } void SetString(const char *inString) { CS504-Software Engineering –
I VU _____________________________________________________________________ ©
Copyright Virtual University of Pakistan 218 sString = new char [strlen(inString+1)]; if(inString
== NULL) return; strncpy(sString, inString, strlen(inString)); nStringLength = strlen(inString)
+1; } }; Let’s discuss what happens. In most cases, the Foo object is created and nothing bad
happens. If, however, you call the SetString method, all bets are off. The previously allocated
string is overwritten by the new allocation, and the old allocated memory goes nowhere. We
have an instant memory leak. Worse, we can do this multiple times if we accidentally call the
method with a NULL string because it first allocates the block, and then checks to see if the
input string was correct. Don’t do things like this. If you see an allocation in a class, check to see
if the string can be allocated before it gets to that point. Logical Errors A logical error occurs
when the code is syntactically correct but does not do what you expect it to do. Symptoms  The
code is misbehaving in a way that isn't easily explained.  The program doesn't crash, but the
flow of the program takes odd branches through the code.  Results are the opposite of what is
expected.  Output looks strange, but has no obvious symptoms of corruption. Example // Make
sure that the input is valid. For this value, valid ranges are 1-10 and 15-20 if((input >= 1 &&
input <= 10) && (input >= 15 && input <= 20)) { // Do something for valid case } else { // Do
something for invalid case } In order for the code to enter the valid case, the number must be
between 1 and 10 and be between 15 and 20. If a number is between 1 and 10, how can it
possibly be between 15 and 20 as well? Seems unlikely, doesn’t it? This is a typical logical error.
Coding errors CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 219 A coding error is a simple problem in writing the
code. IT can be a failure to check error returns, a failure to check for certain valid conditions, or
a failure to take into account other parts of the system. Yet another form of a coding error is
incorrect parameter passing and invalid return type coercion. Symptoms  Unexpected errors in
black box testing.  The errors that unexpectedly occur are usually caused by coding errors. 
Compiler warnings.  Coding errors are usually caused by lack of attention to details. Example In
the following example, a function accepts an input integer and converts it into a string that
contains that integer in its word representation. void convertToString(int InInteger, char*
OutString, int* OutLength) { switch(InInteger){ case 1: OutString = "One";OutLength = 3;
break; case 2: OutString = "Two";OutLength = 3; break; case 3: OutString = "Three";OutLength
= 5; break; case 4: OutString = "Four";OutLength = 4; break; case 5: OutString =
"Five";OutLength = 4; break; case 6: OutString = "Six";OutLength = 3; break; case 7: OutString
= "Seven";OutLength = 5; break; case 8: OutString = "Eight";OutLength = 5; break; case 9:
OutString = "Nine";OutLength = 4; break; } } There are a few things to notice in the preceding
code. The ConvertToString function does not handle all cases of inputs, which becomes very
obvious you pass a zero value. Worse, the function does not initialize the output variables,
leaving them at whatever they happened to be when they came into the function. This isn’t a
problem for the output string, necessarily, but it will become a serious issue for the output length.
Memory over-runs CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 220 a memory overrun occurs when you use memory
that does not belong to you. This can be caused by overstepping an array boundary or by copying
a string that is too big for the block of memory it is defined to hold. Memory overruns were once
extremely common in the programming world because of the inability to tell what the actual size
of something really was. Symptoms  Program crashes quite regularly after a given routine is
called, that routine should be examined for a possible overrun condition.  If the routine in
question does not appear to have any such problem the most likely cause is that another routine,
called in the prior sequence, has already trashed variables or memory blocks.  Checking the
trace log of the called routines leading up to one with the problem will often show up the error.
Example This particular example shows not only a memory overrun, but also how most
programmers “fix” problems in an application. The ZeroArray function steps all over the array
boundaries by initializing 100 separate entries. The problem is that that particular array only has
50 slots available in its allocation. What happens at that point is that the function goes past the
end of the array and starts to walk on things beyond its control. const kMaxEntries = 50; int
gArray[kMaxEntries]; char szDummyBuffer[256]; int nState = 10; int ZeroArray (int *pArray) {
for (inti=0;i<100;++i) pArray[i] = 0; } Loop Errors  Loop errors break down into several
different subtypes.  They occur around a loop construct in a program.  Infinite loops, off-by-
one loops, and improperly exited loops. Symptoms  If your program simply locks up, repeatedly
displays the same data over and over, or infinitely displays the same message box, you should
immediately suspect an infinite loop error.  Off-by-one loop errors are quite often seen in
processes that perform calculations.  If a hand calculation shows that the total or final sum is
incorrect by the last data point, you can quickly surmise that an off-by-one loop error is to blame.
 Likewise, if you were using graphics software and saw all of the points on the screen, but the
last two were unconnected, you would suspect an off-by-one error. CS504-Software Engineering
– I VU _____________________________________________________________________ ©
Copyright Virtual University of Pakistan 221  Watching for a process that terminates
unexpectedly when it should have continued. Example bool doneFlag; doneFlag = false; while(!
doneFlag){ ... if( impossibleCondition ) doneFlag = true; } The preceding code fragment
contains an indirect looping error such that the loop will continue until the impossibleCondition
becomes true which is impossible to happen. int anArray[50]; int i; i = 50; while(i >= 0)
{ anArray[i] = 0; i = i - 1; } In this example, the programmer was trying to be smart. He worked
his way backward though the array, assuming that this way there would be no chance of an error.
Of course, if you examine the loop, you will find that it performed 51 times. There are only fifty
elements in the array. This will certainly lead to a problem later on in the program, if it doesn’t
first trigger an immediate error from the system. int nIndex = 0; for(int i=0; i<kMaxIterations; +
+i) { while (nIndex < 20) { ComputeSomething(i*20 + nIndex); nIndex ++; } } The final
situation is an improper exit condition for a loop. This is most easily illustrated using a set of
nested loops. In the above example, we are trying to compute something within the inner loop for
some number of iterations. The code appears to do what we said it should do. It computes
whatever it is we are computing in the inner loop for each iteration of the outer loop, 20 times.
The problem, however, is that the exit condition simply says that nIndex should be less than 20.
The first time through the outer loop, this variable will become 21, and the inner loop will exit.
This is correct, and exactly the way CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 222 you would expect this to happen. The problem,
however, is that the next time through the loop, the inner loop will not be executed at all. Pointer
errors  A pointer error is any case where something is being used as an indirect pointer to
another item.  Uninitialized pointers: These are pointers that are used to point at something, but
we fail to ever assign them something to point at.  Deleted pointers, which continue to be used.
 An Invalid pointer is something that is pointing to a valid block of memory, but that memory
does not contain the data you expect it to. Symptoms  The program usually crashes or behaves
in an unpredictable and baffling way.  You will generally observe stack corruptions, failure to
allocate memory, and odd changing of variable values.  Changing a single line of code can
change where the problem occurs.  If the problem "goes away" when you place a print
statement or new variable into the code that you suspect contains the problem. Example Let’s
take a look at one of the most common forms of the pointer error, using a pointer after it has
been deleted. As you can see by the following example, it is not always clear when you are doing
this incorrectly: void cleanup_function(char *ptr) { SaveToDisk(ptr); delete ptr; } int func()
{ char *s = new char[80]; cleanup_function(s); delete s; } In this example, the programmer
meant to be neat and tidy. He probably wrote the code originally to allocate the pointer at the top
of this function, and then by habit put in a deallocation at the bottom of the function. This is,
after all, good programming practice to avoid memory leaks. The problem is introduced with
th3e cleanup_function routine. This function was probably written at a later time by another
developer and might have been used to ensure that all allocated pointers were stored to the
permanent drive and then freed up to avoid any possible leaks. In fact, the code in question
comes from a set of functions that was reused from a memory pool system that saved its data to
disk before destroying the pointer, so that another pointer could be retrieved from the persistent
pool. CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 223 The problem is, the reuse did not take into account
the existing system. This problem is common when reusing only parts of a system. When the
second delete occurs at the bottom of func(), the results are unpredictable. At best, the delete
function recognizes that this pointer has been deleted already and doesn’t do anything. At worst,
memory is corrupted and you are left with a memory crash somewhat later in the program, which
will be very difficult to find and fix. Boolean bugs Boolean bugs occur because the mathematical
precision of Boolean algebra has virtually nothing to do with equivalent English words. When
we say "and", we really mean the boolean "or" and vice versa. Symptoms  When the program
does exactly the opposite of what you expect it to. For example, you might have thought you
needed to select only one entry from a list in order to proceed. Instead, the program will not
continue until you select more than one. Worse, it keeps telling you to select only one value. 
For true/false problems, you will usually see some sort of debug output indicating an error in a
function, only to see the calling function proceed as though the problem had not occurred.
Examples The following code contains a simple example of a function that returns a
counterintuitive Boolean value and shows how that value leads to problems in the rest of the
application code. This case performs an action and indicates to the user whether or not the action
succeeded. int DoSomeAction(int InputNum) { if(InputNum < 1 || InputNum > 10)
DoSomeAction = 0; else { PerformTheAction(InputNum); DoSomeAction = NumberOfAction +
1; NumberOfAction = NumberOfAction + 1; } return DoSomeAction; } After looking at the
function, ask yourself this – What is the return value in the case of success? What would it be in
the case of failure? How would you know this by simply glancing at the function declaration? int
Value; int Ret; Value = 11; CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 224 Ret = DoSomeAction(Value); if(Ret) cout <<Error
in DoSomeAction<<; If you were debugging this application, you might start out by wondering
why it didn’t work. Further investigation would show that the error statement is never triggered
in spite of the fact that the routine did, in fact, encounter an error.. Why did this happen? It
happened because the routine didn’t return nonzero value for failure, it returned nonzero for
success. This Boolean value was not intuitive given the condition of the function and led to a
poor assumption by the programmer who wrote the code that used it.
ChatGPT said:
ChatGPT
Lecture No. 43 discusses several types of bugs in software, focusing on memory and resource
leaks, logical errors, coding errors, memory overruns, loop errors, pointer errors, and Boolean
bugs. Here's a summary:
1. Memory and Resource Leak:
 A memory leak happens when memory is allocated but not deallocated, leading to system
slowdowns or random crashes.
 Example: In the code, if an error occurs during processing, memory allocated for a buffer is not
freed, causing a memory leak.
2. Logical Errors:
 These errors occur when code is syntactically correct but behaves unexpectedly.
 Example: A condition that checks if a number is between 1-10 and 15-20, which is logically
impossible.
3. Coding Errors:
 Simple mistakes in code writing, such as incorrect error handling or passing incorrect
parameters.
 Example: A function to convert integers to strings doesn't handle all cases, such as when the
input is 0.
4. Memory Overruns:
 This occurs when you access memory that you shouldn't, like exceeding the bounds of an array.
 Example: A function tries to initialize more elements than the array can hold, causing a memory
overrun.
5. Loop Errors:
 Common loop errors include infinite loops, off-by-one errors, and improper loop exits.
 Example: A loop that runs more times than the array size or one that doesn't exit properly.
6. Pointer Errors:
 These happen when a pointer is used incorrectly, such as using an uninitialized or deleted
pointer.
 Example: Deleting a pointer twice, causing unpredictable behavior or memory corruption.
7. Boolean Bugs:
 Boolean bugs occur when the logic of a condition is misinterpreted. For instance, confusing
"and" with "or" leads to unexpected results.
 Example: A function that returns success for invalid input and failure for valid input, causing
confusion for the rest of the application.
These are important bugs to watch out for during software development, as they can lead to
unpredictable behavior, crashes, or resource inefficiencies.
You said:
Lecture No. 44 12.7 Holistic approach Holistic means  Emphasizing the importance of the
whole and the interdependence of its parts.  Concerned with wholes rather than analysis or
separation into parts What this meant is that a holistic approach focuses on the entire system
rather than whatever piece appears to be broken. Holistic medicine, for example, concerns itself
with the state of the body as a whole, not the disease that is currently attacking it. Similarly,
programmers and debuggers must understand that you cannot treat the symptoms of a problem,
you must focus on the application system as a whole. Now that you understand what holistic
means, how do you apply holistic concepts to debugging the software programs and procedures?
Let’s start by thinking about what emphasizing the whole over the individual pieces means in
debugging. When you are debugging an application, you often simply look at the problem
reported by the user and how you can make that problem go away. As a simple example,
consider the case where the program mysteriously crashes at a particular point in the code each
and every time the program is run. The point at which the code crashes makes no sense at all. For
example, in C++, you might have a member function of a class that reads: void SetX(int X)
{ mX = X; } The program always crashes on the line that assigns X to the member variable
mX(mX = X). Looking at this code snippet, we see very few valid ways in which the program
could be encountering a problem at this point. Oddly, we might discover that inserting a message
statement makes the problem stop occurring when the indicated steps are followed for
reproducing the problem. Is the problem fixed? CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 225 Most experienced programmers, managers, or
debuggers would say that the problem is not fixed, although few could tell you exactly why that
is the case. The fact is, in this case, we are directly treating the symptoms (the program crashing)
rather than looking into the actual problem and ignoring the overall problem is endemic in our
industry. 12.8 The debugging process In normal circumstances, you will have a user description
of the problem. This description might have been given to you directly by the user, or it might
have been gathered by a customer support person or other no technical person, In any event, this
data has to be considered suspiciously until you can get a first hand description of what really
happened. First hand accounts of the problem are always useful, so be sure to write down exactly
what you are told. That way, you can compare several accounts of the same problem and look for
similarities. Consider, for example, the following accounts of a reported bug in a system: 1. "I
started by trying to setup a Favorites list. I first went to the home page. Then, I selected Favorites
from the menu on the right. I scrolled down to the third entry and pressed Enter on the keyboard.
Then I moved to the fourth entry on the submenu and clicked it with the mouse. Finally, I
entered my name as Irving, clicked on OK, and the program crashed." 2. "I went to the
programming menu and selected the New menu option. I then clicked on the Create Object menu
entry and entered the name HouseObject for the object name field. Then I clicked OK, and the
program crashed." 3. "I selected the New Project menu option, and then clicked on the icon that
looks like a Gear. I entered the name Rudolph for the project name, and then clicked OK. The
program crashed."  After all, they appear to relate to three different sections of the program.
Most people, when reporting bugs, focus on what they think was the important step in the
process.  Filter out the unimportant information and see what each case really has in common.
First, each user clicked on the OK button to finalize the process, and the program crashed.  It
might seem likely, therefore, that the program OK handler contains a fatal flaw.  A bit of
experimentation will show whether this is the case or not.  When we examine the statements of
the witnesses, we notice that each was working with a menu.  We can see that each person used
both the keyboard and the mouse to select from the menus before clicking the OK button. "I
selected Favorites from the menu on the right. I scrolled down to the third entry and pressed
Enter on the keyboard. Then I moved to the fourth entry on the submenu and CS504-Software
Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 226 clicked it with the mouse." "... selected the New
menu option. I then clicked on the Create Object menu entry ..." "I selected the New Project
menu option, and then clicked on the icon that looks ..."  We can infer, therefore, that the user in
this case used both the keyboard and mouse. The next logical place to look is in the menu
handler to see whether it deals with mouse and keyboard entries differently.  The final clue is in
the third entry, where the user did not select anything from the menu with the mouse, but instead
clicked on an icon.  While looking at the code, I would try to see what happens when the
program deals with a combination of keyboard and menu entry.  It is likely, given the user
discussion, that you will find the root of the problem in this area.  After finding such a common
bug, it is likely that the fix will repair a whole lot of problems at once  This is a debugger's
dream. Good clues, Easy Bugs Get A Stack Trace In the debugging process a stack trace is a
very useful tool. Following stack trace information may help in debugging process.  Source line
numbers in stack trace is the single, most useful piece of debugging information.  After that,
values of arguments are important o Are the values improbable (zero, very large, negative,
character strings with non-alphabetic characters?  Debuggers can be used to display values of
local or global variables. o These give additional information about what went wrong. Non-
reproducible bugs  Bugs that won't "stand still" (almost random) are the most difficult to deal
with.  Randomness itself, however, is information.  Are all variables initialized? (random data
in variables could affect output).  Does bug disappeared when debugging code is inserted?
Memory allocation (malloc) problems are probably a culprit.  Is the crash site far away from
anything that could be wrong?  Check for dangling pointers. Example char *msg(int n, char *s)
{ char buf[100]; CS504-Software Engineering – I VU
_____________________________________________________________________ ©
Copyright Virtual University of Pakistan 227 sprintf(buf, "error %d: %s\n", n, s); return buf; } ...
p = msg(20, "Output values"); ... q = msg(30, "Input values"); ... printf("%s\n",p);
ChatGPT said:
ChatGPT
12.7 Holistic Approach
A holistic approach means looking at the system as a whole, rather than focusing on individual
parts. This approach is important in debugging because it helps you understand that fixing one
issue (like a program crash) might not be enough to solve the overall problem.
For example, when debugging a C++ program that crashes every time it hits a specific line of
code (e.g., mX = X), the issue might not be the line itself but something else affecting the
program as a whole. Fixing the symptom (the crash) might seem like a solution, but the deeper
issue needs to be addressed, which could be due to a problem in the system as a whole.
12.8 The Debugging Process
The debugging process begins with gathering the user’s report of the issue. It is essential to get a
detailed first-hand description of the problem. Sometimes, different users might describe the
same issue in different ways. For example:
1. "I was working with the Favorites list, and the program crashed after clicking OK."
2. "I created a new object, entered a name, and clicked OK, and it crashed."
3. "I selected a new project, entered a name, and clicked OK, and the program crashed."
Although these descriptions seem unrelated, they share a common factor: the program crashes
when the user clicks OK. This could suggest that the issue lies in the OK button handler. By
analyzing the problem from this holistic perspective, the debugger can focus on the root cause
rather than treating each user’s description separately.
Debugging Tips:
 Stack Trace: A stack trace shows the sequence of function calls that lead to the error. It’s very
useful for identifying the exact location of the problem.
 Check Arguments and Variables: Pay attention to argument values (e.g., zero, very large, or
negative numbers). Unexpected values can often cause issues.
 Memory Issues: Random or non-reproducible bugs might be related to memory issues, such as
uninitialized variables or dangling pointers.
Example:
In the example where a function returns a pointer to a local variable:
c
Copy code
char * msg(int n, char *s) { char buf[ 100 ]; sprintf (buf, "error %d: %s\n" , n, s); return
buf; }
The function msg() returns a pointer to buf, a local variable, which goes out of scope when the
function returns, resulting in a dangling pointer. This can cause crashes when trying to use the
pointer after the function returns.
To avoid such issues, always ensure that returned pointers are pointing to valid memory
locations.
ChatGPT can make mistakes. Check important info.
?

You might also like