Marsic Revised With Solutions 0923
Marsic Revised With Solutions 0923
Ivan Marsic
1
Copyright © 2012 by Ivan Marsic. All rights reserved. Rutgers University, New Brunswick, New
Jersey
Permission to reproduce or copy all or parts of this material for non-profit use is granted on the
condition that the author and source are credited. Suggestions and comments are welcomed.
Author’s address:
Rutgers University
Department of Electrical and Computer Engineering
94 Brett Road
Piscataway, New Jersey 08854
[email protected]
2
Preface
This book reviews important technologies for software development with a particular focus on
Web applications. In reviewing these technologies I put emphasis on underlying principles and
basic concepts, rather than meticulousness and completeness. In design and documentation, if
conflict arises, clarity should be preferred to precision because, as will be described, the key
problem of software development is having a functioning communication between the involved
human parties. My main goal in writing this book has been to make it useful.
The developer should always keep in mind that software is written for people, not for
computers. Computers just run software—a minor point. It is people who understand, maintain,
improve, and use software to solve problems. Solving a problem by an effective abstraction and
representation is a recurring theme of software engineering. The particular technologies evolve
or become obsolete, but the underlying principles and concepts will likely resurface in new
technologies.
Audience
This book is designed for upper-division undergraduate and graduate courses in software
engineering. It intended primarily for learning, rather than reference. I also believe that the book’s
focus on core concepts should be appealing to practitioners who are interested in the “whys”
behind the software engineering tools and techniques that are commonly encountered. I assume
that readers will have some familiarity with programming languages and I do not cover any
programming language in particular. Basic knowledge of discrete mathematics and statistics is
desirable for some advanced topics, particularly in Chapters 3 and 4. Most concepts do not
require mathematical sophistication beyond a first undergraduate course.
4
5
Table of Contents
Table of Contents
Chapter 1................................................................................................................................................................... 1
1.1 What is Software Engineering? ...................................................................................................................................................... 2
1.2 Software Engineering Lifecycle..................................................................................................................................................... 10
1.3 Case Studies ................................................................................................................................................................................. 29
1.4 The Object Model......................................................................................................................................................................... 40
1.5 Student Team Projects ................................................................................................................................................................. 54
1.6 Summary and Bibliographical Notes............................................................................................................................................. 63
Chapter 2................................................................................................................................................................. 69
2.1 Software Development Methods ................................................................................................................................................. 79
2.2 Requirements Engineering ........................................................................................................................................................... 86
2.3 Software Architecture .................................................................................................................................................................. 99
2.4 Use Case Modeling ..................................................................................................................................................................... 107
2.5 Analysis: Building the Domain Model ......................................................................................................................................... 131
2.7 Test-Driven Implementation ......................................................................................................................................................... 162
2.8 Summary and Bibliographical Notes........................................................................................................................................... 183
Practice Problems ............................................................................................................................................................................... 188
Chapter 3............................................................................................................................................................... 225
3.1 What is a System? ...................................................................................................................................................................... 226
3.2 Notations for System Specification............................................................................................................................................. 246
3.3 Problem Frames ......................................................................................................................................................................... 281
3.4 Specifying Goals ......................................................................................................................................................................... 291
3.5 Summary and Bibliographical Notes........................................................................................................................................... 292
Practice Problems ............................................................................................................................................................................... 293
Chapter 4............................................................................................................................................................... 298
4.1 Fundamentals of Measurement Theory ..................................................................................................................................... 303
4.2 What to Measure? ..................................................................................................................................................................... 306
4.3 Measuring Module Cohesion ..................................................................................................................................................... 319
4.4 Coupling ..................................................................................................................................................................................... 324
4.5 Psychological Complexity ........................................................................................................................................................... 326
4.6 Effort Estimation ........................................................................................................................................................................ 328
4.7 Summary and Bibliographical Notes .......................................................................................................................................... 330
Practice Problems ............................................................................................................................................................................... 332
Chapter 5............................................................................................................................................................... 334
5.2 More Patterns ............................................................................................................................................................................ 352
5.3 Concurrent Programming ........................................................................................................................................................... 369
5.4 Broker and Distributed Computing............................................................................................................................................. 386
5.5 Information Security................................................................................................................................................................... 402
5.6 Summary and Bibliographical Notes........................................................................................................................................... 410
Practice Problems ............................................................................................................................................................................... 413
5
Chapter 1
Introduction
Contents
“There is nothing new under the sun but there are lots of old What is Software Engineering?
things we don’t know.”
—Ambrose Bierce, The Devil’s Dictionary Software Engineering Lifecycle
Case Studies
Software engineering is a discipline for solving business
problems by designing and developing software-based The Object Model
systems. As with any engineering activity, a software engineer Student Team Projects
starts with problem definition and applies tools of the trade to Summary and Bibliographical Notes
obtain a problem solution. That said, the term ‘engineering’
can be misleading as designing software is very different from
say, designing a bridge or a house. Software is a lot more
dynamic, in that you need to account for the ever-changing
context in which the software resides. As an example, an
iPhone application that allows you to send money to your
Facebook friends needs to be constantly changing based on
factors both internal to the software, and external. A few
factors to consider in this one example: Operating System (iOS)
updates, changes in the banking industry APIs, Facebook API
changes, privacy regulations and the like. In this regard,
software needs to be uniquely robust to these outside (and
inside) forces.
1
2
1.1 What is Software Engineering?
The purpose of software engineering is to develop software-based systems that let customers
achieve business goals. Some examples of customers in this context:
• A hospital administrator who needs patient-record software to be used by receptionists
in doctors’ offices
• A manufacturing manager who needs software to coordinate multiple production
activities that feed into a final assembly stage.
The software engineer must understand the customer’s business needs and design software to
help meet them, and it requires:
• The ability to quickly learn new and diverse disciplines and business processes
• The ability to communicate with domain experts which also requires:
• Extracting an abstract model of the problem from the customer and experts
A software engineer’s focus is on understanding the interaction between the system-to-be and
its users and the environment, and then designing the software-to-be based on this
2
understanding. On the other hand, a programmer’s focus is on implementing the code and
ensuring that the code properly implements the given design.
3
Customer:
Requires a computer system to achieve some business goals
by user interaction or interaction with the environment
in a specified manner
System-to-be
Environment
Software-to-be
User
This is not a one-way process. Sometimes, the designs provided by the software engineer cannot
be taken at "face value", they should be discussed and thoroughly considered before
implementing. In some instances, both activities are done by the same person to ensure a
consistent result. However, given their different nature and demands, software engineering and
programming may be done by different people.
Software engineering is about understanding business problems, inventing solutions, evaluating
alternatives, and making design tradeoffs and choices. It is helpful to document this process (not
only the final solution) to know what alternatives were considered and why particular choices
were made. But software engineering, at its core, is not about writing documentation, it is about
delivering value for the customer, and both code and documentation are valuable.
4
Setting posts Cutting wood Nailing Painting
I hope to convey in this text that software is many parts, each of which individually may be easy,
but the problem is that there are too may of them. It is not the difficulty of individual
components; it is the multitude that overwhelms you—you simply lose track of bits and pieces.
Let me illustrate this point on a simple example. Suppose one wants to construct a fence around
a house. The construction involves four tasks: setting posts, cutting wood, painting, and nailing
(Figure 1-2). Setting posts must precede painting, which must precede nailing, which must
precede cutting, which must precede nailing. Suppose that setting posts takes 3 units of time,
cutting wood takes 2 units of time, painting takes 5 units of time for uncut wood and 4 units of
time otherwise, and nailing takes 2 units of time for unpainted wood and 3 units of time
otherwise. In what order should these tasks be carried out to complete the project in the shortest
possible time?
It is difficult to come up with a correct solution (or, solutions) without writing down possible
options and considering them one by one. It is hard to say why this problem is complicated
because no individual step seems to be difficult. After all, the most complicated operation involves
adding small integer numbers. This leads to a key point, when embarking on a software
engineering project, the complexity can seem overwhelming. However, the software engineer’s
job is to break the problem down into achievable steps (e.g., eating an elephant one bite at a
time).
Mistakes may occur both in understanding the problem or implementing the solution. The
problem is, for discrete logic, closeness to being correct is not acceptable; one flipped bit can
change the entire sense of a program. Software developers have not yet found adequate methods
to handle such complexity, and this text is mostly dedicated to present the current state of the
knowledge of handling the complexity of software development.
5
Communication link
Bank’s
remote
ATM machine
datacenter
Bank
customer
Figure 1-3: Example: developing software for an Automatic Teller Machine (ATM).
Software engineering relies on our ability to think about space and time, processes, and
interactions between processes and structures. Consider an example of designing a software
system to operate an automatic banking machine, known as Automatic Teller Machine (ATM)
(Figure 1-3). Most of us do not know what is happening on the inside of an ATM; nonetheless, we
could offer a naïve explanation of how ATM machines work. We know that an ATM machine allows
us to deposit or withdraw money, and we can imagine how to split these activities into simpler
activities to be performed by imaginary little “agents” working inside the machine. Figure 1-4
illustrates how one might imagine what should be inside an ATM to make it behave as it does.
We will call the entities inside the system “concepts” because they are imaginary. As seen, there
are two types of concepts: “workers” and “things.”
We know that an ATM machine plays the role of a bank window clerk (teller). The reader may
wonder why we should imagine many virtual agents doing a single teller’s job. Why not simply
imagine a single virtual agent doing the teller’s job? To understand a complex thing, one needs to
develop ideas about relationships among the parts inside. By dividing a complicated job into
simpler tasks and describing how they interact, we simplify the problem and make it easier to
understand and solve. There is a creative aspect to this activity. There are few ‘right’ or ‘wrong’
answers, just ‘best’ solutions achieved through trial and error, collaboration and discussion.
Of course, it is not enough to uncover the static structure of the system-to-be, as is done in Figure
1-4. We also need to describe how the system concepts (“workers” and “things”) interact during
the task accomplishment. Figure 1-5 illustrates the working principle (or operational principle) of
the ATM model from Figure 1-4 by a set of step-by-step interactions.
6
Domain Model
Transaction
Cash
Bookkeeper
Speakerphone Safe
Safe keeper
Phone
Window clerk
Datacenter
liaison
Dispenser
Bank’s
remote
datacenter
Figure 1-4: Imagined static structure of ATM shows internal components and their roles.
A Enter
Enter B C Verify
account
D
your PIN
XYZ
XYZ
Verify
this
account
Typing
Typing in XYZ Account
in XYZ valid.
valid. Account
PIN
PIN number
number Balance: valid.
valid.
Balance:
… $100 Balance:
Balance:
$100
$100
$100
Dispensing!
Withdraw
Withdraw Dispense
Dispense
$60
$60 $60
$60
Please take
your cash
Figure 1-5: Dynamic interactions of the imagined components during task accomplishment.
7
Programming languages, like any other formal language, are sets of symbols and rules for
manipulating those symbols. It is when they need to meet the real world that you discover that
associations can be made in different ways and some rules were not specified. A novice all too
often sees only benefits of building a software product and ignores and risks. An expert sees a
broader picture and anticipates the risks. After all, dividing the problem into subproblems and
conquering them individually does not guarantee logical rigor and strict consistency between the
pieces. Risks typically include conditions such as, the program can do what is expected of it and
then some more, unexpected capabilities (that may be exploited by bad-intentioned people).
Another risk is that not all environment states are catalogued before commencing the program
development. Depending on how you frame your assumptions, you can come up with a solution.
The troubles arise if the assumptions happen to be inaccurate, wrong, or get altered due to the
changing world.
If you are developing software you need to know about software domain (the coding language,
software and hardware, and other tools of the trade) and you need to know about the problem
domain (the business ‘context’ within which the problems to be solved exist).
A further problem is that software is a formal domain, where the inputs and goal states are well
defined. Unlike software, the real world is informal with ill-defined inputs and goal states. Solving
problems in these different domains demands different styles and there is a need to eventually
reconcile these styles. A narrow interpretation of software engineering deals only with
engineering the software itself. This means, given a specific problem, narrow-scope software
engineering is only concerned with the design, implementation, and testing of a program that
represents a solution to that problem. A broader interpretation of software engineering includes
discovering a solution for the underlying real-world problem. Interestingly, the real-world
problem may have nothing to do with software. For example, the real-world problem may be a
medical problem of patient monitoring, or a financial problem of devising trading strategies. In
broad-scope software engineering there is no precise statement of what needs to be
programmed. Our task amounts to nothing less than engineering of change in a current business
practice.
8
Software engineering is mainly about modeling the physical world and finding good abstractions.
If you find a representative set of abstractions, the development flows naturally. However, finding
abstractions in a problem domain (also known as “application domain”) involves certain level of
“coarse graining.” This means that our abstractions are unavoidably just approximations—we
cannot describe the problem domain in perfect detail. Given that every physical system has very
many parts, the best we can do is to describe it in terms of only some of its variables. Working
with approximations is not necessarily a problem by itself should the world structure be never
changing. But, we live in a changing world. Things wear out and break. Organizations go bankrupt
or get acquired or restructured. Business practices change. Government regulations change.
Fads and fashions change, and so on. On a fundamental level, one could argue that the second
law of thermodynamics works against software engineers, which states that the universe tends
towards increasing disorder. Whatever order was captured in those comparatively few variables
that we started with, tends to get dispersed, as time goes on, into other variables where it is no
longer counted as order. Our (approximate) abstractions necessarily become invalid with passing
time and we need to start afresh. This requires time and resources which we may not have
available. We will continue discussion of software development difficulties in Sections 2.4.5 and
2.6.3.
Software development still largely depends on heroic effort of select few developers. Product line
and development standardization may always be in question, but the efforts and tools derived
from them will always find value. Tools and metrics for product development and project
management are the key and will be given considerable attention in this text.
9
1.2 Software Engineering Lifecycle
10
Requirements
Design
Implementation
Testing
Waterfall
method Deployment &
Maintenance
The early inspiration for software lifecycle came from other engineering disciplines, where the
above activities usually proceed in a sequential manner (or at least it was thought so). This
method is known as Waterfall Process because developers build monolithic systems in one fell
swoop (Figure 1-6). It requires completing the artifacts of the current phase before proceeding to
the subsequent one. In civil engineering, this approach would translate to:
finish all blueprints neatly before starting construction;
finish the construction before testing it for soundness; etc.
There is also psychological attraction of the waterfall model; it is a linear process that leads to a
conclusion by following a defined sequence of steps. However, over the years developers realized
that software development is unlike any other product development in these aspects:
• Unlike most other products, software is intangible and hard to visualize. Most people
experience software through what it does: what inputs it takes and what it generates as
outputs
• Software is probably the most complex artifact—a large software product consists of so
many bits and pieces as well as their relationships, every single one having an important
role—one flipped bit can change the entire sense of a program
• Software is probably the most flexible artifact—it can be easily and radically modified at
any stage of the development process, so it can quickly respond to changes in customer
requirements (or, at least it is so perceived)
11
Therefore, the software development process that follows a linear order of understanding the
problem, designing a solution, and implementing that solution does not produce the best results.
It is easier to understand a complex problem by implementing and evaluating pilot solutions.
These insights led to adopting incremental and iterative (or, evolutionary) development methods,
which are characterized by:
1. Break the big problem down into smaller pieces (increments) and prioritize them.
2. In each iteration progress through the development in more depth.
3. Seek customer feedback and change course based on improved understanding.
The incremental and iterative process seeks to get to a working instance1 as soon as possible.
Having a working instance available lets the interested parties have something tangible to play
with, make inquiries, and receive feedback. Through this experimentation, preferably by end
users, unsuspected deficiencies are discovered that drive new development. We can use the
knowledge discovered from those deficiencies as a springboard into new ways of thinking about
solutions. This exercise of getting to a better solution greatly facilitates both:
• Agreement on whether or not the software as built meets those agreed upon
requirements.
So, the key of incremental and iterative methods is to progressively deepen the understanding or
“visualization” of the target product, by both advancing and retracting to earlier activities to
rediscover more of its features. A popular incremental and iterative process is called Unified
Process [Jacobson et al., 1999]. Methods that are even more aggressive in terms of short
iterations and heavy customer involvement are characterized as Agile. The customer is
continuously asked to prioritize the remaining work items and provide feedback about the
delivered increments of software.
All lifecycle processes have a goal of incremental refinement of the product design, but different
people hold different beliefs on how this is to be achieved. This has been true in the past and it
continues to be true, and I will occasionally comment on different approaches. Personally, I
enthusiastically subscribe to the incremental and iterative approach, and in that spirit the
exposition in this text progresses in an incremental and iterative manner, by successively
elaborating the software lifecycle phases. For every new topic, we will scratch the surface and
move on, only to revisit later and dig deeper.
1 This is not necessarily a prototype. “Prototype” creates impression of something to be thrown away
after initial experimentation, but a “working instance” can evolve into the actual product.
12
A quick review of existing software engineering textbooks reveals that software engineering is
largely about management. Project management requires organizational and managerial skills
such as identifying and organizing the many tasks comprising a project, allocating resources to
complete those tasks, and tracking actual against expected/anticipated resource utilization.
Successful software projects convey a blend of careful objective evaluation, adequate
preparation, continuous observation and assessment of the environment and progress, and
adjusting tactics.
It is interesting to compare the issues considered by Brooks [1975] and compare those of the
recent agile methods movement—both put emphasis on communication of the development
team members. My important goal here is, therefore, to present the tools that facilitate
communication among the developers. The key tools are:
• Modular design: Breaking up the system in modules helps to cope with complexity; we
have already seen how the ATM system was made manageable by identifying smaller
tasks and associated “modules” (Figure 1-4). Modules provide building blocks or “words”
of a language when describing complex solutions.
• Symbol language: The Unified Modeling Language (UML) is used similar to how the
symbols such as ∞, ∫, ∂, and ∩, are used in mathematics. They abbreviate the exposition
of the material and facilitate the reader’s understanding of the material.
• Project and product metrics: Metrics for planning and measuring project progress, and
metrics for measuring the quality of software products provide commonly agreeable tools
for tracking the work quality and progress towards the completion.
• Design heuristics: Also known as patterns, they create a design language for naming and
describing the best practices that were proven in many contexts and projects.
Decomposing a problem into simpler ones, so called divide-and-conquer approach, is common
when dealing with complex problems. In software development it is embodied in modularity: The
source code for a module can be written and maintained independently of the source code for
other modules. As with any activity, the value of a structured approach to software development
becomes apparent only when complex problems are tackled.
13
BaseInterface
ClassName «⋅⋅⋅» provides
additional info/
annotation/
# attribute_1 : int
explanation
Three common # attribute_2 : boolean
compartments: # attribute_3 : String
1. Classifier name
+ operation_1() : void
+ operation_2() : String
2. Attributes
+ operation_3(arg1 : int)
3. Operations
Software Class
Actor Comment Software Interface Implementation
doSomething()
doSomethingElse()
Interaction Diagram
doSomethingYetElse()
This means that as we organize information on higher levels of abstraction, we can still remember
seven of whatever it is. This item-level thinking is called chunking. Symbols can be easier chunked
into patterns, which are represented by new symbols. Using symbols and hierarchical abstraction
makes it easier for people to think about complex systems.
Diagrams and symbols are indispensable to software engineers. Program code is not the best way
to document a software system, although some agile methodologists have claimed that it is (more
discussion in Section 2.1.1). Code is precise, but it is also riddled with details and idiosyncrasies of
the programming language. Because it is essentially text, is not well-suited for chunking and
abstraction. The visual layout of code can be used to help the reader with chunking and
abstraction, but it is highly subjective with few widely accepted conventions.
Our primary symbol language is UML, but it is not strictly adhered to throughout the text. I will
use other notations or an ad-hoc designed one if I feel that it conveys the message in a more
elegant way. I would prefer to use storyboards and comic-strip sequences to represent that
problem and solution in a comprehensible manner. On the other hand, they are time-consuming
and often ambiguous, so we will settle for the dull but standardized graphics of the UML.
Example UML symbols are shown in Figure 1-7. To become familiar with UML, you can start at
http://www.uml.org, which is the official standard’s website. People usually use different symbols
for different purposes and at different stages of progression. During development there are many
ways to think about your design, and many ways to informally describe it. Any design model or
modeling language has limits to what it can express, and no one view of a design tells all.
14
(a)
Datacenter
(b) Window clerk
liaison
Bookkeeper Safe keeper Dispenser
Figure 1-8: Gallery of actors (a) and concepts (b) of the system under discussion. The actors are
relatively easy to identify because they are external to the system and visible; conversely, the
concepts are hard to identify because they are internal to the system, hence invisible/imaginary.
As can be observed throughout this text, the graphic notation is often trivial and can be mastered
relatively quickly. The key is in the skills in creating various models—it can take considerable
amount of time to gain this expertise.
15
A Enter
B Verify
account C How may
your PIN Verify I help
XYZ
Enter account How may
you?
your PIN XYZ I help
you?
D
Please take
E XYZ
XYZ
your cash withdrew
$60
Collecting
Collecting
cash …
cash … Acknowledged
Acknowledged
Figure 1-9: Scenario for use case “Withdraw Cash.” Unlike Figure 1-5, this figure only shows
interactions of the actors and the system.
Given the statement of work, the first step in the software development process is called
requirements analysis or systems analysis. During this activity the developer attempts to
understand the problem and delimit its scope. The result is an elaborated statement
of requirements. The goal is to produce the system specification—the document that is an exact
description of what the planned system-to-be is to do. Requirements analysis delimits the system
and specifies the services it offers, identifies the types of users that will interact with the system,
and identifies other systems that interact with ours. For example, the software engineer might
ask the customer to clarify if the ATM machine (Figure 1-3) will support banking for customers of
other banks or only the bank that owns the ATM machine. The system is at first considered a black
box, its services (“push buttons”) are identified, and typical interaction scenarios are detailed for
each service. Requirement analysis includes both fact-finding of how the problem is solved in the
current practice as well as envisioning how the planned system might work.
Recall the ATM example from Figure 1-3. We identified the relevant players in Figure 1-4.
However, this may be too great a leap for a complex system. A more gradual approach is to start
considering how the system-to-be will interact with the external players and defer the analysis of
what happens inside the system until a later time. Figure 1-8(a) shows the players external to the
system (called “actors”). If the ATM machine will support banking for customers of other banks,
then we will need to identify additional actors.
A popular technique for requirements analysis is use case modeling. A set of use cases describes
the elemental tasks a system is to perform and the relation between these tasks and the outside
world. Each use case description represents a dialog between the user and the system, with the
aim of helping the user achieve a business goal. In each dialog, the user initiates actions and the
system will respond with reactions. The use cases specify what information must pass the
16
boundary of the system in the course of a dialog (without considering what happens inside the
system).
Because use cases represent recipes for user achieving goals, each use-case name must include
a verb capturing the goal achievement. Given the ATM machine example (Figure 1-3), Figure 1-9
illustrates the flow of events for the use case “Withdraw Cash.”
Use cases are only the beginning of the software engineering process, or SDLC (Software
Development Lifecycle). When we elaborate use cases of a system, it signifies that we know what
the system needs to accomplish, not how; therefore, it is not just “a small matter of system
building” (programming) that is left after we specify the use cases.
17
1.2.3 Object-Oriented Analysis and the Domain Model
“…if one wants to understand any complex thing—be it a brain or an automobile—one needs to develop good
sets of ideas about the relationships among the parts inside. …one must study the parts to know the
whole.” —Marvin Minsky, The Emotion Machine
Use cases consider the system as a black box and help us understand how the system interacts
with the outside word. The next step is to model the inside of the system. We do this by building
the domain model, which shows what the black box (the system-to-be) encloses. We can imagine
populating the black box with domain concepts that will do the work. In other words, use cases
elaborate the system’s behavioral characteristics (sequence of stimulus-response steps), while
the domain model details the system’s structural characteristics (system parts and their
arrangement) that make it possible for the system to behave as described by its use cases.
It is useful to consider a metaphor in which software design is seen as creating a virtual enterprise
or an agency. The designer is given an enterprise’s mission description and hiring budget, with
the task of hiring appropriate workers, acquiring things, and making it operational. The first task
is to create a list of positions with a job description for each position. The designer needs to
identify the positions, the roles and responsibilities, and start filling the positions with the new
workers. Recall the ATM machine example from Figure 1-3. We need to identify the relevant
players internal to the system (called “concepts”), as illustrated in Figure 1-8(b).
When we talk about requirements analysis, we define the enterprise as the system to be
developed and the employees as the domain concepts. As you would guess, the key task is to hire
the right employees. Somewhat less critical is to define their relationships and each individual’s
attributes, which should be done only if they are relevant for the task the individual is assigned
to. I like this metaphor of “hiring workers” because it is in the spirit of what Richard Feynman
considered the essence of programming, which is “getting something to do something” [Feynman
et al., 2000]. It also sets up the stage for the important task of assigning responsibilities to
software objects.
The idea for conducting object-oriented analysis is inspired by the works of Fritz Kahn where in
the early 20th century Kahn produced a succession of books illustrating the inner workings of the
human body using visual metaphors drawn from industrial society. His illustrations drew a direct
functional analogy between human physiology and the operation of contemporary
technologies—assembly lines, internal combustion engines, refineries, dynamos, telephones, etc.
Kahn’s work is aptly referred to as “illustrating the incomprehensible” and I think it greatly
captures the task faced by a software engineer.
18
Solution
modification
Transaction
How may I record
help you?
(a)
Bookkeeper
Speakerphone
Draftsman
Window clerk
Dispenser
Customer
Transaction
How may I record
help you?
(b)
Bookkeeper
Speakerphone
Window clerk
Dispenser
Remote
bank
Customer
Figure 1-10: Alternative solutions for an ATM system. (Compare to Figure 1-4)
Domain analysis is more than just letting our imagination loose and imagining any model for the
system-to-be. Design problems can have an unlimited number of alternative solutions. For
example, consider again the design for an ATM system from Figure 1-4. One could imagine
countless alternative solutions, two of which are shown in Figure 1-10. In Figure 1-10(a), we
imagine having a draftsman to draw the banknotes requested by the customer then and there.
In Figure 1-10(b), we imagine having a courier run to a nearest bank depository to retrieve the
requested money.
19
How do we know which solution is best, or even feasible? Implementing and evaluating all
imaginable solutions is impossible because it takes time and resources. Two factors help constrain
the options and shorten the time to solution:
• Knowing an existing solution for the same, or similar, problem
• Analyzing the elements of the real world that the system might interact with
There is nothing inherent in any of these solutions that makes some better than others. What
makes some solutions “better” is that they copy existing solutions and consider what other tools
we have to solve the problem. The implication is that the analyst needs to consider not only what
needs to be done, but also how it can be done. We need to know what is at our disposal in the
external world: do we have a stack of blank papers, ink, or a courier to run between the ATM and
a depository? If this information is not given, we need to ask our customer to clarify. For example,
the customer may answer that the system-to-be will only have a communication line to a remote
datacenter that it uses for data transfer, signaling, etc. In this case, we request additional details
of the communication protocol and the format of messages that can be exchanged. We need to
know how the datacenter will answer different messages and what exceptions may occur. We
also need to know about the hardware that accepts bank cards and dispenses banknotes. How
will our software be able to detect that the hardware is jammed?
Our abstractions must be grounded in reality and that comes from knowing what we have at our
disposal in the real world that the system-to-be can use to function. This is why we cannot delimit
domain analysis to what the software-to-be will envelop. Rather, we need to consider entities
that are both external and internal to the software-to-be. The external environment constrains
the problem to be solved and, by implication, constrains the internal design of the software-to-
be. We also need to know what is implementable and what not, either from personal experience,
or from that of a person familiar with the problem domain None of our abstractions are realistic,
but some are useful while others are not.
Object-oriented analysis is detailed in Section 2.5.
The act of design involves assigning form and function to parts such that they create an esthetical
and functional whole. In software development, the key activity in the design phase is assigning
responsibilities to software objects. A software application can be seen as a set or community of
interacting software objects. Each object embodies one or more roles, a role being defined by a
set of related responsibilities. Roles, or objects, collaborate to carry out their responsibilities. Our
goal is to design something efficient. Efficient design contributes to system performance, but
never forget the importance of making the design easier to understand for humans.
20
Design is the creative process of understanding the problem domain and using the tools and
methods you have to implement all of the customer’s requirements. It is a problem-solving
activity and, as such, is very much subject to trial and error. Breaking up the system into modules
and designing their interactions can be done in many ways with varying quality of the results. In
the ATM machine example, we came up with one potential solution for step-by-step interactions,
as illustrated Figure 1-5. The key question for the designer is, “is this the best possible way to
assign responsibilities and organize the activities of virtual agents?”. One could solve the same
design problem with a different list of players and different organization of their step-by-step
interactions. As you might imagine, there is no known way for exactly measuring the optimality
of a design. Creativity and judgment are key for good software design. Knowledge of rules-of-
thumb and heuristics are critical in deciding how good a design is. Luckily, most design work is
routine design where we solve a problem by reusing and adapting solutions from similar
problems.
So, what kinds of designs are out there? Two very popular kinds of software designs are what I
would call Maurits Escher2 and Rube Goldberg3 designs. Both are fun to look at but have little
practical value. Escher designs are impossible to implement in reality. Goldberg designs are highly-
complicated contraptions, which solve the problem, but they are very brittle. If anything changes
in the underlying assumptions, they fail miserably.
A key problem of design is that we cannot know for sure if a design will work unless we implement
it and try it. Therefore, a software engineer who is also a skilled programmer has an advantage in
software design because he knows from experience how exactly to implement the abstract
constructs and what will or will not work. Related to this issue, some agile methodologists claim
that program code is the only faithful representation of program design. Although it may be
faithful, code alone is insufficient to understand software design; it is also helpful to use diagrams
to “see the forest for the trees”. Additionally, code usually does not document the design
objectives, alternative designs that were considered, merits of different designs, and the rationale
for choosing one design over another.
2 Maurits Cornelis Escher (1898-1972) is one of the world’s most famous graphic artists, known for his so-
called impossible structures, such as Ascending and Descending, Relativity, his Transformation Prints,
such as Metamorphosis I, Metamorphosis II and Metamorphosis III, Sky & Water I or Reptiles.
3 Reuben Lucius Goldberg (Rube Goldberg) (1883-1970) was a Pulitzer Prize winning cartoonist, sculptor,
and author. He is best known for his “inventions”—elaborate sets of arms, wheels, gears, handles, cups,
and rods, put in motion by balls, canary cages, pails, boots, bathtubs, paddles, and live animals—that
take simple tasks and make them extraordinarily complicated.
21
Operator (includes motor and radio control mechanism)
1
2
Remotecontrol
Remote controltransmitter
transmitter
6
3
5 7
Rail with a belt or chain
4 5
6
8
Safetyreversing
Safety reversingsensor
sensor
At the same time, the trolley (7) moves along the rail (3) and controls how far
the door opens/closes. It also controls the force the garage door exerts by
way of the curved door arm (8).
Figure 1-11: Top row: A Rube Goldberg machine for garage door opening. Bottom row: An actual
design of a garage door opener.
Consider the garage-door opener designs in Figure 1-11. The top row shows a Rube Goldberg
design and the bottom row shows an actual design. What makes the latter design realistic and
what is lacking in the former design? Some observations:
• The Rube Goldberg design uses complex components (the rabbit, the hound, etc.) with
many unpredictable or uncontrollable behaviors; conversely, a realistic design uses
specialized components with precisely controllable functions.
22
• The Rube Goldberg design makes unrealistic assumptions, such as that the rabbit will not
move unless frightened by an exploding cap.
• The Rube Goldberg design uses unnecessary links in the operational chain.
We will continue discussion of software design when we introduce the object model in Section
1.4. Recurring issues of software design include:
• Design quality evaluation: Optimal design may be an unrealistic goal given the complexity
of real-world applications. A more reasonable goal is to find criteria for comparing two
designs and deciding which one is better. The principles for good object- oriented design
are introduced in Section 2.6 and elaborated in subsequent chapters.
• Design for change: Useful software lives for years or decades and must undergo
modifications and extensions to account for the changing world in which it operates.
Chapter 5 describes the techniques for modifiable and extensible design.
• Design for reuse: Reusing existing code and designs is economical and allows creating
more sophisticated systems. Chapter 7 considers techniques for building reusable
software components.
Other important design issues include design for security and design for testability.
23
Now suppose that you employ incremental and iterative hedge pruning. You start by dividing the
hedges into smaller sections, because people are better at guessing the relative sizes of object parts
as opposed to the entire object. Suppose that you came up with the partitioning labeled with
encircled numbers 1 to 8 in Figure 1-12. Think of hedge pruning as traveling along the hedge at a
certain velocity (while pruning it). The velocity represents your work productivity. To estimate the
travel duration, you need to know the length of the path (or, path size). That is:
(1.1)
Because you have never pruned hedges, you cannot know your velocity, so the best you can do
is to guess it. You could measure the path size using a tape measure, but you realize there is a
problem. Different sections seem to have varying difficulty of pruning, so your velocity will be
different along different sections. For example, it seems that section 3 at the corner of Back and
Side Streets (Figure 1-12) will take much more work to prune than section 6 between the garden
and Main Street. Let us assume you assign “pruning points” to different sections to estimate
their size and complexity. Suppose you use the scale from 1 to 10. Because section 3 seems to
be the most difficult, we assign it 10 pruning points. The next two sections in terms of difficulty
appear to be 2 and 8, and relative to section 3 you feel that they are worth 7 pruning points
each. Next are sections 1, 5, and 7, and you give them 4 pruning points each. Finally, section 4
gets 3 pruning points and section 6 gets 2 pruning points. The total for the entire hedge is
calculated simply by adding the individual sizes. That is:
(1.2)
24
Therefore, the total for the entire hedge is 10 + (2 × 7) + (3 × 4) + 3 + 2 = 41 pruning points. This
represents your size estimate of the entire hedge. It is very important that this is a relative-size
estimate, because it measures how big individual sections are relative to one another. So, a
section estimated at four pruning points is expected to take twice as long work as a section
estimated at two pruning points.
How accurate is this estimate? Should section 4 be weighted 3.5 points instead of 3? There are
two parts to this question: (a) how accurate is the relative estimate for each section, and (b) is it
appropriate to simply add up the individual sizes? As for the former issue, you may wish to
break down the hedge sections into smaller parts, because it is easier to do eyeballing of smaller
parts and comparing to one another. Section 3 is particularly large and it may be a good idea to
split it up into smaller pieces.
25
100%
In the extreme, if you keep subdividing, instead of eyeballing hedge sections you could spend
weeks counting every individual branch and arrive at a much more accurate estimate. You could
even measure density of branches in individual sections, their length, hardness, etc. Obviously,
there is a point beyond which only minor improvement in estimation accuracy is brought at a
huge cost (known as the law of diminishing returns). Many people agree that the cost-accuracy
relationship is exponential (Figure 1-13). It is also interesting to note that, in the beginning of the
curve, we can obtain huge gains in accuracy with modest effort investment. The key points for
size estimation are that (1) the pieces should be fairly small and (2) they should be of similar size,
because it is easier to compare the relative sizes of small and alike pieces.
As for the latter issue about equation (1.2), the appropriateness of using a linear summation, a
key question is if the work on one section is totally independent on the work on another section.
The independence is equivalent to assuming that every section will be pruned by a different
person, and each starts with an equal degree of experience in hedge pruning. I believe there are
confounding factors that can affect the accuracy of the estimate. For example, as you progress,
you will learn about hedge pruning and become more proficient. Thus, your velocity will increase,
not because the size of some section became smaller, but because you became more proficient.
In Section 2.2.3 I will further discuss the issue of linear superposition in the context of software
project estimation.
All you need now is the velocity estimate and using equation (1.1) you can give Mr. McMansion
the estimate of how long the entire hedge pruning will take. Say you guess your velocity at 2
pruning points per day. Using equation (1.1) you obtain 41/2 ≈ 21 working days or 4 weeks. You
tell Mr. McMansion that your initial estimate is 21 days to finish the work. However, you must
make it clear that this is just a guess, not a hard commitment; you cannot make hard
commitments until you do some work and figure out your actual productivity (or “velocity”). You
also tell Mr. McMansion how you partitioned the work into smaller items (sections of the hedge)
and ask him to prioritize the items, so that you know his preferred ordering. Say that Mr.
McMansion prefers that you start from the back of the house and as a result you obtain the work
backlog list shown in Figure 1-14. He will inspect the first deliverable after one week, which is the
duration of one iteration.
26
Work backlog
Estimated work duration
1) Prune Section 8 3.5 days (7pts)
6) Prune Section 1
Work items
21 days
1st iteration 2nd iteration n-th iteration
Figure 1-14: The key concepts for iterative and incremental project effort estimation.
Here comes the power of iterative and incremental work. Given Mr. McMansion’s prioritized
backlog, you pull as many items from the top of the list as will fit into an iteration. Because the
first two items (sections 8 and 7) add up to 5.5 days, which is roughly one week, i.e., one iteration,
you start by pruning sections 8 and 7. Suppose that after the first week, you pruned have about
three quarters of the hedges in sections 8 and 7. In other words, after the first iteration, you
found that your actual velocity is 3/4 of what you originally thought, or 1.5 pruning point per day.
You estimate the new completion date as follows.
Total number of remaining points = 1/4 × 11 points remaining from sections 8 and 7
+ 30 points from all other sections
≈ 33 points
Estimated completion date = 22 days + 5 days already worked = 27 days total
You go to Mr. McMansion and tell him that your new estimate is that it will take you 27 days total,
or 6 more days than the original estimate. Although this is still an estimate and may prove
incorrect, you are much more confident about this estimate, because it is based on your own
experience. Note that you do not need to adjust your size estimate of 41 pruning points, because
the relative sizes of hedge sections have not changed! Because of this velocity adjustment, you
need to calculate new work durations for all remaining items in the backlog (Figure 1-14). For
example, the new durations for sections 6 and 5 will be 1.3 days and 2.7 days, respectively. As a
result, you will pull into the second iteration the remaining work from the first iteration plus
sections 6 and 5. Section 4 that was originally planned for the second iteration (Figure 1-14) will
be left for the third iteration.
27
Good Shape (Low Poor Shape
branches get sun) (Low branches
shaded from sun) Heading back not Remove dead wood
recommended as it
alters the natural
Remove water spouts and
shape of the shrub
suckers
It is important to observe that initially you estimate your velocity, but after the first increment you
use the measured velocity to obtain a more accurate estimate of the project duration. You may
continue measuring your velocity and re-estimating the total effort duration after each
increment, but this probably will not be necessary, because after the first few increments you will
obtain an accurate measurement of your pruning velocity. The advantage of incremental work is
that you can quickly gain an accurate estimate of the entire effort and will not need to rush it later
to complete on time, sacrificing product quality.
Speaking of product quality, next we will see how iterative work helps improve product quality.
You may be surprised to find that hedge pruning involves more than simply trimming the shrub.
Some parameters that characterize the quality of hedge pruning are illustrated in Figure 1-15.
Suppose that after the first iteration (sections 8 and 7), Mr. McMansion can examine the work
and decide if the quality is satisfactory or needs to be adjusted for future iterations.
It is much more likely that Mr. McMansion will be satisfied with your work if he is continuously
consulted than if he simply disappeared to vacation after describing the job requirements.
Regardless of how detailed the requirements description, you will inevitably face unanticipated
situations and your criteria of hedge esthetics may not match those of Mr. McMansion. Everyone
sees things differently, and frequent interactions with your customer will help you better
understand their viewpoint and preferences. Early feedback will allow you to focus on things that
matter most to the customer, rather than facing disappointment when the work is completed.
This is why it is important that the customer remain engaged throughout the duration of the
project, participate in all important decisions, and inspect the quality of work any time visible
progress is made.
28
In summary, we use incremental staging and scheduling strategy to quickly arrive at an effort
estimate and to improve the development process quality. We use the iterative, rework-
scheduling strategy to improve the product quality. Of course, for both strategies it is essential to
have good metrics, but we will describe this more in Chapter 4. We will also see in Section 2.2.3
how user-story points work, similar to hedge-pruning points, and how they can be used to
estimate development effort and plan software releases.
Two case studies will be used in examples throughout the text to illustrate software development
techniques. In addition, several more projects are designed for student teams later in Section 1.5.
Both case studies (as well as student projects) address relatively complex problems. I favor
complex projects rather than simple projects because I feel that they better illustrate the
difficulties and merits of the solutions. Both projects are open-ended and without a clear
objective, so that we can consider different features and better understand the requirements
derivation process. My hope is that by seeing software engineering applied on complex (and
realistic) scenarios, the reader will better grasp compromises that must be made both in terms
of accuracy and richness of our abstractions. This should become particularly evident in Chapter
3, which deals with modeling of the problem domain and the system that will be developed.
Before we discuss the case studies, I will briefly introduce a simple diagrammatic technique for
representing knowledge about problem domains. Concept maps4 are expressed in terms of
concepts and propositions, and are used to represent knowledge, beliefs, feelings, etc. Concepts
are defined as apperceived regularities in objects, events, and ideas, designated by a label, such
as “green,” “high,” “acceleration,” and “confused.” A proposition is a basic unit of meaning or
expression, which is an expression of the relation among concepts. Here are some example
propositions:
• Living things are composed of cells
• The program was flaky
• Ice cubes are cold
We can decompose arbitrary sentences into propositions. For example, the sentence
“My friend is coding a new program” I
have
can be written as the following propositions
friend
engages in
coding
constructs a
4 Read Wikipedia for an introduction to concept maps. pr
CmapTools is free software to help construct them.
is
new
29
Proposition Concept Relation Concept
1. I have friend
2. friend engages in coding
3. coding constructs a program
4. program is new
How do you construct a concept map? A common strategy starts with listing all the concepts that
you can identify in a given problem domain. Next, create the table as above, initially leaving the
“Relation” column empty. Then come up with (or consult a domain expert for) the relations
among pairs of concepts. Note that, unlike the simple case shown above, in general cases some
concepts may be related to several other concepts. Finally, drawing the concept map is easy when
the table is completed. We will learn more about propositions and Boolean algebra in Chapter 3.
Concept maps are designed for capturing static knowledge and relationships, not sequential
procedures. A concept map provides a semiformal way to represent knowledge about a problem
domain. It has reduced ambiguity compared to free-form text, and visual illustration of
relationships between the concepts is easier to understand. I will use concepts maps in describing
the case study problems and they can be a helpful tool in software engineering in general. But
obviously we need other types of diagrammatic representations, and our main tool will be UML.
30
Central
Computer
Alarm bell
Backyard doors:
External &
Light bulb Internal lock
Figure 1-16: Our first case-study system provides several functions for controlling the home
access, such as door lock control, lighting control, and intrusion detection and warning.
31
tenant
enters
wishes key
can be
upper bound on failed attempts
causes valid key invalid key
lock opened
can be prevented by enforcing
may signal
32
IF validKey AND holdOpenInterval THEN unlock
locked unlocked
33
CASE STUDY 1: SMART HOME ACCESS CONTROL
Figure 1-16 shows our case-study system that is used in the rest of the text to illustrate the
software engineering methods. In this section, we describe this case study and raise several
challenges to think about when considering it. These challenges are not meant to scare students.
With time and practice, students can excel in handling problems like these. We mention these
difficulties to get you thinking about them at an early stage. Unexpected issues do arise, but you
should patiently break them down and reach a reasonable solution.
There are even more possible features to consider. You could program the system to use timers
to
automatically arm itself based on your daily routine. Today, you could even incorporate a camera
doorbell that automatically feeds video to your smartphone wherever you are. More gadgets
include motion-detecting outdoor floodlights, visitor announcement bells, and active badges to
detect and track the tenants. Also, an outside motion sensor may turn on the outdoors light even
before the user
unlocks the door. We could dream up all sorts of services; for example, you may want to be able
to open the door for a pizza-deliveryman remotely, as you are watching television, by point-and-
click remote controls.
In a broader business context, it is unlikely that many households from the target audience will
be computer-savvy enough to maintain the system by themselves. Hence, in the age of
34
outsourcing, what better than to contract a security company to manage such systems? This
introduces many new questions, however, as we need to deal with potentially thousands of
distributed systems, and at any moment many new users may need to be registered or
unregistered with the system.
One of the first decisions we need to make pertains to the user identification. Generally, identity
can be proved in the following ways:
• Something carried by the valid user (physical key or another gadget)
• Something known only to the valid user (password)
• Direct proof of identity (biometric feature—fingerprint, voice, face, or iris)
For simplicity, let’s place two constraints on this specific system. (1) The user should not need to
carry any
physical gadgets for identification. (2) The identification mechanism should be cheap. Constraint
(1) rules out the first option. Constraint (2) rules out expensive biometric identifiers, which would
be needed in the third option. Thus, for now, we assume that the user memorizes a password.
Note that this choice means we do not check the person’s true identity (i.e. no authentication).
As long as she knows a
valid key (i.e. validation), she will be granted access.
Another software engineering challenge is in understanding exactly what the user wants from the
system. Figure 1-18 illustrates some of the difficulties in doing so. If all we care about is whether
the door is unlocked or locked, we can identify two possible states: “unlocked” and “locked.” The
system should normally be in the “locked” state, unlocking only when the valid password is
provided. To lock, the user should vocally tell the voice recognition device (i.e. Alexa) to “Lock”.
Alternatively, the user could press a button on her smartphone. Most importantly, to
accommodate forgetful users, the system should lock automatically autoLockInterval (an
integer) seconds after being unlocked. The voice recognition software should then inform the
user that the lock is complete. If the user needs the door open longer, she may specify the
holdOpenInterval (another integer). As seen, even with only two clearly identified
states, the rules for transitioning between them can become very complex.
I cannot overstate the importance of clearly stating the user’s goals. An example of a goal state is
“doors unlocked, light on”. This state must be temporary, because the door should be locked once
the user enters the house. Additionally, the user may choose to turn off the hallway light and turn
on the one in the kitchen, so the end state ends up being “doors locked, light off”. Of course, this
is a very limited goal state in comparison to the overall problem.
Clearly, establishing the preconditions of an action is hard. Such “algorithms” frequently become
quite complex, which leads us to take on heuristics. However, large software systems have too
many moving parts to conform to any set of simple heuristics. What appears a simple problem
often turns out not to have an algorithmic solution, we cannot guarantee either that heuristics
will always work. This creates the risk of ending up with an unhappy customer.
Note that we only scratched the surface of what appeared a simple problem. Any of the above
issues can be elaborated even further, in ways that the designer may not expect. This illustrates
the real problem of heuristics. At a certain point, the designer/programmer must take a step back
and avoiding falling too deep into details. But of course, this doesn’t stop problems from arising
sooner or later. Furthermore, notice that all of this has not even mentioned program bugs, which
can easily sneak into a complex program.
36
Once again, the intention of this section is not to scare students—one can excel in software
engineering with time and practice. Rather, we mention these difficulties to get you thinking
about the important questions and considerations software engineers must think about.
Unexpected problems can (and often do) arise, but a motivated software engineer will break each
problem down and reach the best possible solution.
Let us consider an online, web-based system that enables students enrolled at a university to
register for classes; and let’s call it “SchoolReg”. While the smart home demonstrates several
abstractions, or models, in physical form (such as locks and alarms), the “SchoolReg” system is
abstracted in less clear ways. Special attention should be paid to these abstractions, as they serve
as good examples of how to ‘model’ real world concepts (e.g. students, classes) or phenomena
(e.g. registering for a class) within software.
Before we move on, let’s introduce the notion of ‘happy path’ through software. This is simply the
most common, error free path a user takes to use software.
We can see that the registration process communicates with many other systems, such as various
37
administrative databases that contain student information. This will be used for various purposes,
such as confirming the student as active, whether or not a financial hold is placed on the student’s
account, if that student is able to register for certain courses (pre-requisites, different college,
graduate vs. undergraduate), and much more. It is clear to see how the assertion made in the
smart home, where complexity can become burdensome, holds true in many different ways.
Again, this is not meant to frighten you, rather it is meant to open your eyes to the possibilities of
how a complex system interacts with itself and its surroundings. It is our job as Software Engineers
to break the complex down into simpler pieces.
Now let us take a step back and look at the happy path we declared before, but this time let’s
expand into defining certain behaviors of the system.
Expressing Abstraction
Let’s start by clearly re-stating the “happy path”:
We sign-in to our college’s online portal, we then navigate to the administration tools,
we choose the “SchoolReg” link as we desire to register for next semester’s courses. We
click that link which launches a new page requiring you to sign-in again, we comply. After
a successful sign-in, the “SchoolReg” landing page is visible to us and prompts us to select
the desired semester [multi-figure]. We submit and are taken to a page that allows us to
view all available courses, plus some other utilities. We choose to view all courses and
select our semester’s course-load. We register for those courses; the system
acknowledges our choices and returns a success message stating we have registered!
The above captures a standard student experience, even if there are other aspects of the problem
we have not yet considered. However, if we were given the task of formulating a solution that
encapsulates this process, where do we start?
First consider the “happy path”, this is a reasonably clear understanding of the process, but could
there be a better way to describe what is happening? Clearly detailed writing is excellent for
clarity, but we would rather not have to re-read this paragraph to extract information.To this end
let’s break the “happy path” down.
• Sign-in
• Land on College portal
• Navigate to Admin tools
• Select “SchoolReg”
• Sign-in again
• Land on “SchoolReg” portal
• Select semeter
• View courses
• Select courses
• Submit courses for registration
• Behind the scenes work
• Given success message -> you are now registered
With this set-up we can more clearly see the linear progression of the “happy path” without
having to read through the entire paragraph we constructed earlier. While we will keep record of
this paragraph, we will no longer refer to it for most of our purposes as we just restated it in a
38
more simplified, direct way.
Now let’s consider each of these points in turn.
• Sign-in
o The student navigates to their university’s page and enters their account credentials to log-in to the
student portal.
• Select “SchoolReg”
o A simple click of a button. This launches a new application provided by the registrar.
• Sign-in again
o The new application requires additional validation, re-enter credentials.
• Select semeter
o A simple click of a button. Maybe an additional button for “submit”, too.
• View courses
o Finally, what the student is interested in. The side panel will have many different options for sorting and
filtering the available course, but all courses are viewable.
• Select courses
o The student views applicable courses, comparing course attributes (day/time held, pre-reqs, teachers,
etc.) to the student’s needs. After consideration, the student has selected their courses.
Hopefully it is becoming a bit clearer how complex a seemingly simple task can become. The above
break-down of something rather mundane highlights the “hidden layers” not evident when we
stated the happy path in its original paragraph. This is one of the challenges a Software Engineer
faces, to take a general problem statement, break it down, abstract it, then devise a solution.
Let’s examine what we have done so far. We started with a statement (more like a description of
work as opposed to a problem), we then simplified the problem by “boiling down” the statement
into actions. Then we took a step back and described the actions based on obvious interactions and
questions that arise as we do so. It is important to discover the unknowns early, so they may be
addressed in turn. Imagine how annoying it would be to get very far into the process only to realize
some aspect of the problem was overlooked, causing progress to halt, or even be undone.
39
What do we notice about these descriptions? Aside from the fact that they don’t drastically differ
from the first iteration of the statement. We notice that we are describing other systems
interacting with “SchoolReg”, or aspects of “SchoolReg” that are more clearly defined. There are
two instances of the student logging-in using their credentials, this means we have some security
and usability issues to deal with and we will need to connect to the university’s information
regarding validating credentials. There is also the implication that we need a lot of information
from various departments across the university to populate the page with courses offered.
However, this may not have been clearly communicated in the problem statement, so we had to
discover this on our own. This highlights the importance of clearly understanding the problem
given to you. From this new discovery we may ask, “how does this information get into our
system?”. Does a digital mechanism already exist to serve this purpose, will another solution for
this problem need to be engineered? These are questions in need of clarification. Moving on we
see a section that states “Behind the scene work…”. Many things may be happening in the
background after the student submits their classes for registration. The university may want to
impose certain automated checks to ensure the registration process is correct and does not
require additional oversight. This may come in the form of communicating with other
administrative departments to determine
• Is the student ‘active (i.e., not expired)’ in the system?
• Are they registering for the correct courses (pre-requisites are satisfied, enrolled in the correct
college, etc.)?
• Is there any hold on the student’s account preventing them from registering?
• Does this specific course require additional validation (independent study courses, instructor
override, etc.)?
In many ways this process can become a rabbit hole with no end. After years of experience, we
will get good at learning the appropriate level of diligence and detail. This desired level will likely
change from project to project based on many factors we will discuss later (such as team
experience, budget, timing, methodology). We were given a seemingly simple problem statement,
but in evaluating it we observe layers of complexity that were not initially obvious, and we must
address these discoveries. However, we must take care not to “fall down the rabbit hole”, it might
be more helpful to think of the problem as an onion. We are given the onion and told to make
something with it, but before we can use the onion we must break it down. Thankfully, unlike the
rabbit hole, there are only so many layers to an onion and the deeper you go the smaller it
becomes. This, then, should be the mentality of approaching the problem; study it, peel back the
layers, understand what lies beneath, but remember the deeper you go the farther you get from the
original problem.
“You cannot teach beginners top-down programming, because they don’t know which end is up.”
—C.A.R. Hoare
An object is a software packaging of data and code together into a unit within a running computer
program. Objects can interact by calling other objects for their services. In Figure 1-25,
40
object Stu calls object Elmer to find out if 905 and 1988 are coprime. Two integers are said to be
coprime or relatively prime if they have no common factor other than 1 or, equivalently, if their
greatest common divisor is 1. Elmer performs computation and answers positively. Objects do not
accept arbitrary calls. Instead, acceptable calls are defined as a set of object “methods.” This fact
is indicated by the method areCoprimes() in Figure 1-25. A method is a function (also known as
operation, procedure, or subroutine) associated with an object so that other objects can call on
its services. Every software object supports a limited number of methods. Example methods for
an ATM machine object are illustrated in Figure 1-26. The set of methods along with the exact
format for calling each method (known as the method “signature”) represents the object’s
interface (Figure 1-27). The interface specifies object’s behavior—what kind of calls it accepts and
what it does in response to each call.
41
elmer . areCoprimes( Prime factorization:
905, 1988 905 = 5 × 181
1988 = 2 × 2 × 7 × 71
Result:
YES!
Stu Elmer
Figure 1-25: Client object (Stu) sends a message to a server object (Elmer) by invoking a method
on it.
Software objects work together to carry out the tasks required by the program’s business logic.
In object-oriented terminology, objects communicate with each other by sending messages. In
the world of software objects, when an object A calls a method on an object B we say, “A sends a
message to B.” In other words, a client object requests the execution of a method from a server
object by sending it a message. The message is matched up with a method defined by the
software class to which the receiving object belongs. Objects can alternate between a client role
and a server role, as such an object is in a client role when it is the originator of an object
invocation, irrespective of memory location. Conversely, an object is in a server role when it
receives a “message” from a client. It logically follows that objects may play both client and server
roles.
In addition to methods, software objects have attributes or properties. An attribute is an item of
data named by an identifier that represents some information about the object. For example, a
person’s attribute is the age, or height, or weight. The attributes contain the information that
differentiates between the various objects. The currently assigned values for object attributes
describe the object’s internal state or its current condition of existence. Everything that a
software object knows and can do (state & behavior) is expressed by the attributes and the
methods within that object. A class is a collection of objects that share the same set of attributes
and methods (i.e., the interface). Think of a class as a template or blueprint from which objects
are made. When an instance object is created, we say that the objects are instantiated. Each
instance object has a distinct identity and its own copy of attributes and methods. Because objects
are created from classes, you must design a class and write its program code before you can
create an object.
Objects also have special methods called constructors, which are called at the creation of an
object to “construct” the values of object’s data members (attributes). A constructor prepares the
new object for use, often accepting parameters which the constructor uses to set the attributes.
Unlike other methods, a constructor never has a return value. A constructor should put an object
in its initial, valid, safe state, by initializing the attributes with meaningful values. Calling a
constructor is different from calling other methods because the caller needs to know what values
are appropriate to pass as parameters for initialization.
42
Object:
ATM machine
method-3:
method-1: Take selection
Accept card method-2:
Read code
1234
5678
12345
7
8
Figure 1-26: Acceptable calls are defined by object “methods,” as shown here by example
methods for an ATM machine object.
Interface
attributes
method-1
method-2
method-3
Figure 1-27: A software object interface is a set of object methods with the format for
calling each method.
43
A traditional approach to program development, known as procedural approach, is process
oriented in that the solution is represented as a sequence of steps to be followed when the
program is executed. The processor receives certain input data and first does this, then that, and
so on, until the result is outputted. The object-oriented approach starts by breaking up the whole
program into software objects with specialized roles and creating a division of labor. Object-
oriented programming then, is describing what messages get exchanged between the objects in
the system. This contrast is illustrated on the safe home access system case study (Section 1.3.1).
Listing 1-1: Object-oriented code for classes KeyChecker (left) and LockCtrl (right).
public class KeyChecker { protected LockCtrl public class LockCtrl { protected boolean
lock_; protected java.util.Hashtable locked_ = true; // start locked
validKeys_; protected LightCtrl switch_;
... ...
/** This method waits for and /** This method sets the lock state
* validates the user-supplied key * and hands over control to the switch
*/ */
public keyEntered( String public unlock() {
key ... operate the physical lock device locked_ =
){ false; switch_.turnOn();
if ( validKeys.containsKey(key) }
){
44
} }
} }
Let’s point out two important observations:
Object roles/responsibilities are focused (each object is focused on one task, as its name says); later,
we will see that there are more responsibilities, such as calling other objects.
The object’s level of abstraction must be chosen carefully. Here we chose key checker and its method
keyEntered(), instead of specifying the method of key entry (e.g., type in a code vs. fingerprint scan),
and LockCtrl does not specify how exactly the lock device functions. Too low of an abstraction specifies
such details (which could be specified in a derived class), while too high of an abstraction simply
stipulates “control_the_access()”.
A key skill in object-oriented software development is performing the division of labor for software
objects. Ideally, each object should have only one clearly defined task ,or responsibility, and that is
relatively easy to achieve. A major difficulty in assigning responsibilities arises when an object needs to
communicate with other objects in accomplishing a task.
When something goes wrong you want to know where to look or what to single out. This is particularly
important for a complex system, which has many functions and interactions. Object-oriented approach
is helpful because the responsibilities tend to be known. However, the responsibilities must be
assigned adequately in the first place. That is why assigning responsibilities to software objects is
among the most important skills in software development. Some responsibilities are obvious. For
example, in Figure 1-28 it is natural to assign the control of the light switch to the LightCtrl object.
However, assigning the responsibility of communicating messages is harder. For example, who should
send the message to the LightCtrl object to turn the switch on? In Figure 1-28, LockCtrl is charged with
this responsibility. Another logical choice is KeyChecker, perhaps even more suitable, because it is the
KeyChecker who ascertains the validity of a key and knows whether or not unlocking and lighting
actions should be initiated. More details about assigning responsibilities are presented in Section 2.6.
Light
(b)
Key
Lock
(c) Checker
Ctrl
Light
Ctrl
45
The concept of objects allows us to divide software into smaller pieces to make it manageable.
The divide-and-conquer approach goes under different names: reductionism, modularity, and
structuralism.
The “object oriented” approach is along the lines of the reductionism paradigm: “the
tendency to or principle of analyzing complex things into simple constituents; the view that a
system can be fully understood in terms of its isolated parts, or an idea in terms of simple
concepts” [Concise Oxford Dictionary, 8th Ed., 1991]. If your car does not work, the mechanic looks
for a problem in one of the parts—a dead battery, a broken fan belt, or a damaged fuel pump. A
design is modular when each activity of the system is performed by exactly one unit, and when
inputs and outputs of each unit are well defined. Reductionism is the idea that the best way to
understand any complicated thing is to investigate the nature and workings of each of its parts.
This approach is how humans solve problems, and it comprises the very basis of science.
There are three important aspects of object orientation that will be covered next:
• Controlling access to object elements, known as encapsulation
• Object responsibilities and relationships
• Reuse and extension by inheritance and composition
46
class, we decide what internal state it has and how that state is to appear on the outside (to other
objects). The internal state is held in the attributes, also known as class instance variables.
47
Subprograms
(behavior)
(a)
Data
(state)
Methods
(behavior) Attributes
/data
(state)
(b)
UML notation for a general software class is shown in Figure 1-30. Many programming languages
allow making the internal state directly accessible through a variable manipulation, which is bad
practice. Instead, the access to object’s data should be controlled. The external state should be
exposed through method calls, called getters and setters, to get or set the instance variables.
Getters and setters are sometimes called accessor and mutator methods, respectively. For
example, for the class LightController in Figure 1-31 the getter and setter methods for the
attribute lightIntensity are getLightIntensity() and setLightIntensity(), respectively. Getter and
setter methods are considered part of the object’s interface. In this way, the interface exposes
the object’s behavior, as well as its attributes via getters and setters.
Access to the object’s attributes and methods is controlled using access designations, also known
as visibility of attributes and methods. When an object attribute or method is defined as public,
other objects can directly access it. When an attribute or method is defined as private, only that
specific object can access it (not even the descendant objects that inherit from this class). Another
access modifier, protected, allows access by related objects, as described in the next section. The
UML symbols for access designations in class diagrams are as follows (Figure 1-30):
+ for public, global visibility; # for protected visibility; and, − for private within-the- class-only
visibility.
We separate object design into three parts: its public interface, the terms and conditions of use
(contracts), and the private details of how it conducts its business (known as implementation).
The services presented to a client object comprise the interface. The interface is the fundamental
means of communication between objects. Any behavior that an object provides must be invoked
by a message sent using one of the provided interface methods. The interface should precisely
describe how client objects of the class interact with the class. Only the methods that are
48
designated as public comprise the class interface (“+” symbol in UML class diagrams). For example,
in Figure 1-31 the class HouseholdDeviceController has three public methods that constitute its
interface. The private method sendCommandToUSBport() is not part of the interface. Note that
interfaces do not normally include attributes—only methods. If a client needs to access an
attribute, it should use the getter and setter methods.
49
Figure 1-30: UML notation for software class.
Encapsulation is fundamental to object orientation. Encapsulation is the process of packaging
your program in a way that exposes the ‘interfaces’ or levers you want other developers to be
able to interact with, and keeps the rest ‘protected’. Encapsulation also groups those interfaces
and internal attributes into logical groupings that will make sense to other engineers and
developers and will make developing the software efficient.
When deciding ‘how’ to encapsulate, the first question is: which elements in a class should be
exposed and which should be hidden? This question pertains equally to attributes and behavior.
Recall that attributes should never be exposed directly, but by using getter and setter methods.
By localizing attributes and behaviors and preventing logically unconnected functions from
manipulating object elements, we ensure that a change in a class will not cause a rippling effect
around the system. This property makes for easier maintaining, testing, and extending of classes.
In Section 1.2.2, the whole system was thought of as a black box, and here we focus on the micro-
level of individual objects. When specifying an interface, we are only interested in what an object
does, not how it does it. The “how” part is considered in implementation. Class implementation
is the program code that specifies how the class conducts its business, i.e., performs the
computation. Normally, the client object does not care how the computation is performed if it
produces the correct answer. Thus, the implementation can change, and it will not affect the
client’s code. For example, in Figure 1-25, object Stu does not care that the object Elmer answers if
two numbers are coprime. Instead, it may use any other object that provides the method
areCoprimes() as part of its interface.
Result:
YES!
50
Contracts can specify different terms and conditions of the object. Contract may apply at design
time or at run time. Programming languages such as Java and C# have two language constructs
for specifying design-time contracts.
Run time contracts specify the conditions under which an object’s methods can be called upon
(conditions-of-use guarantees), and what outcome methods achieve when they are finished
(aftereffect guarantees).
It must be stressed that the interchangeable objects must be identical in every way—as far as the
client object’s perceptions go.
51
- Composition relationship (UML symbol ♦): The contained item is an integral
part of the containing item, such as a leg in a desk
- Aggregation relationship (hollow diamond symbol ◊): The contained item is an
element of a collection, but it can also exist on its own, such as a desk in an
office
• Uses-a relationship (arrow symbol ↓ in UML diagrams): A class “uses” another class
• Creates relationship: A class “creates” another class (calls a constructor method)
52
be reused in other hierarchies.
53
HouseholdDeviceController
– deviceStatus : boolean
+ activate( ) Inheritance
+ deactivate( ) relationship:
+ isActivated( ) : boolean Base class
– sendCommandToUSBport(cmd : string) is extended
by two classes
LockController LightController
– autoLockInterval : long – lightIntensity : int
+ activate( ) + activate( )
+ deactivate( ) + getLightIntensity(value : int)
– startAutolockTimer(
startAutolockTimer( ) + setLightIntensity( ) : int
– performAutoLock(
performAutoLock( ) : boolean
boolean
“Knowledge must come through action; you can have no test which is not fanciful, save by trial.”
—Sophocles
“I have been impressed with the urgency of doing. Knowing is not enough; we must apply.
Being willing is not enough; we must do.” —Leonardo da Vinci
The book website, given in the Preface, describes several student team projects. These projects
are selected so each can be accomplished by a team of undergraduate students during one
semester. At the same time, the basic version can be extended so to be suitable for graduate
courses in software engineering and some of the projects can be extended even to graduate
theses. Here I describe only two projects, but more projects, along with additional information
about the projects, is available at the book’s website, given in the Preface.
Each project requires the student to learn one or more technologies specific for that project. In
addition, all student teams should obtain a UML diagramming tool.
54
Stock Market Investment Fantasy League System
Servers
Web and data
clients storage
System
Players admin
Stock Real-world
Payment
reporting stock
system
website exchanges
Figure 1-32: Stock market fantasy league system and the context within which it operates.
This project is fashioned after major sports fantasy leagues, but in the stock investment domain.
You are to build a website which will allow investor players to make virtual investments in real-
world stocks using fantasy money. The system and its context are illustrated in Figure 1-32. Each
player has a personal account with fantasy money in it. Initially, the player is given a fixed amount
of startup funds. The player uses these funds to virtually buy stocks. The system then tracks the
actual stock movement on real-world exchanges and periodically adjusts the value of players’
investments. The actual stock prices are retrieved from a third-party source, such as Yahoo!
Finance, that monitors stock exchanges and maintains up-to-date stock prices. Given a stock in a
player’s portfolio, if the corresponding actual stock loses value on a real-world stock exchange,
the player’s virtual investment loses value equally. Likewise, if the corresponding actual stock
gains value, the player’s virtual investment grows in the same way.
The player can sell the existing stocks or buy new ones at any time. This system does not provide
any investment advice. When player sells a stock, his/her account is credited with fantasy money
in the amount that corresponds to the current stock price on a stock exchange. A small
commission fee is charged on all trading transactions (deducted from the player’s account).
Your business model calls for advertisement revenues to financially support your website.
Advertisers who wish to display their products on your website can sign-up at any time and create
their account. They can upload/cancel advertisements, check balance(s) due, and make payments
(via a third party, e.g., a credit card company or PayPal.com). Every time a player navigates to a
new window (within this website), the system randomly selects an advertisement and displays
the advertisement banner in the window. At the same time, a small advertisement fee is charged
on the advertiser’s account. A more ambitious version of the system would fetch an
advertisement dynamically from the advertiser’s website, just prior to displaying it.
To motivate the players, we consider two mechanisms. One is to remunerate the best players, to
increase the incentive to win. For example, once a month you will award 10 % of advertisement
profits to the player of the month. The remuneration is conducted via a third party, such as
PayPal.com. In addition, the system may support learning by analyzing successful traders and
extracting information about their trading strategies. The simplest service may be in the form
stock buying recommendations: “players who bought this stock also bought these five others.”
More complex strategy analysis may be devised.
55
User Functions
System Administration
Functions
• User management
• Selection of players for awards
• Dashboard for monitoring
the league activities
Backend Operations
Figure 1-33: Logical grouping of required functions for Stock Market Fantasy League.
Statement of Requirements
Figure 1-33 shows logical grouping of functions requested from our system-to-be.
Player portfolio consists of positions—individual stocks owned by the player. Each position should
include company name, ticker symbol, the number of shares owned by this player, and date and
price when purchased. The player should be able to specify stocks to be tracked without owning
any of those stocks. Player should also be able to specify buy and sell thresholds for various stocks;
the system should alert (via email) the player if the current price exceeds any of these thresholds.
Stock prices should be retrieved periodically to valuate the portfolios, and when the user wishes
to trade. Because price retrieval can be highly resource demanding, the developer should consider
smart strategies for retrieval. For example, cache management strategies could be employed to
prioritize the stocks based on the number of players that own it, the total number of shares
owned, etc.
Additional Information
I would strongly encourage the reader to look at Section 1.3.2 for an overview of financial
investment. Additional information about this project can be found at the book website.
You may also find http://finance.yahoo.com/or http://www.marketwatch.com/ useful.
See also Problem 2.29 and Problem 2.32 at the end of Chapter 2, the solutions of which can be
found at the back of the text.
56
1.5.2 Web-based Stock Forecasters
“Business prophets tell what is going to happen, business profits tell what has happened.” —Anonymous
There are many tools available to investors but none of them removes entirely the element of
chance from investment decisions. Large trading organizations can employ sophisticated
computer systems and armies of analysts. Our goal is to help the individual investor make better
investment decisions. Our system will use the Delphi method,8 which is a systematic interactive
forecasting method for obtaining consensus expectation from a panel of independent experts.
The goal of this project is to have multiple student teams implement Web services (Chapter 8) for
stock-prediction. Each Web service (WS) will track different stocks and, when queried, issue a
forecast about the price movement for a given stock. The client module acts as a “facilitator”
which gathers information from multiple Web services (“independent experts”) and combines
their answers into a single recommendation. If different Web services offer conflicting answers,
the client may repeat the process of querying and combining the answers until it converges
towards the “correct” answer.
There are three aspects of this project that we need to decide on:
• What kind of information should be considered by each forecaster? (e.g., stock prices, trading
volumes, fundamental indicators, general economic indicators, latest news, etc. Stock prices
and trading volumes are fast-changing so must be sampled frequently and the fundamental
and general-economy indicators are slow-moving so could be sampled at a low frequency.)
• Who is the target customer? Organization or individual, their time horizon (day trader vs. long-
term investor)
• How the application will be architected? The user will run a client program which will poll the
WS-forecasters and present their predictions. Should the client be entirely Web-based vs.
locally-run application? A Web-based application would be downloaded over the Web every
time the user runs the client; it could be developed using AJAX or a similar technology.
57
As a start, here are some suggested answers:
• Our target customers are individuals who are trading moderately frequently (up to several times
per week), but not very frequently (several times per day).
• The following data should be gathered and stored locally. Given a list of about 50–100
companies, record their quoted prices and volumes at the maximum available sampling
density (check http://finance.yahoo.com/); also record some broad market indices, such as
DJIA or S&P500.
• The gathered data should be used for developing the prediction model, which can be a simple
regression-curve fitting, artificial neural network, or some other statistical method. The model
should consider both the individual company’s data as well as the broad market data. Once
ready for use, the prediction model should be activated to look for trends and patterns in stock
prices as they are collected in real time.
Potential services that will be provided by the forecaster service include:
• Given a stock x, suggest an action, such as “buy,” “sell,” “hold,” or “sit-out;” we will
assume that the forecaster provides recommendation for one stock at a time
• Recommend a stock to buy, from all stocks that are being tracked, or from all in a given
industry/sector
A key step in specifying the forecaster service is to determine its Web service interface: what will
go in and what will come out of your planned Web service? Below I list all the possible parameters
that I could think of, which the client and the service could exchange. The development team
should use their judgment to decide what is reasonable and realistic for their own team to achieve
within the course of an academic semester, and select only some of these parameters for their
Web service interface.
Parameters sent by the facilitator to a forecaster (from the client to a Web service) in the inquiry
include:
• Stock(s) to consider: individual (specified by ticker symbol), select-one-for-sector (sector
specified by a standard category), any (select the best candidate)
• Trade to consider: buy, sell, hold, sit-out OR Position to consider: long, short, any
• Time horizon for the investment: integer number
• Funds available: integer number for the capital amount/range
• Current portfolio (if any) or current position for the specified symbol
Some of these parameters may not be necessary, particularly in the first instantiation of the
system. Also, there are privacy issues, particularly with the last two items above, that must be
taken into account. The forecaster Web-services are run by third parties and the trader may not
wish to disclose such information to third parties.
Results returned by a forecaster to the facilitator (for a single stock per inquiry):
• Selected stock (if the inquiry requested selection from “sector” or “any”)
• Prediction: price trend or numeric value at time t in the future
• Recommended action and position: buy, sell, hold, sit-out, go-short
58
• Recommended horizon for the recommended action: time duration
• Recommendation about placing a protective sell or buy Stop Order.
• Confidence level (how confident is the forecaster about the prediction): range 0 – 100 %
59
The performance of each prediction service should be evaluated as follows. Once activated, each
predicted price value should be stored in a local database. At a future time when the actual value
becomes known, it should be recorded along with the previously predicted value. A large number
of samples should be collected, say over the period of tens of days. We use absolute mean error
and average relative error as indices for performance evaluation.
Statement of Requirements
Extensions
Risk analysis to analyze “what if” scenarios.
Additional information about this project can be found at the book website, given in Preface.
60
Frederick Brooks, a pioneer of software
engineering, wrote that “the hardest
single part of building a software system
is deciding precisely what to build”
[Brooks, 1995: p. 199]. By this token, the
hardest work on these projects is already
done. The reader should not feel short-
changed, though, because difficulties in
deriving system requirements will be
illustrated.
61
Otherwise, the current offer must be based exclusively on the currently removed item and not on
prior shopping history.
Next, we come up with an idea of installing RFID tag readers in the shopping carts, so we can track the
current items in each shopping cart. However, the supermarket management decides against this
approach, because of a high price of the readers and concerns about their robustness to weather and
handling or vandalism.
As a result, we conclude that logical IF-THEN-ELSE rules for deciding about special offers will take as
input only a single product identity, based on the RFID tag of the item the customer has just removed
from the shelf. The discount coupon will be a “virtual coupon,” which means that the customer is told
about the discounted product, and the discount amount will be processed at the cashier’s register
during the checkout. The display will persist for a specified amount of time and then automatically
vanish. The next question is whether each display will be dedicated to a single product or shared
among several adjacently shelved products? If the display will be shared, we have a problem if other
items associated with this display are removed (nearly) simultaneously. How do we show multiple
offers, and how to target each to the appropriate customer? A simple but difficult question is, when
the displayed coupon should vanish? What if the next customer arrives and sees it before it vanishes?
Perhaps there is nothing bad with that, but now we realize that we have a difficulty targeting the
coupons. In addition, because the system does not know what is in the customer’s cart, it may be that
the customer already took the product that the system is suggesting. After doing some market
research, we determine that small displays are relatively cheap and an individual display can be
assigned to each product. We give up targeting customers, and just show a virtual coupon as specified
by the logical rules.
Given that the store already operates in the same way with physical, paper-based coupons, the
question is if it is worth to install electronic displays or use RFID tags? Is there any advantage of
upgrading the current system? If the RFID system input is used, then the coupon will appear when an
item is removed. We realize that this makes no sense and just show the product coupon all the time,
same as with paper-based coupons. An advantage of electronic displays is that they preclude having
the store staff go around and place new coupons or remove expired ones.
We started with the idea of introducing RFID tags and ended up with a solution that renders them
useless. An argument can be made that tags can be used to track product popularity and generate
promotional offers based on the current demand or lack thereof. A variation of this project, with a
different goal, will be considered in Problem 2.15 at the end of Chapter 2.
There are several lessons to be learned about software engineering from the above example:
• One cannot propose a solution without a deep understanding of the problem domain
and working closely with the customer.
• Requirements change dynamically because of new insights that were not obvious initially.
• Final solution may be quite different from the initial idea.
he projects presented earlier in this chapter are relatively precise and include more
T information than what is usually known as the customer statement of work, which is an
expression, from a potential customer, of what they require from a new software system. I
expressed the requirements more precisely to make them suitable for one-semester
(undergraduate) student projects. Our focus here will be on what could be called “core
software engineering.”
62
On the other hand, the methods commonly found in software engineering textbooks would not
help you to arrive at the above descriptions. Software engineering usually takes from here—it
assumes a defined problem and focuses on finding a solution. Having defined a problem sets the
constraints within which you may derive a solution.
That said, we are rarely, in the real world, presented with a comprehensive and well-defined
problem and solution. Major subfields of of software engineering (all of which will be addressed
in subsequent chapters) are
• problem framing
Because software is pure invention, it does not have physical reality to keep it in check. That is,
we can build more and more complex systems, and pretend that they simply need a little added
debugging. The search for simplicity is the search for a structure within which the complex
becomes transparent. It is important to constantly simplify the structure. Detail must be
abstracted away, and the underlying structure exposed.
Although this text is meant as an introduction to software engineering, I focus on critical thinking
rather than prescriptions about structured development process. Software development can by
no means be successfully mastered from a single source of instruction. I expect that the reader is
already familiar with programming, algorithms, and basic computer architecture. The reader may
also wish to start with an introductory book on software engineering, such as [Larman, 2005;
Sommerville, 2004]. The Unified Modeling Language (UML) is used extensively in this text’s
diagrams, and the reader unfamiliar with UML should consult a text such as [Fowler, 2004]. I also
assume solid knowledge of the Java programming language. However, there is a refresher of the
Java programming language in Appendix A, but the reader lacking in Java knowledge should
consult the excellent source by Eckel [2003].
The problem of scheduling construction tasks (Section 1.1) is described in [Goodaire & Parmenter,
2006], in Section 11.5, p. 361. One solution involves first setting posts, then cutting, then nailing,
and finally painting. This sequence is shown in Figure 1-2 and completes the job in 11 units of
time. There is a second solution that also completes the job in 11 units of time: first cut, then set
posts, then nail, and finally paint.
Although I emphasized that complex software systems defy simple models, there is an interesting
view advocated by Stephen Wolfram in his NKS (New Kind of Science):
http://www.wolframscience.com/ , whereby some systems that appear extremely complex can
be captured by very simple models.
63
In a way, software development parallels the problem-solving strategies in the field of artificial
intelligence or means-ends analysis. First, we need to determine what are our goals (“ends”); next,
represent the current state; then, consider how (“means” to employ) to minimize the difference
between the current state and the goal state. As with any design, software design can be seen
as a difference-reduction, or simplificaiotn activity, formulated in terms of a symbolic description
of differences.
There are many reasons why some systems succeed (e.g., the Web, the Internet, personal
computer) and others fail, including:
• They meet a real need
• They were first of their kind
• They coevolved as part of a package with other successful technologies and were
more convenient or cheaper (think MS Word versus WordPerfect)
• Because of their technical excellence
Engineering excellence alone is not guarantee for success but a clear lack of it is a recipe for failure.
There are many excellent and/or curious websites related to software engineering, such as:
Teaching Software Engineering – Lessons from MIT, by Hal Abelson and Philip Greenspun:
http://philip.greenspun.com/teaching/teaching-software-engineering
Software Architecture – by Dewayne E. Perry: http://www.ece.utexas.edu/~perry/work/swa/
Software Engineering Academic Genealogy – by Tao Xie:
http://www.csc.ncsu.edu/faculty/xie/sefamily.htm
64
65
Section 1.2.3: Object-Oriented Analysis and the Domain Model
I feel that this is a more gradual and intuitive approach than some existing approaches to domain
analysis. However, I want to emphasize that it is hard to sort out software engineering approaches
into right or wrong ones—the developer should settle on the approach that produces the best
results for them.
Some authors consider object-oriented analysis (OOA) to be primarily the analysis of the existing
practice and object-oriented design (OOD) to be concerned with designing a new solution (the
system-to-be).
Further Reading
Modular design was first introduced by David Parnas in the 1960s.
A brief history of object orientation [from Technomanifestos] and of UML, how it came together
from 3 amigos. A nice introduction to programming is available in [Boden, 1977, Ch. 1], including
the insightful parallels with knitting which demonstrates surprising complexity.
Also, from [Petzold] about ALGOL, LISP, PL/I.
Objects: http://java.sun.com/docs/books/tutorial/java/concepts/object.html
N. Wirth, “Good ideas, through the looking glass,” IEEE Computer, vol. 39, no. 1, pp. 28-39, January
2006.
H. van Vliet, “Reflections on software engineering education,” IEEE Software, vol. 23, no. 3, pp.
55-61, May-June 2006.
[Ince, 1988] provides a popular account of the state-of-the-art of software engineering in mid
1980s. It is worth reading if only for the insight that not much has changed in the last 20 years.
The jargon is certainly different and the scale of the programs is significantly larger, but the issues
remain the same and the solutions are very similar. Then, the central issues were reuse, end-user
programming, promises and perils of formal methods, harnessing the power of hobbyist
programmers (today known as open source), and prototyping and unit testing (today’s equivalent:
agile methods).
67
Regarding the Object Model
The concept of information hiding originates from David Parnas [1972].
68
Chapter 2
Object-Oriented Software Engineering
Contents
Software Development Methods
69
2.1 Software Development Methods
“Plan, v.t. To bother about the best method of accomplishing an accidental result.”
—Ambrose Bierce, The Devil’s Dictionary
The goal of software methodologists is to understand how high quality software can be developed
efficiently. Students and experts should learn and apply methodology; it may help them develop
their own ideas and methods. Following prescribed steps is likely to result in a successful project,
even with limited developer expertise. Methodology development often works by observing how
expert developers work and deriving an abstract model of the development process. In reality,
life cycle methods are often not followed; when they are, it is usually because of employer’s policy
in place. Why is it so, if following a method should be a recipe for success?
There are several reasons why methodologies are ignored or resisted in practice. One reason is
that methodology is usually derived from past experience. But, what worked for one person may
not work for another. Both developers and projects have different characteristics and it is difficult
to generalize across either one. Software development is so complex that it is impossible to create
precise instructions for every scenario. In addition, method development takes relatively long
time to recognize and extract “best practices.” By the time a method is mature, the technologies
it is based on may become outdated. The method may simply be inappropriate for the new and
emerging technologies and market conditions.
A development method usually lays out a prescriptive process by mandating a sequence of
development tasks. Some methods devise very elaborate processes with a rigid, documentation-
heavy methodology. The idea is that even if key people leave the project or organization, the
project should go on as scheduled because everything is properly documented. This approach is
known as “Big Design Up Front” (BDUF). However, experience teaches us that it is impossible to
consider all potential scenarios just by thinking. And, regardless of how well the system is
documented, if key people leave, the project suffers. It is much more sensible to develop initial
versions of the system-to-be from a partial understanding of the problem, let users play with such
a prototype, and then redesign and develop a new iteration based on the gained understanding.
One difficulty with product development is that when thinking about a development plan,
engineers usually think in terms of methodology: what to do first, what next, etc. Naturally, first
comes discovery. Discovery is studying the problem domain to understand the current solution
and propose a better solution. Development comes second. Development is designing and
implementing the system. Finally, the system is deployed and evaluated. This sequential thinking
naturally leads to the “waterfall model” (Section 1.2) and heavy documentation.
The customer does not see it that way. The customer would rather see some rudimentary
functionality soon, and then refinement and extension.
Recent methods, known as agile, attempt to deemphasize process-driven documentation and
detailed specifications. They also consider the number and experience of the people on the
development team.
Throughout this book, We focus mostly on Waterfall methodology and its components. The
reasons for this approach are:
• Agile is simply an evolution of Waterfall. It is difficult to understand Agile without knowing
the prevalanet way of developing software before it came about. To put it another way,
79
it’s easy to understand Agile methodology once you know the problems it addresses
• Agile is not an entirely new method, but rather a re-ordering of components of the
Waterfall model, so Waterfall is a more foundational place to start learning Software
Engineering
While we will not spend too much time on Agile specifically, once you understand the concepts in
this book, it is an easy step to learn Agile Methodology and I recommend doing so.
80
Below are the four major software development methodologies, from oldest to newest:
• Structured analysis and design (SAD),
• Object-oriented analysis and design (OOAD),
• Agile software development (ASD),
• Aspect-oriented software development (AOSD).
The structured analysis and design (SAD) methodology emerged in the 1970s and introduced
functional decomposition and data-flow analysis as key modeling tools.
The object-oriented analysis and design (OOAD) methodology emerged in the late 1980s and was
widely adopted by the mid 1990s. It introduced use cases and the Unified Modeling Language
(UML) as key modeling tools.
The ideas of agile software development (ASD) emerged at the end of 1990s and rapidly gained
popularity in the software industry as a “lightweight” way to develop software. Agile development
is reviewed in Section 2.1.1.
The aspect-oriented software development (AOSD) methodology emerged in the late 1990s. It is
not a replacement for any of the other methodologies. Rather, it helps deal with scattered
crosscutting concerns. Functional features of a software system could be divided into two
categories: (1) core features that provide basic functionality and allow the end-user to achieve
specific business goals; and, (2) supplementary features that provide support for entitlements,
connectivity, concurrency, system interface, etc. Many of the complementary features can be
scattered across the application and tangled with core features, which is why they are called
crosscutting concerns.
81
agile-development tool is user stories, which are intended to represent the system requirements,
estimate effort and plan software releases (Section 2.2.3).
SIDEBAR 2.1: Agile vs. Sloppy
♦ I have had students complain that demanding readability, consistency, and completeness in
project reports runs against the spirit of agile development. Some software engineering textbooks
insist on showing snapshots of hand drawn UML diagrams, as opposed to neat diagrams created
electronically, to emphasize the evanescent nature of designs and the need for dynamic and
untidy artifacts. This may work for closely knit teams of professionals, working in adjacent
offices exclusively on their project. But, I found it not to be conducive for the purpose of grading
student reports: it is very difficult to discern sloppiness from agility and assign grades fairly.
Communication, after all, is the key ingredient of teamwork, and communication is not improved
if readability, consistency, and completeness of project reports are compromised. I take it that
agility means: reduce the amount of documentation but not at the expense of the communicative
value of project artifacts. Brevity is a virtue, but we also know that redundancy is the most
effective way to protect the message from noise effects. (Of course, you need to know the right
type of redundancy!)
Agile methodologists seem not to have much faith in visual representations, so one can find few
if any graphics and diagrams in agile software development books. Some authors take the agile
principles to the extreme and I would caution against this. I have seen claims that working code is
the best documentation of a software product. I can believe that there are people for whom
program code is the most comprehensible document, but I believe that most people would
disagree. Most people would find easiest to understand carefully designed diagrams with
accompanying narrative in a plain natural language. Of course, the tradeoff is that writing proper
documentation takes time, and it is difficult to maintain the documentation consistent with the
code as the project progresses.
Even greater problem is that the code documents only the result of developer’s design decisions,
but not the reasoning behind those decisions. Code is a solution to a problem. It is neither a
description of the problem, nor of the process by which the problem was solved. Much of the
rationale behind the solution is irretrievably lost or hidden in the heads of the people who chose
it, if they are still around. After a period of time, even the person who made a design decision
may have difficulty explaining it if the reasons for the choice are not explicitly documented.
However, although documentation is highly desirable it is also costly and difficult to maintain in
synchrony with the code as the lifecycle progresses. Outdated documentation may be source of
confusion. It is said that the code is the only unambiguous source of information. Such over-
generalizations are not helpful. It is like saying that the building itself is the only unambiguous
source of information and one need not be bothered with blueprints. You may not have blueprints
for your home or even not know where to find them, but blueprints for large public buildings are
carefully maintained as they better be. After all, it is unethical to leave a customer with working
code, but without any documentation. There is a spectrum of software projects, so there should
be a matching spectrum of documentation approaches, ranging from full documentation, through
partial and outdated one, to no documentation. I believe that even outdated documentation is
better than no documentation. Outdated documents may provide insight into the thinking and
evolution that went into the software development. On most projects, documentation should be
created with the understanding that it will not always be
82
up to date with the code, resulting in “stale” parts. A discrepancy usually arises in subsequent
iterations, so we may need to prioritize and decide what to keep updated and what to mark as
stale.
There are other issues with maintaining adequate documentation. The developer may even not
be aware of some choices that he made, because they appear to be “common sense.” Other
decisions may result from company’s policies that are documented separately and may be
changed independently of the program documentation. It is useful to consider again the
exponential curve in Figure 1-13, which can be modified for documentation instead of estimation.
Again, a relatively small effort yields significant gains in documentation accuracy. However, after
a certain point the law of diminishing returns triggers and any further improvement comes at a
great cost. It is practically impossible to achieve perfect documentation.
SIDEBAR 2.2: How Much Diagramming?
♦ I often hear inquiries and complaints that the amount of diagramming in this book is
excessive. This book is primarily intended for students learning software engineering and
therefore it insists on tidiness and comprehensiveness for instructive purposes. If I were doing
real projects, I would not diagram and document every detail, but only the most difficult and
important parts. Unfortunately, we often discover what is “difficult and important” only long
after the project is completed or after a problem arises. Experience teaches us that the more effort
you invest in advance, the more you will be thankful for it later. The developer will need to use
their experience and judgment as well as contextual constraints (budget, schedule, etc.) to decide
how much diagramming is appropriate.
Many books and software professionals place great emphasis on the management software
engineering projects. In other words, it is not about the engineering per se but it is more about
how you go about engineering software, in particular, knowing what are the appropriate steps to
take and how you put them together. Management is surely important, particularly because most
software projects are done by teams, but it should not be idolized at the detriment of product
quality. This book focuses on techniques for developing quality software.
83
rationale that motivated its existence. Traceability must be maintained across the lifecycle.
Maintaining traceability involves recording, structuring, linking, grouping, and maintaining
dependencies between requirements and other software artifacts. We will see how traceability
works on examples in this chapter.
Requirements Use Cases Concepts/Objects Source Code
CO-3 Code-3
Testing
The key idea of Test-Driven Development (TDD) is that every step in the development process
must start with a plan of how to verify that the result meets some goal. The developer should not
create a software artifact (such as a system requirement, a UML diagram, or source code) unless
he has a plan of how it will be tested. For example, a requirement is not well-specified if an
automated computer program cannot be written to test it for compliance. Such a requirement is
vague, subjective, or contradictory and should be reworked.
The testing process is not simply confined to coding. Testing the system design with walkthroughs
and other design review techniques is very helpful. Agile TDD methodology prescribes to make
progress just enough to pass a test and avoid detailed analysis. When a problem is discovered, fix
it. This approach may not be universally appropriate, e.g., for mission critical applications. Therein,
when a problem is discovered, it might have led to a major human or economic loss. Discovering
that you missed something only when system failed in actual use may prove very costly. Instead,
a thorough analysis is needed in advance of implementation. However, the philosophy of thinking
while creating a software artifact about how it will be tested and designing for testability applies
more broadly than agile TDD.
Software defects (or, bugs) are typically not found by looking at source code. Rather, defects are
found by mistreating software and observing how it fails, by reverse engineering it (approach
used by people who want to exploit its security vulnerabilities), and by a user simply going about
his business until discovering that a program has done something like delete all of the previous
hour’s work. Test plans and test results are important software artifacts and should be preserved
along with the rest of software documentation. More about testing in Section 2.7.
84
Agile TDD claims to improve the code, and detect design brittleness and lack of focus. It may well
do that, but that is not the main purpose of testing, which is to test the correctness, not quality of
software. Even a Rube-Goldberg design can pass tests under the right circumstances. And we
cannot ever check all circumstances for complex software systems. Therefore, it would be helpful
to know if our system works correctly (testing) and if it is of high quality, not a Rube- Goldberg
machine. This is why we need software measurement.
Measurement
While testing is universally practiced and TDD widely adopted, metrics and measurement are
relatively rarely used, particularly for assessing software product quality. Agile methods have
emphasized using metrics for project estimation, to track progress and plan the future iterations
and deliverables. Software product metrics are intended to assess program quality, not its
correctness (which is assessed by testing and verification). Metrics do not uncover errors; they
uncover poor design.
More about software measurement in Chapter 4.
Security
Most computers, telephones, and other computing devices are nowadays connected to the public
Internet. Publicly accessible Web applications and services can be abused and twisted to
nefarious ends. Even if the computer does not contain any “sensitive” information, its computing
and communication resources may be abused to send out spam and malware as part of a
distributed botnet. Such hijacked systems provide a “safe” means of distribution of illicit goods or
services on someone else’s server without that person’s knowledge. Because of ubiquitous
connectivity, anyone’s security problems impact everyone else, with only rare exceptions.
There are two kinds of technology-based security threats in software systems. One arises because
of bad software, where the attacker exploits software defects. The other arises because of
network interconnectedness, when the attacker exploits other infected systems to poison the
traffic to or from targeted computers. Hence, even if software is designed with security features
to prevent unauthorized use of system resources, it may be denied data or services from other
computers. Attackers rely on exploitable software defects as well as continuing to develop their
own infrastructure. An experienced developer must understand both the principles of software
design and the principles of network security. Otherwise, he will be prone to making naïve
mistakes when assessing the security benefits of a particular approach to software development.
This book focuses on better software design and does not cover network security.
The Security Development Lifecycle (SDL), promoted by Microsoft and other software
organizations, combines the existing approaches to software development with security-focused
activities throughout the development lifecycle. Security risk management focuses on minimizing
design flaws (architectural and design-level problems) and code bugs (simple implementation
errors in program code). Identifying security flaws is more difficult than looking for bugs, because
it requires deep understanding of the business context and software architecture and design. We
work to avoid design flaws while building secure software systems. Techniques include risk
analysis, abuse cases (trying to misuse the system while thinking like an attacker), and code quality
auditing.
85
Functional security features should not be confused with software security. Software security is
about developing high quality, problem-free software. Functional security features include
cryptography, key distribution, firewalls, default security configuration, privilege separation
architecture, and patch quality and response time. Poorly designed software is prone to security
threats regardless of built-in security functionality. Security functionality design is detailed in
Section 5.5.
“The hardest single part of building a software system is deciding what to build. No part of the work so cripples
the resulting system if done wrong. No other part is more difficult to rectify later.”—Fred Brooks
“You start coding. I’ll go find out what they want.” —Computer analyst to programmer
Requirements engineering helps software engineers understand the problem they are to solve. It
involves activities that lead to understanding the business context, what the customer wants, how
end-users will interact with the software, and what the business impact will be. Requirements
engineering starts with the problem definition: customer statement of requirements. This is an
informal description of what the customers think they need from a software system to do for
them. The problem could be identified by management personnel, through market research, by
simple observation, or other means. The statement of work captures the perceived needs and,
because it is opinion-based, it usually evolves over time, with changing market conditions or
better understanding of the problem. Defining the requirements for the system-to-be includes
both researching how the problem is currently solved as well as envisioning how the planned
system might work. Requirements engineering ends with creating a requirements specification
document.
The key task of requirements engineering is formulating a well-defined problem to solve. A well-
defined problem includes
• A set of criteria (“requirements”) according to which proposed solutions either definitely
solve the problem or fail to solve it
• The description of the resources and components at disposal to solve the problem.
Requirements engineering involves different stakeholders in defining the problem and specifying
the solution. A stakeholder is an individual, team, or organization with interests in, or concerns
related to, the system-to-be. Generally, the system-to-be has several types of stakeholders:
customers, end users, business analysts, systems architects and developers, testing and quality
assurance engineers, project managers, the future maintenance organization, owners of other
systems that will interact with the system-to-be, etc. The stakeholders all have a stake, but the
stakes may differ. End users will be interested in the requested functionality. Architects and
developers will be interested in how to effectively implement this functionality. Customers will
be interested in costs and timelines. Often compromises and tradeoffs need to be made to satisfy
different stakeholders.
86
Requirements Requirements Requirements
gathering analysis specification
Although different methodologies provide different techniques for requirements engineering, all
of them follow the same requirements process: requirements gathering, requirements analysis,
and requirements specification (Figure 2-1). The process starts with customer’s requirements or
surveying the potential market and ends with a specification document that details how the
system-to-be will behave. This is simply a logical ordering of requirements engineering activities,
regardless of the methodology that is used. Of course, the logical order does not imply that each
step must be perfectly completed before the next is taken.
Requirements gathering (also known as “requirements elicitation”) helps the developer
understand the business context. The customer needs to define what is required: what is to be
accomplished, how the system will fit into the needs of the business, and how the system will be
used on a day-to-day basis. This turns out to be very hard to achieve, as discussed in Section
2.2.2. The statement of work is rarely precise and complete enough for the development team to
start working on the software product.
Requirements analysis involves refining of and reasoning about the requirements received from
the customer during requirements gathering. Analysis is driven by the creation and elaboration of
user scenarios that describe how the end-user will interact with the system. Negotiation with the
customer will be needed to determine the priorities, what is essential, and what is realistic. A
popular tool is the use cases (Section 2.4). It is important to ensure that the developer’s
understanding of the problem coincides with the customer’s understanding of the problem.
Requirements specification represents the problem statement in a semiformal or formal manner
to ensure clarity, consistency, and completeness. It describes the function and quality of the
software-to-be and the constraints that will govern its development. A specification can be a
written document, a set of graphical models, a formal mathematical model, a collection of usage
scenarios (or, “use cases”), a prototype, or any combination of these. The developers could use
UML or another symbol language for this purpose.
87
As mentioned, logical ordering of the development lifecycle does not imply that we must achieve
perfection in one stage before we progress to the next one. Quite opposite, the best results are
achieved by incremental and iterative attention to different stages of the requirements
engineering process. This is an important lesson of the agile development philosophy. Traditional
prescriptive processes are characterized by their heavy emphasis on getting all the requirements
right and written early in the project. Agile projects, on the other hand, acknowledge that it is
impossible to identify all the requirements in one pass. Agile software development introduced a
light way to model requirements in the form of user stories, which are intended to capture
customer needs, and are used to estimate effort and plan releases. User stories are described in
Section 2.2.3.
Section 2.3.1 introduces different problem types and indicates that different tools for
requirements engineering work best with different types of problems. In addition to problem
types, the effectiveness of requirements tools depends on the intended stakeholders. Different
requirements documents may be needed for different stakeholders. For example, the
requirements may be documented using customer’s terminology so that customers unfamiliar
with software engineering jargon may review and approve the specification of the system-to-be.
A complementary document may be prepared for developers and testing engineers in a semi-
formal or formal language to avoid ambiguities of natural languages.
The statement of requirements is intended to precisely state the capabilities of the system that
the customer needs developed. Software system requirements are usually written in the form of
statements “The system shall …” or “The system should …” The “shall” form is used for features
that must be implemented and the “should” form for desirable but not mandatory features. IEEE
has published a set of guidelines on how to write software requirements. This document is known
as IEEE Standard 830.
88
Table 2-1: Requirements for Case Study 1: SMART home access system (1.3.1).
89
Note that Table 2-1 contains two types of requirement prioritization. There is an implicit priority
in “shall” vs. “should” wording, as well as explicit Priority Weight column. We need to ensure that
they are consistent. To avoid ambiguities, agile methods adopt a work backlog (Figure 1-14) that
simply lists the work items in the order in which they should be done. Customers and engineers
work together on this.
More on individually testable requirements, as well as User Acceptance Tests (introduced below),
can be found in Section 2.7.1.
User Acceptance Tests
Following the Test-Driven Development paradigm, we write tests for the requirements during the
requirements analysis. These tests are known as user acceptance tests (UATs) and they are
specified by the customer (2.7.1). The system-to-be will be created to fulfill the customer’s vision,
so the customer decides that a requirement has been correctly implemented and therefore
“accepts” the implementation. Acceptance tests capture the customer’s assumptions about how
the functionality specified with the requirement will work, as well as what could go wrong or make
it work differently. The customer can work with a programmer or tester to write the actual test
cases. A test case is a particular choice of input values to be used in testing a program and
expected output values. A test is a finite collection of test cases. For example, for the requirement
REQ3, the customer may suggest these test cases:
• Test with the valid key of a current tenant on his or her apartment (pass)
• Test with the valid key of a current tenant on someone else’s apartment (fail)
• Test with an invalid key on any apartment (fail)
• Test with the key of a removed tenant on his or her previous apartment (fail)
• Test with the valid key of a just-added tenant on his or her apartment (pass)
These test cases provide only a coarse description of how a requirement will be tested. It is
insufficient to specify only input data and expected outcomes for testing functions that involve
multi-step interaction. Use case acceptance tests in Section 2.4.3 will provide step-by-step
description of acceptance tests.
The table includes the requirement REQ7 that allows the user to configure the preferences for
activating various household devices in response to different events. The preferences would be
set up using a user interface (sketched in Figure 2-2). This is not to advocate user interface design
at this early stage of project development. However, the developer should use all reasonable
means to try and understand the customer’s needs as early as possible. Drawing sketches of user
interfaces is a useful tool for eliciting what the customer needs and how he would like to interact
with the system.
Table 2-1 contains only a few requirements that appear to be clear at the outset of the project.
Some of the requirements are somewhat imprecise and will be enhanced later, as we learn more
about the problem and about the tools used in solving it. Other requirements may be discovered
or the existing ones altered as the development lifecycle iteratively progresses. Refining and
modifying the initial requirements is the goal of requirements analysis.
90
Device Preferences
File Configure Help
Apply Close
Figure 2-2: Envisioning the preference configuration for the control of household devices.
91
Table 2-2: Requirements for the second case study, investment assistant (see Section 1.3.2).
Identifier PW Requirement
REQ1 5 The system shall support registering new investors by providing a real-world email,
which shall be external to our website. Required information shall include a unique login
ID and a password that conforms to guidelines, as well as investor’s first and last name
and other demographic information. Upon successful registration, the system shall set
up an account with a zero balance for the investor.
REQ2 5 The system shall support placing orders by filling out a form known as “order ticket,”
which contains the client’s information, the stock information, the order information,
and any special instructions. The ticket shall be emailed to the client and enqueued for
execution when the specified conditions are satisfied.
REQ3 5 The system shall periodically review the enqueued orders and for each order ticket in
the queue take one of the following actions:
(i) If the order type is Market Order, the system shall execute the trade instantly;
(ii) Else, if the order conditions are matched, convert it to a Market Order at the
current stock price;
(iii) Else, if the order has expired or been cancelled, remove it from the queue, declare it
a Failed Order and archive as such;
(iv) Else, leave the order untouched.
If either of actions (i), (ii), or (iii) is executed, the system shall archive the transaction and
notify the trader by sending a “brokerage trade confirmation.”
REQ4 2 The system shall allow the trader to manage his or her pending orders, for example to
view the status of each order or modify the order, where applicable.
REQ5 2 The system shall continuously gather the time-series of market data (stock prices,
trading volumes, etc.) for a set of companies or sectors (the list to be decided).
REQ6 3 The system shall process the market data for two types of information:
(i) on-demand user inquiries about technical indicators and company fundamentals
(both to be decided), comparisons, future predictions, risk analysis, etc.
(ii) in-vigilance watch for trading opportunities or imminent collapses and notify
the trader when such events are detected
REQ6 3 The system shall record the history of user’s actions for later review.
User Stories
Agile development methods have promoted “user stories” as an alternative to traditional
requirements. A user story is a brief description of a piece of system functionality as viewed by a
user. It represents something a user would be likely to do in a single sitting at the computer
terminal. User stories are written in a free-form, with no mandatory syntax, but generally they
are fitting the form:
user-role + capability + business-value
Here is an example of a user story for our case study of secure home access:
As a tenant, I can unlock the doors to enter my apartment.
92
Table 2-3: User stories for the first case study, safe home access. (Compare to Table 2-1.) The
last column shows the estimated effort size for each story (described in Section 2.2.3).
The business-value part is often omitted to maintain the clarity and conciseness of user stories.
Table 2-3 shows the user stories for our first case study of home access control (Section 1.3.1). If
we compare these stories to the requirements derived earlier (Table 2-1), we will find that stories
ST-1 and ST-2 roughly correspond to requirement REQ1, story ST-3 corresponds to REQ2, story
ST-4 corresponds to REQ3 and REQ4, and story ST-6 corresponds to REQ8, etc. Note, however,
that unlike the IEEE-830 statements “The system shall …,” user stories put the user at the center.
Types of Requirements
System requirements make explicit the characteristics of the system-to-be. Requirements are
usually divided into functional and non-functional. Functional requirements determine the
system’s expected behavior and the effects it should produce in the problem domain. These
requirements generally represent the main product features.
Non-functional requirements describe some quality characteristic that the system-to-be shall
exhibit. They are also known as “quality” or “emergent” requirements, or the “-ilities” of the
system-to-be. An example non-functional requirement is: Maintain a persistent data backup, for
the cases of power outages.
The term FURPS+ refers to the non-functional system properties:
• Functionality lists additional functional requirements that might be considered, such as
security, which refers to ensuring data integrity and authorized access to information
• Usability refers to the ease of use, esthetics, consistency, and documentation—a system
that is difficult and confusing to use will likely fail to accomplish its intended purpose
• Reliability specifies the expected frequency of system failure under certain
operating conditions, as well as recoverability, predictability, accuracy, and mean time
to failure
• Performance details the computing speed, efficiency, resource consumption,
throughput, and response time
93
• Supportability characterizes testability, adaptability, maintainability, compatibility,
configurability, installability, scalability, and localizability
For example, in terms of usability of our safe home access case study, we may assume a low-
budget customer, so the system will be installed and configured by the developer, instead of
“plug-and-play” operation.
All requirements must be written so that they are testable in that it should be obvious how to
write acceptance tests that would demonstrate that the product meets the requirement. We have
seen earlier example of acceptance tests for functional requirements in Table 2-1. Non-functional
requirements are more susceptible for vague formulations. For example, we often hear that a
system should be “easy to use.” It is difficult to design tests to verify such a claim. There is little
value in writing requirements that are not testable.
For example, for our case study of safe home access system, we envisioned three types of
computing devices. Users will use these devices in different contexts and for different tasks, so
we can expect that they have different usability requirements. We should consider the time
constraints of user type and produce order-of-magnitude time limits for computer interaction
required to accomplish a certain activity. For example, the user interacting with the door device
expects that the number of keystrokes, clicks, or touches will be minimized for quick task
completion. The property manager interacting with the desktop computer is less concerned with
efficiency and more with rich features to review the data and examine trends. Similarly, the
reliability requirements for different devices are likely to be different. The door device must be
highly reliable (e.g., system failure rate of 4 in a year or less), while the desktop application can
tolerate much lower reliability level.
Although at first it may appear easy, the distinction between functional and non-functional
requirements is often difficult to make. More often than not, these requirements are intertwined
and satisfying a non-functional requirement usually necessitates modifications in the system
function. For example, if performance objectives cannot be met, some functional features may
need to be left out.
The reader should be cautioned against regarding non-functional requirements as secondary to
functional requirements. The satisfaction of non-functional requirements must be as thoroughly
and rigorously ensured as that of functional requirements. In either case, satisfaction of a
requirement results in visible properties of the system-to-be, which means they will affect
customer or user satisfaction with the product.
n most cases, not all requirements can be realized because of time or budget constraints.
I Therefore, it is necessary to prioritize the requirements. We have seen examples of assigning
priority weights to requirements in Table 2-1 and Table 2-2, where the weights were guessed by
the customer. A systematic method for prioritizing software product requirements is the
cost- value approach. The basic idea is to determine for each candidate requirement its
cost of implementing and how much value the requirement would have. It is critical that the
customer is involved in requirements prioritization, assisted by tools that help highlight
the tradeoffs.
Requirements prioritization is not helpful if all or most requirements are assigned high priority.
We distinguish four types of requirements:
94
1. Essential: must be fulfilled to make the system acceptable to the customer.
2. Desirable: highly desirable, but not mandatory requirements
3. Optional: might be realized if time and resources permit
4. Future: will not be realized in the current version of the system-to-be, but should
be recorded for consideration in future versions
The priority of requirements determines the order in which they will be implemented.
If the developer is lucky, the customer will arrive with a clear statement of work that needs to be
done (“customer statement of requirements”). In reality, this rarely happens. Requirements for
the system-to-be should be devised based on observing the current practice and interviewing the
stakeholders, such as end users, managers, etc.
To put it simply, you can’t fix something if you don’t know what’s broken. Structured interviews
help in understanding what stakeholders do, how they might interact with the planned system,
and the difficulties they are facing with the existing technology. Agile methodologists recommend
that the customers or users stay continuously involved throughout the project duration, instead
of only providing the requirements initially and disappearing until the system is completed.
(Benefits of this approach were described in 1.2.5).
Sometimes, it is difficult to get the customer to say what they expects from the system.
Using interviews to obtain domain knowledge is helpful, but also difficult. This is because domain
experts use domain-specific terminology and jargon that is unfamiliar to an outsider like a
software engineer. While listening to a domain expert talk, a software engineer may find herself
thinking “These all are words that I know, but together they mean nothing to me.” Some things
may be so fundamental or seem too obvious to a person doing them habitually, that he thinks
those are not worth explaining.
In addition, it is often difficult for the user to imagine the work with a yet-to-be-built system.
People can easily offer suggestions on how to improve the work practices in small ways, but very
rarely can they visualize great leaps. Imagine trying to predict how the Internet would change
doing business before the Internet existed, or how word processors would change writing before
word processors existed. So, they often cannot tell you what they need or expect from the system.
What often happens is that the customer is paralyzed by not knowing what technology could do
and the developer is stuck by not knowing what the customer needs to have. One solution in these
situations is freely drafting up some working instance or prototype, or performing an open
experiment with a mock-up system. That mock-up system can then be used as a base. The
customer can point out what they don’t like about it, and the engineer can ask follow-up
questions, better understand the client, and start work on productive changes.
A popular technique for functional requirements engineering is the use case modeling, which is
described in Section 2.4.
95
e should keep in mind that we are trying to achieve several goals in requirements
W engineering. As Figure 2-1 illustrates, we are trying to understand the problem in the
context of current practice (requirements gathering), then envision, elaborate, and
negotiate potential solutions (requirements analysis), and finally write down an engineering
description of what needs to be developed (requirements specification). Different tools have
been proposed for requirements engineering. As one would expect, none of these tools works
best for all tasks of requirements engineering and for all types of problems. Some tools work
great for requirements gathering, but may not be suitable for requirements analysis or
specification. For example, user stories (2.2.1) work well in requirements gathering and analysis,
but may be less suitable for specification. Other tools work well on all three tasks, but not
for all problem types. For example, use case modeling (2.4) works well on all three tasks,
but only for certain problem types.
Further details are provided in the sections that follow. More tools that are better
suited for different problem types will be described in Chapter 3.
96
(b) C
City C
City B
B
Figure 2-3: Combining the part sizes illustrated on a highway building example (a). Cities may
be connected independently (b), or parts of the product may be “reused” (c).
You eyeball a geographic map of the area and you estimate that the highway A–C will be twice
longer than the highway A–B. So, you estimate the size for the entire effort as 1×s + 2×s = 3×s,
where s is a scaling constant. However, upon more careful inspection you realize that parts of
highways to cities B and C can be shared (reused), as illustrated in Figure 2-3(c). If you choose this
option, you cannot estimate the total size just by adding the individual sizes (AB + AC). Instead,
you need to consider them together. The total effort will be considerably smaller.
Reuse is common in software objects. Consider how common subroutines and libraries are!
Therefore, my concern is that simply adding the story sizes introduces a gross inaccuracy in the
overall effort estimation. Recall the exponential relationship of cost and accuracy (Figure 1-13).
The reader would be mistaken to assume that reuse always means less work. Considering again
the highway analogy, the solution in Figure 2-3(c) may require more effort or cost than the one in
Figure 2-3(b). The infrastructure-sharing solution in Figure 2-3(c) requires building highway
interchanges and erecting traffic signs.
You may wonder, why should anyone bother with reuse if it increases the effort? The reason may
be to preserve resources (conserve the land and protect nature), or to make it easier to connect
all three cities, or for aesthetic reasons, etc. Reducing the developer’s effort is not always the
most important criterion for choosing problem solutions. The customer--who is sponsoring the
project--decides the priorities.
Agile methodologists recommend avoiding dependencies between user stories.
High dependencies between stories make story size estimation difficult. The
assumption is that the dependencies are tackled by adjusting the individual size
estimates, despite that the individual story sizes are still combined in a linear
Sum.
When dependencies are detected, the developer can try the following:
• Combine the dependent user stories into one larger, independent story
• Find a different way of splitting the stories
While an expert developer might easily do this beginner developers simply need to hone these
skills in time. With that said, we encourage beginners to think about and research their own
97
ideas for project effort estimation methods!
98
Product line (or product family) highest abstraction level
System or product
Subsystems/Modules
Packages
Classes/Objects
A simplest manifestation of a system-level design is the familiar “block diagram,” which shows the
subsystems or modules (as rectangular boxes) and their relations (lines connecting the boxes).
However, software architecture is much more than decomposing the system into subsystems.
Software architecture is a set of high-level decisions made during the development and evolution
of a software system. A decision is “architectural” if, given the current level of system scope
(Figure 2-4), the decision must be made by considering the current scope level. Such decision
could not be made from a more narrowly-scoped, local perspective.
99
product line
product or system architecture decisions
architecture decisions
systemic impact
Product line scope
Subsystem scope
local impact
Class scope
Figure 2-5: Architectural decisions are made at certain scope levels and cannot be made at lower
hierarchical levels.
Architectural decisions should focus on high impact, high priority areas that are in strong
alignment with the business strategy. We already discussed some architectural decisions for our
case study system for safe home access in Section 1.3.1 (footnote 5). It might have looked as a
simple decision with a self-evident choice to have a central computer and embedded computers
at each door.
Some key questions that we are faced with include:
Q1: How to decompose the system (into parts)?
Q2: How the parts relate to one another?
Q3: How to document the system’s software architecture?
One way to start is by considering a model hierarchy of different parts of the system. Figure 2-4
shows this. Such diagrams show only the parts of the system and their inclusion hierarchy. They
do not show the dependencies in terms of mutual service uses: which part uses the services of
which other parts?
A good path to designing software architecture (i.e., solution architecture) starts by considering
the problem architecture (Section 2.3.1). That is, we start with the requirements (i.e., the problem
statement), which define how the system will interact with its environment.
Objects through their relationships form confederations, which are composed of potentially many
objects and often have complex behavior. The synergy of the cooperative efforts among the
members creates a new, higher-level conceptual entity.
Organizations are partitioned into departments—design, manufacturing, human resources,
marketing, etc. Of course, partitioning makes sense for certain size of the organization;
partitioning a small organization into departments and divisions does not make much sense.
Similarly, software systems should be partitioned into subsystems or modules where each
subsystem performs a set of logically related functions.
100
Subsystem Subsystem Subsystem
for device for for remote
Decision on system
control administration data access decomposition
RS-232
Interface cable
Computer
Assume we have an embedded processor with a keypad, wired to other hardware components of
the system, as shown in Figure 2-7. The embedded processor accepts “commands” from the
computer via a port and simply passes them on the corresponding device. The intricacies of serial
communication are omitted. If you’re interested in those, see the bibliography review at the end
of this chapter. The embedded processor may in an advanced design become a full-featured
computer, communicating with the main computer via a local area network (LAN).
System architects may decompose an application into subsystems early in design. But subsystems
can be also discovered later, as the complexity of the system unfolds.
The most powerful ways of dealing with complex problems include recognizing patterns, dividing
the problem into smaller subproblems, and solving each individually. This is known as the divide-
and-conquer approach.
101
(a) (b)
Figure 2-8: Contrasting decomposition types: (a) projection; (b) partition.
Problem can be decomposed in different ways, such as “projection” vs. “partition” (Figure 2-8).
Partition isolates the parts from one another—it simplifies by removing the relationships.
Projection just simplifies the representation (by removing some dimensions), while preserving the
relationships between the parts. It allows any kind of overlap between the elements of one
subproblem and the elements of another. We favor projection over partition. It preserves
relationships better than partition does.
For example, consider our first case study of smart home access. Figure 2-9 shows the elements
of the problem domain and how they relate to the system. There are 11 sub-domains of the
problem domain. The key sub-domains are the tenant (1), landlord (2), and the lock (3). Some sub-
domains are physical (people/objects). Others are digital artifacts, such as the list of valid keys
(4), tenant accounts (10), and log of accesses (11). We see that the system is composed of
subsystems (shown as smaller boxes inside the system’s box) that implement different
requirements from Table 2-1. As seen, the concerns of different requirements overlap and the
system-to-be cannot be partitioned neatly . Initially, we consider different requirements as
subproblems of the entire problem and describe the subsystems that solve them. Then we
consider how the subsystems integrate and interact to satisfy all requirements.
We start by identifying some typical elementary problems encountered by software engineers.
There are three key players in software engineering problems:
• the user who uses the system to achieve a goal,
• the software system to be developed,
• and the environment—the rest of the world that may include other systems. These
other systems are considered as “black boxes”. This is because we do not know/care
about their structure.
102
Software-to-be
Figure 2-9: Components of the problem domain for safe home access. Requirements from Table
2-1 specify what the software-to-be should accomplish in the problem domain. We can
decompose the software-to-be into subsystems related to the requirements satisfaction.
Figure 2-10 illustrates some typical elementary software engineering problems. In problems of
type 1.a) the user feeds the system with a document.The system transforms the input document
to an output document. An example is a compiler that transforms source code written in a the
source language into a target language like “object code”. Another example is a PDF writer, which
takes a Web page or a word-processor document and generates a PDF document.
In problems of type 1.b) the system helps the user edit and maintain a richly structured body of
information (Figure 2-10). The information must typically be manipulated in many ways. The data
is long-lived and its integrity is important. Example applications include word- processing, graphics
authoring, or relational database systems.
In problems of type 2 the system is programmed to control the environment. (See second row of
Figure 2-10.) The system continuously observes the environment and reacts to predefined events.
For example, a thermostat monitors the room temperature and regulates it by switching heating
or cooling devices on or off to maintain the temperature near a desired setpoint value.
103
1. User works with computer system 1.a) System transforms input document to output document
(environment irrelevant/ignored)
User
System Environment
3. Computer system intermediates between the 3.a) System observes the environment and displays information
user and the environment
User System Environment 3.b) System controls the environment as commanded by the user
Figure 2-10: Some of the typical elementary problems encountered in software engineering.
In problems of type 3.a) the system monitors the environment and displays the information for
the user. The display may be continuous, or it may be filtered to notify the user only of predefined
events. For example, a patient-monitoring system measures physiological signals and displays
them continuously on a computer screen. Additionally, the system may be programmed to look
for trends, or sudden changes, or anomalous values and alert the clinician (user) by audio signals.
In problems of type 3.b) the system helps the user control the environment. The system receives
and executes the user’s commands. An example is controlling industrial processes. In our first case
study of safe home access (Section 1.3.1), the user commands the system to disarm the door lock
(and possibly activate other household devices).
Complex software engineering problems may combine several elementary problems from Figure
2-10. Consider our case study of safe home access (Figure 2-9). We already mentioned that it
includes the type 3.b) problem of commanding the system to disarm the lock. The requirements
(Table 2-1) also include managing the database of current tenant accounts (REQ5), which is a
problem of type 1.b). The system should also monitor if the door is unlocked for an extended
period of time and lock it automatically (REQ1), which is a problem of type 2.
104
Feeding Transformation Receiving
1. Transformation: subsystem subsystem subsystem
Data
2. b) Simple
editor
workpieces:
User Data repository
Controlling Controlled
2. Required behavior: subsystem subsystem
Monitoring Monitored
3.a) Information display: subsystem subsystem
Display
Controlling Controlled
3.b) Commanded behavior: subsystem subsystem
Operator
To deal with complex problems that involve several subproblems, we apply the divide-and-
conquer approach. We break down the problem into simpler problems, design computer
subsystems to solve each subproblem alone, and then integrate the subsystems into a system
that solves the original problem.
Figure 2-11 illustrates the elementary “building bricks” that correspond to different subproblem
types in Figure 2-10. We continue the discussion of problem decomposition and subsystem
specification in Section 2.4.2. More details will be provided later, in Section 3.3, when we
introduce problem frames.
105
User Interaction User Authentication Archiving
Management of Communication w.
Sensors and Devices Police Station
Figure 2-12: Software packages for the case study system. The system has a layered
architecture, with the three layers as indicated.
Most problems do not start completely new development, but rather reuse existing designs,
software packages, libraries, etc. For example, many contemporary systems are based on Web
architecture, using a browser to access a database or Web services (see Appendix D).
Complementary to bottom-up approach are “top-down” approaches. These are also called
system-level (macro-level), global design approaches. They decompose the system into logical
units or follow some global organizational patterns. By doing this, they help us to “see the forest
for the trees”.
106
The key problem of recombination of subsystems or frames into the system-to-be is the diversity
of infrastructures and platforms used for development. Modern software applications are rarely
written as a single monolithic program. Instead, they are built on top of complex middleware
frameworks such as .NET and Java technology, using multiple programming languages, and run
on several computers with different operating systems. Developers rely on outside libraries,
frameworks, COTS components,(Commercial Off The Shelf) components, etc. The subsystems are
usually distributed over different computers. This diversity of platforms introduces many
unknowns that are hard or impossible to control by the developer.
Even most secure components can be assembled into an unsecure mess.
1. Initiating actor (also called primary actor or simply user): initiates the use
107
case to realize a goal, which depends on the actor’s responsibilities and the
current context.
2. Participating actor (also called secondary actor): participates in the use
case but does not initiate it.
Actors have their responsibilities and seek the system’s assistance in managing them. In our case-
study example of secure home access, the resident’s responsibilities are to maintain the home
secured and in proper order, as well as seek comfortable living. The property manager’s
responsibilities include keeping track of current and departed residents. Maintenance personnel’s
responsibilities include checks and repairs. There are also some physical devices that are not part
of the system but interact with it. They also count as actors for our system, as will be seen later.
To carry out its responsibilities, an actor sets goals, which are time and context-dependent. For
example, a resident leaving the apartment for work has a goal of ensuring the door is locked;
when coming back, the resident’s goal is to open the door and enter the apartment.
To achieve its goals, an actor performs some actions. An action is the triggering of an interaction
with the system. While preparing a response to the actor’s action, the system may need assistance
from external entities besides the actor.
108
At this point, it’s important to define the two main types of actors:
- Initiating: the actor that starts the use case. This actor has an end goal and they
are necessary for the completion of the use case.
- Participating: an actor that plays a ‘helper role’. That is, they support the
completion of the use case.
In Figure 1-9, the system-to-be (ATM machine) needed assistance from a remote datacenter to successfully
complete the use case “Withdraw Cash.” This is why we will distinguish initiating actors and participating actors.
If a participating actor delivers, then the initiating actor is closer to reaching the goal. All actors should have
defined responsibilities. Even the system-to-be itself is an actor--its responsibility is to assist the (initiating) actors
in achieving their goals. To this point we have identified the following actors:
• Resident is the home occupant
• Landlord is the property owner or manager
• Security System is a SMART device to be controlled by the system, such as lock-
mechanism and light-switch, that are controlled by our system (see Figure 1-16)
• Other potential actors: Maintenance, Police, etc. (some will be introduced later)
When considering introducing new actors, the key question is: “Does the system provide different
service(s) to the new actor?” It is important to keep in mind that an actor is associated with a role,
not with a person. Hence, a single actor should be created per role. However, a single person can
appear as different actors—since they may have multiple roles. Similarly, different people may
play the same actor role, perhaps at different times.
To determine our different actors, we must determine the different roles of the system. Roles
may include administrator, teacher, registrar staff, etc. Each actor should represent a role.
Our system may be assisted by other systems in fulfilling the actor’s goal. In this case, the other
systems will become different actors if they offer different type of service to the system. Examples
will be seen later.
Table 2-4 summarizes preliminary use cases for our case-study example.
Table 2-4: Actors, goals, and the associated use cases for the home access control system.
Actor Actor’s Goal (what the actor intends to accomplish) Use Case Name
Landlord To disarm the lock and enter, and get space lit up. Unlock (UC-1)
Landlord To lock the door & shut the lights (sometimes?). Lock (UC-2)
Landlord To create a new user account and allow access to home. AddUser (UC-3)
Landlord To retire an existing user account and disable access. RemoveUser (UC-4)
Resident To find out who accessed the home in someinterval of InspectAccessHistory
time and potentially file complaints. (UC-5)
Resident To disarm the lock and enter, and get space lit up. Unlock (UC-1)
Resident To lock the door & shut the lights (sometimes?). Lock (UC-2)
Resident To configure the device activation preferences. SetDevicePrefs (UC-6)
LockDevice To control the SMART lock mechanism. UC-1, UC-2
LightSwitch To control the lightbulb. UC-1, UC-2
[to be To auto-lock the door if it is left unlocked for a given AutoLock (UC-2)
identified] interval of time.
109
Because the Tenant and Landlord actors have different responsibilities and goals, they will utilize
different use cases and thus they should be seen differently by the system. The new actor can use
more, less, or different use cases than the existing actor(s), as seen in Table 2-4.
Resident
Table 2-4 implies that a software system is developed with a purpose/responsibility—this purpose
is assisting its users (actors) to achieve their goals. There must be an actor intentionally using this
system. The issue of developer’s intentions vs. possible usage scenarios is an important one and
can be tricky to resolve. There is a tacit but important assumption made by individual developers
and large organizations alike. This assumption is that they are able to control the types of
applications in which their products will ultimately be used. Even a very focused tool is designed
not without potential to do other things—a clever user may come up with unintended uses,
whether serendipitously or intentionally.
110
cases do not describe details of the business process. They just identify the user’s role (actor type)
and the capability that the system will provide (i.e. its contribution in achieving the actor’s goals).
The same technique for effort estimation that works for user stories (Section 2.2.3) can be applied
to summary use cases. We can use again user story points and the development velocity to
estimate the project duration by applying equation (1.1), given in Section 1.2.5.. There is another
point system called use case points, but these actually can’t be used for summary use cases. This
is because use case points require detailed use case descriptions. Detailed use case descriptions
require time and effort to obtain, so they will become available only at a later stage in the project
lifecycle (see Section 2.4.3).
111
l
«inc UC7: AuthenticateUser
«particip
«initi
rticip
UC1: Unlock «pa
LockDevice
«in «particip
iti
Tenant
iti «particip
«in
LightSwitch
«pa
«initi UC2: Lock rticip
«initiate +
Landlord Timer
Figure 2-13: UML use case diagram for the device-control subsystem of the home access
system. Compare with Table 2-4.
112
Account Management Subsystem
«initi
UC3: AddUser
«ini «in
ti cl
Landlord ip
«incl
r tic UC4: RemoveUser
« pa
UC8: Login
cl
«in
«initi
UC5: InspectAccessHistory
l
nc
«i
«initi
Tenant UC6: SetDevicePrefs
Figure 2-14: Use cases for the account-management subsystem of the home access system.
The reader should not mistake the use case diagram for use cases. The diagram serves only to
capture an overview of the system services in a concise visual form. It summarizes the system
features and their relationships, without detailing how each feature should operate. Unlike this,
use cases are text stories that detail exactly what happens when an actor attempts to obtain a
service from the system. A helpful analogy is a book’s index vs. contents: a table-of-contents or
index is certainly useful, but the actual content represents the book’s main value. Similarly, a use
case diagram provides a useful overview index, but you need the actual use cases (contents) to
understand what the system does or is supposed to do.
Figure 2-14 shows the use cases for the second subsystem of the safe home access system, which
supports various account management activities. The diagrams in Figure 2-13 and Figure 2-14
form the use case diagram of the entire system.
113
Figure 2-15 shows additional relationships among use cases that can be used to improve the
informativeness of use case diagrams. For example, use cases that share common functionality
can be abstracted in a more general, “base” use case (Figure 2-15(a)). If a user’s goal has several
subgoals, some of which are optional, we can indicate this information in a use case diagram using
the «extend» stereotype. For example, we may design a use case to allow the user to manage his
account. As part of account management, optional activities that may or may not take place are
the inspection of access history and configuring the device-activation preferences (Figure 2-15(b)).
♦ A novice developer frequently identifies user login as a use case. On the other hand, expert
developers argue that login is not a use case. Recall that use case is motivated by user’s goal;
The user initiates interaction with the system to achieve a certain goal. You are not logging in
for the sake of logging in—you are logging in to do some work, and this work is your use case.
ManageUsers
t UC5: InspectAccessHistory
«ex
ManageAccount
«ex
UC3: AddUser UC4: RemoveUser t
UC6: SetDevicePrefs
(a) (b)
Figure 2-15: More relationships among use cases: (a) Use case generalization; (b) Optional use
cases, denoted with the «extend» stereotype.
Unfortunately, there are no firm guidelines of delineating the boundary of the system under
development. Drawing the system boundary is a matter of choice. However, once the boundary
is drawn, the interactions for all the actors must be shown in use cases in which they interact with
the system.
114
Case (a):
Local face recognition
Local
computer
Face
image
Apartment building
Security
camera
NNeetwwoorkk
Case (b):
Remote face recognition FaceReco, Ltd.
Figure 2-16: Alternative cases of face recognition for the secure home access system.
Consider a variation of the home access control system which will be used for an apartment
building, or a community of apartment buildings, rather than a single-family home. The
management demands user identification based on face recognition,. Roughly speaking, a face
recognition system works by taking an image of a person’s face (“mug shot”), compares it with
the known faces, and outputs a Boolean result: “authorized” or “unauthorized” user. Here are
two variations (see Figure 2-16):
(a) You procure face recognition software, install it on your local computer, and link it up with
a standard relational/SQL database for memorizing the faces of legitimate users.
(b) After a preliminary study, you find that maintaining the database of legitimate faces,
along with training the recognition system on learning and unlearning faces, , are overly
complex and costly. You decide that the face recognition processing should be outsourced
to a specialized security company, FaceReco, Ltd. This company specializes in user
authentication, but they do not provide any application- specific services. Thus, you still
need to develop the rest of the access control system.
The first task is to identify the actors, so the issue is: Are the new tools (face recognition software
and relational database) new actors?Or are they part of the system and should not be
distinguished from the system?
In case (a), they are not worth distinguishing, so they are part of the planned system. Although
each of these is a complex software system developed by a large organization, as far as we
developers are concerned, they are just modules that provide data-storage and user-
authentication. Most importantly, they are under our control, so there is nothing special about
them as opposed to any other module of the planned system.
Therefore, for case (a), everything remains the same as in the original design. The use case
diagram is shown in Figure 2-13 and Figure 2-14.
115
UC2: Lock
«pa
rtici
p
UC3: AddUser «particip
«initi
«in
«initi cl «particip
FaceReco, Ltd.
UC4: RemoveUser
Landlord
«incl
UC8: Login
Figure 2-17: Part of the modified use case diagram for that includes a new actor: FaceReco. See
text for details.
For case (b), a part of the new use case diagram is shown in Figure 2-17 (the rest remains the same
as in Figure 2-13 and Figure 2-14). Now we need to distinguish a new actor, the FaceReco
Company, which provides authentication services. There is a need-to-know that they are part of
the process of fulfilling some goal(s) of initiating actors.
Figure 2-18 shows the traceability matrix that maps the system requirements to use cases. Its
purpose is to check that all requirements are covered by the use cases and none of the use cases
is created without a reason (i.e., without a requirement from which it was derived). If a use case
is derived from a requirement, then the corresponding entry in the matrix is checked. The Max
PW (priority weight) row shows the maximum priority of any checked requirement in the column
above. The bottom row shows the Total PW of each use case obtained by summing up the
priorities of the checked requirements in the column above. The Max PW and Total PW values
are used to schedule the work on implementing the sue cases. The highest-priority use cases will
be elaborated, implemented, and delivered the first.
116
Req’t PW UC1 UC2 UC3 UC4 UC5 UC6 UC7 UC8
REQ1 5 X X
REQ2 2 X
REQ3 5 X X
REQ4 4 X X
REQ5 2 X X
REQ6 1 X X X
REQ7 2 X X
REQ8 1 X X
REQ9 1 X X
Max PW 5 2 2 2 1 5 2 1
Total PW 15 3 2 2 3 9 2 3
Figure 2-18: Requirements-to-use-cases traceability matrix for the safe home access case
study. Priority weight (PW) given in Table 2-1. (Traceability continued in Figure 2-28.)
117
Although we do not know what new uses the user will invent for the system, purposeful
development ultimately governs the system design. For example, attempt to burglarize a home
may be a self-contained and meaningful goal for certain types of system users, but this is not what
we are designing the system for. It is not a legal use case; rather, it must be anticipated and treated
as an exception to a legal use case. We will consider such “abuse cases” as part of security and
risk management (Section 2.4.4).
9 The UML standard does not specify a use case schema, so the format for use cases varies across different
textbooks. Additional fields may be used to show other important information, such as non-functional
requirements associated with the use case.
118
Use Case UC-#: Name / Identifier [verb phrase]
Related Require’ts: List of the requirements that are addressed by this use case
Initiating Actor: Actor who initiates interaction with the system to accomplish a
goal Actor’s Goal: Informal description of the initiating actor’s goal
Participating Actors: Actors that will help achieve the goal or need to know about the outcome
Preconditions: What is assumed about the state of the system before the interaction
starts Postconditions: What are the results after the goal is achieved or abandoned; i.e.,
what
must be true about the system at the time the execution of this use case
is
completed
Flow of Events for Main Success Scenario:
→ 1. The initiating actor delivers an action or stimulus to the system (the arrow indicates
the direction of interaction, to- or from the system)
← 2. The system’s reaction or response to the stimulus; the system can also send a
message to a participating actor, if any
→ 3. …
Flow of Events for Extensions (Alternate Scenarios):
What could go wrong? List the exceptions to the routine and describe how they are handled
→ 1a. For example, actor enters invalid data
← 2a. For example, power outage, network failure, or requested data unavailable
…
The arrows on the left indicate the direction of communication: → Actor’s action; ← System’s reaction
Observe that use cases are not specific to object-oriented software engineering. In fact, they are
decidedly process-oriented. This is because they focus on the description of activities. As
illustrated in Figure 1-9(a), at this stage we are not seeing any objects. We see the system as a
“black box” and focus on the interaction protocol between the actor(s) and the black box. Objects,
which populate the black box, are something that we encounter only at the stage of building the
domain model.
119
In step 5, the activity of locking the door is in
brackets, because this is covered under the use
wrong:
120
Use Case UC-7: AuthenticateUser (sub-use case)
Related Requirements: REQ3, REQ4 stated in Table 2-1
Initiating Actor: Any of: Tenant, Landlord
Actor’s Goal: To be positively identified by the system (at the door interface).
Participating Actors: AlarmBell, Police
Preconditions: • The set of valid keys stored in the system database is non-empty.
• The counter of authentication attempts equals zero.
Postconditions: None worth mentioning.
Flow of Events for Main Success Scenario:
121
When writing a usage scenario, you should focus on what is essential to achieve
Latch
the initiating actor’s goal. Avoid the details of how this actually happens. Focus on bolt
the “what” and leave out the “how” for the subsequent stages of the development
lifecycle. For example, in Step 2 of the use case AuthenticateUser, I just state that
Strike
the user should verify their entry ; I do not detail whether this is done by typing on plate Dead
bolt
a keypad, by scanning an RFID tag, or by some biometric technology.
At the time of writing detailed use cases, we also write the corresponding user acceptance tests.
In the context of use cases, a user acceptance test case is a detailed procedure that fully tests a
use case or one of its flows of events. Recall that use cases are part of requirements engineering,
and the customer should help with specifying the acceptance tests. The focus is on what the user
does, not what the system does. This means that the test cases must be designed around the
actual tasks that the user will need to perform. Use-case-based acceptance tests are similar to
acceptance tests described in Section 2.2.1. As mentioned, testing functions that involve multi-
step interaction requires more than just specifying the input data and expected outcomes. Here
we are able to provide detailed steps for pass and fail conditions, because by now we have
elaborated step-by- step scenarios for use cases. Here is an example test case for testing the use
case UC-1.
Test-case Identifier: TC-1.01
Use Case Tested: UC-1, main success scenario, and UC-7
Pass/fail Criteria: The test passes if the user properly verifies they are entering, with less
than a maximum allowed number of unsuccessful attempts to enter(?)
Input Data: Button press on smart device app
Test Procedure: Expected Result:
Step 1. Type in an incorrect keycode and a System beeps to indicate failure;
valid door identifier records unsuccessful attempt in the database;
prompts the user to try again
Step 2. Type in the correct keycode and door System flashes a green light to indicate success;
identifier records successful access in the database;
disarms the lock device
An acceptance test needs to convince the customer that the system works as expected.
122
We continue the elaboration of use cases with the main success scenario for the Lock use case:
Use Case UC-2: Lock
Related Requirements: REQ1, REQ2, and REQ5 stated in Table 2-1
Initiating Actor: Any of: Tenant, Landlord, or Timer
Actor’s Goal: To lock the door & get the lights shut automatically (?)
Participating Actors: LockDevice, LightSwitch, Timer
Preconditions: The system always displays the menu of available functions.
Postconditions: The door is closed and lock armed & the auto-lock timer is reset.
Flow of Events for Main Success Scenario:
→ 1. Tenant/Landlord selects the menu item “Lock”
← 2. System (a) signals affirmation, e.g., “lock armed,” (b) signals to LockDevice to arm
the lock (if not already armed), (c) signal to Timer to reset the auto-lock counter, and
(d) signals to LightSwitch to turn the light off (?)
Flow of Events for Extensions (Alternate Scenarios):
2a. System senses that the door is not closed, so the lock cannot be armed
System (a) signals a warning that the door is open, and (b) signal to Timer to start the
alarm counter
Tenant/Landlord closes the door or pressed a smart device button to do so
System (a) senses the closure, (b) signals affirmation to the Tenant/Landlord, (c)
signals to LockDevice to arm the lock, (d) signal to Timer to reset the auto-lock counter,
and (e) signal to Timer to reset the alarm counter
Note that in this case, the auto-lock timer appears as both the initiating and participating actor
for this use case. (This is also indicated in the use case diagram in Figure 2-13.) This is because if
the timeout time expires before the timer is reset, Timer automatically initiates the Lock use case,
so it is an initiating actor. Alternatively, if the user locks the door before the timeout expires, the
timer will be reset, so it is an offstage actor, as well.
I also assume that a single Timer system can handle multiple concurrent requests. In the Lock use
case, the timer may be counting down the time since the lock has been disarmed. At the same
time, the system may sense that the door is not closed, so it may start the alarm timer. If the door
is not shut within a given interval, the system activates the AlarmBell actor and may notify the
Police actor.
You may wonder: why not just say that the system will somehow handle the auto-lock
functionality? Why go into the details of how it works? Technically, the timer is part of the system,
so why should it be declared an external actor?! Recall that the system is always passive—it reacts
to an external stimulus, but does nothing on its own initiative. Thus, to get the system perform
auto-lock, somebody or something must trigger it to do so. This is the responsibility of Timer.
Timer is an external stimulus source relative to the software under development--even though it
will be part of the end hardware-plus-software bundle .
Next follows the description of the AddUsers use case:
123
Initiating Actor: Landlord
Actor’s Goal: To register new or remove departed residents at runtime.
Participating Actors: Tenant
Preconditions: None worth mentioning. (But note that this use case is only available
on the main computer and not at the door keypad.)
Postconditions: The modified data is stored into the database.
Flow of Events for Main Success Scenario:
Landlord selects the menu item “ManageUsers”
2. Landlord identification: Include Login (UC-8)
System (a) displays the options of activities available to the Landlord (including “Add
User” and “Remove User”), and (b) prompts the Landlord to make selection
Landlord selects the activity, such as “Add User,” and enters the new data
System (a) stores the new data on a persistent storage, and (b) signals completion
Flow of Events for Extensions (Alternate Scenarios):
4a. Selected activity entails adding new users: Include AddUser (UC-3)
4b. Selected activity entails removing users: Include RemoveUser (UC-4)
The Resident is a supporting actor for this use case, because theywill input their identification
(smart device verification) during the registration process. Note that in UC-3 we include the
subordinate use case Login (UC-8), which is not the same as AuthenticateUser, numbered UC-7.
The reason is that UC-7 is designed to authenticate persons at the entrance(s). Conversely, user
management is always done from the smart device application, so we need to design an entirely
different use case. The detailed description of the use case AddUser will be given in Problem 2.19.
RemoveUser is similar to AddUser.
In Table 2-4 we introduced UC-5: Inspect Access History, which roughly addresses REQ8 and REQ9
in Table 2-1. I will keep it as a single use case, although it is relatively complex and the reader may
wish to split it into two simpler use cases. Here is the description of use case UC-5:
124
retrieves the records from the Database
→ 5. Database returns the matching records
6. System (a) additionally filters the retrieved records to match the actor’s search criteria;
← (b) renders the remaining records for display; and (c) shows the result for
Resident/Landlord’s consideration
→ 7. Resident/Landlord browses, selects “interesting” records (if any), and requests further
investigation (with an accompanying complaint description)
8. System (a) displays only the selected records and confirms the request; (b) archives the
← request in the Database and assigns it a tracking number; (c) notifies Landlord about
the request; and (d) informs Tenant/Landlord about the tracking number
The following example illustrates deriving a use case in a different domain. The main point of this
example is that use cases serve as a vehicle to understand the business context and identify the
business rules that need to be implemented by the system-to-be.
So then, dismissing an employee is as simple as deleting a list item! I pointed out that in real world
nothing works so simple. We are not talking about some arbitrary database entries that can be edited
as someone pleases. These entries have certain meaning and business significance and there must be
some rules on how they can be edited. This is why the developer must talk to the customer to learn
the business rules and local laws. Even a student team doing an academic project (and not having a real
customer) should visit a local restaurant and learn how it operates. As a minimum, they could do some
Web research. For example, employee rights in the state of New York are available here:
http://www.ag.ny.gov/bureaus/labor/rights.html. The manager must ensure that the employee
received any remaining pay; that the employee returned all company belongings, such as a personal
computer, or whatever; the manager may also need to provide some justification for the termination;
etc. As a result, it is helpful to refine our requirement:
125
REQ8′: The manager shall be able to manage employee records for newly hired employees, job
reassignments and promotions, and employment termination in accord with local laws.
Back to the drawing board, and the second version looked like this:
Use Case UC-10: Terminate Employee (SECOND VERSION)
Related Requirem’ts: REQ8
Initiating Actor: Manager
Actor’s Goal: To fire or layoff an employee.
Participating Actors:
Preconditions: Removed employee is not currently logged into the system.
Removed employee is has already clocked out.
Removed employee has returned all company belongings.
Postconditions: System successfully updated Employee List.
Failed End Condition: Employee List failed to update.
Flow of Events for Main Success Scenario:
Manager selects Delete option next to employee’s name
2. System asks Manager to confirm that the selected employee should be deleted from list
Manager confirms action to delete the employee
4. (a) System confirms the employee has been paid; (b) removes the employee from
Employee List; (c) updates Employee List
Flow of Events for Extensions (Alternate Scenarios):
3a. Manager selects Cancel option
System does not delete the employee
4a. System alerts Manager that the employee has not been paid
1. System does not remove the employee from employee roster and aborts this use case
System opens Payroll <<include>> ManagePayroll (UC-13)
I thought this is an amazing trick: whatever you find difficult to solve in your use case, just state it in
the preconditions, so that the initiating actor ensures that the system can do its work smoothly! The
user serves the system instead of the system serving the user. Compared to the first version, almost
nothing was changed except that all the hard issues are now resolved as preconditions. Also, in step
4(a), it is not clear how can System confirm that the employee has been paid? And if the employee has
not been paid, the alternative scenario throws the user into another use case, ManagePayroll (UC-13),
where he will again just update some database record. However, updating a database record does not
ensure that the employee has actually received his payment!
In the age of automation, we should strive to have computer systems do more work and human users
do less work. A professionally prepared use case for restaurant employment termination should look
something like this:
Use Case UC-10: Terminate Employee (THIRD VERSION)
Related Requirem’ts: REQ8
Initiating Actor: Manager
Actor’s Goal: To fire or layoff an employee.
Participating Actors:
Preconditions:
Postconditions: Employee’s record is moved to a table of past employees for auditing
purposes.
Failed End Condition:
Flow of Events for Main Success Scenario:
Manager enters the employee’s name or identifier
System displays the employee’s current record
Manager reviews the record and requests termination of employment
4. System asks the manager to select the reason for termination, such as layoff, firing, etc.
and the date when the termination will take effect
126
→ 5. Manager selects the reason for termination and the effective date
← 6. (a) System checks that in case of firing the decision is justified with past written warnings
documenting poor performance, irresponsibility or breaches of policy.
(b) System informs the Manager about the benefits the employee will receive upon
departure, such as severance pay or unused vacation days, and asks the Manager to
confirm after informing the employee (in person or by email)
→ 7. Manager confirms that the employee has been informed.
← 8. (a) System makes a record of any outstanding wages and the date by which they should
be mailed to the employee as required by the local laws. A new record is created in a
table of pending actions.
(b) System checks if the employee is currently logged in into the company’s computer
system; if yes, it automatically logs off and blocks the employee’s account
(c) System checks the employee record and informs the Manager the employee before
leaving should return any restaurant-owned uniforms, keys or property that was issued
to the employee
→ 9. Manager confirms that the employee has returned all company belongings
← 10. System moves the employee record to a table of past employees, informs the Manager,
and queries the Manager if he or she wishes to post a classifieds advertisement for a new
employee
→ 11. Manager declines the offer and quits this use case
Flow of Events for Extensions (Alternate Scenarios):
6a. System determines that the decision firing has not been justified with past written warnings
← 1. System informs the Manager that because of a lack of justification, the company may be
liable to legal action, and asks the Manager to decide whether to continue or cancel
→ 2. Manager selects one option and the System continues from Step 6(b) in Main
Scenario 9a. Manager selects the items currently with the employee
← 1. System (a) asks the Manager whether to continue or suspend the process until the
employee has returned all company belongings; (b) saves the current unfinished
transaction in a work-in-progress table and sets a period to send reminders for completion
Note that preconditions are not indicated because I could not think of any condition that, if not met,
would make impossible the execution of this use case. Similarly, no postconditions are indicated. One
may think that an appropriate precondition is that this employee should exist in the database.
However, it is conceivable that in Step 2 of the main success scenario the system cannot find any record
of this employee, in which case this should be handled as an alternate scenario.
An additional feature to consider may be that the system initiates a web advertisement to fill the vacant
position created by terminating this employee. It is great to invent new features, but the developer
must make it clear that adding new features will likely postpone the delivery date and increase project
costs. Only our customer can make such decisions.
127
(a)
: System
User AlarmBell Police
«initiating actor» «supporting actor» «offstage actor»
select function(“unlock")
signal: invalid
press prompt to
retry
sound alarm
notify intrusion
Figure 2-20: UML System sequence diagrams for the Unlock use case: (a) main success scenario;
(b) burglary attempt scenario. The diagram in (b) shows UML “loop” interaction frame, which
delineates a fragment that may execute multiple times.
The key purpose of system sequence diagrams is the same as the key purpose of use cases. They
serve to represent what information must pass the system boundary and in what sequence.
Therefore, a system sequence diagram can contain only a single box, named System, in the top
row, which is our system-to-be. All other entities must be actors. At this stage of the development
cycle, the system is still considered a whole and must not be divided into parts.
It may appear that we are “peering inside the black box” when stating what the system does
internally during the actor ↔ system exchanges. But, note that we are specifying the “what” that
the black box does, not “how” this gets done.
128
Use Cases for Requirements Engineering
Use cases are a popular tool for gathering requirements and specifying system behavior.
However, I do not want to leave the reader with an illusion that use cases are the ultimate solution
for requirements analysis. As any other tool, they have both virtues and shortcomings. I hope that
the reader got some thoughts going from the preceding presentation. One shortcoming is that,
since you start writing the use cases given the requirements, you might question the suitability of
use cases for gathering requirements. It may be questioned.
Also, use cases are not equally suitable for all problems. Considering the projects defined in
Section 1.5 and the book website (given in Preface), Use cases seem to be suitable for the
restaurant automation project, as shown by the projects defined in Section 1.5 and the book
website. In general, the use case approach is best suited for the reactive and interactive systems.
However, they do not adequately capture activities for systems that are heavily algorithm-
driven.Such systems include the virtual biology lab and financial markets simulation (see book
website for these).The use case approach also may not adequately capture activities for systems
that are heavily data-intensive, such as databases or large data collections. Some alternative or
complementary techniques are described in Chapter 3.
Identifying a fault in a process requires determining the condition upon which the fault occurs. If
a risk is detected
129
during requirements analysis, actions should be taken to fix the use case design and prevent a
fault from occurring. A fault is an undesired state of the business process.
The Risk Identification phase is where risk analysis is carried out to identify risks in the process
model to be designed. In this phase, traditional risk analysis methods--such as IEC 61025 Fault
Tree Analysis (FTA) and Root Cause Analysis-can be employed. The output of this phase is a set of
risks, each expressed as a risk condition.
For more on risk analysis, seek out the following resource:
M. Soldal Lund, B. Solhaug, and K. Stolen. Model-Driven Risk Analysis. Springer, 2011.
Risk analysis involves more than just considering “what could go wrong” in different steps of
use-case scenarios. It is possible that each step is executed correctly, but the overall system fails.
Such scenarios represent misuse cases. For example, an important requirement for our
safe- home-access system is to prevent dictionary attacks (REQ4 in Table 2-1). As described later
in Section 2.5.2, we need to count the unsuccessful attempts, but also need to reset the
counter if the user leaves before providing a valid key or reaching the maximum
allowed number of unsuccessful attempts. To detect such situations, the system may
run a timer for maxAttemptPeriod duration and then reset the counter of unsuccessful
attempts.
Assume that an intruder somehow learned the maximum of allowed unsuccessful
attempts and maxAttemptPeriod. The intruder can try a dictionary attack with the
following misuse case:
invalid-key, invalid, … ≤ maxNumOfAttempts ; wait maxAttemptPeriod ; invalid, invalid, …
To ensure fault tolerance, a stand-alone system should monitor the state-variable values and
prohibit the values out of the safe range, e.g., by overwriting the illegal value. Ideally, a different
backup system should be designed for each state variable. This mechanism can work even if it is
unknown which part of the main program is faulty and causes the illegal value to occur.
A positive aspect of a stand-alone, one-task-only system is its simplicity and lack of dependencies.
This simplicity isinherent in the main system, which makes it resilient. A negative aspect is that
the lack of dependencies makes it short-sighted----not much aware of its environment and unable
to respond in sophisticated way.
130
(a) (b)
System Domain Model
Actor
Actor
(AT
M
Figure 2-21: (a) Use case model sees the system as a “black box.” (b) Domain model peers inside
the box to uncover the constituent entities and their (static) relations that make the black box
behave as described by its use cases.
History shows that projects succeed more often when requirements are well managed.
Requirements provide the basis for agreement with the users and the customer, and they serve
as the foundation for the work of the development team. Software defects often result
from misunderstanding the requirements, not just because of inadequate developmental skills.
This means that requirements provide a vital linkage to ensure that teams deliver systems that
solve real business problems. You need to ensure that you “do the right thing—not just the right
way.”
When faced with a difficult decision, it is a good idea to ask the customer for help. After all, the
customer can judge best what solution will work best for them--and they will easier accept
compromises if they were involved in making them. However, this is not always simple. Consider
the projects described at the book website. Asking the customer works fine in the restaurant
automation project. Even in the virtual biology lab, we can interview a biology course instructor
to help with clarifying the important aspects of cell biology. Course registration systems can be
evaluated by asking students and registrar staff for feedback. However, who is your customer in
the cases of vehicle traffic monitoring (Section 1.5.1)? As discussed in the description of the traffic-
monitoring project, we are not even sure whom the system should be targeted to.
More about requirements engineering and system specification can be found in Chapter 3.
“I am never content until I have constructed a mechanical model of the subject I am studying. If I
succeed in making one, I understand; otherwise I do not.” —Lord Kelvin (Sir William Thomson)
Use cases looked at the system’s environment (actors) and the system’s external behavior. Now
we turn to consider the inside of the system. This shift of focus is contrasted in Figure 2-21.
131
Step 1: Identifying the boundary concepts Step 2: Identifying the internal concepts
Concept 1
Actor C Actor C
Concept 6
Actor A Actor A
Concept 5
Internal
concepts
Concept 4 Concept 4
Actor B Actor D Actor B Actor D
Figure 2-22: A useful strategy for building a domain model is to start with the “boundary”
concepts that interact directly with the actors (Step 1), and then identify the internal concepts
(Step 2).
132
Organizations are often fronted by a point-of-contact person. A common pattern is to have a
specialized worker to take orders from the clients and orchestrate the workings of the workers
inside the system. This type of object is known as Controller. For a complex system, each use case
or a logical group of use cases may be assigned a different Controller object.
When identifying positions, remember that no task is too small—if it needs to be done, it must be
mentioned explicitly and somebody should be given the responsibility. Table 2-5 lists the
responsibilities and the worker titles (concept names) to whom the responsibilities are assigned.
In this case, it just so happens that a single responsibility is assigned to a single worker, but this is
not always the case. Complex responsibilities may be assigned to multiple workers. Many simple
responsibilities may be assigned to a single worker. Further discussion of this issue is available in
the solution of Problem 2.29 after this chapter.
Table 2-5: Responsibility descriptions for the home access case study used to identify the
concepts for the domain model. Types “D” and “K” denote doing vs. knowing responsibilities,
respectively.
Responsibility Description Typ Concept Name
Coordinate actions of all concepts associated with a use case, a logical D Controller
grouping of use cases, or the entire system and delegate the work to other
concepts.
Container for user’s authentication data, such as pass-code, K Key
timestamp,
door identification, etc.
Verify whether or not the key-code entered by the user is valid. D KeyChecker
Container for the collection of valid keys associated with doors and users. K KeyStorage
Operate the lock device to armed/disarmed positions. D LockOperator
Operate the light switch to turn the light on/off. D LightOperator
Operate the alarm bell to signal possible break-ins. D AlarmOperator
Block the input to deny more attempts if too many unsuccessful attempts. D Controller
Log all interactions with the system in persistent digital storage. D Logger
Based on Table 2-5 we draw a draft domain model for our case-study #1 in Figure 2-23. During
analysis, objects are used only to represent possible system state. No effort is made to describe
how they behave. It is the task of design (Section 2.6) to determine how they behave. For this
reason, objects at analysis time have no methods/operations (as seen in Figure 2-23).
UML does not have designated symbols for domain concepts, so it is usual to adopt the symbols
that are used for software classes. I added a smiley face or a document symbol to distinguish
“worker” vs. “thing” concepts. Workers are assigned mainly doing responsibilities, while things
are assigned mainly knowing responsibilities. This labeling serves only as a “scaffolding,” to aid
the analyst in the process of identifying concepts. The distinction may not always be clear cut,
because some concepts may combine both knowing- and doing types of responsibilities. In such
cases, the concepts should be left unlabeled. This is the case for KeycodeEntry and StatusDisplay
in Figure 2-23. Like a real-world scaffolding, which is discarded once construction is completed,
this scaffolding is also temporary in nature.
133
Another useful kind of scaffolding is classifying concepts into three categories:
134
Symbolizes Symbolizes
“worker”-type “thing”-type
Symbolizes
concept. concept.
«entity» “thing”-type
KeyChecker concept.
«entity»
KeyStorage
«boundary» «entity»
KeycodeEntry Key
«boundary» «control»
StatusDisplay Controller LockDevice
«boundary»
HouseholdDeviceOperator
Resident LightSwitch
Figure 2-23: Partial domain model for the case study #1, home access control.
«boundary»
HouseholdDeviceOperator
At first, Key may be considered a «boundary» because keys are exchanged between the actors
and the system. On the other hand, keys are also stored in KeyStorage. This particular concept
corresponds to neither one of those, because it contains other information, such as timestamp
and the door identifier. Only pass-codes identifying the actors are exchanged between the actors
and the system (concept: KeycodeEntry). This information is transferred to the Key concept.
Figure 2-23 shows a single concept for operating household devices. This concept is obtained by
modeling common properties of different device-operating concepts in Table 2-5. This kind of
modeling is sometimes called abstracting in the industry. We show such generalization
diagrammatically as in Figure 2-24. Later, more detailed consideration will reveal the need for
distinguishing different device operators (see Figure 2-25(b)).
Use Case 5 is Inspect Access History. Its responsibilities can be derived based on the detailed use
case description. The detailed use case description can be found in Section 2.4.3. We can gather
the doing (D) and knowing (K) responsibilities as given in Table 2-6.
135
136
Table 2-6: Responsibility descriptions for UC-5: Inspect Access History of the home access case
study.
I t is worth noting at this point how an artifact from one phase directly feeds into the
subsequent phase. We have use case scenarios feed into the system sequence diagrams, which
in turn feed into the domain model. This traceability property is critical for a good
development process. This is because the design elaboration progresses systematically,
without great leaps that are
hard to follow.
A domain model is similar to a concept map (described in Section 1.3). Both represent concepts
and their relations/associations. However, domain model is a bit more complex. It can indicate
the concept’s stereotype as well as its attributes. Attributes are described in the next section.
Note that we construct a single domain model for the whole system. The domain model is
obtained by examining different use case scenarios, but they all end up contributing concepts to
the single domain model.
137
2.5.2 Concept Associations and Attributes
Associations
Associations describe who needs to work together and why., It does not describe how they work
together. Associations for use case UC-5: Inspect Access History can be derived based on the detailed
description of UC-5 (Section 2.4.3). Let’s use the detailed description of UC-5 to derive its
associations.
138
Table 2-7: Identifying associations for use case UC-5: Inspect Access History.
Figures 2-25(a) and 2-26 also show the associations between the concepts, represented as lines
connecting them. Each line also has the name of the association and sometimes an optional
“reading direction arrow”, ►. The labels on the association links do not signify the function
calls.Think of these labels as just indicating that there is some collaboration anticipated between
the linked concepts. It is as if to know whether person X and person Y collaborate, so they can be
seated in adjacent offices. Similarly, if objects are associated, they logically belong to the same
“package.”
139
Keep in mind that it is more important to identify the domain concepts than their associations
(and attributes). Every concept that the designer can discover should be mentioned. But an
association should only be shown if it needs to be mentioned. If the association is obvious, it
should be omitted from the domain model. For example, in Figure 2-25(a), the association 〈
Controller– obtains–Key 〉 is fairly redundant. Several other associations could as well be omitted,
because the reader can easily infer them. Thisshould be done particularly in schematics that are
about to become cluttered. Remember, clarity should be preferred to accuracy. If the designer is
not sure, some of these can be mentioned in the text accompanying the schematic, rather than
drawn in the schematic itself.
140
«entity» retrieves valid keys «entity»
(a) KeyChecker
KeyChecker KeyStorage
verifies
conveys requests
«boundary»
KeycodeEntry
«control»
Controller «boundary»
HouseholdDeviceOperator
numOfAttempts
maxNumOfAttempts deviceStatuses
(b)
Figure 2-25: (a) Domain model from Figure 2-23 completed with attributes and
associations. (b) Concepts derived from HouseholdDeviceOperator in Figure 2-24.
Attributes
The domain model may also include concept attributes, as is for example shown in Figure 2-25.
Example attributes are deviceStatuses, with valid values “activated” and “stopped”.
deviceStatuses record the current state of the physical devices operated by
HouseholdDeviceOperator. Careful consideration reveals that a single household-device-operator
concept is not sufficient. Although all physical devices share a common attribute (deviceStatus),
they also have specific needs. This is shown in Figure 2-25(b). The lock device needs to be armed
after an auto-lock interval, so the corresponding concept needs an extra attribute called
autoLockInterval.
141
conveys requests
Landlord
prepares
Resident Database
Figure 2-26: The domain model for UC-5: Inspect Access History of home access control.
We also discussed allowing the user to explicitly set the interval to hold the lock open. This
requires another attribute. The LightOperator needs to check the room before activating the light
bulb, so it is associated with the illumination detector concept. The MusicPlayerOperator needs
the playlist of tracks, so it is associated with the Playlist concept. Even the deviceStatus attribute
may have different values for different devices. For example, “disarmed” and “armed” for
LockOperator; “unlit” and “lit” for LightOperator; etc .These are more descriptive than the generic
“activated” and “stopped”. Although device operators differ from one another, they also share
common properties, so it is useful to indicate in the domain model diagram that they are related
through the common base concept HouseholdDeviceOperator (Figure 2-25(b)).
Table 2-8 shows how the subset of attributes related to use case UC-5: Inspect Access History is
systematically derived based on the detailed description of UC-5 (Section 2.4.3).
Table 2-8: Attributes for use case UC-5: Inspect Access History.
Concept Attributes Attribute Description
user’s Used to determine the actor’s credentials, which in turn specify
Search identity what kind of data this actor is authorized to view.
Request search Time frame, actor role, door location, event type (unlock, lock,
parameters power failure, etc.).
search Copied from search request; needed to Filter the retrieved
Postprocessor
parameters records to match the actor’s search criteria.
records list List of “interesting” records selected for further investigation.
complaint
Investigation Describes the actor’s suspicions about the selected access records.
description
Request tracking
Allows tracking of the investigation status.
number
current
Archiver tracking Needed to assign a tracking number to complaints and requests.
number
contact Contact information of the Landlord who accepts complaints and
Notifier information requests for further investigation.
One more attribute that could be considered is for the Page Maker to store the data received
from Database Connection. Recall that earlier we merged the Postprocessor concept with Page
142
Maker, which now also has the responsibility to filter the retrieved records to match the actor’s
search criteria.
143
In Figure 2-25(a), another possible candidate attribute is numOfKeys in the KeyStorage. However,
this is a kind of trivial attribute not worth mentioning, because it is self-evident that the storage
should know how many keys it holds inside.
An attribute numOfAttempts counts the number of failed attempts for the user before sounding
the alarm bell, to tolerate inadvertent errors when entering the key code. In addition, there
should be defined the maxNumOfAttempts constant.
But here is the issue: which concept should possess these attributes? Because a correct key is
needed to identify the user, the system cannot track a user over time if it does not know user’s
identity (a chicken and egg problem!). One option is to introduce real-world constraints, such as
temporal continuity, which can be stated as follows. It is unlikely that a different user would
attempt to open the doors within a very short period of time. Thus, all attempts within, say, two
minutes can be ascribed to the same user10. For this we need to introduce an additional attribute
maxAttemptPeriod or, alternatively, we can specify the maximum interval between two
consecutive attempts, maxInterAttemptInterval.
The knowledge or expertise required for the attempts-counting worker comprises the knowledge
of elapsed time and the validity of the last key typed in within a time window. The responsibilities
of this worker are:
1. If numOfAttempts ≥ maxNumOfAttempts, sound the alarm bell and reset
numOfAttempts = 0
2. Reset numOfAttempts = 0 after a specified amount of time (if the user discontinues the
attempts before reaching maxNumOfAttempts)
3. Reset numOfAttempts = 0 after a valid key is presented
A likely candidate concept to contain these attributes is the KeyChecker, because it is the first to
know the validity of a presented key. On the other hand, if we introduce the AlarmOperator
concept (Figure 2-24), then one may argue that AlarmOperator should contain all the knowledge
about the conditions for activating the alarm bell. However, we should remember:once the
threshold of allowed attempts is exceeded, the system should activate the alarm, but also deny
further attempts. Keep in mind the detailed description of UC-7 (*insert link*).
In Table 2-5, the responsibility for blocking the input of more attempts was assigned to the
Controller. Therefore, we decide that the Controller is the best concept to place the attributes
related to counting unsuccessful attempts, This is shown in Figure 2-25(a).
10 Of course, this assumption is only an approximation. We already considered a misuse case in Section
2.4.4. We could imagine, for instance, the following scenario. An intruder makes numOfAttempts =
maxNumOfAttempts - 1 failed attempts at opening the lock. At this time, a tenant arrives and the intruder
sneaks away unnoticed. If the tenant makes a mistake on the first attempt, the alarm will be activated,
and the tenant might assume that the system is malfunctioning. Whether the developer should try to
address this scenario depends on the expected damages, as well as the time and budget constraints.
144
Professional user
Speaks Esperanto
Conformity-driven Potential user
Expected commands: Speaks Klingon
Ekzemplero, Alglui, Curiosity-driven
Redakti,
e a Viŝi Expected commands:
nIH, Suq, naw‘, Degh
Figure 2-27: In domain analysis, we look at the external world from inside out and specify only
what the system-to-be needs to know to function as required.
As already said, during the analysis we should make every effort to stay with what needs to be
done and avoid considering how things are done. Unfortunately, as seen, this is not always
possible. The requirement of tolerating inadvertent typing errors (“unsuccessful attempts”) has
led us into considerable design detail and, worse, we are now committed to a specific technical
solution of the problem.
When developing a software-based system, we are modeling the user and environment in the
software. The model incorporates internal structures representing the problem domain. These
structures include data representing entities and relations in the domain, and a program
prescribing how these data may be manipulated. Modeling involves simplification and abstraction.
The purpose of simplification is to make the development manageable: The system should solve
one problem, not all problems.
As discussed in Section 1.2.3, the analyst needs to consider not only what needs to be done, but
also how it can be done—what are feasible ways of doing it. We cannot limit the domain analysis
to the inside of the system-to-be, because all useful systems operate by interacting with their
environment. We need to know what is at our disposal in the external world.
145
Domain Concepts
SearchRequest InterfacePage
Notifier InvestigationRequest
HouseholdDeviceOperator
DatabaseConnection
Key KeyStorage
Controller-SS2
Controller-SS1
KeycodeEntry
StatusDisplay
KeyChecker
PageMaker
Archiver
Use
PW
Case
UC1 15 X X X X X
UC2 3 X X X
UC3 2 X X X X
UC4 2 X X X X
UC5 3 X X X X X X X X
UC6 9 X X X X
UC7 2 X X X X X
UC8 3 X X X X
Figure 2-28: Use-cases-to-domain-model traceability matrix for the safe home access case
study. (PW = priority weight) (Continued from Figure 2-18 and continues in Figure 2-36.)
he traceability matrix of the domain model is shown in Figure 2-28. This matrix traces the
T domain concepts to the use cases that the domain concepts were derived from. This mapping
continues the development lifecycle traceability from Figure 2-18. Note that we have two
Controller concepts. Controller-SS1 for the first subsystem controls the devices (Figure 2-
25(a)). Controller-
SS2 for the second subsystem supports desktop interaction with the system (Figure 2-26).
A more detailed traceability may be maintained for critical projects. For example, risk analysis
traceability (Section 2.4.4) traces potential hazards to their specific cause; identified mitigations
to the potential hazards; and specific causes of software-related hazards to their location in the
software.
146
Preconditions
• set of valid keys known to the system is not empty
• numOfAttempts < maxNumOfAttempts
• numOfAttempts = 0, for the first attempt of the current user
Postconditions
• numOfAttempts = 0, if the entered Key ∈ Valid keys
• current instance of the Key object is archived and destroyed
The system should be fair, so each user should be allowed the same number of retries
(maxNumOfAttempts). Thus, the precondition about the system for a new user is that
numOfAttempts starts at zero value. (I already discussed the issue of detecting a new user and left
it open, but let us ignore it for now.) The postcondition for the system is that, after the current
user ends the interaction with the system, numOfAttempts is reset to zero.
Operation
Lock
Preconditions
None (that is, none worth mentioning)
Postconditions
• lockStatus= “armed”, and
• lightStatusremains unchanged (see below)
In the postconditions for Lock, we explicitly state that lightStatus remains unchanged
because this issue may need further design attention before it’s fully solved. For example, we may
want to somehow detect the last person leaving the home and turn off the light behind them.
The operation postconditions specify the guarantees of what the system will do, given that the actor
fulfilled the preconditions for this operation. The postconditions must specify the outcomes for
worst-case vs. average-case vs. best-case scenarios, if such are possible.
“A designer knows he has achieved perfection not when there is nothing left to add, but when there is
nothing left to take away.” —Antoine De Saint-Exupéry
“… with proper design, the features come cheaply. This approach is arduous, but continues to succeed.”
—Dennis Ritchie
Analysis (Section 2.5) dealt with what is needed for our system. We determined what workers
(concepts) are needed to make this possible.. Design deals with how the elements of the system
work and interact. Therefore, design is mainly focused on the dynamics of the system. Unlike
analysis, where we deal with abstract concepts, here we deal with concrete software objects.
147
Design
System Sequence Diagram Sequence Diagram
checkKey()
User
«initiating actor»
: System tem Timer
«offstage actor»
sk := getNext()
select function(“unlock")
start ("duration“)
Figure 2-29: Designing object interactions: from system sequence diagrams to interaction
diagrams. The magnifier glass symbolizes looking at interactions inside the system.
We already encountered system sequence diagrams in Section 2.4.3. As Figure 2-29 illustrates, in
the design phase, we are zooming-in inside the system and specifying how its software objects
interact to produce the behaviors observed by actors. Figure 2-30 illustrates one way to think
about the design problem is illustrated in Figure 2-30. Imagine that you draw a map showing the
actors and objects as “stations” to be visited while executing a use case scenario. The goal of
design is to “connect the stations” in a way that is in some sense “optimal.” We know the path
starts with the initiating actor, because the purpose of the system is to assist the initiating actor
in achieving a goal. The path also ends with the initiating actor after the system returns the
computation results. In between, the path should visit the participating actors. So, we know the
entry and exit point(s), and we know the computing responsibilities of concepts. To refresh this,
see Section 2.5.
Objects need to be called to fulfill their doing responsibility, and we need to decide how to
connect the stations. That is, we need to assign the calling responsibilities.Who calls each object?
Who creates, uses, and updates each object? A software designer’s key activity is assigning
responsibilities to the software objects acquired in domain analysis.
Initially, we start designing the object interactions using the concepts from the domain model. As
we progress, we elaborate our design and better understand what can be implemented and how.
In doing this, we start to need to substitute some concepts with actual classes. It is important to
trace the evolution from the abstract domain model to specific classes.Section 2.6.2 discusses
this.
148
:InterfacePage :SearchRequest :Controller :PageMaker :DatabaseConn :Archiver :Notifier :InvestigRequest
Resident Database Landlord
Figure 2-30: The design problem seen as “connecting the dots” on the “map” of
participating objects.
Consider, for example, use case UC-5: Inspect Access History. For this use case, the doing (D) and
knowing (K) responsibilities are given in Table 2-6 . Suppose that we want to design the
interactions only for Steps 4 – 6 of this use case. Start at the point when the system receives the
search criteria from the actor. Stop at the point when an HTML page is prepared and sent to
actor’s browser for viewing. Dynamic object interactions can be represented using UML sequence
diagrams . See Sidebar 2.5).
Figure 2-31(a) shows the dilemma of responsibility assignment for the example of use case UC-5.
First, Table 2-6 has objects Archiver, Notifier, and Investigation Request. We observe that these
do not participate in Steps 4–6 of UC-5. Hence, we need to consider only Database Connection
and Page Maker. (Controller participates in every interaction with the initiating actor.)
Second, because this is a Web-based solution, the design will need to be adjusted for the Web
context. For example, Interface Page will not be a class, but an HTML document (with no class
specified). The Search-Request will be sent from the browser to the server as plain text embedded
in an HTTP message.
List of the responsibilities to be assigned (illustrated in Figure 2-31(a)):
R1. Call Database Connection (to fulfill Rs5, defined in Table 2-6 as: retrieve the records from
the database that match the search criteria)
R2. Call Page Maker (to fulfill Rs3, defined in Table 2-6 as: render the retrieved records into
an HTML document)
There is also the responsibility (R3) to check if the list of records retrieved from the database is
empty (because there are no records that match the given search criteria). Based on the outcome,
a different page will be shown to the actor.
149
R1.
interfacePage :=
R2.
(a) (b)
Figure 2-31: Example of assigning responsibilities. (a) Which objects should be assigned
responsibilities R1 and R2? (b) Once the Key Checker decides the key is valid, the DeviceCtrl
should be notified to unlock the lock. Whose responsibility should this be?
Another example is shown in Figure 2-31(a) for use case 1: Unlock. Here the dilemma is this. Once
the key validity is established, who should invoke the method activate("lock") on the DeviceCtrl to
disarm the lock? One option is the KeyChecker (or Checker for short) because it is the first to
acquire the information about the key validity. Another option is the Controller, because the
Controller would need to know this information anyway—to signal to the user the outcome of the
key validation. An advantage of the latter choice is that it maintains the Checker focused on
its specialty (key
150
Checking) and avoids assigning other responsibilities to it. Recall that in Figure 2-25 the Controller
has an association with the HouseholdDeviceOperator. This association is named
“conveysRequests.” Domain model concept associations provide only a useful hint for assigning
communication responsibilities in the design. However, more is needed for making design
decisions. Before I present solutions to problems in Figure 2-31, I first describe some criteria that
guide our design decisions.
Our goal is to derive a “good” design or, ideally, an optimal design. Unfortunately, the software
engineering discipline today is unable to precisely specify the quantitative criteria for evaluating
designs. Some criteria are commonly accepted, but there is no systematic framework. For
example, good software designs are characterized with:
• Short communication chains between the objects
• Balanced workload across the objects
• Low degree of connectivity (associations) among the objects
While optimizing these parameters, we must ensure that important constraints are satisfied. For
example, we must ensure that messages are sent in the correct order. As already stated, there are
no automated methods for software design. Software engineers rely on design heuristics. The
design heuristics used to achieve “optimal” designs can be roughly divided as:
1. Bottom-up (inductive) approaches: applying design principles and design patterns locally
at the level of software objects (micro-level design). See the next section for design
principles. See Chapter 5 for design patterns.
2. Top-down (deductive) approaches: applying architectural styles globally, at the system
level, in decomposing the system into subsystems (macro-level design). See Section 2.3
for software architectures.
Software engineers normally combine both approaches opportunistically. While doing design
optimization, it is also important to enforce the contracts and other constraints, such as non-
functional requirements. See 2.5.4 for contracts. See 3.2.3 for object constraint specification.
151
Hence, we need to decide what properties/methods belong to what object, as well as what
messages are sent by objects. We have already assigned responsibilities in the analysis phase.
(See 2.5 for analysis). There, we “hired workers” to perform certain tasks. This, in effect, covers
the first two types of responsibility: assigning attributes, associations, and methods for
performing computations. In the design stage of software lifecycle, we are dealing mainly
with the third responsibility type: sending messages to other objects—in other words, invoking
methods on them.
Low cohesion
High cohesion
Below are some important design principles at the local, objects level.
• Expert Doer Principle: This states that “he who knows should do the task”.
• High Cohesion Principle: T h i s s a y s t o not take on too many
responsibilities of Type 2 (computation).
• Low Coupling Principle: T h i s says to not take on too many responsibilities
of Type 3 (communication).
152
The Expert Doer Principle helps shorten the communication chains between the objects. It
essentially states that, when assigning a responsibility for message sending, y o u s h o u l d
select the object which first learns the information needed to send the message.
The High Cohesion Principle helps in balancing the workload across the objects and keeping them
focused. An object’s functional cohesion is inversely proportional to the number of computing
responsibilities assigned to it.
Finally, the Low Coupling Principle helps to reduce the number of associations among the objects.
An object’s coupling is directly proportional to the number of different messages the object sends
to other objects.
Consider how to employ these design principles to the example of Figure 2-31(b). For example,
the Checker is the first to acquire the information about the key validity. Therefore, by the Expert
Doer Principle, it is considered a good candidate to send a “disarm lock” message to the
DeviceCtrl.. However, the High Cohesion Principle favors keeping the Checker functionally focused
on its specialty (key checking), and opposes assigning it other responsibilities.
Ideally, High Cohesion allows one non-trivial responsibility per object. Suppose we let High
Cohesion override Expert Doer. A reasonable compromise is to give the Controller the
responsibility of notifying the DeviceCtrl. Note that this solution violates the Low Coupling
Principle, because Controller acquires a relatively large number of associations. We will revisit this
issue later.
As seen, design principles are not always in agreement with each other. Enforcing any particular
design principle to the extreme would lead to absurd designs. Often, the designer is faced with
conflicting demands and must use judgment and experience to select a compromise solution that
they feel is “optimal” in the current context.
Another problem is that cohesion and coupling are defined only qualitatively: “do not take on too
many responsibilities.” Chapter 4 describes attempts to quantify the cohesion and coupling.
Because precise rules are lacking and so much depends on the developer’s judgment, it is critical
to record all the decisions and reasoning behind them. It is essential to document the alternative
solutions that were considered in the design process, identify all the tradeoffs encountered, and
explain why the alternatives were abandoned. The process may be summarized as follows:
1. Identify the responsibilities. Domain modeling provides a starting point. Some will be
missed at first and identified in subsequent iterations. (See 2.5 for domain modeling).
2. For each responsibility, identify the alternative assignments. If the choice appears to be
unique, then move to the next responsibility.
3. Consider the merits and tradeoffs of each alternative by applying the design principles.
Select what you consider the “optimal” choice.
4. Document the process by which you arrived at each responsibility assignment.
Some responsibility assignments will be straightforward, while some may require extensive
deliberation. The software engineer will use their experience and judgment to decide.
153
«html»
interfacePage : : Controller : PageMaker : DatabaseConnection
Resident Database
result
Figure 2-32: Sequence diagram for part of use case UC-5: Inspect Access History.
1. Database Connection is the first to get hold of the access list records
2. The Controller receives the return value directly from Page Maker. This would be
convenient, since the Controller will probably be posting the Interface Page—which
is rendered by the Page Maker.
Finally, responsibility R3 is to check whether the list of records is empty. This could be assigned to:
1. The object that will get responsibility R2, which can call different methods on Page
Maker.
2. Page Maker itself, which will simply generate a different page for different list contents.
Next, let us employ the design principles, such as Expert Doer, High Cohesion, or Low Coupling,
to decide on which object should be given which responsibility.
Both options (Database Connection vs. Controller) contribute the same amount of coupling.
Therefore, we have a conflict among the design principles: Expert Doer favors assigning R2 to the
Database Connection, while High Cohesion favors assigning R2 to the Controller. In this
154
case, one may argue that maintaining high cohesion is more valuable than satisfying Expert Doer.
Database Connection already has a relatively complex responsibility. Adding new responsibilities
will only make things worse. Therefore, we opt for assigning R2 to the Controller.
Responsibility R3 should be assigned to Page Maker, because this choice yields highest cohesion.
Figure 2-32 shows the resulting UML sequence diagram. Note that in Figure 2-32, the system is
not ensuring that only authorized users access the database. This will be corrected later in Section
5.2.4, where it is used as an example for the Protection Proxy design pattern.
N ext, consider UC-1: Unlock. Table 2-9 lists the communication responsibilities for the system
function “enter key”.
Table 2-9: Communicating responsibilities identified for the system function “enter key.”
Compare to Table 2-5.
Responsibility Description
Send message to Key Checker to validate the key entered by the user.
Send message to DeviceCtrl to disarm the lock device.
Send message to DeviceCtrl to switch the light bulb on.
Send message to PhotoObserver to report whether daylight is sensed.
Send message to DeviceCtrl to sound the alarm bell.
Based on the responsibilities in Table 2-9, Figure 2-33 shows an example design for the system
function “enter key.” The Controller object orchestrates all the processing logic related to this
system function. The rationale for this choice was discussed earlier, related to Figure 2-31(b).
If this rule is implemented, the object has the knowledge of conditions under which a method can
or cannot be invoked. Hence, the question is which object is responsible to know this rule? The
needs-to-know responsibility has implications for the future upgrades or modifications. Changes
to the business rules require changes in the code of the corresponding objects. (Techniques for
anticipating and dealing with change are described in Chapter 5.)
Apparently--while trying to preserve high cohesion/specialization for all other objects--we have built
undue complexity into the Controller object.This implies low cohesion in the design. Poor cohesion
is equivalent to low specialization of (some) objects.
155
enterKey()
«create»
compare(k, sk)
logTransaction( k, val )
«destroy»
dl := isDaylight()
[else] numOfAttempts++
activate( "alarm" )
[else]
Figure 2-33: Sequence diagram for the system function “enter key” (Figure 2-20). Several UML
interaction frames are shown, such as “loop,” “alt” (alternative fragments, of which only the
one with a condition true will execute), and “opt” (optional, the fragment executes if the
condition is true).
The design in Figure 2-33 is not the only one possible. Example variations are shown in Figure 2-
34.
In variation (a), the Checker sets the key validity as a flag in the Key object, rather than reporting
it as the method call return value. The Key is now passed on, and DeviceCtrl obtains the key
validity flag and decides what to do. The result: business logic is moved from the Controller into
the object that operates the devices. Such a solution--where the correct functioning of the system
depends on a flag in the Key object--is fragile.Data can become corrupted as it is moved around
the system.
A more elegant solution is presented in Chapter 5, where we will see how the Publish/Subscribe
design pattern protects critical decisions by implementing them as operations, rather than
arguments of operations. It is harder to make a mistake of calling a wrong operation than to pass
a wrong argument value.
156
: Controller k : Key : Checker : KeyStorage : DeviceCtrl : PhotoObsrv : Logger
c : DeviceCtrl : PhotoSObs
activate( "light" )
dl := isDaylight()
dl == false
a
: Controller k : Key ]
: Checker
numOfTrials+
: KeyStorage : DeviceCtrl
Figure 2-34: Variations on the design for the use case “Unlock,” shown in Figure 2-33.
Although the variation in Figure 2-34(b) is exaggerated, I have seen similar designs. Firstly, it
assigns an awkward method name, checkIfDaylightAndIfNotThenSetLit(). Even worse, it imparts
the knowledge encoded in the name onto the caller. It can easily be inferred from this diagram
that the caller rigidly controls the callee’s work. The caller is tightly coupled to the callee because
it knows the callee’s business logic.. A better solution is in Figure 2-34(c).
Note that the graphical user interface (GUI) design is missing, but that is acceptable because the
GUI can be designed independently of the system’s business logic.
157
Key
1..*
– code_ : string
– timestamp_ : long
– doorLocation_ : string
Figure 2-35: Class diagram for the home access software-to-be. Compare to Figure 2-25.
Based on review of the class diagram, we may need to go back and revise (or, refactor, see Section
2.7.6) the domain model and interaction diagrams. For example, one may see that the Controller
has significantly more connections than other classes (Figure 2-35), which will be addressed in
Chapter 5. This approach is characteristic of iterative development methodology.
W
We also continue maintaining the traceability between the software artifacts. Figure 2-36
traces how software classes evolved from the domain model (Section 2.5). The class
diagram in Figure 2-35 is partial, so we include the classes from Figure 2-32. We see that some
concepts have not yet been () implemented as classes. Generally, it should be possible to trace
all concepts from the domain model to the class diagram. Some concepts will be mapped
directly to individual classes, though perhaps be renamed. Others may be split into several
classes.
Concepts are derived from the system requirements, and they cannot disappear without a
reason. There are two reasons for a concept to be missing in the traceability matrix:
(i) the concept was derived from a low-priority requirement, and the implementation
of this functionality has been deferred for later.
or
(ii) the corresponding requirement was dropped.
On the other hand, all classes must be traceable back to domain concepts. In iterative and
incremental development, the domain model is not derived completely up front. Rather, the
analysis process we explored in Section 2.5 only represents a first iteration. During the design, we
may realize that the domain model is incomplete and we need additional concepts to implement
the requested functionality. In this case, we go back and modify our domain model.
Some classes may not have a directly corresponding abstract concept, because they are
158
introduced for reasons specific to the programming language. Both missing concepts and
emerged (non-traceable) classes must be documented, and their reason for disappearance or
emergence must be explained. Tracing elements from the requirements specification to the
corresponding elements in the design specification is a part of design verification and validation.
159
Software Classes
«html» interfacePage
DatabaseConnection
SearchRequest
Controller-SS2
Controller-SS1
PhotoSObsrv
KeyChecker
KeyStorage
PageMaker
DeviceCtrl
Logger
Key
Domain Concepts
Controller-SS1 X
StatusDisplay
Key X
KeyStorage X
KeyChecker X
HouseholdDeviceOperator X
IlluminationDetector X
Controller-SS2 X
SearchRequest X
InterfacePage X
PageMaker
X
Archiver
DatabaseConnection
X
Notifier
InvestigationRequest
Figure 2-36: Domain-model-to-class-diagram traceability matrix for the safe home access case
study. (Continued from Figure 2-28.)
Class Relationships
Class diagrams both describe classes and show the relationships among them. We already
discussed object relationships in Section 1.4. In our case, Figure 2-35, there is an “aggregation”
relationship between KeyStorage and Key. All other relationships happen to be of the “uses”
type—that is, one uses the other. Recall the access designations that signify the visibility of class
attributes and operations to other classes. They are listed below:
+ for public, global visibility;
# for protected visibility within the class and its descendant classes;
and − for private within-the-class-only visibility (not even for its descendants).
A class diagram is static, unlike interaction diagrams, which are dynamic.
160
AA BB
SS2
AA BB PP
161
2.7 Test-Driven Implementation
“The good news about computers is that they do what you tell them to do. The bad news is that they do
what you tell them to do.” —Ted Nelson
Given a feature selected for implementation, test-driven implementation works by writing the
code for tests, writing the code that implements the feature, running the tests, and finally
verifying and validating the test results (Figure 2-38). If the results meet the expectations, we
move onto the next feature; otherwise, we need to debug the code, identify and fix the problem,
and test again.
Testing is often viewed as executing a program to see if it produces the correct output for a given
input. This implies testing the end-product—in other words, it means that testing only takes place
late in the lifecycle. This is not true. Experience has shown that errors introduced during the early
stages of software lifecycle are the costliest and most difficult to discover.
A better, more general definition is that testing is the process of finding faults in software artifacts,
such as UML diagrams or code. A fault, also called “defect” or “bug,” is an erroneous hardware or
software element of a system that can cause the system to fail, i.e., to behave in a way that is not
desired or is even harmful. We say that the system experienced failure because of an inbuilt fault.
Any software artifact can be tested, including requirements specification, domain model, and
design specification. Testing activities should be started as early as possible. An extreme form of
this approach is test-driven development (TDD), one of the practices of Extreme Programming
(XP), in which development starts with writing tests. The form and rigor of testing should be
adapted to the nature of the artifact that is being tested. Testing of design sketches will be
approached differently than testing a software code.
Testing works by probing a program with different combinations of inputs to detect faults.
Therefore, testing shows only the presence of faults, not their absence. Showing the absence of
faults requires exhaustively trying all possible combinations of inputs (or following all possible
paths through the program). The number of possible combinations generally grows exponentially
with software size. However, it is not only about inadvertent bugs—a bad-intended programmer
might have introduced purposeful malicious features for personal gain or revenge, which are
activated only by a very complex input sequence. Therefore, it is impossible to test that a program
will work correctly for all imaginable input sequences. An alternative to the brute force approach
of testing is to prove the correctness of the software by reasoning (or, theorem proving).
Unfortunately, proving correctness generally cannot be automated and requires human effort. In
addition, it can be applied only in the projects where the requirements are specified in a formal
(mathematical) language. We will discuss this topic further in Chapter 3.
A key tradeoff is between testing as many potential cases as possible and keeping economic
162
costs limited. Our goal is to find faults as cheaply and quickly as possible. Ideally, we would design
a single “right” test case to expose each fault and run it. In practice, however, we have to run
many “unsuccessful” test cases that do not expose any faults. Some strategies that help keep costs
down include:
• complementing testing with other methods, such as design/code review, reasoning, or static
analysis.
• using automation to increase coverage and frequency of testing.
• testing early in the lifecycle and often.
163
Component
code Unit
test Te
st
ed
Component
code Unit
test
Integrated
modules
Integration System
test test
System
in use
Ensures that all
components work
together
Component
code Unit
test
Verifies that functional Verifies non-functional Customer verifies Testing in user environment
requirements are satisfied requirements all requirements
Automatic checking of test results is preferred to keep the costs low, but it may not always be
feasible. For example, how can we check the display content of a graphical user interface?
Testing is usually guided by the hierarchical structure of the system as designed in the analysis
and design phases. See Figure 2-39. See Section 2.3 for information about hierarchical structure—
and, more broadly, software architecture.
We may start by testing individual components, which is known as unit testing. These
components are incrementally integrated into a system. Testing the composition of the system
components is known as integration testing. System testing ensures that the whole system
complies with the functional and non-functional requirements. The customer performs
acceptance testing of the whole system. (Acceptance tests and examples are described in
Sections 2.2 and 2.4, when describing requirements engineering.) As always, the logical
organization does not imply that testing steps should be ordered in time as shown in Figure 2-39.
Instead, the development lifecycle evolves incrementally and iteratively, and corresponding
cycles will occur in testing as well.
Unit testing finds differences between the object design model and its corresponding
implementation. There are several benefits of focusing on individual components. One is the
common advantage of the divide-and-conquer approach—it reduces the complexity of the
problem and allows us to deal with smaller parts of the system separately. Second, unit testing
makes it easier to locate and correct faults because only few components are involved in the
process. Lastly, unit testing supports division of labor, so several team members can test different
components in parallel. Practical issues with unit testing are described in Section 2.7.3.
Regression testing seeks to test current software for either prior issues resurfacing or long-
standing functionality breaking. For example, a bug may have been fixed and it would be a
regression if the reported inappropriate behavior still occurs. Similarly, if a core function of the
program is misbehaving, even is it never exhibited this before, that would also be considered a
regression. It is important to distinguish the type of regression when reporting them, as well as
164
the flow of reproducing the regression. A new test is added for every discovered fault, and tests
are run after every change to the code. Regression testing helps to populate test suite with good
test cases, because every regression test is added after it uncovered a fault in one version of the
code. Regression testing protects against reversions that reintroduce faults. Because the fault that
resulted in adding a regression test already happened, it may be an easy error to repeat.
165
Another useful distinction between testing approaches is what document or artifact is used for
designing the test cases. Black box testing refers to analyzing a running program by probing it
with various inputs. It involves choosing test data only from the specification, without looking at
the implementation. This testing approach is commonly used by customers, for example for
acceptance testing. White box testing chooses test data with knowledge of the implementation,
such as knowledge of the system architecture, used algorithms, or program code. This testing
approach assumes that the code implements all parts of the specification, although possibly with
bugs (programming errors). If the code omitted a part of the specification, then the white box test
cases derived from the code will have incomplete coverage of the specification. White box tests
should not depend on specific details of the implementation, which would prevent their
reusability as the system implementation evolves.
166
Heuristics similar to the following can be used to partition the values of input parameters into
equivalence classes: For an input parameter specified over a range of values, partition the value
space into one valid and two invalid equivalence classes. For example, if the allowed input values
are integers between 0 and 100, the valid equivalence class contains integers between 0
and 100, one invalid equivalence class contains all negative integers, and the other invalid
equivalence class contains all integers greater than 100.
• For an input parameter specified with a single value, partition the value space into one
valid and two invalid equivalence classes. For example, if the allowed value is a real
number 1.4142, the valid equivalence class contains a single element {1.4142}, one invalid
equivalence class contains all real number smaller than 1.4142, and the other invalid
equivalence class contains all real number greater than 1.4142.
• For an input parameter specified with a set of values, partition the value space into one
valid and one invalid equivalence class. For example, if the allowed value is any element
of the set {1, 2, 4, 8, 16}, the valid equivalence class contains the elements {1, 2, 4, 8, 16},
and the invalid equivalence class contains all other elements.
• For an input parameter specified as a Boolean value, partition the value space into one
valid and one invalid equivalence class (one for TRUEand the other for FALSE).
Equivalence classes defined for an input parameter must satisfy the following criteria:
1. Coverage: Every possible input value belongs to an equivalence class.
2. Disjointedness: No input value belongs to more than one equivalence class.
3. Representation: If an operation is invoked with one element of an equivalence class as an
input parameter and returns a particular result, then it must return the same result if any
other element of the class is used as input.
If an operation has more than one input parameter, we must define new equivalence classes for
combinations of the input parameters. This is known as Cartesian product or cross product. See
Section 3.2.1.
For example, consider testing the Key Checker’s operation checkKey(k : Key) : boolean.
As shown in Figure 2-35, the class Key has three string attributes: code, timestamp, and
doorLocation. The operation checkKey(), as implemented in Listing 2-4, does not use a
timestamp, so its value is irrelevant. However, we need to test that the output of checkKey() does
not depend on the value of a timestamp. The other two attributes, code and doorLocation, are
specified with a set of values for each.
Suppose that the system is installed in an apartment building with the apartments numbered as
{196, 198, 200, 202, 204, 206, 208, 210}. Assume that the attribute doorLocation takes the value
of the associated apartment number. On the other hand, the tenants may have chosen their four-
digit access codes as {9415, 7717, 8290, …, 4592}. Although a code value “9415” and doorLocation
value “198” are each valid separately, their combination is invalid, because the code value for the
tenant in apartment 198 is “7717.”
Therefore, we must create a cross product of code and doorLocation values and partition this value
space into valid and invalid equivalence classes. For the pairs of test input values chosen from the
valid equivalence class, the operation checkKey() should return the Boolean value TRUE.
Conversely, for the pairs of test input values from invalid equivalence classes it should return
FALSE.
167
168
When ensuring test coverage, we should consider not only the current snapshot, but also historic
snapshots as well. For example, when testing the Key Checker’s operation checkKey(), the
previously-valid keys of former tenants of a given apartment belong to an invalid equivalence
class, although in the past they belonged to the valid equivalence class. We need to include the
corresponding test cases, particularly during integration testing (see Section 2.7.4).
Boundary Testing
Boundary testing is a special case of equivalence testing that focuses on the boundary values of
input parameters. After partitioning the input domain into equivalence classes, we test the
program using input values not only “inside” the classes, but also at their boundaries. Rather than
selecting any element from an equivalence class, boundary testing selects elements from the
“edges” of the equivalence class, or “outliers,” such as zero, min/max values, empty set, empty
string, and null. Another frequent “edge” fault results from the confusion between > and >=. The
assumption behind this kind of testing is that developers often overlook special cases at the
boundary of equivalence classes.
For example, if an input parameter is specified over a range of values from a to b, then test cases
should be designed with values a and b as well as just above and just below a and b.
Control Flow Testing
Statement coverage selects a test set such that every elementary statement in the program is
executed at least once by some test case in the test set.
Edge coverage selects a test set such that every edge (branch) of the control flow is traversed at
least once by some test case. We construct the control graph of a program so that statements
become the graph edges, and the nodes connected by an edge represent entry and exit to/from
the statement. A sequence of edges (without branches) should be collapsed into a single edge.
a; a; b; if a then b; if a then b else c; while a do b;
a a not a a
a a
b not a b c b
Condition coverage (also known as predicate coverage) selects a test set such that every condition
(Boolean statement) takes TRUE and FALSE outcomes at least once in some test case.
Path coverage determines the number of distinct paths through the program that must be
traversed (travelled over) at least once to verify the correctness. This strategy does not account
for loop iterations or recursive calls. Cyclomatic complexity metric (Section 4.2.2) provides a
simple way of determining the number of independent paths.
State-based Testing
State-based testing defines a set of abstract states that a software unit can take and tests the
unit’s behavior by comparing its actual states to the expected states. This approach has become
popular with object-oriented systems. The state of an object is defined as a constraint on the
values of object’s attributes. Because the methods use the attributes in computing the object’s
behavior, the behavior depends on the object state.
169
event guard condition
invalid-key / invalid-key
signal-failure [numOfAttemps > maxNumOfAttempts] /
Locked Accepting sound-alarm
state
valid-key /
signal-success
transition
valid-key / Blocked
signal-success
Unlocked
Figure 2-40: UML state diagram for the Controller class in Figure 2-35. The notation for UML
state diagrams is introduced in Section 3.2.
The first step in using state-based testing is to derive the state diagram for the tested unit. We
start by defining the states. Next, we define the possible transitions between states and
determine what triggers a transition from one state to another. For a software class, a state
transition is usually triggered when a method is invoked. Then we choose test values for each
individual state.
The second step is to initialize the unit and run the test. The test driver exercises the unit by calling
methods on it, as described in Section 2.7.3. When the driver has finished exercising the unit,
assuming no errors have yet occurred, the test then proceeds to compare the actual state of the
unit with its expected state. If the unit reached the expected state, the unit is considered correct
regardless of how it got to that state.
Assume that we are to test the Controller class of our safe home access case study (the class
diagram shown in Figure 2-35). The process of deriving the state diagrams and UML state diagram
notation are described in Chapter 3. A key responsibility of the Controller is to prevent the
dictionary attacks by keeping track of unsuccessful attempts because of an invalid key. Normally,
we assume that the door is locked (as required by REQ1 in Table 2-1). The user unlocks the door
by providing a valid key. If the user provided an invalid key, the Controller will allow up to
maxNumOfAttempts unsuccessful attempts, after which it should block and sound alarm.
Therefore, we identify the following elements of the state diagram (Figure 2-40):
• Four states { Locked, Unlocked, Accepting, Blocked }
• Two events { valid-key, invalid-key }
• Five valid transitions { Locked→Unlocked, Locked→Accepting, Accepting→Accepting,
Accepting→Unlocked, Accepting→Blocked }
A test set consists of scenarios that exercise the object along a given path through the
state diagram. In general the number of state diagram elements is
all-events, all-states ≤ all-transitions ≤ all-paths
170
Because the number of possible paths in the state diagram is generally infinite, it is not practical
to test each possible path. Instead, we ensure the following coverage conditions:
• Cover all identified states at least once (each state is part of at least one test case)
• Cover all valid transitions at least once
• Trigger all invalid transitions at least once
Testing all valid transitions implies (subsumes) all-events coverage, all-states coverage, and all-
actions coverage. This is considered a minimum acceptable strategy for responsible testing of a
state diagram. Note that all-transitions testing is not exhaustive, because exhaustive testing
requires that every path over the state machine is exercised at least once, which is usually
impossible or at least unpractical.
171
Test driver Tested component Test stubs
testDriver :
(a) (b)
Figure 2-41: Testing the Key Checker’s operation checkKey() (use case Unlock).
(a) Relevant part of the sequence diagram excerpted from Figure 2-33. (b) Test stubs and
drivers for testing the Key Checker.
As shown in Figure 2-41(b), the test driver passes the test inputs to the tested component and
displays the results. In JUnit testing framework for Java, the result verification is done using the
assert*() methods that define the expected state and raise errors if the actual state differs. The
test driver can be any object type, not necessarily an instance of the Controller class. Unlike this,
the test stubs must be of the same class as the components they are simulating. They must
provide the same operation APIs, with the same return value types. The implementation of test
stubs is a nontrivial task and, therefore, there is a tradeoff between implementing accurate test
stubs and using the actual components. That is, if KeyStorage and Key class implementations are
available, we could use them when testing the Key Checker class.
Listing 2-1: Example test case for the Key Checker class.
public class CheckerTest {
// test case to check that invalid key is rejected @Test public void
checkKey_anyState_invalidKeyRejected() {
// 1. set up
Checker checker = new Checker( /* constructor params */ );
// 2. act
Key invalidTestKey = new Key( /* setup with invalid code */ ); boolean result =
checker.checkKey(invalidTestKey);
172
We use the following notation for methods that represent test cases (see Listing 2-1):
1. Set up
〈methodName〉_〈startingState〉_〈expectedResult〉
2. Act
3. Verify
where methodName is the name of the method (i.e., event) we are testing on the tested object;
startingState are the conditions under which the tested method is invoked; and, expectedResult
is what we expect the tested method to produce under the specified condition. In our example,
we are testing Checker’s method checkKey(). The Checker object does not have any attributes, so
it is always in an initial state. The expected result is that checkKey() will reject an
invalid key. Thus the test case method
name checkKey_anyState_invalidKeyRejected().
Testing objects with different states is a bit more complex, because we must bring the object to
the tested state and in the end verify that the object remains in an expected state. Consider the
Controller object and its state diagram shown in Figure 2-40. One test case needs to verify that
when Controller receives maxNumOfAttempts invalid keys, it correctly transitions to the Blocked
state.
// 1. set up: bring the object to the starting state Controller cntrl = new Controller( /*
constructor params */ );
// bring Controller to the Accepting state, just before it blocks Key invalidTestKey = new Key( /*
setup with invalid code */ ); for (i=0; i < cntrl.getMaxNumOfAttempts(); i++) {
cntrl.enterKey(invalidTestKey);
}
assertEqual( // check that the starting state is set up cntrl.getNumOfAttempts(),
cntrl.getMaxNumOfAttempts() – 1
);
// 2. act cntrl.enterKey(invalidTestKey);
// 3. verify
assertEqual( // the resulting state must be "Blocked"
cntrl.getNumOfAttempts(), cntrl.getMaxNumOfAttempts()
);
assertEqual(cntrl.isBlocked(), true);
}
}
173
It is left to the reader to design the remaining test cases and ensure the coverage conditions
(Section 2.7.2).
A key challenge of unit testing is to sufficiently isolate the units so that each unit can be tested
individually. Otherwise, you end up with a “unit” test that is really more like an integration test.
The most important technique to help achieve this isolation is to program to interfaces instead of
concrete classes.
174
Controller
Level-4
Level-3 KeyChecker
Test
Logger Test Controller &
KeyChecker & KeyStorage &
Test Key & Logger & PhotoSObsrv
PhotoSObsrv & DeviceCtrl
Test
DeviceCtrl Test KeyChecker
(b)
& KeyStorage &
Key
Test Key &
KeyStorage
Figure 2-42: Integration testing strategies for the system from Figure 2-35. (a) Units
hierarchy; (b) Bottom-up integration testing; (c) Top-down integration testing.
Sandwich integration approach combines top-down and bottom-up by starting from both ends
and incrementally using components of the middle level in both directions. The middle level is
known as the target level. In sandwich testing, usually there is need to write stubs for testing the
top components, because the actual components from the target level can be used. Similarly, the
actual target-level components are used as drivers for bottom-up testing of low-level
components. In our example system hierarchy of Figure 2-42(a), the target layer contains only one
component: Key Checker. We start by top-down testing of the Controller using the Checker. In
parallel, we perform bottom-up testing of the Key Storage again by using the Checker. Finally, we
test all components together.
There are advantages and drawbacks of each integration strategy. Bottom-up integration is
suitable when the system has many low-level components, such as utility libraries. Moving up the
hierarchy makes it easier to find the component-interface faults: if a higher-level component
violates the assumption made by a lower-level component, it is easier to find where the problem
is. A drawback is that the topmost component (which is usually the most important, such as user
interface), is tested last—if a fault is detected, it may lead to a major redesign of the system.
175
User User User
story-1 story-2 story-N
Write a
Write a failing Make the
failing
acceptance test test pass
unit test
Refactor
(a) (b)
Figure 2-43: Vertical integration in agile methods develops functional vertical slices (user
stories) in parallel (a). Each story is developed in a cycle that integrates unit tests in the inner
feedback loop and the acceptance test in the outer feedback loop (b).
Top-down integration has the advantage of starting with the topmost component (usually the
user interface, which means possibility of early end-user involvement). The test cases can be
derived directly from the requirements. Its disadvantage is that developing test stubs is time
consuming and error prone.
Sandwich testing’s advantages include the fact that there is no need to write stubs or drivers, the
ability of early testing of the user interface, and thus, early involvement of end users. A drawback
is that sandwich testing does not thoroughly test the units of the target (middle) level before
integration. This problem can be remedied by the modified sandwich testing that tests the lower,
middle, and upper levels individually before combining them in incremental tests with one
another.
Vertical Integration Testing Strategies
Agile methods use the vertical integration approach to develop the user stories in parallel (Figure
2-43(a)). Each story is developed in a feedback loop (Figure 2-43(b)), where the developers use
unit tests in the inner loop and the customer runs the acceptance test in the outer loop. Each
cycle starts with the customer/user writing the acceptance test that will test a particular user
story. Based on the acceptance test, the developer writes the unit tests and develops only the
code that is relevant, i.e., needed to pass the unit tests. The unit tests are run on daily basis, soon
after the code is written, and the code is committed to the code base only after it passes the unit
tests. The acceptance test is run at the end of each cycle (order of weeks or months).
The advantage of vertical integration is that it yields a working deliverable quickly. A potential
drawback is that because each subsystem (vertical slice—user story) is developed independently,
the system may lack uniformity and “grand design.” Therefore, the system may need a major
redesign late in the development cycle.
176
Security Testing
Functional testing is testing for “positives”—that the required features and functions are correctly
implemented. However, a majority of security defects and vulnerabilities are not directly related
to security functionality, such as encryption or privilege management. Instead, security issues
involve often unexpected but intentional misuses of the system discovered by an attacker.
Therefore, we also need to test for “negatives,” such as abuse cases, to determine how the system
behaves under attack. Security tests are often driven by known attack patterns.
This section shows how the designed system might be implemented. If you want to refresh your
Java programming before proceeding, you may refer to Appendix A.
One thing that programmers often neglect is that the code must be elegant and readable. This is
not for the sake of the computer which will run the code, but for the sake of humans who will
read, maintain, and improve on the original code. I believe that writing good comments is at least
as difficult as writing good code. It may be even more important, because comments describe the
developer’s intention, while the code expresses only what the developer did. The code that lacks
aesthetics and features poor writing style in comments is likely to be a poor quality code.11
The following code uses threads for concurrent program execution. If you are unfamiliar with
threads, consult Section 5.3.
The key purpose of the main class is to get hold of the external information: the table of valid keys
and a connection to the embedded processor that controls the devices. The following is an
implementation for the main system class.
11 On a related note, writing user messages is as important. The reader may find that the following funny
story is applicable to software products way beyond Microsoft’s: “There was once a young man
who wanted to become a great writer and to write stuff that millions of people would read and react to
on an emotional level, cry, howl in pain and anger, so now he works for Microsoft, writing error
messages.” [ Source: A Prairie Home Companion, February 3, 2007. Online at:
http://prairiehome.publicradio.org/programs/2007/02/03/scripts/showjokes.shtml ]
177
import javax.comm.SerialPort; import
javax.comm.SerialPortEvent;
import javax.comm.SerialPortEventListener;
import javax.comm.UnsupportedCommOperationException;
ctrler_ =
new Controller(new KeyChecker(ks), lkc, lic, sns, ac);
try {
ctrlPort.addEventListener(this);
} catch (TooManyListenersException e) { e.printStackTrace(); // limited to one listener
per port
}
start(); // start the thread
}
178
try {
ctrlPort.setSerialPortParams(
9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE
);
} catch (UnsupportedCommOperationException e) { e.printStackTrace();
}
}
try {
while (inputStream_.available() > 0) {
int numBytes = inputStream_.read(readBuffer);
// could check if "numBytes" == 1 ...
}
} catch (IOException e) { e.printStackTrace(); }
// append the new char to the user key key_.append(new
String(readBuffer));
The class HomeAccessControlSystem is a thread that runs forever and accepts the input from the
serial port. This is necessary to keep the program alive, because the main thread just sets up
everything and then terminates, while the new thread continues to live.
Next shown is an example implementation of the core system, as it was designed in Figure 2-33.
The coding of the system is directly driven by the interaction diagrams.
Listing 2-4: Implementation Java code of classes Controller, KeyChecker, and LockCtrl.
179
public class Controller { protected KeyChecker
checker_; protected LockCtrl lockCtrl_;
protected LightCtrl lightCtrl_; protected
PhotoObsrv sensor_; protected AlarmCtrl
alarmCtrl_;
public static final long maxNumOfAttempts_ = 3;
public static final long attemptPeriod_ = 600000; // msec [=10min] protected long
numOfAttempts_ = 0;
public Controller(
KeyChecker kc, LockCtrl lkc, LightCtrl lic, PhotoObsrv sns, AlarmCtrl
ac
){
checker_ = kc;
lockCtrl_ = lkc; alarmCtrl_ = ac; lightCtrl_ = lic; sensor_
= sns;
}
KeyChecker {
protected KeyStorage validKeys_;
checkKey(Key user_key) {
for (Iterator e = validKeys_.iterator(); e.hasNext(); ) { if (compare((Key)e.next(),
user_key) { return true; }
}
return false;
}
}
}
import javax.comm.SerialPort;
180
public class LockCtrl {
protected boolean armed_ = true;
In Listing 2-4, I assume that KeyStorage is implemented as a list, java.util.ArrayList. If the keys are
simple objects, e.g., numbers, then another option is to use a hash table, java.util.HashMap. Given
a key, KeyStorage returns a value of a valid key. If the return value is null, the key is invalid. The
keys must be stored in a persistent storage, such as relational database or a plain file and loaded
into the KeyStorage at the system startup time, which is not shown in Listing 2-4.
If you’ve been following along step-by-step from the requirements to the code, you may observe
that--regardless of the programming language--the code contains many details that usually
obscure the high-level design choices and abstractions. Due to the need for precision about every
detail and unavoidable language-specific idiosyncrasies, it is difficult to understand and reason
about software structure from code only. I hope that at this point, you understand == the value
of traceable stepwise progression and diagrams.
181
2.7.6 Refactoring: Improving the Design of Existing Code
A refactoring of existing code is a transformation that improves its design while preserving its
behavior. Refactoring changes the internal structure of software to make it easier to understand
and cheaper to modify that does not change its observable behavior. The process of refactoring
involves removing duplication, simplifying complex logic, and clarifying unclear code. Examples of
refactoring include small changes, such as changing a variable name, as well as large changes,
such as unifying two class hierarchies.
Refactoring applies sequences of low-level design transformations to the code. Each
transformation improves the code by a small increment, in a simple way, by consolidating ideas,
removing redundancies, and clarifying ambiguities. A major improvement is achieved gradually,
step by step. The emphasis is on tiny refinements, because they are easy to understand and track,
and each refinement produces a narrowly focused change in the code. Because only small and
localized block of the code is affected, it is less likely that a refinement will introduce defects.
Agile methods recommend test-driven development (TDD) and continuous refactoring. They go
together because refactoring (changing the code) requires testing to ensure that no damage was
done.
182
: System
Behavior : Communicator : Checker : KeyStorage
import javax.com
checkKey()
Lock User : System
sk := getNext() import java.io.I
«primary acttor»
Tenant
sellecttFuncttiion((““unlock"))
alt val != null setOpen(true) import java.io.I
prompt fforr tthe key import java.util
Unlock val == null : setLit(true)
implemen
protected Co
protected In
System Description
protected St
public stati
public HomeA
KeyChecker Key KeyStora
# code_ : long
+ checkKey() : boolean
+ getCode() : long ) {
KeyChecker Key
# code_ : long
1..* try {
+ checkKey() : boolean
+ getCode() : long
Communiicatorr obtaiins Key 1 checkerr inpu
} catch
Structure
conveysRequestts
Controller
PhotoSObsrv
# numOfTrials_ : long
# maxNumOfTrials_ : long
sensor + isDaylight() : boolean
+ enterKey(k : Key) LockCtrl
LightCtr
KeyChecker nottifiesKeyValidiitty LockOperator lockCttrrll 1
1 alarrmCttrrll PhotoObs
numOfTrials lockStatus LockCtrl
maxNumOfTrials # open_ : boolean AlarmCtrl
+ isOpen() : boolean
+ setOpen(v : boolean) + soundAlarm()
Domain Model
Figure 2-44: Summary of a single iteration of the software development lifecycle. The activity
alternates between elaborating the system’s behavior vs. structure. Only selected steps and
artifacts are shown.
There are usually two types of conditionals that can’t be replaced with polymorphism. Those are
comparatives (>, <) (or working with primitives, usually), and—sometimes-- boundary cases.
Both of these are language specific as well, as in Java only. Some other languages allow you to
pass closures around, which obfuscate the need for conditionals.
“Good judgment comes from experience, and experience comes from bad judgment.”
—Frederick P. Brooks
This chapter presents incremental and iterative approach to software design and gradually
introduces software engineering techniques using a running case study. Key phases of the process
are summarized in Figure 2-44. (Note that package diagram, which is a structural description, is
not shown for the lack of space.) To ensure meaningful correspondence between the successive
software artifacts, we maintain traceability matrices across the development lifecycle. The
traceability matrix links requirements, design specifications, hazards, and validation. Traceability
among these activities and documents is essential.
Figure 2-44 shows only the logical order in which activities take place and does not imply that
software lifecycle should progress in one direction as in the waterfall method. In practice there is
significant intertwining and backtracking between the steps and Figure 2-44 shows only one
iteration of the process. The sequential presentation of the material does not imply how the
183
actual development is carried out. Teaching works from a known material and follows logical
ordering, but practice needs to face unknown problem and the best ordering is known only after
the fact.
A general understanding of the problem domain does not guarantee project success; you need a
very detailed understanding of what is expected from the system. A detailed understanding is best
developed incrementally and iteratively.
Key points:
• Object orientation allows creation of software in solution objects which are directly
correlated to the objects (physical objects or abstract concepts) in the problem to be
solved. The key advantage of the object-oriented approach is in the localization of
responsibilities—if the system does not work as intended, it is easier to pinpoint the
culprit in an object-oriented system.
• The development must progress systematically, so that the artifacts created in the
previous phase are always being carried over into the next phase, where they serve as the
foundation to build upon.
• The traceability matrix acts as a map, providing the links necessary for determining where
information is located. It demonstrates the relationship between design inputs and design
outputs, ensures that design is based on predecessor, established requirements, and
helps ensure that design specifications are appropriately verified and the requirements
are appropriately validated. The traceability matrix supports bidirectional traceability,
“forwards” from the requirements to the code and “backwards” in the opposite direction.
• Use case modeling is an accepted and widespread technique to gather and represent the
business processes and requirements. Use cases describe the scenarios of how the system
under discussion can be used to help the users accomplish their goals. Use cases represent
precisely the way the software system interacts with its environment and what
information must pass the system boundary in the course of interaction. Use case steps
are written in an easy-to-understand structured narrative using the vocabulary of the
domain. This is engaging for the end users, who can easily follow and validate the use
cases, and the accessibility encourages users to be actively involved in defining the
requirements.
• The analysis models are input to the design process, which produces another set of
models describing how the system is structured and how the system’s behavior is realized
in terms of that structure. The structure is represented as a set of classes (class diagram),
and the desired behavior is characterized by patterns of messages flowing between
instances of these classes (interaction diagrams).
• Finally, the classes and methods identified during design are implemented in an object-
oriented programming language. This completes a single iteration. After experimenting
with the preliminary implementation, the developer iterates back and reexamines the
requirements. The process is repeated until a satisfactory solution is developed.
The reader should be aware of the capabilities and limitations of software engineering methods.
The techniques presented in this chapter help you to find a solution once you have the problem
properly framed and defined, as is the case with example projects in Section 1.5. Requirements
184
analysis can help in many cases with framing the problem, but you should also consider
ethnography methods, participatory design, and other investigative techniques beyond software
engineering.
A short and informative introduction to UML is provided by [Fowler, 2004]. The fact that I adopt
UML is not an endorsement, but merely recognition that many designers presently use it and
probably it is the best methodology currently available. The reader should not feel obliged to
follow it rigidly, particularly if he/she feels that the concept can be better illustrated or message
conveyed by other methods.
186
Section 2.6: Design: Assigning Responsibilities
Design with responsibilities (Responsibility-Driven Design):
[Wirfs-Brock & McKean, 2003; Larman, 2005]
Coupling and cohesion as characteristics of software design quality introduced in [Constantine et
al., 1974; Yourdon & Constantine, 1979]. More on coupling and cohesion in Chapter 4.
See also: http://c2.com/cgi/wiki?CouplingAndCohesion
J. F. Maranzano, S. A. Rozsypal, G. H. Zimmerman, G. W. Warnken, P. E. Wirth, and D. M. Weiss,
“Architecture reviews: Practice and experience,” IEEE Software, vol. 22, no. 2, pp. 34-43, March-
April 2005.
Design should give correct solution but should also be elegant (or optimal). Product design is
usually open-ended because it generally has no unique solution, but some designs are “better”
than others, although all may be “correct.” Better quality matters because software is a living
thing—customer will come back for more features or modified features because of different user
types or growing business. This is usually called maintenance phase of the software lifecycle and
experience shows that it represents the dominant costs of a software product over its entire
lifecycle. Initial design is just a start for a good product and only a failed product will end with a
single release.
Class diagrams do not allow describing the ordering of the constituent parts of an aggregation.
The ordering is important in some applications, such as XML Schema (Chapter 6). We could use
the stereotype «ordered» on the “Has-a” relationship, although this approach lacks the advantage
of graphical symbols. More importantly, «ordered» relationship just says the collection is
ordered, but does not allow showing each element individually to specify where it is in the order,
relative to other elements.
187
http://en.wikibooks.org/wiki/Serial_communications_bookshelf
A key book on refactoring is [Fowler, 2000]. The refactoring literature tends to focus on specific,
small-scale design problems. Design patterns focus on larger-scale design problems and provide
targets for refactorings. Design patterns will be described in Chapter 5.
A number of studies have suggested that code review reduces bug rates in released software.
Some studies also show a correlation between low bug rates and open source development
processes. It is not clear why it should be so.
The most popular unit testing framework is the xUnit family (for many languages), available at
http://www.junit.org. For Java, the popular version is JUnit, which is integrated into most of the
popular IDEs, such as Eclipse (http://www.eclipse.org). The xUnit family, including JUnit, was
started by Kent Beck (creator of eXtreme Programming) and Eric Gamma (one of the Gang-of-
Four design pattern authors (see Chapter 5), and the chief architect of Eclipse. A popular free open
source tool to automatically rebuild the application and run all unit tests is CruiseControl
(http://cruisecontrol.sourceforge.net).
Testing aims to determine program’s correctness—whether it performs computations correctly,
as expected. However, a program may perform correctly but be poorly designed, very difficult to
understand and modify. To evaluate program quality, we use software metrics (Chapter 4).
Practice Problems
Problem 2.1
Consider the following nonfunctional requirements and determine which of them can be
verified and which cannot. Write acceptance tests for each requirement or explain why it is not
testable.
(a) “The user interface must be user-friendly and easy to use.”
(b) “The number of mouse clicks the user needs to perform when navigating to any
window of the system’s user interface must be less than 10.”
(c) “The user interface of the new system must be simple enough so that any user can use
it with a minimum training.”
(d) “The maximum latency from the moment the user clicks a hyperlink in a web page
until the rendering of the new web page starts is 1 second over a broadband
connection.”
(e) “In case of failure, the system must be easy to recover and must suffer minimum loss
of important data.”
188
Problem 2.2
Problem 2.3
You are hired to develop an automatic patient monitoring system for a
home-bound patient. The system is required to read out the patient’s heart
rate and blood pressure and compare them against specified safe ranges.
The system also has activity sensors to detect when the patient is exercising
and adjust the safe ranges. In case an abnormality is detected, the system
must alert a remote hospital. (Note that the measurements cannot be taken
continuously, since heart rate is measured over a period of time, say 1
minute, and it takes time to inflate the blood-pressure cuff.) The system must
also (i) check that the analog devices for measuring the patient’s vital signs
are working correctly and report failures to the hospital; and, (ii) alert the owner when the battery
power is running low.
Enumerate and describe the requirements for the system-to-be.
Problem 2.4
Problem 2.5
Problem 2.6
Problem 2.7
Problem 2.8
Consider an online auction site, such as eBay.com, with selling, bidding, and
buying services. Assume that you are a buyer, you have placed a bid for an
item, and you just received a notification that the bidding process is closed
and you won it. Write a single use case that represents the subsequent
process of purchasing the item with a credit card. Assume the business
model where the funds are immediately transferred to the seller’s account,
without waiting for the buyer to confirm the receipt of the goods. Also, only
the seller is charged selling fees. Start from the point where you are already
logged in the system and consider only what happens during a single sitting
at the computer terminal. (Unless otherwise specified, use cases are
normally considered only for the activities that span a single sitting.) List
some alternate scenarios as well.
189
Manual
Opener
Remote
Switch
Receiver
External
Light
Motion
Detector Electric
Eye
Motor
Problem 2.9
Consider the online auction site described in Problem 2.8. Suppose that by observation you
determine that the generic Buyer and Seller roles can be further differentiated into more
specialized roles:
• Occasional Buyer, Frequent Buyer, and Collector
• Small Seller, Frequent Seller, and Corporate Seller
Identify the use cases for both situations: generic Buyers and Sellers vs. differentiated Buyers and
Sellers. Discuss the similarities and differences. Draw the use case diagrams for both situations.
Problem 2.10
You are hired to develop a software system for motion detection and garage door control.
The system should turn the garage door lights on automatically when it detects motion within a
given perimeter.
The garage door opener should be possible to control either by a remote radio transmitter or by
a manual button switch. The opener should include the following safety feature. An “electric eye”
sensor, which projects invisible infrared light beams, should be used to detect if someone or
something passes under the garage door while it closes. If the beam is obstructed while the door
is going down, the door should not close—the system should automatically stop and reverse the
door movement.
The relevant hardware parts of the system are as follows (see Figure 2-45):
• motion detector
• external light bulb
• motor for moving the garage door
• “electric eye” sensor
• remote control radio transmitter and receiver
• manual opener button switch
190
«initi
TurnLightOn
AnimateObject
iti TurnLightOff
«in
«initi
StealOpenerCode
«initi
Thief
RemoteOpen
iti
«in
Figure 2-46: A fragment of a possible use case diagram for Problem 2.11.
Assume that all the hardware components are available and you only need to develop a
software system that controls the hardware components.
(a) Identify the actors for the system and their goals
(b) Derive only the use cases relevant to the system objective and write brief or casual text
description of each
(c) Draw the use case diagram for the system
(d) For the use case that deals with the remote-controlled garage door opening, write a
fully dressed description
(e) Draw the system sequence diagram(s) for the use case selected in (d)
(f) Draw the domain model with concepts, associations, and attributes
[Note: derive the domain model using only the information that is available so far—do
not elaborate the other use cases]
(g) Show the operation contracts for the operations of the use case selected in (d)
Problem 2.11
For the system described in Problem 2.10, consider the following security issue. If the remote
control supplied with the garage door opener uses a fixed code, a thief may park near your house
and steal your code with a code grabber device. The thief can then duplicate the signal code and
open your garage at will. A solution is to use so called rolling security codes instead of a fixed
code. Rolling code systems automatically change the code each time you operate your garage
door.
(f) Given the automatic external light control, triggered by motion detection, and the above
security issue with fixed signaling codes, a possible use case diagram is as depicted in
Figure 2-46. Are any of the shown use cases legitimate? Explain clearly your answer.
(g) For the use case that deals with the remote-controlled garage door closing, write a fully
dressed description.
(h) Draw the system sequence diagram(s) for the use case selected in (b).
(i) Draw the domain model with concepts, associations, and attributes .
[Note: derive the domain model using only the information that is available so far—do
not elaborate the other use cases.]
191
(j) Show the operation contracts for the operations of the use case selected in (b).
Problem 2.12
Derive the basic use cases for the restaurant automation system (described at the book website,
given in Preface). Draw the use case diagram.
Problem 2.13
Identify the actors and derive the use cases for the vehicular traffic information system (described
at the book website, given in Preface). Draw the use case diagram. Also, draw the system
sequence diagram for the use case that deals with data collection.
Problem 2.14
Consider the automatic patient monitoring system described in Problem 2.3. Identify the
actors and their goals. Briefly, in one sentence, describe each use case but do not elaborate
them. Draw the use case diagram.
Problem 2.15
Consider a grocery supermarket planning to computerize their inventory management. This
problem is similar to one described in Example 1.2 (Section 1.5.3), but has a different goal. The
items on shelves will be marked with Radio Frequency Identification (RFID) tags and a set of RFID
reader-devices will be installed for monitoring the movements of the tagged items. Each tag
carries a 96-bit EPC (Electronic Product Code) with a Global Trade Identification number, which is
an international standard. The RFID readers are installed on each shelf on the sales floor.
1. Request 2. Response
Tag
Reader
Tag
RFID System:
Tag
Tag
The RFID system consists of two types of components (see figure above): (1) RFID tag or
transponder, and (2) RFID reader or transceiver. RFID tags are passive (no power source), and use
the power induced by the magnetic field of the RFID reader. An RFID reader consists of an
antenna, transceiver and decoder, which sends periodic signals to inquire about any tag in
vicinity. On receiving any signal from a tag it passes on that information to the data processor.
You are tasked to develop a software system for inventory management. The envisioned system
will detect which items will soon be depleted from the shelves, as well as when shelves run out of
192
stock and notify the store management. The manager will be RFID tag
able to assign a store associate to replenish the shelf, and the
manager will be notified when the task is completed.
Based on the initial ideas for the desired functions of the
software system, the following requirements are derived:
REQ1. The system shall continuously monitor the tagged
items on the shelves. Every time an item is removed,
this event is recorded in the system database by
recording the current item count from the RFID reader.
The system should also be able to handle the cases
when the customer takes an item, puts it in her
shopping cart, continues shopping, and then changes
her mind, comes back and returns the item to the shelf.
RFID reader
REQ2. The system shall keep track when stock is running low on
shelves. It shall detect a “low-stock” state for a product
when the product’s item count falls below a given
threshold while still greater than zero.
REQ3. The system shall detect an “out-of-stock” state for a Main computer
product when the shelf becomes empty and the
product’s item count reaches zero.
REQ4. The system shall notify the store manager when a “low-
stock” or “out-of-stock” state is detected, so the
shelves will be replenished. The notification will be sent
by electronic mail, and the manager will be able to read
it on his mobile phone.
REQ5. The store manager shall be able to assign a store
associate with a task to replenish a particular shelf with
a specific product. The store associate shall be notified Store manager
by electronic mail about the details of the assigned
task.
REQ6. While the store associate puts items on the shelf, the RFID system shall automatically
detect the newly restocked items by reading out their EPC. The system should support the
option that customers remove items at the same time while the store associate is
replenishing this shelf.
REQ7. The store associate shall be able to explicitly inform the system when the replenishment
task is completed. The number of restocked items will be stored in the database record.
The item count obtained automatically (REQ5) may be displayed to the store associate
for verification. After the store associate confirms that the shelf is replenished, the task
status will be changed to “completed,” and a notification event will be generated for the
store manager.
To keep the hardware and development costs low, we make the following assumptions:
193
A1. You will develop only the software that runs on the main computer and not that for the
peripheral RFID devices. Assume that the software running the RFID readers will be purchased
together with the hardware devices.
A2. The tag EPC is unique for a product category, which means that the system cannot distinguish
different items of the same product. Therefore, the database will store only the total count of a
given product type. No item-specific information will be stored.
A3. Assume that the RFID system works perfectly which, of course, is not true in reality. As of this
writing (2011) on an average 20% of the tags do not function properly. Accurate read rates on
some items can be very low, because of physical limitations like reading through liquid or metals
still exist or interference by other wireless sources that can disrupt the tag transmissions.
A4. Assume that the item removal event is a clean break, which again, may not be true. For
example, if the user is vacillating between buying and not buying, the system may repeatedly
count the item as removed or added and lose track of correct count. Also, the user may return an
item and take another one of the same kind because she likes the latter more than the former. (A
solution may be periodically to scan all tags with the same EPC, and adjust incorrect counts in the
database.)
A5. Regarding REQ1, each RFID reader will be able to detect correctly when more than one item
of the same type is removed simultaneously. If a customer changed her mind and returned an
item (REQ1), we assume that she will return it to the correct shelf, rather than any shelf.
A6. The communication network and the computing system will be able to handle correctly large
volume of events. Potentially, there will be many simultaneous or nearly simultaneous RFID
events, because there is a large number of products on the shelves and there may be a great
number of customers currently in the store, interacting with the items. We assume that the great
number of events will not “clog” the computer network or the processors.
Do the following:
(a) Write all the summary use cases that can be derived from the requirements REQ1–REQ7.
For each use case, indicate the related requirements. Note that one use case may be
related to several requirements and vice versa, one requirement may be related to
several use cases.
(b) Draw the use case diagram for the use cases described in item (a).
(c) Discuss additional requirements and use cases that could be added to this system.
Problem 2.16
Consider again the Grocery Inventory Management system described in Problem 2.15. Focus only
on the summary use cases that deal with depleted stock detection, related to the requirements
REQ1–REQ4. Write the detailed specification for these use cases only.
Problem 2.17
194
Problem 2.18
Problem 2.19
Consider a variation of the home access control system which will do user identification based on
face recognition, as described in Section 2.4.2. Write the detailed use case descriptions of use
cases UC3: AddUser and UC4: RemoveUser for both cases given in Figure 2-16, that is locally
implemented face recognition (Case (a)) and remotely provided face recognition (Case (b)).
Problem 2.20
Consider an automatic bank machine, known as Automatic Teller Machine (ATM), and a customer
who wishes to withdraw some cash from his or her banking account. Draw a UML activity diagram
to represent this use case.
Problem 2.21
Derive the domain model with concepts, associations, and attributes for the virtual mitosis lab
(described at the book website, given in Preface).
Note: You may wonder how is it that you are asked to construct the domain model without first
having the use cases derived. The reason is, because the use cases for the mitosis lab are very
simple, this is left as an exercise for the reader.
Problem 2.22
Explain the relationship between use cases and domain model objects and illustrate by example.
Problem 2.23
Problem 2.24
Problem 2.25
Problem 2.26
Problem 2.27
195
Problem 2.28
Problem 2.29
An example use case for the system presented in Section 1.5.1 is given as follows. (Although the
advertisement procedure is not shown to preserve clarity, you should assume that it applies
where appropriate, as described in Section 1.5.1.)
Use Case UC-x: BuyStocks
Initiating Actor: Player [full name: investor player]
Actor’s Goal: To buy stocks, get them added to his portfolio automatically
Participating Actors: StockReportingWebsite [e.g., Yahoo! Finance]
Preconditions: Player is currently logged in the system and is shown a hyperlink “Buy
stocks.”
Postconditions: System has informed the player of the purchase outcome. The logs and
the player’s portfolio are updated.
Flow of Events for Main Success Scenario:
Player clicks the hyperlink “Buy stocks”
System prompts for the filtering criteria (e.g., based on company names, industry
sector, price range, etc.) or “Show all”
Player specifies the filtering criteria and submits
System contacts StockReportingWebsite and requests the current stock prices for
companies that meet the filtering criteria
StockReportingWebsite responds with HTML document containing the stock prices
From the received HTML document, System extracts, formats, and displays the stock
prices for Player’s consideration; the display also shows the player’s account balance
that is available for trading
Player browses and selects the stock symbols, number of shares, and places the order
_ 8. System (a) updates the player’s portfolio; (b) adjusts the player’s account balance,
including a commission fee charge; (c) archives the transaction in a database; and (d)
informs Player of the successful transaction and shows his new portfolio standing
Note that in Step 8 above only virtual trading takes place because this is fantasy stock trading.
Derive (a part of) the domain model for the system-to-be based on the use case BuyStocks.
(a) Write a definition for each concept in your domain model.
(b) Write a definition for each attribute and association in your domain model.
(c) Draw the domain model.
(d) Indicate the types of concepts, such as «boundary», «control», or «entity».
196
Customer
enterCard()
askPIN()
enterPIN()
askAmt()
enterAmt()
Figure 2-47: Sequence diagram for the ATM machine of Problem 2.30 (see text for
explanation). GUI = Graphical user interface.
Problem 2.30
Suppose you are designing an ATM machine (also see Problem 2.20). Consider the use case
“Withdraw Cash” and finish the sequence diagram shown in Figure 2-47. The
CustomerID object contains all the information received from the current customer.
IDChecker compares the entered ID with all the stored IDs contained in
CustomerIDStorage. AcctInfo mainly contains information about the current account
balance. AcctManager performs operations on the AcctInfo, such as subtracting the
withdrawn amount and ensuring that the remainder is greater than or equal to zero.
Lastly, CashDispenserCtrl control the physical device that dispenses cash.
One could argued that AcctInfo and AcctManager should be combined into a single
object Account, which encapsulates both account data and the methods that
operate on the data. The account data is most likely read from a database, and the
container object is created at that time. Discuss the pros and cons for both
possibilities.
Indicate any design principles that you employ in the sequence diagram.
Problem 2.31
You are to develop an online auction site, with selling, bidding, and buying services. The buying
service should allow the users to find an item, bid for it and/or buy it, and pay for it. The use case
diagram for the system may look as follows:
Online Auction Site
ListItem
iti
«in
«include»
FindItem
«initi
BidForItem «initi
«init «p
i a
rti icip
Seller c ip art Buyer
«include»
«in ViewBids «p i
iti it
«in
ti
«in
ni
«particip
«i
CloseAuction
iti
BuyItem
Shipping
RateTransaction
Agency
Creditor
197
ItemInfo
ItemInfo ItemsCatalog
ItemsCatalog Controller
Controller
–– name
name :: String
String
–– startPrice
startPrice::float
float
–– reserved
* ++ add(item:
add(item:ItemInfo)
ItemInfo) :: int
int ++ listItem(item:
listItem(item: ItemInfo)
ItemInfo)
reserved ::boolean
boolean
1 ++ remove(idx
remove(idx :: int)
int) ++ findItem(name
findItem(name ::String)
String)
++ getName()
getName() :: String
String ++ getNext():
getNext(): ItemInfo
ItemInfo ++ bidForItem(name
bidForItem(name ::String)
String)
++ getStartPrice()
getStartPrice() ::float
float ++ hasMore()
hasMore() :: boolean
boolean ++ viewBids(itemName
viewBids(itemName :: String)
String)
++ getSeller()
getSeller() :: SellerInfo
SellerInfo ++ closeAuction(itmNam
closeAuction(itmNam ::String)
String)
++ getBidsList()
getBidsList() ::BidsList
BidsList bids ++ buyItem(name
buyItem(name ::String)
String)
++ setReserved(ok
setReserved(ok ::boolean)
boolean) ++ payForItem(price:
payForItem(price: float)
float)
++ isReserved()
isReserved() ::boolean
boolean 1
BidsList
BidsList
seller
1
++ add(bid:
add(bid:Bid)
Bid) ::int
int
SellerInfo
SellerInfo ++ remove(idx
remove(idx :: int)
int) BuyerInfo
BuyerInfo
–– name
name: :String
String ++ getNext():
getNext(): Bid
Bid –– name
name ::String
String
–– address
address::String
String ++ hasMore()
hasMore() :: boolean
boolean –– address
address::String
String
++ getName()
getName() :: String
String ++ getName()
getName() :: String
String
++ getAddress()
getAddress() :: String
String ++ getAddress()
getAddress() :: String
String
1 *
Bid 1 1
seller Bid
–– amount
amount:: float
float bidder
Payment
Payment
++ getBidder()
getBidder() :: BuyerInfo
BuyerInfo
–– amount
amount::float
float
++ getAmount()
getAmount() :: float
float
++ getBuyer()
getBuyer() ::BuyerInfo
BuyerInfo
buyer
item …… Etc.
Etc.
Figure 2-48: A possible class diagram for the online auction site of Problem 2.31.
We assume a simple system to which extra features may be added, such as auction expiration
date on items. Other features may involve the shipment agency to allow tracking the shipment
status.
A possible class diagram for the system is shown in Figure 2-48. Assume that ItemInfo is marked
as “reserved” when the Seller accepts the highest bid and closes the auction on that item only.
Before closing, Seller might want to review how active the bidding is, to decide whether to wait
for some more time before closing the bid. That particular ItemInfo is removed from ItemsCatalog
once the payment is processed.
In the use case CloseAuction, the Seller reviews the existing bids for a given item, selects the
highest and notifies the Buyer associated with the highest bid about the decision (this is why
«participate» link between the use case CloseAuction and Buyer). Assume that there are more
than one bids posted for the selected item.
Complete the interaction diagram shown below for this use case. Do not include processing the
payment (for this use case see Problem 2.8). (Note: You may introduce new classes or modify the
existing classes in Figure 2-48 if you feel it necessary for solving the problem.)
198
Buyer
Problem 2.32
Consider the use case BuyStocks presented in Problem 2.29. The goal is to draw the UML
sequence diagram only for Step 6 in this use case. Start at the point when the system receives the
HTML document from the StockReportingWebsite and stop at the point when an HTML page is
prepared and sent to player’s browser for viewing.
(a) List the responsibilities that need to be assigned to software objects.
(b) Assign the responsibilities from the list in (a) to objects. Explicitly mention any design
principles that you are using in your design, such as Expert Doer, High Cohesion, or Low
Coupling. Provide arguments as to why the particular principle applies.
(c) Draw the UML sequence diagram.
Problem 2.33
Problem 2.34
In the patient-monitoring scenario of Problem 2.3 and Problem 2.14, assume that the hospital
personnel who gets notified about patient status is not office-bound but can be moving around
the hospital. Also, all notifications must be archived in a hospital database for a possible future
auditing. Draw a UML deployment diagram representing the hardware/software mapping of this
system.
Problem 2.35
Consider the automatic patient monitoring system described in Problem 2.3 and analyzed
in Problem 2.14. Focus on the patient device only and ignore any software that might be
running in the remote hospital. Suppose you are provided with an initial software design as
follows.
199
The domain model consists of the following concepts and their responsibilities:
Responsibility Concept
Read out the patient’s blood pressure from a sensor Blood Pressure Reader
Read out the patient’s heart rate from a sensor Heart Rate Reader
Compare the vital signs to the safe ranges and detect if the vitals are outside Abnormality Detector
Hold description of the safe ranges for patient vital signs; measurements Vitals Safe Ranges
outside these ranges indicate elevated risk to the patient; should be
automatically adjusted for patient’s activity
Accept user input for constraints on safe ranges Safe Range Entry
Read the patient’s activity indicators Activity Observer
Recognize the type of person’s activity Activity Classifier
Hold description of a given type of person’s activity Activity Model
Send an alert to a remote hospital Hospital Alerter
Hold information sent to the hospital about abnormal vitals or faulty sensors Hospital Alert
Run diagnostic tests on analog sensors Sensor Diagnostic
Interpret the results of diagnostic tests on analog sensors Failure Detector
Hold description of a type of sensor failure Sensor Failure Mode
Read the remaining batter power Battery Checker
Send an alert to the patient Patient Alerter
Hold information sent to the patient about low battery Patient Alert
Coordinate activity and delegate work to other concepts Controller
A sketchy UML sequence diagram is designed using the given concepts as in Figure 2-49. Note that
this diagram is incomplete: the part for checking the batter power is not shown for the lack of
space. However, it should be clear from the given part how the missing part should look like.
Recall that the period lengths for observations made by our system are related as:
BP Reader & HR Reader < Sensor Diagnostic < Activity Observer < Battery Checker
In other words, vital signs are recorded frequently and battery is checked least frequently. These
relationships also indicate the priority or relative importance of the observations. However, the
initial design takes a simplified approach and assumes a single timer that periodically wakes up
the system to visit all different sensors, and acquire and process their data. You may but do not
need to stick with this simplified design in your solution.
Using the design principles from Section 2.6 or any other principles that you are aware of, solve:
(a) Check if the design in Figure 2-49 already uses some design principles and, if so, explain
your claim.
- If you believe that the given design or some parts of it are sufficiently good
then explain how the application of any interventions would make the design
worse.
- Be specific and avoid generic or hypothetical explanations of why some designs
are better than others. Use concrete examples and UML diagrams or pseudo-
code to illustrate your point and refer to specific qualities of software design.
(b) Carefully examine the sketchy design in Figure 2-49 and identify as many opportunities
as you can to improve it by applying design principles.
- If you apply a principle, first argue why the existing design may be problematic.
- Provide as much details as possible about how the principle will be
implemented and how the new design will work (draw UML sequence diagrams
or write pseudo-code).
200
Blood pressr
Heart rate
ranges := getValues()
abnormal := isOutOfRange(vital)
check if in/out
result :=
faulty := isFaulty() run tests
isFailed(result)
classify(data)
Figure 2-49: A sketchy UML sequence diagram for patient monitoring in Problem 2.35.
- Explain how the principle that you introduced improved the original design (i.e.,
what are the expected benefits compared to the original design).
Feel free to introduce new concepts, substitute the given concepts with different ones, or modify
their responsibilities. You may also discard existing concepts if you find them redundant. In
addition, you may change how acquisition of different sensory data is initiated. However, when
you do so, explain the motivation for your actions.
Problem 2.36
201
Chapter 3
Modeling and System Specification
Contents
“The beginning is the most important part of the work.” —Plato What is a System?
The term “system specification” is used both for the process of Notations for System Specification
deriving the properties of the software system as well as for Problem Frames
the document that describes those properties. As the system Specifying Goals
is developed, its properties will change during different stages Summary and Bibliographical Notes
of its lifecycle, and so it may be unclear which specification is
being referred to. To avoid ambiguity we adopt a common Practice Problems
meaning: The system specification states what should be valid
(true) about the system at the time when the system is
delivered to the customer. Specifying system means stating
what we desire to achieve, not how we plan to accomplish it or
what has been achieved at an intermediate stage. The focus of
this chapter is on describing the system function, not its form.
Chapter 5 will focus on the form, how to build the system.
There are several aspects of specifying the system under
development, including:
• Understanding the problem and determining what
needs to be specified
• Selecting notation(s) to use for the specification
• Verifying that the specification meets the
requirements
Of course, this is not a linear sequence of activities. Rather, as we achieve better understanding
of the problem, we may wish to switch to a different notation; also, the verification activity may
uncover some weaknesses in understanding the problem and trigger an additional study of the
problem at hand.
We have already encountered one popular notation for specification, that is, the UML standard.
We will continue using UML and learn some more about it as well as about some other notations.
Most developers agree that a single type of system model is not enough to specify any non-trivial
system. You usually need several different models, told in different “languages” for different
stakeholders. The end user has certain requirements about the system, such as that the system
allows him to do his job easier. The business manager may be more concerned about the policies,
225
rules, and processes supported by the system. Some stakeholders will care about engineering
design’s details, and others will not. Therefore, it is advisable to develop the system specification
as viewed from several different angles, using different notations.
My primary concern here is the developer’s perspective. We need to specify what are the
resting/equilibrium states and the anticipated perturbations. How does the system appear in an
equilibrium state? How does it react to a perturbation and what sequence of steps it goes through
to reach a new equilibrium? We already saw that use cases deal with such issues, to a certain
extent, although informally. Here, I will review some more precise approaches. This does not
necessarily imply formal methods. Some notations are better suited for particular types of
problems. Our goal is to work with a certain degree of precision that is amenable to some form of
analysis.
The system specification should be derived from the requirements. The specification should
accurately describe the system behavior necessary to satisfy the requirements. Most developers
would argue that the hardest part of software task is arriving at a complete and consistent
specification, and much of the essence of building a program is in fact the debugging its
specification—figuring out what exactly needs to be done. The developer might have
misunderstood the customer’s needs. The customer may be unsure, and the initial requirements
will often be fuzzy or incomplete. I should emphasize again and again that writing the
requirements and deriving the specification is not a strictly sequential process. Rather, we must
explore the requirements and system specification iteratively, until a satisfactory solution is
found. Even then, we may need to revisit and reexamine both if questions arise during the design
and implementation.
Although the system requirements are ultimately decided by the customer, the developer needs
to know how to ask the right questions and how to systemize the information gathered from the
customer. But, what questions to ask? A useful approach would be to be start with a catalogue of
simple representative problems that tend to occur in every real-world problem. These elementary-
building-block problems are called “problem frames.” Each can be described in a well-defined
format, each has a well-known solution, and each has a well-known set of associated issues. We
already made initial steps in Section 2.3.1. In Section 3.3 we will see how complex problems can
be made manageable by applying problem frames. In this way, problem frames can help us bridge
the gap between system requirements and system specification.
“All models are wrong, but some are useful.” —George E. P. Box
“There is no property absolutely essential to one thing. The same property, which figures as the essence of a thing on
one occasion, becomes a very inessential feature upon another.” —William James
Up until now, we’ve largely focused on the system itself. Aside from some actors, we’ve mostly
ignored the outside world/environment, aside from actors. By describing different interaction
scenarios as a set of use cases, we were able to develop a software system in an incremental
fashion.
However, there are some limitations with this approach. First, by considering only the “actors”
226
that the system directly interacts with, we may leave out some parts of the environment that have
no direct interactions with the software-to-be but are important to the problem and its solution.A
major part of the problem with school registration is the schedule. Many of our test cases are
derived from certain attributes within the schedule, yet the schedule does not interact with the
registration system. Rather, it is a passive element that the system references, but the schedule
does not initiate any actions nor is it involved in any transactions other than reading.
Second, starting by focusing on interaction scenarios may not be the easiest route in describing
the problem. Use cases describe the sequence of user’s interaction with the system. Use cases,
as we mentioned, are procedural rather than object-oriented. The focus on sequential procedure
may not be difficult to begin with, but it requires being on a constant watch for any branching off
from the “main success scenario.” Decision making points—or “branching points”-- may be
difficult to detect.It may be hard to conceive what could go wrong—particularly if not guided by
a helpful representation of the problem structure.
The best way to start conceptual modeling may be with how users and customers prefer to
conceptualize their world. This makes sense because the developer needs to interact a great deal
with customers when the problem is being defined. This may also vary across different application
domains.
In this chapter I will present some alternative approaches to problem description (i.e.,
requirements and specification), which may be more involved, but are believed to offer easier
routes to solving large-scale and complex problems.
227
- deterministic
- vs. stochastic phenomena.
228
WORLD
Phenomena in Part i
Phenomena in Part j
Shared
phenomena
Phenomena in Part k
First, note that this is only one possible categorization, which seems suitable for software
engineering. Other categorizations are possible and have been proposed. Moreover, any specific
identification of world phenomena is bound to become faulty over time, regardless of the amount
of effort we invest in deriving it.
In Section 1.1.1 , w e m e n t i o n e d the effect of the second law of thermodynamics. When
identifying the world phenomena, we inevitably make approximations. Certain kinds of
information are regarded as important and the rest of the information is treated as unimportant
and ignored. Nature and society are full of random fluctuations. Due to this, some phenomena
that we used to classify important and unimportant information becomes intermingled. Thus, our
original model is invalidated. This shows that we are limited in what our modeling efforts can
achieve.
Individuals
An individual is something that can be named and reliably distinguished from others. Decisions to
treat certain phenomena as individuals are not objective—they depend on the problem at hand.
The selected level of abstraction is relative to the observer. We choose to recognize just those
individuals that are useful to solving the problem and are practically distinguishable. We will
choose to distinguish three kinds of individual: events, entities, and values.
♦ An event is an individual that , occurs at a particular point in time—a happening, essentially.
Each event is indivisible and instantaneous, that is, the event itself has no internal structure and
takes no time to happen. Hence, we can talk about “before the event” and “after the event,” but
not about “during the event.” An example event is the unlock command for a “smart” door;
another example event is submission of class registration; yet another example is displaying an
account balance on a screen. Further discussion of events is in Section 3.1.3.
229
Set of all pairs of persons Set of all 3-tuples of persons
Set of neighbors
Relations
We say that individuals are in relation if they share a certain characteristic. To define a relation,
we also need to specify how many individuals we consider at a time. For example, for any pair of
people, we could decide that they are neighbors if their homes are less than 100 meters apart
from each other. Given any two persons, Person_i and Person_j, if they pass this test then the
relation holds (is true); otherwise it does not hold (is false). All pairs of persons that pass the test
are said to be in the relation Neighbors(Person_i, Person_j). The pairs of persons that are
neighbors form a subset of all pairs of persons. See Figure 3-2(a).
Relations need not be established on pairs of individuals only. We can consider any number of
individuals and decide whether they are in a relation. The number n of considered individuals can
be any positive integer n ≥ 2 and it must be fixed for every test of the relation; we will call it an n-
tuple. We will write a relation as RelationName(Individual1, …, Individualn).
230
When one of the individuals remains constant for all tests of the relation, we may include its name
in the relation’s name. For example, consider the characteristic of wearing eyeglasses. We can test
whether a Person_i is in relation Wearing(Person_i, Glasses), which is a subset of all persons.
Because Glasses remain constant across all tests, we can write WearingGlasses(Person_i), or simply
GlassesWearer(Person_i).
Next, consider the so-called “love triangle” relation as an example for n =3. Obviously, to test for this
characteristic we must consider exactly three persons at a time; not two, not four. Then the relation
InLoveTriangle(Person_i, Person_j, Person_k) will form a set of all 3-tuples (or triplets) of persons
for whom this characteristic is true. This set, in turn, is a subset of all 3-tuples of persons overall.
See Figure 3-2(b). A formal definition of relation will be given in Section 3.2.1 after presenting some
notation.
We will consider three kinds of relations: states, truths, and roles.
♦ A state is a relation among individual entities and values, which can change over time. I will
describe states in Section 3.1.2, and skip them for now.
♦ A truth is a fixed relation among individuals that cannot possibly change over time. Unlike
states, which change over time, truths remain constant. A bit more relaxed definition would be to
consider the relations that are invariable on the time-scale that we are interested in. Example
time-scales could be project duration or anticipated product life-cycle. When stating a truth, the
individuals are always values, and the truth expresses invariable facts, such as GreaterThan(5, 3)
or StockTickerSymbol(“Google, Inc.,” “GOOG”). It is reasonably safe to assume that company stock
symbols will not change (although mergers or acquisitions may affect this!).
♦ A role is a relation between an event and individual that participate in it in a particular way.
Each role expresses what you might otherwise think of as one of the “arguments” (or
“parameters”) of the event.
231
3.1.2 States and State Variables
A state describes what is true in the world at each particular point in time. The state of an
individual represents the cumulative results of its behavior. Consider a device, such as a digital
video disc (DVD) player. How the device reacts to an input command depends not only upon that
input, but also upon the internal state that the device is currently in. So, if the “PLAY” button is
pushed on a DVD player, what happens next will depend on various things, such as whether or
not the player is turned on, contains a disc, or is already playing. These conditions represent
different states of a DVD player.
By considering such options, we may come up with a list of all states for a DVD player, like this:
State 1: NotPowered (the player is not poweredup)
State 2: Powered (the player is powered up)
State 3: Loaded (a disc is in the tray)
State 4: Playing
We can define state more precisely as a relation on a set of objects.This relation simply selects a
subset of that set of objects. For the DVD player example, what we wish to express is “The DVD
player’s power is off.” We could write Is(DVDplayer, NotPowered) or IsNotPowered(DVDplayer).
We will settle on this format: NotPowered(DVDplayer).
NotPowered(x) is a subset of DVD players x that are not powered up. In other words,
NotPowered(x) is true if x is currently off. Assuming that one such player is the one in the living
room, labeled as DVDinLivRm, then NotPowered(DVDinLivRm) holds true if the player in the living
room is not powered up.
Upon a closer examination, we may realize that the above list of states implies that a non-
powered-up player never contains a disc in the tray. If you are charged to develop software for
the DVD player, you must clarify this. (Always ask questions until everything is clear!) Does this
mean that the disc is automatically ejected when the power-off button is pushed? If this is not the
case or the issue is yet unresolved, we may want to redesign our list of DVD player states as:
State 1: NotPoweredEmpty (not powered up + does not contain a disc) State 2:
NotPoweredLoaded ( not powered up + contains a disc) State 3: PoweredEmpty
(powered up + does not contain a disc) State 4:
PoweredLoaded (powered up + contains a disc) State 5: Playing
At this point one may realize that--instead of using such “combined” system states—maybe we
should discern different ”sub-objects” of the DVD player. This way, we could consider the state of
each part. See Figure 3-3. Each part has its “local” states, as in this table.
Note that the relation Off(b) is defined on the set of buttons. Thus, relations such as Off(PowerButton )
and Off(PlayButton)may be true. The case is similar for On(b).
232
DVD player
Power
button
DVD player
button
(a) (b)
Figure 3-3: Abstractions of a DVD player at different levels of detail: (a) The player as a single
entity. (b) The player seen as composed of several entities.
Now, how can we define the state of the whole system? Obviously, we could say that the
aggregate system state is defined by the states of its parts. For example, one state of the DVD
player is { On(PowerButton), Empty(), Off(PlayButton), … }. Note that the relation Empty() is left
without an argument, because it is clear to which object it refers to—the disc tray. In this case,
we could also write Empty without parentheses. The arrangement of the relations in this “state
tuple” is not important, as long as it is clear what part each relation refers to.
The question now arises: is every combination of parts’ states allowed? Are these parts
independent of each other, or there are constraints on the state of one part that are imposed by
the current states of other parts? Some states of parts of a composite domain may be mutually
exclusive. Going back to the issue posed earlier, can the disc tray be in the “loaded” state when
the power button is in the “off” state? Because these are parts of the same system, we must make
explicit any mutual dependencies of the parts’ states. We may end up with a list of valid system
state tuples that does not include all possible tuples that can be constructed.
Both representations of a system state (single aggregate state vs. tuple of parts’ states) are
correct, but their suitability depends on what kind of details you care to know about the system.
In general, considering the system as a set of parts that define state tuples presents a cleaner and
more modular approach than a single aggregate state.
In software engineering, we care about the visible aspects of the software system. In general,
visible aspects do not necessarily need to correspond to “parts” of the system. Rather, they are
any observable qualities of the system. For example, domain-model attributes identified in
Section 2.5 represent observable qualities of the system. We call each observable quality a state
variable.
In our first case-study example, variables include the lock and the bulb. Another variable is the
counter of the number of attempts at opening the lock. Yet another variable is the amount of
timer that counts down the time elapsed since the lock was open, to support auto-lock
functionality. The state variables of our system can be summarized as in this table:
In case of multiple locks and/or bulbs, we have a different state variable for every lock/bulb,
233
similar to the above example of DVD player buttons. So, the state relations for backyard and front
door locks could be defined as Disarmed(Backyard) and Disarmed(Front).
234
The situation with numeric relations is a bit trickier. We could write 2(Counter) to mean that the
counter is currently in state “2,” but this is a bit awkward. Rather, just for the sake of convenience
I will write Equals(Counter, 2) and similarly Equals(Timer, 3).
A system state is defined as a tuple of state variables containing any valid combination of state
relations. State is an aggregate representation of the system characteristics that we care to know
about looking from outside of the system. For the above example, an example state tuple
is:
{ Disarmed(Front), Lit, Armed(Backyard), Equals(Counter, 0), Equals(Timer, 0) }. One
way to classify states is by what the object is doing in a given state:
• A state is a passive quality if the object is just waiting for an event to happen. For the DVD player
described earlier, such states are “Powered” and “Loaded.”
• A state is an active quality if the object is executing an activity. When the DVD player is in the
“Playing” state it is actively playing a disc.
A combination of these options is also possible, i.e., the object may be executing an activity and
also waiting for an event.
The movements between states are called transitions and are most often caused by events (see
Section 3.1.3). Each state transition connects two states. Usually, not all pairs of states are
connected by transitions—only specific transitions are permissible.
Obviously, this system has a great many of possible states, which is, nonetheless, finite. An improvised
graphical representation is shown in Figure 3-4. (UML standard symbols for state diagrams are
described later in Section 3.2.2.)
235
Market
index
Market
gate
Figure 3-4: Graphical representation of states for Example 3.1. The arrows indicate the
permissible paths for transitioning between different states.
As we’ve discussed, the selection of state phenomena depends on the observer and the problem at
hand.
236
Figure 3-5: Graphical representation of states for “registration status” from Example 3.2. Course
microstates from Figure 3-4 represented within registration macrostates.
237
Elevation of the ball
2 Event: Kick
3 State:
Ball flying
Event: Kick
State:
Ball standing
Event: Splash
Event: Splash
State:
5 Ball floating Time
The states that are directly observable at a given level of detail (i.e. coarse graining) will be called
microstates. A group of microstates is called a macrostate (or superstate). The states defined in
Example 3.2 are macrostates.
Sometimes our abstraction may identify simultaneous (or concurrent) activities that the object
executes in a given state. For example, when the DVD player is in the “Playing” state it may be
simultaneously playing a disc (producing video output) and updating the time-progress display.
Section 3.2.2 describes UML state machine diagrams as a standardized graphical notation for
representing states and transitions between them.
238
6. We cannot have an
239
uninterrupted sequence of events—this would simply be an insufficient model, and would require
refining the time scale to identify the intervals between successive events.
The developer may need to make a choice of what to treat as a single event. Consider the SMART-
home-access control case study (Section 1.3.1). When the tenant is punching in an identification
key, should this be treated as a single event, or should each keystroke be considered a different
event? The answer depends on whether your problem statement requires you to treat it one way
or another. Are there any exceptions that are relevant to the problem, which may arise between
different keystrokes? If so, then we need to treat each keystroke as an event.
In a state transition diagram of this case study, the action “turnLightOff” may be marked with a
question mark because, say, we haven’t yet found an acceptable solution for this case. Further,
the state [disarmed, unlit] may not be shown because the lock is not supposed to stay for long in
a disarmed state—it will be closed shortly, either by the user or automatically.
You may wonder about the relationship between events and messages, or operations in object-
oriented approach. Our above definition of “event” is more general, because it is not limited to
object-orientation. The notion of message implies that a signal is sent from one entity to another.
Unlike a message, an event is something that happens—it may include one or more individuals,
but it is not necessarily directed from one individual to another. Events just mark transitions
between successive states. The advantage of this view is that we can avoid specifying processing
detail at an early stage of problem definition. Use case analysis (Section 2.4.3) is different from
this. It requires explicitly stating the sequential processing procedure (“scenarios”), which leads
to system operations.
------------------ SIDEBAR: More Differences Between Messages and Events --------------------
Another difference between messages and events is that events always signify state change. Even
where the system remains in the same state, there is an explicit description of an event and state
change. Hence, events depend on how the corresponding state set is already defined. Messages,
on the other hand, may not be related to state changes. For example, an operation that simply
retrieves the value of an object attribute does not affect the object’s state.
----------------------------------------------------------------------------------------------------------------------------- -
Example events:
displayCourse – this event marks the moment the course is visible to students as a class offered
during the coming semester
courseActivate – this event denotes either a manual or automated change in the course’s status
(can view, can register). This is specific to the first-time availability of a course
studentRegister – this event signifies a student sending desired coursework to a part of the
program that validates that student. Separates student’s status from unregistered to pending
registerSuccess – this event denotes a response from the verification part of the system and
indicates that the student is allowed to resister for the submitted courses
You may have observed that event names are formed as verb phrases. The reason for this is to
distinguish events from states. Although this is reminiscent of messages in object-oriented
approach, events do not necessarily correspond to messages, as already discussed earlier.
240
Figure 3-7: Graphical representation of events marking state transitions of a course registration.
Event Description
Submit The student submits the request for course registration
Success The system allows the student to be registered for the course
241
Figure 3-8: Context diagram for our case study 2: course registration system.
A context diagram shows parts of the world (Figure 3-1) that are relevant to our problem--and
only the relevant parts. Each box in a context diagram represents a different domain. A domain is
a part of the world that can be distinguished from other parts, and is itself considered some kind
of “whole”. Each domain is a different subject matter that appears in the description of the
problem. A domain is described by the phenomena that exist or occur in it. In every software
development problem, there are at least two domains: the application domain
(environment/ real world—what is given) and the machine (system-to-be—what is to be
constructed). Some of the domains in Figure 3-8 correspond to what we called “actors” in Chapter
2. However, there are other subject matters, as well, such as “Course Schedule”.
To simplify, we decide that all the domains in the context diagram are physical. In Figure 3-8, while
this may be clear for other domains, even ”Course Schedule” should be a physical domain. We
assume that the corresponding box stands for the physical representation of the information
about the classes in which the student has registered. In other words, this is the representation
stored in computer memory or displayed on a screen or printed on paper. The reason for
emphasizing physical domains and physical interactions is because the point of software
development is to build systems that interact with the physical world and help the user solve
problems.
Domain Types
Domains can be distinguished as to whether they are given or are to be designed. A given domain is
a problem domain whose properties are given—we are not allowed to design such a domain. In some
cases the machine can influence the behavior of a given domain. For example, in Figure 3-8 a student
registering for courses might influence what courses are available in SchoolReg (given
242
domain). A designed domain is a problem domain for which data structures--and, to some extent, its
data content--need to be determined and constructed. An example is the “Student’s Course load”
domain in Figure 3-8, which is “designed” around each student.
243
Play
enable/disable button
notify
start
activate stop
shut down activate
Power shut down
Disc tray eject (?) button
Display
eject
enable load notify
disable
Eject
button
Figure 3-9: Domains and shared phenomena in the problem of controlling a DVD player.
Often, one kind of problem is distinguished from another by different domain types. To a large
degree, these distinctions arise naturally out of the domain phenomena. But it is also useful to
make a broad classification into three main types.
♦ A causal domain is one whose properties include predictable causal relationships among its
causal phenomena.
A causal domain may control some or all or none of the shared phenomena at an interface with
another domain.
♦ A biddable domain usually consists of people. The most important characteristic of a biddable
domain is that it lacks positive predictable internal causality. This means that, in most situations,
it is impossible to compel a person to initiate an event: the most that can be done is to issue
instructions to be followed.
Shared Phenomena
So far, we’ve considered world phenomena as belonging to particular domains. Some phenomena
are shared. Shared phenomena, viewed from different domains, are the essence of domain
interaction and communication. You can think of the domains as seeing the same event from
different points of view.
Figure 3-9 shows domains and shared phenomena in the scenario of controlling a DVD player.
245
Given an external stimulus, the system responds by going through a set of transient states until it
settles at an equilibrium state. An equilibrium state may involve stable oscillations, e.g., a behavior
driven by an internal clock.
In mechanics, when an external force acts on an object, we describe its behavior through a set of
mathematical equations. Here we describe it as a sequence of (discrete) action-reaction or
stimulus-response events, in plain English.
“… psychologically we must keep all the theories in our heads, and every theoretical physicist who is any good
knows six or seven different theoretical representations for exactly the same physics. He knows that they are all
equivalent, and that nobody is ever going to be able to decide which one is right at that level, but he keeps them in
his head, hoping that they will give him different ideas for guessing.”
—Richard Feynman, The Character of Physical Law
This section reviews some basic discrete mathematics that often appears in specifications. First I
present a brief overview of sets notation.
NOTE: These next couple sub-sections will review concepts from discrete mathematics. Skim
these if you are familiar with sets, proofs, and logical relations from a discrete math standpoint.
A Brief Review on Sets
A set is a well-defined collection of objects that are called members or elements. A set is
completely defined by its elements. To declare that object x is a member of a set A, write x ∈ A.
Conversely, to declare that object y is not a member of a set A, write x ∉ A. A set which has no
members is the empty set and is denoted as { } or ∅.
Sets A and B are equal (denoted as A = B) if they have exactly the same members.
If A and B are not equal, write A ≠ B. Set B is a subset of a set A if all of the members of B are
members of A, and this is denoted as B ⊆ A. The set B is a proper subset of A if B is a subset of A
and B ≠ A, which is denoted as B ⊂ A.
The union of two sets A and B is the set whose members belong to A, B or both, and is denoted as
A ∪ B.
The intersection of two sets A and B is the set whose members belong to both A and B, and is
denoted as A ∩ B.
Two sets A and B are disjoint if their intersection is the empty set: A ∩ B = ∅.
When B ⊆ A, the set difference A \ B is the set of members of A which are not members of B.
246
The members of a set can themselves be sets. Of particular interest is the set that contains all the
subsets of a given set A, including both ∅ and A itself. This set is called the power set of set A and
is denoted (A), or A, or 2A.
The ordered pair 〈x, y〉 is a pair of objects in which x is the first object and y is the second object.
Two ordered pairs 〈x, y〉 and 〈a, b〉 are equal if and only if x = a and y = b.
Recall the discussion of relations among individuals in Section 3.1.1. An n-ary relation R on A, for
n > 1, is defined as a subset of the n-fold Cartesian product, R ⊆ A × A × … × A.
248
Table 3-1: Operators of the propositional and predicate logics.
Propositional Logic
∧ conjunction (p and q) ⇒ implication (if p then q)
∨ disjunction (p or q) ⇔ biconditional (p if and only if q)
¬ negation (not p) ≡ equivalence (p is equivalent to q)
Predicate Logic (extends propositional logic with two quantifiers)
∀ universal quantification (for all x, P(x))
∃ existential quantification (there exists x, P(x))
2 The reader might have noticed that we already encountered predicates in Section 3.1.2 where the
state relations for objects actually are predicates.
249
Two types of compound propositions are of special interest. A compound proposition that is
always true, regardless of the truth values of its constituent propositions is called a tautology. A
simple example is p ∨ ¬p, which is always true because either p is true or it is false. On the other
hand, a compound proposition that is always false is called a contradiction. A simple example is p
∧ ¬p, because p cannot be true and false at the same time. Obviously, the negation of a tautology
is a contradiction, and vice versa. Finally, a compound proposition that is neither a tautology nor
a contradiction is called a contingency.
The compound propositions p and q are said to be logically equivalent--denoted as p ≡ q--if p ⇔
q is a tautology. In other words, p ≡ q if p and q have the same truth values for all possible truth
values of their component variables.
For example, the statements r ⇒ s and ¬r ∨ s are logically equivalent. The proof of this goes as
follows. Earlier we stated that a conditional statement is false only when its premise is true and
its conclusion is false, and true otherwise. We can write this as
r ⇒ s ≡ ¬(r ∧ ¬s)
≡ ¬r ∨ ¬(¬s) by the first De Morgan’s law: ¬(p ∧ q) ≡ ¬p ∨ ¬q
≡ ¬r ∨ s
[For the sake of completeness, I state here, as well, the second De Morgan’s law: ¬(p ∨ q) ≡
¬p ∧ ¬q.]
Translating sentences in natural language into logical expressions is an essential part of system
specification. Consider, for example, the following requirements in a system that acts as a financial
investment assistant.
REQ1
The system shall support the "registration" of new users (aka, newly matriculated students),
but only by admins. Students are not allowed to register themselves. This registration will
link the student's credentials to their academic record, as well as other university
administrative records for that student (e.g., financial records [which are kept separate for
obvious reasons]). For "new users" a sequence of onboarding actions may be required, but a
student is ineligible to register for classes without this account.
REQ2
The system shall support the student registering for class. This system will provide an
environment that admins and students can interact with. Admins may add courses, allow
registration by valid students, and close registrations (and other actions). Students may view
posted courses, submit registration for courses, and to add/drop classes while the registration
period is open. The system shall take student requests for registration and verify that the
student can register for the given classes. This may come in the form of many internal
verifications, such as checking that the student does not have a financial hold against their
account prior to allowing registration or making sure the student has completed the necessary
prerequisites. In the case of a student registering for multiple courses, the system may allow
some of the requested courses that are "valid", but disallow those that are "invalid".
We start by listing the declarative sentences that can be extracted from the requirements.
REQ1 yields the following declarative sentences. Keep in mind that these are not necessarily
propositions because we still do not know whether they have a truth value.
264
Label
a - The student has a valid registration
b - The student has completed all onboarding actions
c - The student has set up their password (onboarding action - may require other steps)
d - The student has completed Title IX training course (onboarding action)
e - The student has read and signed the academic honesty (onboarding action)
f - The student is capable of registering for classes
Note that there may be many propositions before we get to `a`, which implies many
requirements before we get to REQ1, as we stated it.
Next, we need to ascertain their truth values. Recall that the specifications state what is true
about the system at the time it is delivered to the customer. The truth value of `a` must be
established by the developer before the system is delivered. The truth values of `c`, `d`, and
`e` depend on the student’s actions and will affect the truth value of `b` and `f`. Consider the
sentence `c`. Assuming that `password` denotes the password entered by the student and `C`
denotes the predicate in `c`, the propositional function is C(password). Similarly, `d` can be
written as D(titleNine), and `e` as E(honest).
Extracting declarative sentences from REQ2 is a bit more involved than for REQ1. There are
various complicating factors within this requirement, such as communicating with the
financial aid systems to verify no financial hold against the student, or the more mundane
checking previously completed classes for prerequisites. While it is very easy to form
complicated logical propositions from REQ2, we will keep it simple for now, leaving the
formation of complication propositions as an exercise for you to perform.
We will list the following declarative statements to capture some of REQ2’s propositions:
p - The student submits a registration request
q - The request-response is SUCCESS
r - The class has at least one prerequisite
s - The student has taken the given prerequisite
t - The student is in good financial standing
u - The student’s registration is updated
It may be obvious that some propositions are missing from the representative propositions for
REQ2. In the interest of keeping this review brief, we will only enumerate those propositions
listed above. You are encouraged to formulate a more complete set of statements and a more
thorough set of propositions.
From our declarative statements, we derive the following propositions for REQ2:
`p`
(∀ classes ∈ `p`)(∀ prerequisites ∈ `r`) [S(taken) ⇒ R(satisfied)]
→ check every other class ∈ `p` for prerequisites
(∀ R(satisfied) ∈ `p`) [R(satisfied) ∧ T(satisfied) ⇒ `q`]
`q` ⇒ `u`
Now, you may ask, “when will I use this when designing software?”. A good question, the
answer is, “likely, never…at least, not explicitly”. What this means is that this type of
thinking helps to orient you with respect to the problem statement and helps to establish a
logical “flow” through the system.
265
System specifications should be consistent, which means that they should not contain conflicting
requirements. In other words, if the requirements are represented as a set of propositions, there
should be an arrangement of truth values of the propositional variables that makes all required
propositions true.
In Section 3.2.3, we will see how logic plays a role in the part of the UML standard called Object
Constraint Language (OCL). Another notation based on Boolean logic is TLA+, described in Section
3.2.4.
266
lock unlock unlock
lock
Here, the entries in the body of the table show the next state the machine enters,
depending on the present state (armed or disarmed) and input (lock or unlock).
We can also represent our machine graphically, using a transition diagram, which is a
directed graph with labeled edges. In this diagram, each state is represented by a circle.
Arrows are labeled with the input for each transition. An example is shown in Figure
3-10. Here the states “Disarmed” and “Armed” are shown as circles, and labeled arrows
indicate the effect of each input when the machine is in each state.
A finite state machine is formally defined to consist of a finite set of states S, a finite set
of inputs I, and a transition function with S × I as its domain and S as its codomain (or
range) such that if s ∈ S and i ∈ I, the f(s, i) is the state the machine moves to when it is
in state s and is given input i. Function f can be a partial function, meaning that it can be
undefined for some values of its domain. In certain applications, we may also specify an
initial state s0 and a set of final (or accepting) states S′ ⊂ S, which are the states we would
like the machine to end in. Final states are depicted in state diagrams by using double
concentric circles. An example is shown in Figure 3-11, where M = maxNumOfAttempts
is the final state: the machine will halt in this state and needs to be restarted externally.
A string is a finite sequence of inputs. Given a string i1i2 … in and the initial state s0, the
machine successively computes s1 = f(s0, i1), then s2 = f(s1, i2), and so on, finally ending up with
state sn. For the example in Figure 3-11, the input string iiv transitions the FSM through the
states s0s1s2s0. If sn ∈ S′, i.e., it is an accepting state, then we say that the string is accepted;
otherwise it is rejected. It is easy to see that in Figure 3-11, the input string of M i’s (denoted
as iM) will be accepted. We say that this machine recognizes this string and, in this sense, it
recognizes the attempted intrusion.
Figure 3-11: State transition diagram for the counter of unsuccessful lock-opening attempts.
267
lock unlock / beep unlock
(a)
lock / beep
(b)
lock / beep
Figure 3-12: State transition diagram from Figure 3-10, modified to include output labels
(a) and guard labels (b).
A slightly more complex machine is an FSM that yields output when it transitions to the next state.
Suppose that, for example, the door lock in Figure 3-10 also produces an audible signal to let the
user know that it is armed or disarmed. The modified diagram is shown in Figure 3-12(a). We use
a slash symbol to separate the input and the output labels on each transition arrow. (Note that
here we choose to produce no outputs when the machine receives duplicate inputs.)
We define a finite state machine with output to consist of a finite set of states S, a finite set of
inputs I, a finite set of outputs O, along with a function f : S × I → S that assigns to each (state,
input) pair a new state and another function g : S × I → O that assigns to each (state, input) pair
an output.
We can enrich the original FSM model by adding new features. Figure 3-12(b) shows how we can
add guards to transitions. The full notation for transition descriptions is then
〈input[guard]/output〉, where each element is optional. A guard is a Boolean proposition that
permits or blocks the transition. When a guard is present, the transition takes place if the guard
evaluates to true, but the transition is blocked if the guard is false.
Now, we’ll look at how UML adds other features to extend the FSM model into UML state machine
diagrams.
268
Figure 3-13: UML state machine diagram showing the states and transitions of course registration.
Basic Notation
In every state machine diagram, there must be exactly one default initial state, which we
designate by writing an unlabeled transition to this state from a special icon, shown as a filled
circle. An example is shown in Figure 3-13.
Sometimes we also need to designate a stop state. In most cases, a state machine associated with
an object or the system as a whole never reaches a stop state—the state machine just vanishes
when the object it represents is destroyed. However, we can designate a stop state by drawing
an unlabeled state transition from this state to a special icon, shown as a filled circle inside a
slightly larger hollow circle.3 Initial and final states are called pseudostates.
Transitions between pairs of states are shown by directed arrows. Moving between states is
referred to as firing the transition. A state may transition to itself, and it is common to have many
different state transitions from the same state. All transitions must be unique, meaning that there
will never be any circumstances that would trigger more than one transition from the same state.
There are various ways to control the firing of a transition. A transition with no annotation is
referred to as a completion transition. This simply means that when the object completes the
execution of an activity in the source state, the transition automatically fires, and the target state
is entered.
3 The “Course Inactive” state in Figure 3-13 is the stop state in the context of our case study.
269
In Figure 3-13, annotations are “Course Activated”, "Student Registers”, “Semester Ends”. With
the first and final transitions being complete. Note that “Student Registers” does not change the
state of the course, but that event will likely happen many times during the registration period.
Events were discussed in Section 3.1. In other cases, certain events have to occur for the transition
to fire. Such events are annotated on the transition. In Figure 3-13, one may argue that
“registration pending” should be considered a state rather than an event, because a pending
status may last longer than an instant in time. The correct answer here is relative to the observer.
Our student would not care how long their registration is pending, only that they know the
registration has been successful. Even a failed registration is useful information to the student, as
they will know an issue has occurred and to take steps to address it.
We have already seen for FSMs that a guard condition may be specified to control the transition.
These conditions act as guards so that when an event occurs, the condition will either allow the
transition (if the condition is true) or disallow the transition (if the condition is false).
270
Figure 3-14: Example of state activities (course registration). Compare with Figure 3-7.
Figure 3-15: Example of composite and nested states (course registration). Compare with Figure
3-13.
271
State Activities: Entry, Do, and Exit Activities
We mentioned that states can be passive or active. In particular, an activity may be specified to
be carried out at certain points in time with respect to a state:
Nesting may be to any depth, and thus substates may be composite states to other lower-level
substates. For simplicity in drawing state transition diagrams with depth, we may zoom in or zoom
out relative to a particular state. Zooming out conceals substates, as in Figure 3-13, and
zooming in reveals substates, as in Figure 3-15. Zoomed out representation may improve
comprehensibility of a complex state machine diagram.
272
Applications
State machine diagrams are typically used to describe the behavior of individual objects. However,
they can also be used to describe the behavior of any abstractions that the developer is currently
considering. We may also provide state machine diagrams for the entire system under
consideration. During the analysis phase of the development lifecycle (described in Section 2.5),
we are considering the event-ordered behavior of the system as a whole; hence, we may use state
machine diagrams to represent the behavior of the system. During the design phase (described in
Section 2.6), we may use state machine diagrams to capture dynamic behavior of individual
classes or of collaborations of classes.
In Section 3.3 we will use state machine diagrams to describe problem domains when trying to
understand and decompose complex problems into basic ones.
The UML standard defines Object Constraint Language (OCL) based on Boolean logic. Instead of
using mathematical symbols for operators (Table 3-1), OCL uses only ASCII characters which
makes it easier for typing and computer processing. It also makes OCL a bit wordy in places.
OCL is not a standalone language, but an integral part of the UML. An OCL expression needs to be
placed within the context of a UML model. In UML diagrams, OCL is primarily used to write
constraints in class diagrams and guard conditions in state and activity diagrams. OCL expressions,
known as constraints, are added to express facts about elements of UML diagrams.
273
Table 3-2: Basic predefined OCL types and operations on them.
Type Values Operations
Boolean true, false and, or, xor, not, implies, if-then-else
Integer 1, 48, −3, 84967, … *, +, −, /, abs()
Real 0.5, 3.14159265, 1.e+5 *, +, −, /, floor()
String 'With more exploration comes more text.' concat(), size(), substring()
Any implementation derived from such a design model must ensure that each of the constraints
always remains true.
We should keep in mind that for software classes there is no notion of a computation to specify
in the sense of having well-defined start and end points. A class is not a program or subroutine.
Rather, any of object’s operations can be invoked at arbitrary times with no specific order. And
the state of the object can be an important factor in its behavior, rather than just input-output
relations for the operation. Depending on its state, the object may act differently for the same
operation. To specify the effect of an operation on object’s state, we need to be able to describe
the present state of the object which resulted from any previous sequence of operations invoked
on it. Because object’s state is captured in its attributes and associations to other objects, OCL
constraints usually relate to these properties of objects.
OCL Syntax
OCL’s syntax is similar to object-oriented languages such as C++ or Java. OCL expressions consist
of model elements, constraints, and operators. Model elements include class attributes,
operations, and associations. However, unlike programming languages OCL is a pure specification
language, meaning that an OCL expression is guaranteed to be without side effects. When an OCL
expression is evaluated, it simply returns a value. The state of the system will never change
because of the evaluation of an OCL expression, even though an OCL expression can be used to
specify a state change, such as in a post-condition specification.
OCL has four built-in types: Boolean, Integer, Real, and String. Table 3-2 shows example values
and some examples of the operations on the predefined types. These predefined value types are
independent of any object model and are part of the definition of OCL.
When writing an OCL contract, the first step is to decide the context, which is the software class
for which the OCL expression is applicable. Within the given class context, the keyword self refers
to all instances of the class. Other model elements can be obtained by navigating using the dot
notation from the self object. Consider the example of the class diagram in Figure 2-35. To access
the attribute numOfAttempts_of the class Controller, we write
self.numOfAttempts_
Due to encapsulation, object attributes frequently must be accessed via accessor methods. Hence,
we may need to write self.getNumOfAttempts().
274
(a) Local attribute (b) Directly related class (c) Indirectly related class
Class_A Class_A Class_A
– attribute1 * assocBA * assocBA
– attribute2
– …
assocAB assocAB
* *
Class_B Class_B
* assocCB
* assocBC
Class_C
Figure 3-17: Three basic types of navigation in a UML class diagram. (a) Attributes of class A
accessed from an instance of class A. (b) Accessing a set of instances of class B from an instance
of class A. (c) Accessing a set of instances of class C from an instance of class A.
Starting from a given context, we can navigate associations on the class diagram to refer to other
model elements and their properties. The three basic types of navigation are illustrated in Figure
3-17. In the context of Class_A, to access its local attribute, we write self.attribute2. Similarly, to
access instances of a directly associated class we use the name of the opposite association-end in
the class diagram. So in Figure 3-17(b), in the context of Class_A, to access the set of instances of
Class_B, we write self.assocAB. Lastly in Figure 3-17(c), in the context of Class_A, to access
instances of an indirectly associated class Class_C, we write self.assocAB.assocBC. (This approach
should not come as a surprise to the reader familiar with an object programming language, such
as Java or C#.)
We already know from UML class diagrams that object associations may be individual objects
(association multiplicity equals 1) or collections (association multiplicity > 1). Navigating a one- to-
one association yields directly an object. Figure 2-35 shows a single LockCtrl (assuming that a
single lock device is controlled by the system). Assuming that this association is named lockCtrl_
as in Listing 2.2, the navigation self.lockCtrl_ yields the single object lockCtrl_ : LockCtrl. However,
if the Controller were associated with multiple locks, e.g., on front and backyard doors, then this
navigation would yield a collection of two LockCtrlobjects.
OCL specifies three types of collections:
• OCL sets are used to collect the results of navigating immediate associations with one-to-
many multiplicity.
• OCL sequences are used when navigating immediate ordered associations.
• OCL bags are used to accumulate the objects when navigating indirectly related objects.
In this case, the same object can show up multiple times in the collection because it was
accessed via different navigation paths.
Note that in the example in Figure 3-17(c), the expression self.assocAB.assocBC evaluates to the
set of all instances of class Class_C objects associated with all instances of class Class_Bobjects
that, in turn, are associated with class Class_A objects.
275
Table 3-3: Summary of OCL operations for accessing collections.
OCL Notation Meaning
EXAMPLE OPERATIONS ON ALL OCL COLLECTIONS
c->size() Returns the number of elements in the collection c.
c->isEmpty() Returns true if chas no elements, false otherwise.
c1->includesAll(c2) Returns true if every element of c2is found in c1.
c1->excludesAll(c2) Returns true if no element of c2is found in c1.
c->forAll(var | expr) Returns true if the Boolean expression expr true for all
elements in c. As an element is being evaluated, it is bound
to the variable var, which can be used in expr. This
implements universal quantification ∀.
c->forAll(var1, var2 Same as above, except that expr is evaluated for every
| expr) possible pair of elements from c, including the cases where
the pair consists of the same element.
c->exists(var | expr) Returns true if there exists at least one element in c for
which expr is true. This implements existential
quantification ∃.
c->isUnique(var | Returns true if expr evaluates to a different value when
expr) applied to every element of c.
c->select(expr) Returns a collection that contains only the elements of cfor
which expris true.
EXAMPLE OPERATIONS SPECIFIC TO OCL SETS
s1->intersection(s2) Returns the set of the elements found in s1and also in s2.
s1->union(s2) Returns the set of the elements found either s1or s2.
s->excluding(x) Returns the set swithout object x.
EXAMPLE OPERATION SPECIFIC TO OCL SEQUENCES
seq->first() Returns the object that is the first element in the
sequence
seq.
To distinguish between attributes in classes from collections, OCL uses the dot notation for
accessing attributes and the arrow operator -> for accessing collections. To access a property of a
collection, we write the collection’s name, followed by an arrow ->, and followed by the name of
the property. OCL provides many predefined operations for accessing collections, some of which
are shown in Table 3-3.
Constants are unchanging (non-mutable) values of one of the predefined OCL types (Table 3-2).
Operators combine model elements and constants to form an expression.
276
expected and the class user about the obligations that must be met before the class is used. There
are three types of constraints in OCL: invariants, preconditions, and postconditions.
One important characterization of object states is describing what remains invariant throughout
the object’s lifetime. This can be described using an invariant predicate. An invariant must always
evaluate to true for all instance objects of a class, regardless of what operation is invoked and in
what order. An invariant applies to a class attribute.
In addition, each operation can be specified by stating a precondition and a postcondition. A
precondition is a predicate that is checked before an operation is executed. A precondition
applies to a specific operation. Preconditions are frequently used to validate input parameters to
an operation.
A postcondition is a predicate that must be true after an operation is executed. A postcondition
also applies to a specific operation. Postconditions are frequently used to describe how the
object’s state was changed by an operation.
We already encountered some preconditions and postconditions in the context of domain models
(Section 2.5.4). Subsequently, in Figure 2-35 we assigned the domain attributes to specific classes.
Therein, we used an informal, ad-hoc notation. OCL provides a formal notation for expressing
constraints. For example, one of the constraints for our case study system is that the maximum
allowed number of failed attempts at disarming the lock is a positive integer. This constraint must
be always true, so we state it as an invariant:
context Controller inv: self.getMaxNumOfAttempts() > 0
Here, the first line specifies the context, i.e., the model element to which the constraint applies,
as well as the type of the constraint. In this case the inv keyword indicates the invariant constraint
type. In most cases, the keyword self can be omitted because the context is clear.
Other possible types of constraint are precondition (indicated by the pre keyword) and
postcondition (indicated by the post keyword). A precondition for executing the operation
enterKey()is that the number of failed attempts is less than the maximum allowed number:
context Controller::enterKey(k : Key) : boolean pre: self.getNumOfAttempts() <
self.getMaxNumOfAttempts()
The postconditions for enterKey()are that (Poc1) a failed attempt is recorded, and (Poc2) if the
number of failed attempts reached the maximum allowed number, the system becomes blocked
and the alarm bell is sounded. The first postcondition (Poc1) can be restated as:
(Poc1′) If the provided key is not element of the set of valid keys, then the counter of failed
attempts after exiting from enterKey() must be by one greater than its value before
entering enterKey().
277
The above two postconditions (Poc1′) and (Poc2) can be expressed in OCL as:
context Controller::enterKey(k : Key) : Boolean
-- postcondition (Poc1′):
post: let allValidKeys : Set = self.checker.validKeys() if allValidKeys.exists(vk | k =
vk) then
getNumOfAttempts() = getNumOfAttempts()@pre
else
getNumOfAttempts() = getNumOfAttempts()@pre + 1
-- postcondition (Poc2):
post: getNumOfAttempts() >= getMaxNumOfAttempts() implies self.isBlocked() and
self.alarmCtrl.isOn()
There are three features of OCL used in stating the first postcondition above that the reader
should note. First, the let expression allows one to define a variable (in this case allValidKeys of
the OCL collection type Set) which can be used in the constraint.
Second, the @pre directive indicates the value of an object as it existed prior to the operation.
Hence, getNumOfAttempts()@pre denotes the value returned by getNumOfAttempts()before
invoking enterKey(),
and getNumOfAttempts()denotes the value
returned by the same operation after invoking enterKey().
Third, the expressions about getNumOfAttempts() in the if-then-else operation are not
assignments. Recall that OCL is not a programming language and evaluation of an OCL expression
will never change the state of the system. Rather, this just evaluates the equality of the two sides
of the expression. The result is a Boolean value true or false.
The OCL standard specifies only contracts. Although not part of the OCL standard, nothing
prevents us from specifying program behavior using Boolean logic.
278
3.2.4 TLA+ Notation
This section presents TLA+ system specification language, defined by Leslie Lamport. The book
describing TLA+ can be downloaded from http://lamport.org/. There are many other specification
languages, and TLA+ reminds in many ways of Z (pronounced Zed, not Zee) specification
language.
279
1 MODULE AccessController
2 CONSTANTS validKeys, The set of valid keys.
3 ValidateKey( _ ) A ValidateKey(k) step checks if k is a valid key.
4 ASSUME validKeys ⊂ STRING
5 ASSUME ∀ key ∈ STRING : ValidateKey(key) ∈ BOOLEAN
6 VARIABLE status
7 TypeInvariant =ˆ status ∈ [lock : {“disarmed”, “armed”}, bulb : {“lit”, “unlit”}] 8
9 Init =ˆ ∧ TypeInvariant The initial predicate.
10 ∧ status.lock = “armed”
11 ∧ status.bulb = “unlit”
12 Unlock(key) =ˆ ∧ ValidateKey(key) Only if the user enters a valid key, then
13 ∧ status′.lock = “disarmed” unlock the lock and
14 ∧ status′.bulb = “lit” turn on the light (if not already lit).
15 Lock =ˆ ∧ status′.lock = “armed” Anybody can lock the doors
16 ∧ UNCHANGED status.bulb but not to play with the lights.
17 Next =ˆ Unlock(key) ∨ Lock The next-state
action. 18
19 Spec =ˆ Init ∧ □[Next]status The specification.
20
21 THEOREM Spec ⇒ □ TypeInvariant Type correctness of the specification.
22
Figure 3-18: TLA+ specification of the cases study system.
My reason for choosing TLA+ is that it uses the language of mathematics, specifically the language
of Boolean algebra, rather than inventing another formalism.
A TLA+ specification is organized in a module, as in the following example, Figure 3-18, which
specifies our home access case study system (Section 1.3.1). Observe that TLA+ language reserved
words are shown in SMALL CAPS and comments are shown in a highlighted text. A module comprises
several sections
• Declaration of variables, which are primarily the manifestations of the system visible to
an outside observer
• Definition of the behavior: the initial state and all the subsequent (next) states,
which combined make the specification
• The theorems about the specification
The variables could include internal, invisible aspects of the system, but they primarily address
the external system’s manifestations. In our case-study of the home access controller, the
variables of interest describe the state of the lock and the bulb. They are aggregated in a single
status record, lines 6 and 7.
The separator lines 8 and 20 are a pure decoration and can be omitted. Unlike these, the module
start and termination lines, lines 1 and 22, respectively, have semantic meaning and must appear.
280
Lines 2 and 3 declare the constants of the module and lines 4 and 5 list our assumptions about
these constants. For example, we assume that the set of valid passwords is a subset of all
character strings, symbolized with STRING. Line 5 essentially says that we expect that for any key
k, ValidateKey(k) yields a BOOLEAN value.
TypeInvariant in line 7 specifies all the possible values that the system variable(s) can assume in
a behavior that satisfies the specification. This is a property of a specification, not an assumption.
That is why it is stated as a theorem at the end of the specification, line 21.
The definition of the initial system state appears in lines 9 and 10.
Before defining the next state in line 17, we need to define the functions that could be requested
of the system. In this case we focus only on the key functions of disarming and arming the lock,
Disarm and Arm, respectively, and ignore the rest (see all the use cases in Section 2.2). Defining
these functions is probably the most important part of a specification.
The variable status′ with an apostrophe symbol represents the state variable in the next step, after
an operation takes place.
“Computers are useless. They can only give you answers.” —Pablo Picasso
“Solving a problem simply means representing it so as to make the solution transparent.”
—Herbert Simon, The Sciences of the Artificial
281
(a) The Problem The
Machine Domain Requirement
Domain properties
seen by the requirement
(b)
Figure 3-19: (a) The Machine and the Problem Domain. (b) Interfaces between the problem
domain, the requirements and the machine.
Each individual frame can then be considered and solved independently of other frames. A key
step in solving a frame is to address the frame concerns, which are generic aspects of each
problem type that need to be addressed for solving a problem of a particular type. frame concerns,
which are generic aspects of a problem type that need to be addressed to solve it.
Finally, we need to determine how to compose the individual solutions into the overall solution
for the original problem. We need to determine how individual frames interact with each other
and we may need to resolve potential conflicts of their interaction.
283
A problem diagram as in Figure 3-19 provides a basis for problem analysis because it shows you
what you are concerned with, and what you must describe and reason about to analyze the
problem completely. The key topics of your descriptions will be:
• The requirement that states what the machine must do. The requirement is what your customer
would like to achieve in the problem domain. Its description is optative (it describes the option
that the customer has chosen). Sometimes you already have an exact description of the
requirement, sometimes not. For example, requirement REQ1 given in Table 2-2 states
precisely how users are to register with our system.
• The domain properties that describe the relevant characteristics of each problem domain. These
descriptions are indicative because they describe what is true regardless of the machine’s
behavior. For example, Section 1.3.2 describes the functioning of university registration,
which we must understand to implement a useful system that will provide students with a
useful means to register, as well as a cost savings measure for the university.
• The machine specification. Like the requirement, this is an optative description: it describes the
machine’s desired behavior at its interfaces with the problem domain.
Obviously, the indicative domain properties play a key role: without a clear understanding of how
financial markets work we would never be able to develop a useful investment assistant system.
284
Control
CM!C1 Controlled
C3 Required
Machine Domain C Behavior
CD!C2
(a) Required behavior frame concern: To describe precisely how the controlled domain
currently behaves; the desired behavior for the domain, as stated by the requirement;
and what the software will be able to observe about the domain state, by way of the
sensors used by the system.
(b) Commanded behavior frame concern: To identify all the commands that will be possible
in the envisioned system-to-be; the commands that will be supported or
allowed under different scenarios; and what should happen if the user tries to execute a
command that is not supported under the current scenario.
(c) Information display frame concern: To identify the information that the machine will be
able to observe from the problem domain, by way of the sensors that will be used in the
system; the information that needs to be displayed, as stated by the requirement; and
the transformations needed to process the raw observed information to obtain
displayable information.
(d) Simple workpieces frame concern: To precisely describe the data structures of the
workpieces; all the commands that will be possible in the envisioned system-to-be;
the commands that will be supported or allowed under different scenarios; and what
should happen if the user tries to execute a command that is not supported under the
current scenario.
(e) Transformation frame concern: To precisely describe the data structures of the input data
and output data; how each data structure will be traversed; and
how each element of the input data structure will be transformed to obtain the
corresponding element in the output data structure.
285
Figure 3-21: Required Behavior Frame: Example. (Course Registration study)
ET!E1 Work
WP!Y2 pieces Y3
X
Editing Command
tool effects
US!E3 User E3
B
An example in Figure 3-21 is shown for how a university’s registration system will handle
registration requests from students. A student will select from the various courses provided; she
will then submit the courses to the system to verify, then be given a response for the given
request. One possible outcome is the request is fully valid. The student’s request is not blocked in
any way, so the system registers the student successfully. While other responses may be partial
successes, or complete rejections, depending on the nature of the request. For example, a student
may request to register for a nursing course, but the system knows that this is a Fine Arts student.
This means our required behavior will identify the student’s acceptable courses for registration,
or maybe more accurately, what they are not allowed to register for. Such a scenario may happen
inadvertently, so we may require only the course in violation to be rejected. On the other hand,
if a student has a delinquent account (meaning they owe the college money) the system may
reject all courses on this premise.
287
Figure 3-23: Simple Workpieces Frame: Example. (course registration environment)
Figure 3-24 shows the key idea behind the frame decomposition. Given a problem represented as
a complex set of requirements relating to a complex application domain, our goal is to represent
the problem using a set of basic problem frames.
289
Domain
1
The
Requirements
Domain
5
Domain Domain
4 3
Requirement
2
uire
re
qui
Req
Re
ire
qu
Domain
Re
Do
2
m
m
(b)
Do
Do
m
Do
m
Machine
Machine
chi
2
2
Ma
Ma
chi
a
M i
ch
Figure 3-24: The goal of frame decomposition is to represent a complex problem (a) as a set of
basic problem frames (b).
290
3.4 Specifying Goals
“Correctness is clearly the prime quality. If a system does not do what it is supposed to do, then everything
else about it matters little.” —Bertrand Meyer
The basic idea of goal-oriented requirements engineering is to start with the aggregate goal of the
whole system, and to refine it by successive steps into a goal hierarchy.
AND-OR refinements …
Problem frames can be related to goals. Goal-oriented approach distinguishes different kinds of
goal, as problem-frames approach distinguishes different problem classes. Given a problem
decomposition into basic frames, we can restate this as an AND-refinement of the goal hierarchy:
to satisfy the system requirement goal, it is necessary to satisfy each individual subgoal (of each
basic frame).
When programmed, the program “knows” its goals implicitly rather than explicitly, so it cannot
tell those to another component. This ability to tell its goals to others is important in autonomic
computing, as will be seen in Section 9.3.
State the goal as follows: given the states A=armed, B=lightOff, C=user positively identified,
D=daylight
(Goal is the equilibrium state to be reached after a perturbation.)
Initial state: A ∧ B, goal state: ¬A ∧ ¬B.
Possible actions: α—setArmed; α−1—setDisarmed; β—setLit; β−1—setUnlit
Preconditions for α−1: C; for β: D
We need to make a plan to achieve ¬A ∧ ¬B by applying the permitted actions.
Program goals, see also “fuzzy” goals for multi-fidelity algorithms, MFAs, [Satyanarayanan &
Narayanan, 2001]. http://www.cs.yale.edu/homes/elkin/ (Michael Elkin)
The survey “Distributed approximation,” by Michael Elkin. ACM SIGACT News, vol. 36, no. 1,
(Whole Number 134), March 2005. http://theory.lcs.mit.edu/~rajsbaum/sigactNewsDC.html
The purpose of this formal representation is not to automatically build a program; rather, it is to
be able to establish that a program meets its specification.
291
3.5 Summary and Bibliographical Notes
People often complain about software quality (for example Microsoft products). The issue of
software quality is complex one. Software appeal depends on what it does (functionality), how
good it is (quality), and what it costs (economy). Different people put different weights on each of
these, but in the end all three matter. Microsoft figured that the functionality they deliver is
beyond the reach of smeller software vendors who cannot produce it at a competitive price, so
they emphasized functionality. It paid off. It appears that the market has been more interested in
low-cost, feature-laden products than reliability (for the mass market kind of products). It worked
in the market, thus far, which is the ultimate test. Whether this strategy will continue to work, we
do not know. But the tradeoff between quality / functionality / economy will always be present.
Also see the virtues of the “good enough” principle explored here:
S. Baker, “Why ‘good enough’ is good enough: Imperfect technology greases innovation—and
the whole marketplace,” Business Week, no. 4048, p. 48, September 3, 2007. Online at:
http://www.businessweek.com/magazine/content/07_36/b4048048.htm
Comment
Formal specifications have had lack of success, usually blamed on non-specialists finding such
specifications difficult to understand, see e.g., [Sommerville, 2004, p. 116; Larman, 2005, p. 65].
The usual rationale given for avoiding rigorous, mathematically driven program development is
the time-to-market argument—rigor takes too much time and that cannot be afforded in today’s
world. We are also told that such things make sense for developing safety-critical applications,
such as hospital systems, or airplane controls, but not for everyday use. Thanks to this philosophy,
we can all enjoy Internet viruses, worms, spam, spyware, and many other inventions that are
thriving on lousy programs.
The problem is that software ends up being used for purposes that it was not intended for. Many
off-the-shelf software products end up being used in mission-critical operations, regardless of the
fact that they lack robustness necessary to support such operations.
It is worth noticing that often we don’t wear what we think is “cool”—we often wear what the
“trend setters” in our social circle, or society in general, wear [Gladwell, 2000]. But, as Petroski
[1992], echoing Raymond Loewy, observes, it has to be MAYA—most advanced, yet acceptable.
So, if hackers let the word out that some technique is cool, it shall become cool for the masses of
programmers.
Bibliographical Notes
Much of this chapter is directly inspired by the work of Michael Jackson [1995; 2001]. I have tried
to retell it in a different way and relate it to other developments in software engineering. I
292
hope I have not distorted his message in the process. In any case, the reader would do well by
consulting the original sources [Jackson, 1995; 2001].
This chapter requires some background in discrete mathematics. I tried to provide a brief
overview, but the reader may need to check a more comprehensive source. [Rosen, 2007] is an
excellent introduction to discrete mathematics and includes very nice pieces on logic and finite
state machines.
[Guttenplan, 1986]
[Woodcock & Loomes, 1988]
J. P. Bowen and M. G. Hinchey, “Ten commandments of formal methods… ten years later,”
IEEE Computer, vol. 39, no. 1, pp. 40-48, January 2006.
The original sources for problem frames are [Jackson, 1995; 2001]. The reader can also find a great
deal of useful information online at: http://www.ferg.org/pfa/
Problem 3.7: Elevator Control given below is based on the classical elevator problem, which first
appeared in Donald Knuth’s book, The Art of Computer Programming: Vol. 1, Fundamental
Algorithms. It is based on the single elevator in the mathematics building at the California Institute
of Technology, where Knuth was a graduate student. Knuth used the elevator problem to illustrate
co-routines in an imaginary computing machine, called MIX. A detailed discussion of software
engineering issues in elevator control is available in [Jackson, 2001].
Practice Problems
Problem 3.1
Problem 3.2
Consider a system consisting of a button and two light bulbs, as shown in the figure. Assume that
the system starts from the initial state where both bulbs are turned off. When the button is
pressed the first time, one of the bulbs will be lit and the other remains unlit. When the button is
pressed the second time, the bulb which is currently lit will be turned off and the other bulb will
be lit. When the button is pressed the third time, both bulbs will be lit. When the button is pressed
the fourth time, both bulbs will be turned off. For the subsequent button presses, the cycle is
repeated.
293
Name and describe all the states and events in this system. Draw the UML state diagram and be
careful to use the correct symbols.
Problem 3.3
Consider the auto-locking feature of the case study of the home access-control system. In Section
2.4 this feature is described via use cases (a timer is started when the doors are unlocked and if it
counts down to zero, the doors will be automatically locked).
Suppose now that you wish to represent the auto-locking subsystem using UML state diagrams.
The first step is to identify the states and events relevant for the auto-locking subsystem. Do the
following:
(a) Name and describe the states that adequately represent the auto-locking subsystem.
(b) Name and describe the events that cause the auto-locking subsystem to transition
between the states.
(Note: You do not need to use UML notation to draw a state diagram, just focus on identifying
the states and events.)
Problem 3.4
Suppose that in the virtual mitosis lab (described at the book website, given in Preface), you are
to develop a finite state machine to control the mechanics of the mitosis process. Write down the
state transition table and show the state diagram. See also Problem 2.21.
Problem 3.5
Consider the grocery inventory management system that uses Radio Frequency Identification
(RFID), described in Problem 2.15 (Chapter 2). Identify the two most important entities of the
software-to-be and represent their states using UML state diagrams. Do the following:
(a) List and describe the states that adequately represent the two most important entities
(b) List and describe the events that cause the entities to transition between the states
(c) Draw the UML state diagrams for both entities
Note: Consider all the requirements REQ1 – REQ7.
Problem 3.6
294
Problem 3.7: Elevator Control
Consider developing a software system to control an elevator in a building. Assume that there will
be a button at each floor to summon the elevator, and a set of buttons inside the elevator car—
one button per floor to direct the elevator to the corresponding floor. Pressing a button will be
detected as a pulse (i.e., it does not matter if the user keeps holding the button pressed). When
pressed, the button is illuminated. At each floor, there will be a floor sensor that is “on” when the
elevator car is within 10 cm of the rest position at the floor.
There will be an information panel above the elevator doors on each floor, to show waiting people
where the elevator car is at any time, so that they will know how long they can expect to wait
until it arrives.
The information panels will have two lamps representing each floor (see the figure below). A
square lamp indicates that the car is at the corresponding floor, and a round lamp indicates that
there is a request outstanding for the elevator to visit the corresponding floor. In addition, there
will be two arrow-shaped lamps to indicate the current direction of travel. For example, in the
figure below, the panel indicates that the elevator car is currently on the fifth floor, going up, and
there are outstanding requests to visit the lobby, third, fourth, and sixth floor.
After the elevator visits a requested floor, the corresponding lamp on all information panels
should be turned off. Also, the button that summoned the elevator to the floor should be turned
off.
Let us assume that the outstanding requests are served so that the elevator will first visit all the
requested floors in the direction to which it went first after the idle state. After this, it will serve
the requests in the opposite direction, if any. When the elevator has no requests, it remains at its
current floor with its doors closed.
Suppose that you already have designed UML interaction and class diagrams. Your system will
execute in a single thread, and your design includes the following classes:
ElevatorMain: This class runs an infinite loop. During each iteration it checks the physical buttons
whether any has been pressed and reads the statuses of all the floor sensors. If a button has
been pressed or the elevator car arrived/departed a floor, it calls the appropriate classes to
do their work, and then starts a new iteration.
CarControl: This class controls the movement of the elevator car. This
class has the attribute requests that lists the outstanding requests for the elevator to visit the
corresponding floors. It also has three operations:
addRequest(floorNum : int)adds a request to visit the floor floorNum;
295
stopAt(floorNum : int) requests the object to stop the car at the floor floorNum. This operation
calls DoorControl.operateDoors() to open the doors, let the passengers in or out, and close
the doors.
When operateDoors() returns, the CarControl object takes this as a signal that it is safe to start
moving the car from the current floor (in case there are no pending requests, the car remains
at the current floor).
InformationPanel: This class controls the display of information on the elevator
information panel. It also has the attribute requestsand these operations:
arrivedAt(floorNum : int)informs the software object that the car has arrived at the floor
floorNum.
departed()which informs the object that the car has departed from the current floor.
OutsideButton: This class represents the buttons located outside the elevator on each floor that
serve to summon the elevator. The associated physical button should be illuminated when
pressed and turned off after the car visits the floor.
This class has the attribute illuminatedthat indicates whether the button was pressed. It also
has two operations:
illuminate()requests the object to illuminate the associated physical button (because it was
pressed);
turnOff()requests the object to turn off the associated physical button (because the
elevator car has arrived at this floor).
InsideButton: This class represents the buttons located inside the elevator car that serve to direct
the elevator to the corresponding floor. The associated physical button should be illuminated
when pressed and turned off after the car visits the floor. It has the same attributes and
operations as the class OutsideButton.
DoorControl: This class controls opening and closing of the elevator doors on each floor. This
class has the Boolean attribute doorsOpen that is set true when the associated doors are open
and falseotherwise. It also has the operation:
operateDoors() : void tells the software object when to open the doors. This operation sets a
timer for a given amount of time to let the passengers in or out; after the timer expires, the
operation closes the doors automatically and returns.
Note that some classes may have multiple instances (software objects), because there are
multiple corresponding physical objects. For example, there is an information panel, outside
button, and doors at each floor. In addition, we do not have a special class to represent a floor
sensor that senses when the elevator car is in or near the rest position at the floor. The reason for
this choice is that this system is single-threaded and the ElevatorMain object will notify the
interested objects about the floor sensor status, so there is no reason to keep this information in
a class dedicated solely for this purpose.
Draw the interaction and class diagrams corresponding to the design described above.
Problem 3.8
Consider the class diagram for an online auction website given in Figure 2-48, and the system as
described in Problem 2.31 for which the solution is given on the back of this text. Suppose that
you want to specify a contract for the operation closeAuction(itemName : String)of
296
the class Controller. To close auction on an item means that no new bids will be accepted; the
item is offered to the current highest bidder. If this bidder fails to pay within the specified time
interval, the auction may be reopened.
You want to specify the preconditions that the auction for the item itemName is currently open
and the item is not reserved. The postconditions should state that the auction is closed, and the
item is reserved to the name of the highest bidder, given that there was at least one bidder. Write
this contract as statements in OCL.
You may add more classes, attributes, or operations, if you feel that this is necessary to solve the
problem, provided that you justify your modification.
Problem 3.9
Problem 3.10
Problem 3.11
Consider the automatic patient monitoring system described in Problem 2.3. Solve the
following:
(a) Identify the problem frames and describe the frame concerns for each frame.
(b) Draw the state diagram for different subsystems (problem frames). Define
each state and event in the diagrams.
(c) Explain if the system needs to behave differently when it reports abnormal vital
signs or device failures. If yes, incorporate this behavior into your state
diagrams.
Problem 3.12
Derive the domain model for the patient monitoring system from Problem 3.11.
(a) Write a definition for each concept in your domain model.
(b) Write a definition for each attribute and association in your domain model.
(c) Draw the domain model.
(d) Indicate the types of concepts, such as «boundary», «control», or «entity».
Note that you are not asked to derive the use cases for this system (see Problem 2.14). The
description of the system behavior that you will generate in the solution of Problem 3.11 should
suffice for deriving its domain model.
Problem 3.13
297
Chapter 4
Software Measurement and Estimation
Contents
“What you measure improves.” Fundamentals of Measurement Theory
—Donald Rumsfeld, Known and Unknown: A Memoir
What to Measure?
Measurement is a process by which numbers or symbols are Measuring Module Cohesion
assigned to properties of objects. To have meaningful Coupling
assignment of numbers, it must be governed by rules or theory 4.5 Psychological Complexity
(or, model). There are many properties of software that can be 4.6 Effort Estimation
measured. Similarities can be drawn with physical objects: we
4.7 Summary and Bibliographical Notes
can measure height, width, weight, chemical composition,
etc., properties of physical objects. The numbers obtained
Practice Problems
through such measurement have little value by themselves—
their key value is relative to something we want to do with
those objects. For example, we may want to know the weight
so we can decide what it takes to lift an object. Or, knowing
physical dimensions helps us decide whether the object will fit
into a certain space. Similarly, software measurement is
usually done with purpose. A common purpose is for
management decision making. For example, the project
manager would like to be able to estimate the development
cost or the time it will take to develop and deliver a software
product. Similar to how knowing the object weight helps us to
decide what it takes to lift it, the hope is that by measuring
certain software properties we will be able to estimate the
necessary development effort.
Uses of software measurements:
• Estimation of cost and effort (preferably early in the lifecycle)
• Feedback to improve the quality of design and implementation
Obviously, once a software product is already completed, we know how much effort it took to
complete it. The invested effort is directly known, without the need for inferring it indirectly via
some other properties of the software. However, that is too late for management decisions.
Management decisions require knowing (or estimating) effort before we start with the
development, or at least early enough in the process, so we can meaningfully negotiate the budget
and delivery terms with the customer.
298
Therefore, it is important to understand correctly what measurement is about:
Measured property → [ model for estimation ] → Estimated property (e.g., number of
functional features, such as development effort required)
Notice also that we are trying to infer properties of one entity from properties of another entity:
the entity the properties of which are measured is software (design documents or code) and the
entity the properties of which are estimated is development process (people’s effort). The
“estimation model” is usually based on empirical evidence; that is, it is derived based on
observations of past projects. For past projects, both software and process characteristics are
known. From this, we can try to calculate the correlation of, say, the number of functional
features to, say, the development effort required. If correlation is high across a range of values,
we can infer that the number of functional features is a good predictor of the development effort
required. Unfortunately, we know that correlation does not equal causation. A causal model,
which not only establishes a relationship, but also explains why, would be better, if possible to
have.
Feedback to the developer is based on the knowledge of “good” ranges for software modules and
systems: if the measured attributes are outside of “good” ranges, the module needs to be
redesigned. It has been reported based on many observations that maintenance costs run to
about 70 % of all lifetime costs of software products. Hence, good design can not only speed up
the initial development, but can significantly affect the maintenance costs.
Most commonly measured characteristics of software modules and systems are related to its size
and complexity. Several software characteristics were mentioned in Section 2.5, such as coupling
and cohesion, and it was argued that “good designs” are characterized by “low coupling” and
“high cohesion.” In this chapter I will present some techniques for measuring coupling and
cohesion and quantifying the quality of software design and implementation. A ubiquitous size
measure is the number of lines of code (LOC). Complexity is readily observed as an important
characteristic of software products, but it is difficult to operationalize complexity so that it can be
measured.
taking a well-reasoned, thoughtful approach that goes beyond the simplest correlative
relationships between the most superficial details of a problem.
Although it is easy to agree that more complex software is more difficult to develop and maintain,
it is difficult to operationalize complexity so that it can be measured. The reader may already be
familiar with computational complexity, or the RAM model of time complexity, and its most
infamous measure, “big oh”, represented as O(n). O(n) measures software complexity from the
machine’s viewpoint in terms of how the size of the input data affects an algorithm’s usage of
computational resources (usually running time or memory). However, the kind of complexity
measure that we need in software engineering should measure complexity from the viewpoint of
human developers.
303
stimulus of being singled out and made to feel important. It describes a temporary change to
behavior or performance in response to a change in the environmental conditions. This change is
typically an improvement. Others have broadened this definition to mean that people’s behavior
and performance change following any new or increased attention.
Individual behaviors may be altered because they know they are being studied was demonstrated
in a research project (1927–1932) of the Hawthorne Works plant of the Western Electric Company
in Cicero, Illinois.
Initial improvement in a process of production caused by the obtrusive observation of that
process. The effect was first noticed in the Hawthorne plant of Western Electric. Production
increased not as a consequence of actual changes in working conditions introduced by the plant's
management but because management demonstrated interest in such improvements (related:
self- fulfilling hypothesis).
Measurement Scale
In measurement theory, we have five types of scales: nominal, ordinal, interval, ratio, and
absolute.
In nominal scale we can group subjects into different categories. For example, we designate the
weather condition as “sunny,” “cloudy,” “rainy,” or “snowy.” The two key requirements for the
categories are: jointly exhaustive and mutually exclusive. Mutually exclusive means a measured
attribute can be classified into one and only one category. Jointly exhaustive means that all
categories together should cover all possible values of the attribute. If the measured attribute has
more categories than we are interested in, an “other” category can be introduced to make the
categories jointly exhaustive. Provided that categories are jointly exhaustive and mutually
exclusive, we have the minimal conditions necessary for the application of statistical analysis. For
example, we may want to compare the values of software attributes such as defect rate, cycle
time, and requirements defects across the different categories of software products.
Ordinal scale refers to the measurement operations through which the subjects can be compared
in order. An example ordinal scale is: “bad,” “good,” and “excellent,” or “star” ratings used for
products or services on the Web. An ordinal scale is asymmetric in the sense that if A > B is true
then B > A is false. It has the transitivity property in that if A > B and B > C, then A > C. Although
ordinal scale orders subjects by the magnitude of the measured property, it offers no information
304
about the relative magnitude of the difference between subjects. For example, we only know that
“excellent” is better than “good,” and “good” is better than “bad.” However, we cannot compare
that the relative differences between the excellent-good and good-bad pairs. A commonly used
ordinal scale is an n-point Likert scale, such as the Likert five-point, seven-point, or ten-point
scales. For example, a five-point Likert scale for rating books or movies may assign the following
values: 1 = “Hated It,” 2 = “Didn’t Like It,” 3 = “Neutral,” 4 = “Liked It,” and 5 = “Loved It.” We
know only that 5 > 4, 4 > 3, 5 > 2, etc., but we cannot say how much greater is 5 than 4. Nor can
we say that the difference between categories 5 and 4 is equal to that between 3 and 2. This
implies that we cannot use arithmetic operations such as addition, subtraction, multiplication and
division. Nonetheless, the assumption of equal distance is often made and the average rating
reported (e.g., product rating at Amazon.com uses fractional values, such as 3.7 stars).
Interval scale indicates the exact differences between measurement points. An interval scale
requires a well-defined, fixed unit of measurement that can be agreed on as a common standard
and that is repeatable. A good example is a traditional temperature scale (centigrade or
Fahrenheit scales). Although the zero point is defined in both scales, it is arbitrary and has no
meaning. Thus we can say that the difference between the average temperature in Florida, say
80°F, and the average temperature in Alaska, say 20°F, is 60°F, but we do not say that 80°F is four
times as hot as 20°F. The arithmetic operations of addition and subtraction can be applied to
interval scale data.
Ratio scale is an interval scale for which an absolute or nonarbitrary zero point can be located.
Absolute or true zero means that the zero point represents the absence of the property being
measured (e.g., no money, no behavior, none correct). Examples are mass, temperature in
degrees Kelvin, length, and time interval. Ratio scale is the highest level of measurement and all
arithmetic operations can be applied to it, including division and multiplication.
For interval and ratio scales, the measurement can be expressed in both integer and noninteger
data. Integer data are usually given in terms of frequency counts (e.g., the number of defects that
could be encountered during the testing phase).
Absolute scale is used when there is only one way to measure a property. It is independent of the
physical properties of any specific substance. In practice, values on an absolute scale are usually
(if not always) obtained by counting. An example is counting entities, such as chairs in a room.
305
Figure 4-1: Issues with subjective size measures (compare to Figure 1-10). Left side of the hedge
as seen by a pessimist; right side seen by an optimist.
Given a software artifact (design document or source code), generally we can measure two kinds of attributes:
1. Attributes of any representation/ description of a problem or solution. Two main
categories of representations are structure vs. behavior.
2. Attributes of the development process/methodology--
Measured aspects include quantity (size) and complexity.
If the goal of software measurement is estimation of cost and effort, then we would like to
measure at an early stage in the software life-cycle. Typically, a budget allocation is set at an early
phase, and a decision on contract price i s made on these budget constraints and
306
suppliers’ tender responses. Thus, the functional breakdown of the planned system needs to be
at a high level, but must be of enough detail to quickly flush out as many implied requirements
and hidden complexities as possible. In the ideal world, this would be a full and detailed
decomposition of the use cases--but this is impractical during the estimation process, because
estimates need to be produced within tight time frames.
307
Table 4-1: Actor classification and associated weights.
Actor type Description of how to recognize the actor type Weight
The actor is another system which interacts with our system
Simple 1
through a defined application programming interface (API).
The actor is a person interacting through a text-based user
Average interface, or another system interacting through a protocol, such as 2
a network communication protocol.
Complex The actor is a person interacting via a graphical user interface. 3
Each variable is defined and computed separately using weighted values, subjective values, and
constraining constants. The subjective values are determined by the development team based on
their perception of the project’s technical complexity and the team’s efficiency. Here is the
equation:
UCP = UUCP × TCF × ECF (4.1)
Unadjusted Use Case Points (UUCPs) themselves are the sum of these two components:
1. The Unadjusted Actor Weight (UAW), based on the combined complexity of all the
actors in all the use cases.
2. The Unadjusted Use Case Weight (UUCW), based on the total number of activities (or
steps) contained in all the use case scenarios.
Let’s look at the computation of these components. By computing these components, we can compute UUCPs.
Then, by computing UUCPs, we can compute Use Case Points!
308
Table 4-2: Actor classification for the case study of home access control (see Section 2.3).
Actor name Description of relevant characteristics Complexity Weight
Landlord is interacting with the system via a graphical user
Landlord Complex 3
interface (when managing users on the central computer).
Tenant is interacting through a text-based user interface
(assuming that identification is through a keypad; for
Tenant Average 2
biometrics based identification methods Tenant would be a
complex actor).
LockDevice is another system which interacts with our
LockDevice Simple 1
system through a defined API.
LightSwitch Same as LockDevice. Simple 1
AlarmBell Same as LockDevice. Simple 1
Database Database is another system interacting through a protocol. Average 2
Timer Same as LockDevice. Simple 1
Police Our system just sends a text notification to Police. Simple 1
The UAW is calculated by totaling the number of actors in each category, multiplying each total
by its specified weighting factor, and then adding the products we obtain:
309
Table 4-3: Use case weights based on the number of transactions.
Use case category Description of how to recognize the use-case category Weight
Simple user interface. Up to one participating actor (plus
Simple initiating actor). 3 or fewer steps for the success scenario. If 5
presently available, its domain model includes ≤ 3 concepts.
Moderate interface design. Two or more participating actors. 4-7
Average steps for the success scenario. If presently available, its domain 10
model includes between 5 and 10 concepts.
Complex user interface or processing. 3 or more participating
Complex actors. 7 or more steps for the success scenario. If available, its 15
domain model includes at least 10 concepts.
We first saw UC-7: AuthenticateUser way back in Section 2.3. In this use case, each extension
starts with a result of a transaction, rather than a new transaction itself. For example, extension
2a (“Tenant/Landlord enters an invalid identification key”) is the result of the transaction
described by step 2 of the main success scenario (“Tenant/Landlord supplies an identification
key”). So, item 2a in the extensions section of the use case is not counted. The same, of course, is
true for 2b, 2c, and 3a. The transaction count for the use case in UC-7: AuthenticateUser is then
ten.
Another mechanism for measuring use case complexity is counting the concepts obtained by
domain modeling (Section 2.4). Of course, this assumes that the domain model is already derived
at the time the estimate is being made. The concepts can be used in place of transactions once it
has been determined which concepts model a specific use case. As indicated in Table 4-3, a simple
use case is implemented by 5 or fewer concepts, an average use case by 5 to 10 concepts, and a
complex use case by more than 10 concepts. The weights are as before. Each type of use case is
then multiplied by the weighting factor, and the products are added up to get the UUCW.
The UUCW is calculated by tallying the use cases in each category, multiplying each count by its
specified weighting factor (Table 4-3), and then adding the products:
UUCW(home access) = 1 × Simple + 5 × Average + 2 × Complex = 1×5 + 5×10 + 2×15 = 85
The UUCP is computed by adding the UAW and the UUCW. Based on the scores in Table 4-2 and
Table 4-4, the UUCP for our case study project is UUCP = UAW + UUCW = 12 + 85 = 97.
The UUCP gives the unadjusted size of the overall system, unadjusted because it does not account
for the nonfunctional requirements (TCFs) and the environmental factors (ECFs).
310
Table 4-4: Use case classification for the case study of home access control (see Section 2.3).
Use case Description Category Weight
Simple user interface. 5 steps for the main success
Unlock (UC-1) scenario. 3 participating actors (LockDevice, Average 10
LightSwitch, and Timer).
Simple user interface. 2+3=5 steps for the all
Lock (UC-2)
scenarios. 3 participating actors (LockDevice, Average 10
LightSwitch, and Timer).
Complex user interface. More than 7 steps for the
ManageUsers
main success scenario (when counting UC-6 or Complex 15
(UC-3)
UC-7). Two participating actors (Tenant, Database).
ViewAccessHistory Complex user interface. 8 steps for the main success
Complex 15
(UC-4) scenario. 2 participating actors (Database, Landlord).
AuthenticateUse Simple user interface. 3+1=4 steps for all scenarios.
Average 10
r (UC-5) 2 participating actors (AlarmBell, Police).
Complex user interface. 6 steps for the main success
AddUser (UC-6) scenario (not counting UC-3). Two participating Average 10
actors (Tenant, Database).
Complex user interface. 4 steps for the main success
RemoveUser
scenario (not counting UC-3). One participating actor Average 10
(UC-7) (Database).
Simple user interface. 2 steps for the main success
Login (UC-8) Simple 5
scenario. No participating actors.
311
Table 4-5: Technical complexity factors and their weights.
Technical factor Description Weight
T1 Distributed system (running on multiple machines) 2
Performance objectives (are response time and throughput
T2 1(∗)
performance critical?)
T3 End-user efficiency 1
T4 Complex internal processing 1
T5 Reusable design or code 1
Easy to install (are automated conversion and installation
T6 0.5
included in the system?)
Easy to use (including operations such as backup, startup, and
T7 0.5
recovery)
T8 Portable 2
T9 Easy to change (to add new features or modify existing ones) 1
T10 Concurrent use (by multiple users) 1
T11 Special security features 1
Provides direct access for third parties (the system will be used
T12 1
from multiple sites in different organizations)
T13 Special user training facilities are required 1
(∗) Some sources assign 2 as the weight for the performance objectives factor (T2).
1.2 1.2
1 1
TCF
0.8 0.8
ECF
(a) (b)
Figure 4-2: Scaling constants for technical and environmental factors.
TCF values less than 1 reduce the Use Case Points, because multiplying a positive value by a
positive fraction decreases the value. 100 × 0.6 = 60 (a reduction of 40%).
TCF values greater than 1 increase the UCP, because multiplying a positive value by a positive
mixed number increases the value. 100 × 1.3 = 130 (an increase of 30%).
The constants were determined by interviews with experienced developers, based on their
subjective estimates. The key here, however, is not in these exact constant values, but rather in
the approach. Through your experience, you may discover other values that you find to be more
accurate.
312
Table 4-6: Technical complexity factors for the case study of home access (see Section 2.3).
Calculated Factor
Technical Perceived (Weight×Perceived
Description Weight
factor Complexity Complexity)
Distributed, Web-based system, because
T1 2 3 2×3 = 6
of ViewAccessHistory (UC-4)
Users expect good performance but
T2 1 3 1×3 = 3
nothing exceptional
End-user expects efficiency but there are
T3 1 3 1×3 = 3
no exceptional demands
T4 Internal processing is relatively simple 1 1 1×1 = 1
T5 No requirement for reusability 1 0 1×0 = 0
Ease of install is moderately important
T6 0.5 3 0.5×3 = 1.5
(will probably be installed by technician)
T7 Ease of use is very important 0.5 5 0.5×5 = 2.5
No portability concerns beyond a desire
T8 2 2 2×2 = 4
to keep database vendor options open
T9 Easy to change minimally required 1 1 1×1 = 1
T10 Concurrent use is required (Section 5.3) 1 4 1×4 = 4
T11 Security is a significant concern 1 5 1×5 = 5
T12 No direct access for third parties 1 0 1×0 = 0
T13 No unique training needs 1 0 1×0 = 0
Technical Factor Total: 31
Because the constants limit the TCF from a range of 0.6 to 1.3, the TCF can impact the UCP
equation by anywhere from −40% (0.6) to +30% (1.3). The formula to compute the TCF is as
follows:
Variables:
Constant-1 (C1) = 0.6
Constant-2 (C2) = 0.01
Wi = weight of ith technical factor (Table 4-5)
Fi = perceived complexity of ith technical factor (Table 4-6)
Formula:
13
313
Formula 4.2 is illustrated in Figure 4-2(a) above. Given the data in Table 4-6, the TCF = 0.6 + (0.01 ×
31) = 0.91. According to equation 4.1, this results in a reduction of the UCP by 9%.
314
Table 4-7: Environmental complexity factors and their weights.
Environmental factor Description Weight
E1 Familiar with the development process (e.g., UML-based) 1.5
E2 Application problem experience 0.5
E3 Paradigm experience (e.g., object-oriented approach) 1
E4 Lead analyst capability 0.5
E5 Motivation 1
E6 Stable requirements 2
E7 Part-time staff −1
E8 Difficult programming language −1
For E8, 0 means an easy-to-use programming language will be used, 3 means the language is of
average difficulty, and 5 means a very difficult language is planned for the project.
Each factor’s weight is multiplied by its perceived impact to produce its calculated factor. The
calculated factors are summed to produce the Environmental Factor Total. Larger values for the
Environment Factor Total will have a greater impact on the UCP equation. Table 4-8 calculates the
environmental factors for our home access case study, assuming that the project will be
developed by a team of upper-division undergraduate students.
To produce the final ECF, two constants are computed with the Environmental Factor Total.
Similar to the TCF constants above, these constants were determined based on interviews with
expert developers (but, again, your experience may yield constants with greater accuracy). The
constants constrain the impact the ECF has on the UCP equation from
0.425 to 1.4 An impact of 0.425 would mean part-time workers and difficult languages = 0 and all
other values = 5. A 1.4 impact would mean that perceived impact is all 0.
Therefore, the ECF can reduce the UCP by 57.5% and increase the UCP by 40%. See Figure 4-2(b).
Thus, the ECF has a greater potential impact on the UCP count than the TCF. The formula is:
8
ECF = Constant-1 + Constant-2 × Environmental Factor Total = C1 + C2 ⋅∑Wi ⋅ Fi (4.3)
i =1
where,
Constant-1 (C1) = 1.4
Constant-2 (C2) = −0.03
Wi = weight of ith environmental factor (see Table 4-7)
Fi = perceived impact of ith environmental factor (see Table 4-8)
This formula (4.3) is illustrated in Figure 4-2(b). Given the data below in Table 4-8, the ECF = 1.4 +
(−0.03×11) = 1.07. For the sample case study, the team’s modest software development
experience resulted in an average Environmental Factor Toral. All four factors E1-E4 scored
relatively low. According to equation (4.1), this results in an increase of the Use Case Points by
7%.
315
Table 4-8: Environmental complexity factors for the case study of home access (Section 2.3).
Calculated Factor
Environmental Perceived
Description Weight (Weight×
factor Impact Perceived Impact)
Beginner familiarity with the UML-
E1 1.5 1 1.5×1 = 1.5
based development
Some familiarity with application
E2 Problem 0.5 2 0.5×2 = 1
Some knowledge of object-oriented
E3 1 2 1×2 = 2
approach
E4 Beginner lead analyst 0.5 1 0.5×1 = 0.5
Highly motivated, but some team
E5 1 4 1×4 = 4
members occasionally slacking
E6 Stable requirements expected 2 5 2×5 = 10
E7 No part-time staff will be involved −1 0 −1×0 = 0
Programming language of average
E8 −1 3 −1×3 = −3
difficulty will be used
Environmental Factor Total: 16
316
UCP appears to be based on a great deal of subjective and seemingly arbitrary parameters,
particularly the weighting coefficients. For all its imperfections, UCP has become widely adopted
because it provides valuable estimate early on in the project, when many critical decisions need
to be made. See the bibliographical notes (Section 4.7) for literature on empirical evidence about
the accuracy of UCP-based estimation.
UCP measures how complex the software system will be in terms of functionality. The software
size is the same, regardless of who is building the system or the conditions under which it is being
built. For example, a project with a UCP of 100 may take longer than one with a UCP of 90, but
we do not know by how much. From the discussion in Section 1.2.5, we know that we need to
know the team’s velocity to calculate the project’s completion time using equation (1.2). Later, in
Section 4.6, we will describe how to factor in the team velocity—or productivity--and compute
the estimated number of hours.
317
CODE FLOWCHART GRAPH
(a) statement3
(b)
(c)
The cyclomatic complexity is additive. The complexity of several graphs considered as a group is
equal to the sum of individual graphs’ complexities.
There are more versions of the cyclomatic complexity formula. Each is slightly different from the one provided
above. You are encouraged to research these if you wish – in the field, you may find better experience with one over
the others.
318
refactorings are aimed at reducing the complexity of a program’s conditional logic [Fowler, 2000;
Kerievsky, 2005].
319
An Ordinal Scale for Cohesion Measurement
More reasonable design would have the contents of a module bear some relationship to each
other. Different relationships can be created for the contents of each module. By identifying
different types of module cohesion we can create a nominal scale for cohesion measurement. A
stronger scale is an ordinal scale, which can be created by asking an expert to assess subjectively
the quality of different types of module cohesion and create a rank-ordering. Here is an example
ordinal scale for cohesion measurement:
Rank Cohesion type Quality
6 Functional cohesion Good
5 Sequential cohesion ⏐
4 Communication cohesion ⏐
3 Procedural cohesion
⏐
2 Temporal cohesion
1 Logical cohesion ⏐
0 Coincidental cohesion ↓
Bad
Functional cohesion is judged to provide the tightest relationship because the design unit
(module) performs a single well-defined function or achieves a single goal.
Sequential cohesion is judged as somewhat weaker, because the design unit performs more than
one function, but these functions occur in an order prescribed by the specification, i.e., they are
strongly related.
Communication cohesion is present when a design unit performs multiple functions, but all are
targeted on the same data or the same sets of data. The data, however, is not organized in an
object-oriented manner as a single type or structure.
Procedural cohesion is present when a design unit performs multiple functions that are
procedurally related. The code in each module represents a single piece of functionality defining
a control sequence of activities.
Temporal cohesion is present when a design unit performs more than one function, and they are
related only by the fact that they must occur within the same time span. An example would be a
design that combines all data initialization into one unit and performs all initialization at the same
time even though it may be defined and utilized in other design units.
Logical cohesion is characteristic of a design unit that performs a series of similar functions. At
first glance, logical cohesion seems to make sense in that the elements are related. However, the
relationship is really quite weak. An example is the Java class java.lang.Math, which contains
methods for performing basic numeric operations such as the elementary exponential, logarithm,
square root, and trigonometric functions. Although all methods in this class are logically related
in that they perform mathematical operations, they are entirely independent of each other.
Ideally, object-oriented design units (classes) should exhibit the top two types of cohesion
(functional or sequential), where operations work on the attributes that are common for the class.
One issue with this cohesion measure is that the success of any module in attaining high- level
cohesion relies purely on human assessment.
320
Interval Scales for Cohesion Measurement
We are mainly interested in the cohesion of object-oriented units of software, such as classes.
Class cohesion captures relatedness between various members of a class: attributes and
operations/methods. Class cohesion metrics—or “measures”--can be broadly classified into two
groups:
1. Interface-based metrics compute class cohesion from information in method signatures
2. Code-based metrics compute class cohesion in terms of attribute accesses by methods
There are four sub-types of code-based metrics. They are categorized based on the methods of
quantification of cohesion:
2.a) Disjoint component-based metrics count the number of disjoint sets of methods or
attributes in a given class.
2.b) Pairwise connection-based metrics compute cohesion as a function of number of
connected and disjoint method pairs.
2.c) Connection magnitude-based metrics count the accessing methods per attribute, and
indirectly find an attribute-sharing index in terms of the count (instead of computing
direct attribute-sharing between methods).
2.d) Decomposition-based metrics compute cohesion in terms of recursive decompositions of
a given class. The decompositions are generated by removal of pivotal elements that keep
the class connected.
These metrics compute class cohesion with manipulations of class elements. The key elements of
a class C are its a attributes A1, …, Aa, m methods M1, …, Mm, and the list of p parameter types of
the methods P1, …, Pm.
What approaches exist to computing class cohesion? Many existing metrics qualify the class as
either “cohesive” or “not cohesive,” and do not capture varying strengths of cohesion. However,
this approach makes it hard to compare two cohesive or two non-cohesive classes—or to know
whether a code edit increased or decreased the cohesion level. To compare two different versions
of software and judge which is designed more cohesively, we need to use a more descriptive metric.
We need different degrees of cohesion, not just “cohesive” or “not cohesive”.
321
parameter types
O SerialPort String
DeviceCtrl
DeviceCtrl DeviceCtrl 1 0
#
# devStatuses_
devStatuses_ :: Vector
Vector
activate 0 1
+
+ activate(dev :: String)
activate(dev String) :: boolean
boolean methods deactivate 0 1
+
+ deactivate(dev
deactivate(dev :String)
:String) :: boolean
boolean
+
+ getStatus(dev
getStatus(dev :: String)
String) :: Object
Object getStatus 0 1
(a) (b)
Figure 4-4: Class (a) and its parameter occurrence matrix (b).
This gives a value for the CAMC metric. To formally summarize this, the metric is as follows
(Formula 4.7):
1 k l σ
CAMC(C) =
kl ∑ ∑ oij = kl (4.7)
i =1 j =1
Under LCOM1, the perfect cohesion is achieved when all methods access all attributes. Because
of perfect cohesion, we expect the lack-of-cohesion value to be 0. At the opposite end of the
spectrum, each method accesses only a single attribute (assuming that m = a). In this case, we
322
expect LCOM = 1, which indicates extreme lack of cohesion.
A formal definition of LCOM1 follows. Consider a set of methods {Mi} (i = 1, …, m} accessing a set
of attributes {Aj} (j = 1, …, a}. Let the number of attributes accessed by each method, Mi, be
denoted as α(Mi). Let the number of methods which access each attribute be μ(Aj). Then, the lack
of cohesion of methods for a class Ci is given formally as Formula 4.8 below:
⎛1 a ⎞
m − ⎜ ⋅ ∑ μ(Aj )⎟
⎜⎝ ⎟
LCOM1(C ) = ⎠a j=1 (4.8)
i m −1
This version of LCOM is labeled as LCOM1 to allow for subsequent variations, LCOM2,
LCOM3, and LCOM4. Class cohesion, LCOM3, is measured as the number of connected
323
components in the graph. LCOM2 calculates the difference between the number of method
pairs that do or do not share their class attributes. LCOM2 is classified as a Pairwise Connection-
Based
metric metrics. See the bibliographical notes (Section 4.7) for references on LCOM.
4.4 Coupling
Coupling metrics are a measure of how interdependent different modules are of each other. High
coupling occurs when one module modifies or depends on the internal workings of another
module. Low coupling occurs when there is no communication at all between different modules
in a program. Coupling is different from cohesion, but both are ordinal measurements and are
defined as “high” or “low.” It is most desirable to achieve low coupling and high cohesion.
The following image from Chapter 2 was intended to depict cohesion, but it also serves
as a great representation of coupling -- comparing high coupling (bad) with low coupling (good).
324
325
4.5 Psychological Complexity
“Then he explained that what can be observed is really determined by the theory. He said, you cannot first know
what can be observed, but you must first know a theory, or produce a theory, and then you can define what
can be observed.” —Heisenberg’s recollection of his first meeting with Einstein
“I have had my results for a long time: but I do not yet know how I am to arrive at them.”
—Karl Friedrich Gauss
One frustration with software complexity measurement is that, unlike placing a physical object on
a scale and measuring its weight, we cannot put a software object on a “complexity scale” and
read out the amount. Complexity seems to be an interpreted measure, much like person’s health
condition and it has to be stated as an “average case.” Your doctor can precisely measure your
blood pressure, but a specific number does not necessarily correspond to good or bad health. The
doctor will also measure your heart rate, body temperature, and perhaps several other
parameters, before making an assessment about your health condition. Even so, the assessment
will be the best guess, merely stating that on average such and such combination of physiological
measurements corresponds to a certain health condition. Perhaps we should define software
object complexity similarly: as a statistical inference based on a set of directly measurable
variables.
326
measure. Some problems mentioned above include: size could be measured differently; it
depends on the language in which the program code (or any other accurate description of it) is
written; and, the program description can be unnecessarily stuffed to make it appear complex. A
way out is to ignore the language issue and define complexity in terms of the description length.
Suppose that two persons wish to communicate a system description at distance. Assume they
are employing language, knowledge, and understanding that both parties share (and know they
share) beforehand. The crude complexity of the system can be defined as the length of the
shortest message that one party needs to employ to describe the system, at a given level of coarse
graining, to the distant party.
A well-known such measure is called algorithmic information content, which was introduced in
1960s independently by Andrei N. Kolmogorov, Gregory Chaitin, and Ray Solomonoff. Assume an
idealized general-purpose computer with an infinite storage capacity. Consider a particular
message string, such as “aaaaabbbbbbbbbb.” We want to know: what is the shortest possible
program that will print out that string and then stop computing? Algorithmic information content
(AIC) is defined as the length of the shortest possible program that prints out a given string. For
the example string, the program may look something like: P a{5}b{10}, which means “Print 'a' five
times and 'b' ten times.”
Information Theory
“...I think it better to write a long letter than incur loss of time...” —Cicero “I
apologize that this letter is so long. I did not have the time to make it short.” —Mark Twain
“If I had more time, I would have written a shorter letter.”
—variously attributed to Cicero, Pascal, Voltaire, Mark Twain, George Bernard Shaw, and T.S. Elliot
“The price of reliability is the pursuit of the utmost simplicity. It is a price which the very rich may find hard
to pay.” —C.A.R. Hoare
I already mentioned that algorithmic information content (AIC) does not exactly correspond to
everyday notion of complexity because under AIC random strings appear as most complex. But
there are other aspects to consider, as well. Consider the following description: “letter X in a
random array of letters L.” Then the description “letter T in a random array of letters L” should
have about the same AIC. Figure 4-5 pictures both descriptions in the manner pioneered by my
favorite teacher Bela Julesz. If you look at both arrays, I bet that you will be able to quickly notice
X in Figure 4-5(a), but you will spend quite some time scanning Figure 4-5(b) to detect the T! There
is no reason to believe that human visual system developed a special mechanism to recognize the
pattern in Figure 4-5(a), but failed to do so for the pattern in Figure 4-5(b). More likely, the same
general pattern recognition mechanism operates in both cases, but with much less success on
Figure 4-5(b). Therefore, it appears that there is something missing in the AIC notion
327
Input sequence from the world (time-varying)
of complexity—an apparently complex description has low AIC. The solution is to include the
computation time.
Charles Bennett defined logical depth of a description to characterize the difficulty of going from
the shortest program that can print the description to the actual description of the system.
Consider not just the shortest program to print out the string, but a set of short programs that
have the same effect. For each of these programs, determine the length of time (or number of
steps) needed to compute and print the string. Finally, average the running times so that shorter
programs are given greater weight.
328
4.6.1 Deriving Project Duration from Use Case Points
Use case points (UCP) are a measure of software size (Section 4.2.1). We can use equation (1.2)
given in Section 1.2.5 to derive the project duration. For this purpose we need to know the team’s
velocity, which represents the team’s rate of progress through the use cases (or, the team’s
productivity). Here is the equation that is equivalent to equation (1.2), but using a Productivity
Factor (PF):
Duration = UCP × PF (4.9)
The Productivity Factor is the ratio of development person-hours needed per use case point.
Experience and statistical data collected from past projects provide the data to estimate the initial
PF. For instance, if a past project with a UCP of 112 took 2,550 hours to complete, divide 2,550 by
112 to obtain a PF of 23 person-hours per use case point.
If no historical data has been collected, the developer can consider one of these options:
1. Establish a baseline by computing the UCP for projects previously completed by your team
(if such are available).
2. Use a value for PF between 15 and 30 depending on the development team’s overall
experience and past accomplishments (Do they normally finish on time? Under budget?
etc.). For a team of beginners, such as undergraduate students, use the highest value (i.e.,
30) on the first project.
A different approach was proposed by Schneider and Winters [2001]. Recall that the
environmental factors (Table 4-7) measure the experience level of the people on your project and
the stability of your project. Any negatives in this area mean that you will have to spend time
training people or fixing problems due to instability (of requirements). The more negatives you
have, the more time you will spend fixing problems and training people and less time you will
have to devote to your project.
Schneider and Winters suggested counting the number of environmental factors among E1
through E6 (Table 4-8) that have the perceived impact less than 3 and those among E7 and E8
with the impact greater than 3. If the total count is 2 or less, assume 20 hours per use case point.
If the total is 3 or 4, assume 28 hours per use case. Any total greater than 4 indicates that there
are too many environmental factors stacked against the project. The project should be put on
hold until some environmental factors can be improved.
Probably the best solution for estimating the Productivity Factor is to calculate your organization’s
own historical average from past projects. This is why collecting historic data is important for
improving effort estimation on future projects. After a project completes, divide the number of
actual hours it took to complete the project by the UCP number. The result becomes the new PF
that can be used in the future projects.
When estimating the duration in calendar time, is important to avoid assuming ideal working
conditions. The estimate should account for corporate overhead—answering email, attending
meetings, and so on. Suppose our past experience suggests a PF of 23 person-hours per use case
point and our current project has 94 use case points (as determined Section 4.2.1). Equation (4.9)
329
gives the duration as 94 × 23 = 2162 person-hours. Obviously, this does not imply that the project
will be completed in 2162 / 24 ≈ 90 days! A reasonable assumption is that each developer will
spend about 30 hours per week on project tasks and the rest of their time will be taken by
corporate overhead. With a team of four developers, this means the team will make 4 × 30 = 120
hours per week. Dividing 2162 person-hours by 120 hours per week we obtain a total of
approximately 18 weeks to complete this project.
In this chapter I described two kinds of software measurements. One kind works with scarce
artifacts that are available early on in a project, such as customer statement of requirements for
the planned system. There is a major subjective component to these measurements, and it works
mainly based on guessing and past experience with similar projects. The purpose of this kind of
measurements is to estimate the project duration and cost of the effort, so to negotiate the terms
of the contract with the customer who is sponsoring the project.
The other kind of software measurements works with actual software artifacts, such as UML
designs or source code. It aims to measure intrinsic properties of the software and avoid
developer’s subjective guesses. Because it requires that the measured artifacts already exist in a
completed or nearly completed condition, it cannot be applied early on in a project. The purpose
of this kind of measurements is to evaluate the product quality. It can serve as a test of whether
the product is ready for deployment, or to provide feedback to the development team about the
potential weaknesses that need to be addressed.
An early project effort estimate helps managers, developers, and testers plan for the resources a
project requires. The use case points (UCP) method has emerged as one such method. It is a
mixture of intrinsic software properties, measured by Unadjusted Use Case Points (UUCP) as well
as technical (TCF) and environmental factors (ECF), which depend on developer’s subjective
estimates. The UCP method quantifies these subjective factors into equation variables that can
be adjusted over time to produce more precise estimates. Industrial case studies indicate that the
UCP method can produce an early estimate within 20% of the actual effort.
330
has been controversial since its introduction and has been criticized from many fronts. Halstead’s
work has mainly historical importance for software measurement because it was instrumental in
making metrics studies an issue among computer scientists.
Use case points (UCP) were first described by Gustav Karner [1993], but his initial work on the
subject is closely guarded by Rational Software, Inc. Hence, the primary sources describing
Karner’s work are [Schneider & Winters, 2001] and [Ribu, 2001]. UCP was inspired by Allan
Albrecht’s “Function Point Analysis” [Albrecht, 1979]. The weighted values and constraining
constants were initially based on Albrecht, but subsequently modified by people at Objective
Systems, LLC, based on their experience with Objectory—a methodology created by Ivar Jacobson
for developing object-oriented applications.
My main sources for use case points were [Schneider & Winters, 2001; Ribu, 2001; Cohn, 2005].
[Kusumoto, et al., 2004] describes the rules for a system that automatically computes the total
UCP for given use cases. I believe these rules are very useful for a beginner human when
computing UCPs for a project.
Many industrial case studies verified the estimation accuracy of the UCP method. These case
studies found that the UCP method can produce an early estimate within 20% of the actual effort,
and often closer to the actual effort than experts or other estimation methodologies. Mohagheghi
et al. [2005] described the UCP estimate of an incremental, large-scale development project that
was within 17% of the actual effort. Carroll [2005] described a case study over a period of five
years and across more than 200 projects. After applying the process across hundreds of sizable
software projects (60 person-months average), they achieved estimating accuracy of less than 9%
deviation from actual to estimated cost on 95% of the studied projects. To achieve greater
accuracy, Carroll’s estimation method includes a risk coefficient in the UCP equation.
331
Section 4.5: Psychological Complexity
[Bennett, 1986; 1987; 1990] discusses definition of complexity for physical systems and defines
logical depth.
Practice Problems
Problem 4.1
Problem 4.2
Problem 4.3
(CYCLOMATIC/MCCABE COMPLEXITY) Consider the following quicksort sorting algorithm:
QUICKSORT(A, p, r)
1 if p < r
2 then q ← PARTITION(A, p, r)
3 QUICKSORT(A, p, q − 1)
4 QUICKSORT(A, q + 1, r)
where the PARTITION procedure is as follows:
PARTITION(A, p, r)
1 x ← A[r]
2 i ←p −1
3 for j ← p to r − 1
4 do if A[j] ≤ x
5 then i ← i + 1
6 exchange A[i] ↔ A[j]
7 exchange A[i + 1] ↔ A[r]
8 return i + 1
332
Problem 4.4
333
Chapter 5
Design with Patterns
Contents
“It is not the strongest of the species that survive, nor the most Indirect Communication: Publisher-
intelligent, but the one most responsive to change.” Subscriber
—Charles Darwin
More Patterns
“Man has a limited biological capacity for change. When this
capacity is overwhelmed, the capacity is in future shock.” Concurrent Programming
—Alvin Toffler
Design patterns are convenient solutions for software design Broker and Distributed Computing
problems commonly employed by expert developers. The
Information Security
power of design patterns derives from reusing proven solution
“recipes” from similar problems. In other words, patterns are
codifying practice rather than prescribing practice, or, they are Summary and Bibliographical Notes
engineering. By condensing many structural and behavioral aspects of the design into a few simple
concepts, patterns make it easier for team members to discuss the design. As with any symbolic
language, one of the greatest benefits of patterns is in chunking the design knowledge. Once team
members are familiar with the pattern terminology, the use of this terminology shifts
334
Custodian
Custodian initializes the pattern
Instantiation
Instantiationof
ofthe
the
Design
Design Pattern
Pattern
collection of objects
Client
Client asks for service working to provide service
the focus to higher-level design concerns. No time is spent in describing the mechanics of the
object collaborations because they are condensed into a single pattern name.
This chapter reviews some of the most popular design patterns that will be particularly useful in
the rest of the text. What follows is a somewhat broad and liberal interpretation of design
patterns. The focus is rather on the techniques of solving specific problems; nonetheless, the
“patterns” described below do fit the definition of patterns as recurring solutions. These patterns
are conceptual tools that facilitate the development of flexible and adaptive applications as well
as reusable software components.
Two important observations are in order. First, finding a name that in one or few words conveys
the meaning of a design pattern is very difficult. A similar difficulty is experienced by user interface
designers when trying to find graphical icons that convey the meaning of user interface
operations. Hence, the reader may find the same or similar software construct under different
names by different authors. For example, The Publisher-Subscriber design pattern, described in
Section 5.1, is most commonly called Observer [Gamma et al., 1995], but [Larman, 2005] calls it
Publish-Subscribe. I prefer the latter because I believe that it conveys better the meaning of the
underlying software construct1. Second, there may be slight variations in what different authors
label with the same name. The difference may be due to the particular programming language
idiosyncrasies or due to evolution of the pattern over time.
Common players in a design pattern usage are shown in Figure 5-1. A Custodian object assembles
and sets up a pattern and cleans up after the pattern’s operation is completed. A client object
(can be the same software object as the custodian) needs and uses the services of the pattern.
The design patterns reviewed below generally follow this usage “pattern.”
1 The Publish-Subscribe moniker has a broader use than presented here and the interested reader should
consult [Eugster et al. 2003].
338
Subscribers Publisher
“If you find a good solution and become attached to it, the solution may become your next problem.”
—Robert Anthony
“More ideas to choose from mean more complexity … and more opportunities to choose wrongly.”
—Vikram Pandit
Figure 5-2 depicts the publisher-subscriber design pattern. This design pattern is used to
implement indirect communication between software objects. Indirect communication is usually
used because an object cannot or does not want to know the identity of the object whose method
it calls. It may also be because it does not want to know what the effect of the call will be. Below
are two main benefits of the “pub-sub pattern”.
1) It enables building reusable components. This is the most popular use of the pattern.
2) It facilitates separation of the business logic (responsibilities, concerns) of objects.
The problem with building reusable components can be illustrated on our SMART-home access
case-study example. Assume that we have an extended version of our application: one that
sounds alarm if someone is tampering with the lock. Assume that we want to reuse the
KeyChecker object in this extended application. We need to modify the unlock() method not only
to send a message to LockCtrl, but also to send one to AlarmCtrl--or to introduce a new method.
In either case, we must change the object code--meaning that the object is not reusable as is.
339
Publisher
Knowing Responsibilities:
Registers/Unregisters subscribers
Subscriber
Knowing Responsibilities:
(a) (b)
Figure 5-3: Publisher/Subscriber objects employee cards (a), and the class diagram of their
collaborations (b).
An information source acquires information in some way. We assume that this information is
important for other objects to do the work they are designed for. Once the source acquires
information, it is logical to expect it to pass this information to others and initiate their work.
However, this tacitly implies that the source object “knows” what the doer object should do next.
Thus, this knowledge must be encoded in the source object as an “IF-THEN- ELSE” rule—which
means it must be modified every time the doer code is modified. We saw this earlier in Section
2.5.
In Figure 5-4, we see request vs. event-based communication. In request-based communication,
an object makes an explicit request, while in event-based communication, the object expresses
interest ahead of time and later gets notified by the information source. In a way, the source is
making a method request on the object. Additionally, notice that “request-based” is a
synchronous type of communication--whereas event—based communication is asynchronous.
Another way to design the KeyChecker object is to make it become a publisher of events.
This can be done in the following way. As per Figure 5-3, we need to define two class
interfaces: Publisher and Subscriber. The first one, Publisher, allows any object to
subscribe for information that it is the source of. The second, Subscriber, has a method--
here called receive()--to let the Publisher publish the data of interest.
340
Request: doSomething(info) Request: getInfo() (1) Request: subscribe()
A Content object contains only data--no business logic--and is meant to transfer data from the
Publisher to the Subscriber. Then, the actual classes implement those two interfaces. In our SAFE
home access example, the key Checker object would implement the Publisher, while DeviceCtrl
would implement the Subscriber.
341
342
public void receive(Content content) {
if (content.source_ instanceof Checker) {
if ( ((String)content.data_).equals("valid") ) {
// check the time of day; if daylight, do nothing if (!sensor_.isDaylight())
bulb_.setLit(true);
}
} else (check for another source of the event ...) {
...
}
}
} // Code continues on next page
343
import java.util.ArrayList;
import java.util.Iterator;
344
Figure 5-5: Sequence diagram for publish-subscribe version of the use case “Unlock.” Compare
this with Figure 2-27.
A Subscriber may be subscribed to several sources of data, and each source may provide several
types of content. Thus, the Subscriber must determine the source and the content type before it
takes any action. If it gets subscribed to many sources which publish different content, the
Subscriber code may become quite complex and difficult to manage. The Subscriber would
contain many if()or switch() statements to account for different options.
A more object- oriented solution for this is to use class polymorphism: instead of having one
Subscriber, we should have several Subscribers, each specialized for a particular source. The
Subscribers may also have more than one receive() method, each specialized for a particular data
type.
Here is an example. We could implement a Switch--by inheriting from the generic Subscriber
interface defined above--or, we can define new interfaces specialized for our problem domain.
There is a tradeoff between the number of receive() methods and the switch() statements. On one
hand, having a long switch() statement complicates the Subscriber’s code and makes it difficult to
maintain and reuse. On the other hand, having too many receive() statements results in a long
class interface, difficult to read and represent graphically.
345
Listing 5-3: Subscriber interfaces for “key-is-valid” and “key-is-invalid” events.
public interface KeyIsValidSubscriber {
public void keyIsValid(LockEvent event); // receive() method
}
346
The new design for the Unlock use case is shown in Figure 5-5. The Listing below shows what the
corresponding code might look like. Notice that the attribute numOfAttempts belongs to the
AlarmCtrl--unlike the first implementation in Listing 2-2 (Section 2.7), where it belonged to the
Controller. Notice also that the Controller is a KeyIsInvalidSubscriber, so it can prompt the user to
enter a new key if the previous attempt was unsuccessful.
Listing 5-4: A variation of the Publisher-Subscriber design from Listing 5-2, using the
subscriber interfaces from Listing 5-3.
public class Checker implements LockPublisher { protected KeyStorage
validKeys_;
protected ArrayList keyValidSubscribers_ = new ArrayList(); protected ArrayList
keyInvalidSubscribers_ = new ArrayList();
if (valid) {
for (Iterator e = keyValidSubscribers_.iterator(); e.hasNext(); ) {
((KeyIsValidSubscriber) e.next()).keyIsValid(evt);
}
} else { // the lock is being tampered with
for (Iterator e = keyInvalidSubscribers_.iterator(); e.hasNext(); ) {
((KeyIsInvalidSubscriber) e.next()).keyIsInvalid(evt);
}
}
}
}
347
keyChecker.subscribeKeyIsValid(this);
...
}
Note that what we just did with the original design for Unlock can be considered refactoring. In
software engineering, the term refactoring is often used to describe modifying the design and/or
implementation of a software module without changing its external behavior. It is informally
referred to as “cleaning it up.” Refactoring is often practiced as part of the software development
cycle: developers alternate between adding new tests/functionality and refactoring the code to
improve its internal consistency and clarity. In our case, the design from Figure 2-27 has been
transformed to the design in Figure 5-5.
348
One main reason to have well-divided software components is to enable easy visualization in
integrated development environments (IDEs). This allows the developer to visually assemble the
components. In analogy to hardware design, the components are represented as “integrated
circuits”, and different receive() / subscribe() methods represent “pins” on the circuit. If a
component has too many pins, it becomes difficult to visualize. To continue with our analogy, we
could say the component generates too many wires in the blueprint. The situation is similar to
determining the right number of pins on an integrated circuit. (See more about software
components in Chapter 7.)
Here, I reiterate the key benefits of using the pub-sub design pattern (and indirect communication
in general):
• The components do not need to know each other’s identity.
• For example, in the sample code given in Listing 1-1 (Section
1.4.2), LockCtrl maintains a reference to a LightCtrl object.
• The component’s business logic is contained within the component alone.
• In the same example, LockCtrl explicitly invokes the
LightCtrl’s method setLit(), meaning that it minds LightCtrl’s
business. In the worst case, even the checking of the time-
of-day may be delegated to LockCtrl in order to decide when
to turn the light on.
Both of the above form the basis for component reusability, because making a component
independent of others makes it reusable. The pub-sub pattern is the most basic pattern for
reusable software components--as will be discussed in Chapter 7.
In the “ideal” case, all objects could be made self-contained--and thus reusable--by applying the
pub-sub design pattern. However, there are penalties to pay. As visible from the examples above,
indirect communication requires much more code, which results in increased demand for memory
and decreased performance. Thus, if it is not likely that a component will need to be reused or if
performance is critical, direct communication should be applied and the pub-sub pattern should
be avoided.
So, when should we apply the pub-sub pattern? Whether you should do so always depends on
whether you anticipate that the component is likely to be reused in future projects. If yes, apply
pub-sub. You should understand that decoupled objects are independent, and therefore reusable
and easier to understand. Contrastingly, highly interleaved objects provide fast inter-object
communication and compact code. Decoupled objects are better suited for global understanding,
whereas interleaved objects are better suited for local understanding. As you may guess, in a large
system, global understanding matters more. Why do you think this is?
349
: Key : KeyStorage
getNext()
create() : Checker
checkKey()
: Logger
: Controller setOpen()
logTransaction()
: LockCtrl
isDaylight()
soundAlarm() setLit()
: AlarmCtrl : Key getNext()
create() : Checker
: PhotoSObs
: LightCtrl : KeyStorage
checkKey()
: Controller keyIsValid()
(a) keyIsInvalid()
: LockCtrl
: AlarmCtrl
: Logger : LightCtrl
isDaylight()
(b) : PhotoSObs
Figure 5-6: Flow control without (a) and with the Pub-Sub pattern (b). Notice that these UML
communication diagrams are redrawn from Figure 2-27 and Figure 5-5, respectively.
Although in Figure 5-6(b) it appears as if the Checker plays a central role, this is not so. The checker
is not “aware” of being assigned such a role. Unlike the Controller from Figure 5-6(a), this Checker
does not encode the requisite knowledge to play such a role. The outgoing method calls are shown
in dashed lines to indicate that these are indirect calls, through the Subscriber interface.
Whether the behavior rules are stored in one Controller or distributed around in many objects,
the output--seen from outside the system--is the same. Organization (internal function) matters
only if it simplifies the software maintenance and upgrading.
350
Figure 5-7: Initialization of the pub-sub for the lock control example.
351
5.2 More Patterns
Publisher-Subscriber belongs to the category of behavioral design patterns. Behavioral patterns
separate the interdependent behavior of objects from the objects themselves. In other words,
they separate functionality from the object to which the functionality applies. This promotes
reuse, because different types of functionality can be applied to the same object, as needed. In
Figure 5-8, I review Command as another behavioral pattern.
Separate from behavioral patterns, we also have structural patterns. An example structural pattern
reviewed later is Proxy.
A common drawback of design patterns--particularly behavioral patterns--is that we are replacing
what would be a single method call with many method calls. This results in performance penalties,
which in certain cases may not be acceptable. However, in most cases the benefits of good design
outweigh the performance drawbacks.
352
Figure 5-8: Command pattern interposes Command (and other) objects between a client and a
server object. Complex actions about rolling back and forward the execution history are
delegated to the Command, away from the client object.
5.2.1 Command
Objects invoke methods on other objects as depicted in Figure 1-22.This is abstracted in Figure 5-
8(a). The need for the Command pattern arises if the invoking object (client) needs to reverse the
effect of a previous method invocation. It could also arise if the client needs the ability to trace
the course of the system operation. For example, we may need to keep track of financial
transactions for legal or auditing reasons. The purpose of the Command pattern is to take the
functionality associated with rolling back the server object’s state and logging the history of the
system operation—and delegate that functionality away from the client object to the Command
object. This is depicted in Figure 5-8(b).
Instead of directly invoking a method on the Receiver (server object), the client object appoints a
Command for this task. The Command pattern (Figure 5-9) encapsulates an action or processing
task into an object--thus increasing flexibility in calling for a service. The Command object
represents operations as classes, and is used whenever a method call alone is not sufficient. The
Command object is the central player in the Command pattern--but, as with most patterns,
Command needs other objects to assist with accomplishing the task. At runtime, a control is
passed to the execute() method of a non-abstract-class object derived from the Command object.
Figure 5-9(c) shows a sequence diagram on how to create and execute a command. In addition to
executing requests, we may need to be able to trace the course of the system operation.
CommandHistory maintains history log of Commands in linear sequence of their execution.
353
invoker «interface»
Command
(a) (b)
create(receiver, args)
(c) execute()
log(cmd)
Figure 5-9: (a) Command object employee card. (b) The Command design pattern (class
diagram). The base Command class is an interface implemented by concrete commands.
(c) Interaction diagram for creating and executing a command.
It is common to use the Command pattern in operating across the Internet. For example, suppose
that client code needs to make a function call on an object of a class residing on a remote server.
It is not possible for the client code to make an ordinary method call on this object, because the
remote object cannot appear in the usual compile-execute process. It is also difficult to employ
remote method invocation (Section 5.4.2) here, because often, the client and server cannot be
programmed at the same time (or are programmed by different parties).
Instead, the call is made from the client by pointing the browser to the file containing the servlet.
The servlet is a server-side software component). The servlet then calls its method
service(HttpServletRequest, HttpServletResponse). The object HttpServletRequest includes all the
information that a method invocation requires, such as the argument values, obtained from the
“environment” variables at standardized global locations. The object HttpServletResponse carries
the result of invoking service(). This technique embodies the basic idea of the Command design
pattern. See also Listing 5-5.
Web services allow a similar runtime function discovery and invocation, as will be seen in Chapter
8.
354
«interface»
Command
+ execute()
+ unexecute()
+ isReversible() : boolean
ActionType1Cmd ActionType2Cmd
+ execute() + execute()
+ unexecute() + unexecute()
+ isReversible() + isReversible()
(a) (b)
Figure 5-10: (a) Class diagram for commands that can be undone. (b) Interaction diagram for
undoing a (reversible) command. Compare to Figure 5-9.
Undo/Redo
The Command pattern may optionally be able to support rollback of user’s actions in an elegant
fashion. Anyone who uses computers appreciates the value of being able to undo their recent
actions. Of course, this feature assumes that a command’s effect can be reversed. In this case, the
Command interface would have two more operations: isReversible() to allow the invoker to find
out whether this command can be undone; and unexecute() to undo the effects of a previous
execute()operation. See Figure 5-10(a).
Figure 5-10(b) shows a sequence diagram on how to undo/redo a command, assuming that it is
undoable. Observe also that CommandHistory should decrement its pointer of the current
command every time a command is undone, and increments it every time a command is redone.
An additional requirement on CommandHistory is to manage properly the undo/redo caches. For
example, if the user backs up along the undo queue and then executes a new command, the whole
redo cache should be flushed. Similarly, upon a context switching, both undo/redo caches should
be flushed. Obviously, this does not provide for long-term archiving of the commands; if that is
required, the archive should be maintained independently of the undo/redo caches.
In the physical world, actions are never reversible. Even an approximate reversibility may not be
realistic to expect. Consider a simple light switch. One might think that turning the switch off is
exactly opposite of turning it on. Therefore, we could implement a request to turn the switch off
as an undo operation of the command to turn the switch on. Unfortunately, this may not be true.
For example, we may be unable to recover the energy lost during the period that the switch was
on. Alternatively, perhaps the light bulb is burnt. Obviously, this cannot be undone (unless the
system has a means of automatically replacing a burnt light bulb with a new one).
In the digital world, if the previous state is stored or is easy to compute, then the command can
be undone. Even here, we need to beware of potential error accumulation. If a number is
repeatedly divided and then multiplied by another number, rounding errors or limited number of
bits for number representation may yield a different result than the one we started with.
355
Decorator
client «interface»
Subject
Decorator RealSubject
+ request() + request()
(a)
ConcreteDecorator1 ConcreteDecorator2
(b)
+ request() + request()
addedProcessing( )
addedProcessing( )
moreAddedProcessing( )
Figure 5-11: (a) Decorator object employee card. (b) The Decorator design pattern (class
diagram). (c) Interaction diagram for the Decorator pattern.
5.2.2 Decorator
The Decorator pattern is used to add non-essential behavior to key objects in a software design.
The embellished class (or, decoratee) is wrapped up by an arbitrary number of Decorator classes,
which provide special-case behaviors (embellishments).
Observe Figure 5-11.
Notice that the Decorator is an abstract class (the class and method names are italicized). The
reason for this choice is to collect the common things from all different decorators into a base
decorator class. In this case, the Decorator class will contain a reference to the next decorator.
The decorators are linked in a chain. The client has a reference to the start of the chain, and the
chain is terminated by the real subject. Figure 5-11(c) illustrates how a request from the client
propagates forward through the chain until it reaches the real subject, and how the result
propagates back.
To decide whether you need to introduce Decorator, look for special-case behaviors
(embellishment logic) in your design.
356
Controller
Figure 5-12: Example Decorator class diagram, for implementing the interface in Figure 2-2.
Consider the following example. We wish to implement the code allowing the user to configure
the settings for controlling the household devices when the doors are unlocked or locked. The
corresponding user interface is shown in Figure 2-2, Figure 5-12 and Figure 5-13 show UML
diagrams that use the Decorator design pattern in solving this problem. Notice the slight
differences in the class diagrams in Figure 5-11(b) and Figure 5-12. As we already pointed out, the
actual pattern implementation will not always strictly adhere to its generic prototype.
In this example, the decorating functionalities could be added before or after the main function,
which is to activate the lock control. For example, in Figure 5-13 the decorating operation
LightCtrl.turnOnLight() is added before LockCtrl.activate(), but MusicCtrl.turnOnMusicPlayer() is
added after it. In this case, all of these operations are commutative and can be executed in any
order. This may not always be the case with the decorating functionalities.
5.2.3 State
The State design pattern is usually used when an object’s behavior depends on its state in a
complex way. In this case, the state determines a mode of operation. Recall that the state of a
software object is represented by the current values of its attributes. The State pattern
externalizes the relevant attributes into a State object, and this State object has the responsibility
of managing the state transitions of the original object. The original object is called “Context” and
its attributes are externalized into a State object (see Figure 5-14).
357
: Controller : MusicCtrl : LightCtrl : LockCtrl : PhotoObsrv : AlarmCtrl
enterKey( )
ref
val := check the key validity
(see sequence fragment in Figure 2-20)
opt dl == false
turnOnLight()
activate()
disarmLock()
turnOnMusicPlayer()
[else] numOfAttempts++
[else]
Figure 5-13: Decorator sequence diagram for the class diagram in Figure 5-12.
A familiar example of an object’s state determining its mode of operation is found in tools of
document editors. Desktop computers normally have only a keyboard and mouse as interaction
devices. To enable different manipulations of document objects, the document needs to be put
in a proper state or mode of operation. That is why we select a proper “tool” in a toolbar before
performing a manipulation. The selected tool sets the document state. Consider an example of a
graphics editor, such as Microsoft PowerPoint. When the user clicks the mouse pointer on a
graphical object and drags the mouse, what will happen depends on the currently selected tool.
The default tool will relocate the object to a new location; the rotation tool will rotate the object
for an angle proportional to the distance the mouse is dragged over; etc. Notice that the same
action (mouse click and drag) causes different behaviors, depending on the document state (i.e.,
the currently selected tool).
The State pattern is also useful when an object implements complex conditional logic for changing
its state (i.e., the values of this object’s attributes). We say that the object is transitioning from
one state--to another state (another set of attribute values). To simplify the state transitioning,
we define a State interface. Different classes that implement this interface correspond to different
states of the Context object (Figure 5-14(b)).
358
Each concrete State class implements a state. It also implements the behavior of the Context
associated with that state. The behavior includes calculating the new state of the Context.
Furthermore, specific attribute values are encapsulated in different concrete states. Thus, the
current State class just determines the next state and returns it to the Context.
359
Context
Context «interface»
State currentState State
++ request(evt
request(evt :: Event)
Event)
+ handle()
+ handle() + handle()
(a)
request( event-1 )
handle( event-1 ) nextState := this
result, nextState
event-1 event-2 [condition] / action-2
result
result, nextState
(c) (d)
currentState := nextState
Figure 5-14: (a) State object employee card. (b) The State design pattern (class diagram).
(c) Example state diagram for the Context object. (d) Interaction diagram for the state diagram
in (c).
Let us assume that the UML state diagram for the Context class is represented by the example in
Figure 5-14(c). As shown in Figure 5-14(d), when the Context receives a method call request() to
handle an event, it calls the method handle() on its currentState. The current state processes
the event and performs any action associated with the current state transition. Finally, it returns
the next state to the caller Context object. The Context sets this next state as the current state.
The next request will be handled by the new current state.
5.2.4 Proxy
The Proxy pattern is used to manage or control access to an object. Proxy is needed when the
logistics of accessing the subject’s services is overly complex; i.e. when it is of similar or greater
size than that of the client’s primary responsibility. In such cases, we introduce a helper object
(called “proxy”) for management of the subject invocation. A Proxy object is a surrogate that acts
as a stand-in for the actual subject, and controls or enhances the access to it (Figure 5-15). The
proxy object forwards requests to the subject when appropriate, depending on whether the
constraint of the proxy is satisfied.
360
«interface»
Proxy
client
Subject
+ request()
+ request() + request()
(a) (b)
preprocessRequest( )
∗ denotes possibly
preprocessed
input arguments
opt constraint satisfied
request( args∗ )
(c) result
postprocessResult( )
Figure 5-15: (a) Proxy object employee card. (b) The Proxy design pattern (class diagram).
(c) Interaction diagram for the Proxy pattern.
361
Obtain user role
and credentials
[ user == sys-admin ] Grant full access
to metadata and data
[else]
Figure 5-16: Conditional logic for controlling access to the database of the secure home access
system.
In essence, we could say that a proxy allows client objects to cross a barrier to server objects (or,
“subjects”). The barrier may be physical (such as network between the client and server
computers) or imposed (such as security policies to prevent unauthorized access). As a result, the
client cannot or should not access the server by a simple method call. The additional functionality
needed to cross the barrier is extraneous to the client’s business logic. The proxy object abstracts
the l o g i s t ic a l details of accessing the subject’s services across different barriers. It does this
transparently, so the client has an illusion that it is directly communicating with the subject, and
does not know that there is a barrier in the middle.
Proxies offer the same interface (set of methods and their signatures) as the real subject, and
ensure correct access to the real subject. For this reason, a proxy maintains a reference to the real
subject (Figure 5-15(b)). Because of the identical interface, the client does not need to change its
calling behavior nor syntax from those which it would use if there were no barrier involved.
The Remote Proxy pattern will be incorporated into a more complex Broker pattern (Section 5.4).
The rest of this section provides more detail on the Protection Proxy.
Protection Proxy
The Protection Proxy pattern can be used to implement different policies to constrain the access
to the subject. For example, a security policy may require that a defined service should be seen
differently by clients with different privileges. This pattern helps us customize the access, instead
of using conditional logic to control the service access. In other words, it is applicable where a
subset of capabilities or partial capability should be made available to different actors, based on
their roles and privileges.
362
«interface»
java.sql.Connection
client : Controller
+ createStatement( … ) : Statement
+ getMetaData() : DatabaseMetaData RealSubject
…
ConnectionImpl
…
+ createStatement( … ) : Statement
+ getMetaData() : DatabaseMetaData
tenant’s Proxy admin’s Proxy …
DBConTenant DBConAdmin
# credentials_ : Object # credentials_ : Object
+ createStatement( … ) : Statement + createStatement( … ) : Statement
+ getMetaData() : DatabaseMetaData + getMetaData() : DatabaseMetaData
… …
– checkRequestAuthorized() – checkRequestAuthorized()
– createStatmProxy( … ) : Statement – createStatmProxy( … ) : Statement
Factory
Factory pattern
+ getDbaseConnection(credentials : Object) : java.sql.Connection
Figure 5-17: Class diagram for the example proxy for enforcing authorized database access. See
the interactions in Figure 5-18. (Compare to Figure 5-15(b) for generic Proxy pattern.)
For example, consider our case study system for SMART home access. The sequence diagram for
use case UC-5: Inspect Access History is shown in Figure 2-26. Before the Controller calls the
method accessList := retrieve(params : string) on Database Connection, the system should check
that this user is authorized to access the requested data. (This fragment is not shown in the figure.)
Figure 5-16 depicts the Boolean logic for controlling the access to the data in the system database.
One way to implement this scheme is to write one large conditional IF- THEN-ELSE statement. This
approach would lead to a complex code that is difficult to understand and extend if new policies
or roles need to be considered (e.g., the Maintenance actor). In addition, it serves as a
distraction from the main task of the client or server objects. This is where Protection Proxy enters
the picture and takes on the authorization responsibility.
Figure 5-17 shows how Protection Proxy is implemented in an example of safe database access.
Each different proxy type specifies a set of legal messages--from client to subject--that are
appropriate for the current user’s access rights. If a message is not legal, the proxy will not forward
it to the real subject (i.e. the database connection object ConnectionImpl). Instead, the proxy will
send an error message back to the caller (i.e., the client).
Looking at Figures 5-17 and 5-18, the Factory object acts as a custodian that sets up the Proxy
pattern.
It turns out that, in this example, we need two types of proxies. Let’s call them Type 1 and Type
2. Type 1 proxies implement the database connection interface, such as java.sql.Connection.
Type 2 proxies implement the SQL statement interface, such as java.sql.Statement. The
connection proxy guards access to the database metadata, while the statement proxy guards
access to the database data. The partial class diagram in Figure 5-17 shows only the connection-
proxy classes, and Figure 5-18 mentions the statement proxy only in the last method call
createStatmProxy(). It is by createStatmProxy() that the database proxy “DBConTenant” creates
363
and returns a statement proxy.
364
proxyAD : proxyLL : proxyTN : dBc :
: Controller factory : Factory DBConAdmin DBConLlord DBConTenant ConnectionImpl
[credentls == "landlord"]
proxyLL := create( dBc )
(a) return proxyLL
[credentls == "tenant"]
proxyTN := create( dBc )
return proxyTN
[else]
return NULL
query := createStatement( … )
statm := createStatement( … )
createStatmProxy( statm, … )
(b)
return SQL Statement Proxy
Figure 5-18: Example of Protection Proxy setup (a) and use (b) that solves the access-
control problem from Figure 5-16. (See the corresponding class diagram in Figure 5-17.)
Listing 5-5: Implementation of the Protection Proxy that provides safe access to the
database in the SMART home access system.
import java.sql.Connection; import
java.sql.DriverManager; import
java.sql.ResultSet; import
java.sql.Statement;
import javax.servlet.ServletConfig; import
javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import
javax.servlet.http.HttpServletResponse;
365
public void init(ServletConfig config) throws ServletException { super.init(config);
...
366
// Protection Proxy class for the actual java.sql.Connection public class DBConTenant
implements Connection {
protected Connection dBc_ = null; protected String
dbUserID = null, dbPassword =
null;;
public DBConTenant(
Connection dBc, String dbUserID, String dbPassword
){
...
}
One may wonder if we should similarly use Protection Proxy to control the access to the locks in
a building, so the landlord has access to all apartments and a tenant only to own apartment. When
considering the merits of this approach, the developer first needs to compare it to a
straightforward conditional statement and see which approach would create a more complex
implementation.
The above example illustrated the use of Proxy to implement security policies for authorized data
access. Another example involves safety policies to limit the number of simultaneous accesses.
For example, to avoid inconsistent reads/writes, the policy may allow at most one client at a time
to access the subject. This, in effect, serializes the access to the subject. This constraint is
implemented by passing a token among clients—only the client in possession of the token can
access the subject by presenting the token when requesting access.
The Protection Proxy pattern is structurally identical to the Decorator pattern (compare Figure
5-11 and Figure 5-15). We can also create a chain of Proxies, same as with the Decorators (Figure
5-11(c)). The key difference is in the intent: Protection Proxy protects an object (e.g., from
unauthorized access) while Decorator adds special-case behavior to an object.
367
368
♦ The reader may have noticed that many design patterns look similar to one another. For
example, Proxy is structurally almost identical to Decorator. The difference between them is in
their intention—what they are used for. The intention of Decorator is to add functionality,
while the intention of Proxy is to subtract functionality--particularly for Protection Proxy.
“The test of a first-rate intelligence is the ability to hold two opposed ideas in mind at the same time and
still retain the ability to function.” —F. Scott Fitzgerald
The benefits of concurrent programming include better use of multiple processors and easier
programming of reactive (event-driven) applications. In event-driven applications, such as GUIs,
the user expects a quick response from the system. If the (single- processor) system processes all
requests sequentially, then it will respond with significant delays and most of the requestors will
be unhappy. A common technique is to employ time-sharing or time slicing. This is where a single
processor dedicates a small amount of time for each task, so all of them move forward collectively
by taking turns on the processor. None of the tasks progress as fast as they would if they were
alone—but, on the flip side, none of them have to wait as long as they could have to if the
processing were performed sequentially. The task executions are actually sequential, but are
interleaved with each other, so they virtually appear as concurrent. In the discussion below, I
ignore the difference between real concurrency--when the system has multiple processors--and
virtual concurrency on a single-processor system. From the user’s viewpoint, there is no logical
or functional difference between these two options—the user would only see difference in the
length of execution time.
A computer process is, roughly speaking, a task being executed by a processor. A task is defined
by a temporally ordered sequence of instructions (i.e., program code) for the processor. In
general, a process consists of:
• Memory, which contains executable program code and/or associated data
• Operating system resources that are allocated to the process, such as file descriptors (in
Unix) or handles (in Windows)
• Security attributes, such as the identity of process owner and the process’s set of
privileges
• Processor state, such as the content of registers, physical memory addresses, etc. The
state is stored in the actual registers when the process is executing, and in memory
otherwise.
Threads are similar to processes, but they are not the same. Both represent a single sequence of
instructions executed in parallel with other sequences, either by time slicing on a single processor
or by multiprocessing. A process is an entirely independent program. It carries considerable state
information and interacts with other processes only through system-provided IPC (inter-process
369
communication) mechanisms. Conversely, a thread directly shares the state variables, memory,
and other resources with other threads that are part of the same process. In this section, I focus on
threads, but many concepts apply to processes as well.
370
Waiting for
notification
New Ready
Waiting for
interrupt()
I/O or lock
Waiting for
rendezvous
Dead Interrupted
Sleeping
interrupt()
/ throws InterruptedException
Figure 5-19: State diagram representing the lifecycle of Java threads. (State diagram notation is
defined in Section 3.2.2.)
So far, the objects carried their tasks sequentially, one after another, so in effect the whole system
consists of a single worker taking the guises one-by-one of different software objects. Threads
allow us to introduce true parallelism in the system functioning.
Subdividing a problem into smaller problems (subtasks) is a common strategy in problem solving.
It would be all well and easy if the subtasks were always disjoint, clearly partitioned, and
independent of each other. However, during the execution, the subtasks often operate on the
same resources or depend on results of other task(s). This is what makes concurrent programming
complex: threads (which roughly correspond to subtasks) interact with each other, and must
coordinate their activities to avoid incorrect results or undesired behaviors.
5.3.1 Threads
A thread is a sequence of processor instructions which can share a single address space with other
threads—that is, they can read and write the same program variables and data structures.
Threads are a way for a program to split itself into two or more concurrently running tasks. It is a
basic unit of program execution. A common use of threads is in reactive applications, having one
thread paying attention to the graphical user interface, while others do long calculations in the
background. As a result, the application more readily responds to user’s interaction.
Figure 5-19 summarizes different states that a thread may go through in its lifetime. The three
main states and their sub-states are “New”, “Alive”, and “Dead”. The “Alive” state entails many
different possible sub-states of its own.
371
1. New: The thread object has been created, but it has not been started yet, so it cannot run.
2. Alive: After a thread is started, it becomes alive, at which point it can enter several different
sub-states (depending on the method called or actions of other threads within the same
process):
a. Runnable: The thread can be run when the time-slicing mechanism has CPU cycles
available for the thread. In other words, when there is nothing to prevent it from being
run if the scheduler can arrange it.
b. Blocked: The thread could be run, but there is something that prevents it (e.g., another
thread is holding the resource needed for this thread to do its work). While a thread is in
the blocked state, the scheduler will simply skip over it and not give it any CPU time, so
the thread will not perform any operations. As visible from Figure 5-19, a thread can
become blocked for the following reasons:
i. Waiting for notification: Invoking the method wait() suspends the thread until the
thread gets the notify()or notifyAll()message.
ii. Waiting for I/O or lock: The thread is waiting for an input or output operation to
complete, or it is trying to call a synchronized method on a shared object, and that
object’s lock is not available.
iii. Waiting for rendezvous: Invoking the method join(target) suspends the thread until
the targetthread returns from its run()method.
iv. Sleeping: Invoking the method sleep(milliseconds) suspends the thread for the
specified time.
3. Dead: This normally happens to a thread when it returns from its run() method. A dead thread
cannot be restarted, i.e., it cannot become alive again.
Through the example in Section 5.3.4, we will further clarify the meaning of the states and the
events or method invocations that cause state transitions.
A thread object may appear as any other software object, but there are important differences.
Threads are not regular objects, so we have to be careful with their interaction with other objects.
Most importantly, we cannot just call a method on a thread object, because that would execute
the given method from our current thread—neglecting the thread of the method’s object—which
could lead to conflict. To pass a message from one thread to another, we must use only the
methods shown in Figure 5-19. No other methods on thread objects should be invoked.
If two or more threads compete for the same “resource” which can be used by only one at a time,
then their access must be serialized, as depicted in Figure 5-20. One of them becomes blocked
while the other proceeds. We are all familiar with conflicts arising from people sharing resources.
For example, people living in a house/apartment share the same bathroom. Many people may be
sharing the same public payphone. To avoid conflicts, people follow certain protocols--and
threads work similarly.
372
Thread 1 Shared Resource
Thread 2
Figure 5-20: Illustration of exclusion synchronization. The lock simply ensures that
concurrent accesses to the shared resource are serialized.
D
ID
ID
N
D
NI
IN
N
373
thrd1 : Thread shared_obj : Object thrd2 : Thread
A segment of code in which a thread may modify shared data is known as a critical section or
critical region. The critical-section problem is to design a protocol that threads can use to avoid
interfering with each other. Figure 5-21 demonstrates exclusion synchronization, also known as
mutual exclusion (mutex). This stops different threads from calling methods on the same object
at the same time. In doing this, it helps prevent jeopardizing the integrity of the shared data. If a
thread is executing in its critical region, then no other thread can be executing in its
critical region. Only one thread is allowed in a critical region at any moment.
Java provides exclusion synchronization through the keyword “synchronized”, which simply labels
a block of code that should be protected by locks. Instead of the programmer explicitly acquiring
or releasing the lock, synchronized signals tell the compiler to do so. As illustrated in Figure 5-22,
there are two ways to use the keyword synchronized. One technique declares class methods
synchronized; see Figure 5-22(a). If one thread invokes a synchronized method on an object, that
object is locked. Until the lock is released, another thread invoking this or another synchronized
method on that same object will be blocked until the lock is released.
Nesting method invocations are handled exactly how you might guess. Imagine that a
synchronized method is invoked on an object that is already locked by that same thread. In this
case, the method returns, but the lock is not released until the outermost synchronized method
returns.
The second technique designates a statement or a block of code as synchronized. The
parenthesized expression must produce an object to lock—usually, an object reference. In the
simplest case, it could be this reference to the current object, like so:
374
Figure 5-22: Exclusion synchronization in Java: (a) synchronized methods, and
(b) synchronized statements.
375
5.3.3 Cooperation between Threads: Condition
Synchronization
Exclusion synchronization ensures that threads do not step on each other’s toes, other than for
collision prevention purposes. However, sometimes one thread’s work depends on activities of
another thread, so they must cooperate and coordinate. A classic example of cooperation
between threads is a Buffer object with methods put() and get(). A producer thread calls put() and
a consumer thread calls get(). The producer must wait if the buffer is full, and the consumer must
wait if it is empty. In both cases, threads wait for a condition to become fulfilled. Condition
synchronization includes no assumption that the wait will be brief; threads could wait indefinitely.
Condition synchronization (illustrated in Figure 5-23) complements exclusion synchronization. In
exclusion synchronization, a thread encountering an occupied shared resource becomes blocked
and waits until another thread is finished with using the resource. Conversely, in condition
synchronization, a thread encountering an unmet condition cannot continue holding the resource
on which condition is checked. It cannot just wait until the condition is met. If the thread did so,
then no other thread would have access to the resource, and the condition would never change.
The resource must be released so that another thread can affect it. The thread in question might
release the resource and periodically return to check it, but this would not be an efficient use of
processor cycles. Rather, the thread becomes blocked and does nothing while waiting until the
condition changes, at which point it must be explicitly notified of such changes.
In the buffer example, a producer thread t must first lock the buffer to check whether it is full. If
it is, t enters the “waiting for notification” state (see Figure 5-19). But while t is waiting for the
condition to change, the buffer must remain unlocked so consumers can empty it by calling get().
Because the waiting thread is blocked and inactive, it needs to be notified when it is ready to go.
Every software object in Java has the methods wait() and notify(), which makes sharing and
condition synchronization on every Java object possible, as explained next. The method wait() is
used for suspending threads that are waiting for a condition to change. When t finds the buffer
full, it calls wait(), which atomically releases the lock and suspends the thread (see Figure 5-23).
Saying that the thread suspension and lock release are atomic means that they happen together,
indivisibly from the application’s point of view. After some other thread notifies t that the buffer
may no longer be full, t regains the lock on the buffer and retests the condition.
The standard Java idiom for condition synchronization is the statement:
while (conditionIsNotMet) sharedObject.wait();
Such a wait-loop statement must be inside a synchronized method or block. Any attempt to invoke
the wait() or notify() methods from outside the synchronized code will throw
IllegalMonitorStateException. The above idiom states that the condition test should always be in
a loop—never assume that being woken up means that the condition has been met.
The wait loop blocks the calling thread, t, for as long as the condition is not met. By calling wait(),
t places itself in the shared object’s wait set and releases all its locks on that object. (It is of note
that standard Java implements an unordered “wait set” rather than an ordered “wait queue.”
Real-time specification for Java—RTSJ—corrects this somewhat.)
376
thrd1
thrd1 : Thread
Thread shared_obj
shared_obj :: Object
Object thrd2 :: Thread
Thread
loop
region acquire lock
Successful:
Checks condition
alt Condition
Condition is
is met
met
Does the work
release lock
[else]
[else] wait()
wait()
Atomically releases the
lock and waits blocked
transfer
transfer lock
lock release lock
Consider a thread that executes a synchronized method on an object o. Imagine that the thread
changes a condition that can affect one or more threads in o’s wait set. In this scenario, the thread
must notify those other threads. In standard Java, the call o.notify() reactivates one arbitrarily
chosen thread, t, in o’s wait set. The reactivated thread then reevaluates the condition, and either
proceeds into the critical region or reenters the wait set. The call to o.notifyAll() releases all
threads in the o’s wait set. In standard Java, this is the only way to ensure that the highest priority
thread is reactivated. This is inefficient, though, because all the threads must attempt access while
only one will succeed in acquiring the lock and proceed.
The reader might have noticed resemblance between the above wait/notify mechanism and the
publish/subscribe pattern of Section 5.1. In fact, they are equivalent conceptually, but there are
some differences due to the concurrent nature of condition synchronization.
377
Arrival Service
User 1
Time
Service
Arrival Waiting
User 2
Let’s begin. The first-round implementation in Section 2.7 considered the system to have a single
door lock. We have not yet tackled the architectural issue of running a centralized or a distributed
system. In the centralized case, the main computer runs the system, and at the locks we have only
embedded processors. We could add an extra serial port, daisy-chained with the other one, and
the control would remain as it is in the first-round solution. In the latter case of a distributed
system, each lock would have a proximal embedded computer. The embedded computers would
communicate mutually or with the main computer using a local area network. The main computer
may not even be necessary--the embedded processors could coordinate in a “peer-to-peer”
mode. Assume for now that we implement the centralized PC solution with multiple serial ports.
We also assume a single photosensor and a single light bulb, for the sake of simplicity.
The first questions to answer are:, 1.) how many threads do we need, and 2.) which objects should
be turned into threads? Generally, it is not a good idea to add threads indiscriminately, because
threads consume system resources, such as computing cycles and memory space.
It may appear tempting to attach a thread to each object that operates physical devices, such as
LockCtrl and LightCtrl, but is this the right approach? Could we get away with only two threads,
since there are only two users (interacting with the two locks)?
Let us roll back and see why we consider introducing threads in the first place. The reason is to
improve the user experience, so two users at two different doors can access the home
simultaneously, without waiting. Two completely independent threads would work, which would
378
require duplicating all the resources, but this may be wasteful. Here is the list of resources they
could share:
• KeyStorage, used to lookup the valid keys
• Serial port(s), to communicate with the devices
• System state, such as the device status or current count of unsuccessful attempts
379
Sharing KeyStorage seems reasonable—here it is just looked up, not modified. If we assume that
the communication follows a well-defined protocol, then the serial port can also be shared.
However, sharing the system state needs to be carefully examined. Sharing the current
count of unsuccessful attempts seems to make no sense—there must be two counters, each
counting accesses for its corresponding door.
There are several observations that guide our design. From the system sequence diagram of
Figure 2-15(a), we can observe that the system juggles two distinct tasks: user interaction and
internal processing. Internal processing includes controlling the devices. Since there are two
doors, there are two copies of each task, which should be able to run in parallel.
See Figure 2-27 for the Controller object. This object is the natural point of separation between
the two tasks. It is the entry point of the domain layer of the system. The Controller is a natural
candidate for a thread object, so two internal processing tasks can run in parallel, possibly sharing
some resources.
The threaded controller class, ControllerThd, is defined below. I assume that all objects operating
the devices (LockCtrl, LightCtrl, etc.) can be shared as long as the method which writes to the
serial port is synchronized. LockCtrl must also know which lock (front or backyard) it currently
operates.
380
Listing 5-6: Concurrent version of the main class for SM A RT home access control.
Compare to Listing 2-1.
import javax.comm.*;
import java.io.IOException; import
java.io.InputStream;
import java.util.TooManyListenersException;
try {
ctrlPort.addEventListener(this);
} catch (TooManyListenersException e) {
381
e.printStackTrace(); // limited to one listener per port
}
start(); // start the serial-port reader thread
}
try {
while (inputStream_.available() > 0) {
int numBytes = inputStream_.read(readBuffer);
// could chk if numBytes == 2 (char + lockId) ...
}
} catch (IOException e) { e.printStackTrace(); }
382
if (keyBack_.length() >= keyCodeLen_) {
// awaken the Back door Controller keyBack_.notify();
}
} // END OF THE CRITICAL REGION
} // else, exception ?!
}
}
}
Each Controller object is a thread, and it synchronizes with HomeAccessControlSystem_2x via the
corresponding user key. In the above method serialEvent(), the port reader thread fills in the key
code until completed. Thereafter, it ignores the new keys until the corresponding ControllerThd
processes the key and resets it in its run() method. This is shown below.Observe the reuse of a
StringBuffer to repeatedly build strings. This works here, but in a general case, many subtleties of
Java StringBuffers should be considered.
Listing 5-7: Concurrent version of the Controller class. Compare to Listing 2-2.
import javax.comm.*;
public ControllerThd(
KeyChecker kc, LockCtrl lkc, LightCtrl lic, PhotoObsrv sns, AlarmCtrl ac,
StringBuffer key
){
checker_ = kc;
lockCtrl_ = lkc; alarmCtrl_ = ac;
lightCtrl_ = lic; sensor_ = sns; key_ = key;
383
}
}
} // END OF THE CRITICAL REGION
// Got the key, check its validity:
// First duplicate the key buffer, then release old copy Key user_key = new Key(new
String(key_)); key_.setLength(0); // delete code, so new can be entered
checker_.checkKey(user_key); // assume Publish-Subs. vers.
}
}
}
Observe the thread coordination in the above code. We do not want the Controller to grab a half-
ready Key and pass it to the Checker for validation. The Controller will do so only when notify() is
invoked. Once it is done with the key, the Controller resets it to allow the reader to fill it again.
You may wonder how-- in ControllerThd.run()---we obtain the lock and then loop until the key is
completely typed in. Would this not exclude the port reader thread from the access to the Key
object, so the key would never be completed?! Recall that wait() atomically releases the lock and
suspends the ControllerThd thread, leaving Key accessible to the port reader thread.
It is interesting to consider the last three lines of code in ControllerThd.run(). Copying a
StringBuffer to a new String is a thread-safe operation; so is setting the length of a StringBuffer.
However, although each of these methods acquires the lock, the lock is released in between--and
another thread may grab the key object and in some way compromise it. In our particular case,
this will not happen, because HomeAccessControlSystem_2x.serialEvent() checks the length of
the key before modifying it. Generally, however, this tends to be a concern.
Figure 5-25 summarizes the benefit of a multithreaded solution. Notice that there still may be
micro periods of waiting for both users. Servicing the user who arrived first may also take longer
than in a single-threaded solution. However, the average service time per user is much shorter—
it is close to the single-user average service time.
• Lock splitting: “Split lock” refers to a low-level memory bus lock that a processor may take for a memory
range that steps over a cache line.
• Finer-grain locks: This refers to using the minimum possible area--for example, using synchronized
statements instead of synchronized methods.
384
Threads are an important and deep part of computer programming and operating systems study. You are
encouraged to read more about them online if you wish. It may also be worth reading about race conditions,
which are a type of situation definitely worth avoiding. They take place when a device/system tries to carry out
multiple operations simultaneously, but these operations must actually be carried out in a certain order to be
done properly. Below is TechTarget’s informative page on the subject:
https://www.techtarget.com/searchstorage/definition/race-condition
385
Arrival Service
User 1
Time
(a)
Service
Arrival Waiting
User 2
Arrival Service
User 1
Time
(b) Waiting
Service
Arrival
User 2
Figure 5-25: Benefit of a multithreaded solution. (a) Sequential servicing, copied from Figure
5-24. (b) Parallel servicing by two threads.
Clearly, control of access to shared resources can lead to problems itself. Deadlock—or indefinitely stalled
progress--is one well-known example of such a problem.
“If computers get too powerful, we can organize them into a committee—that will do them in.”
—Bradley’s Bromide
Consider our case-study example of SMART home access control. Let’s assume that the tenants
want to remotely download the list of recent accesses. This requires network communication. The
most basic network programming uses network sockets, which can call for considerable
programming skills (see Appendix B for a brief review). To simplify distributed computing, a set of
software techniques have been developed. These generally go under the name of network
middleware.
When both client and server objects reside in the same memory space, the communication is
carried out by simple method calls on the server object (see Figure 1-22). If the objects reside in
different memory spaces or even on different machines, they need to implement the code for
interprocess communication, such as opening network connections, sending messages across the
network, and dealing with many other aspects of network communication protocols. This
386
significantly increases the complexity of objects. , And in addition to their business logic, objects
obtain responsibility for communication logic that is extraneous to their main function.
387
Figure 5-26: Client object invokes a method on a server object. If the message sending becomes
too complex, introducing middleware offloads the communication intricacies off the software
objects. (Compare with Figure 1-22.)
Employing middleware helps to delegate this complexity away from the objects (see Figure
5-26). A real world example of middleware is the post service—it deals with the intricacies of
delivering letters/packages to arbitrary distances. Another example is the use of different metric
systems, currencies, spoken languages, etc., in which case the functionality for conversion
between the systems is offloaded to middleware services. Middleware is a good software
engineering practice that should be applied any time the communication between objects
becomes complex and starts rivaling the object’s business logic in terms of the implementation
code size.
Middleware is a collection of objects that offer a set of services related to object communication,
so that extraneous functionality can be offloaded. In general, middleware is software used to
make diverse applications work together smoothly. The process of deriving middleware is
illustrated in Figure 5-27.
388
Figure 5-27: Middleware design. Objects A′ and B′ are the proxies of A and B, respectively.
We start by introducing the proxies for both communicating objects. (The Proxy pattern is
described in Section 5.2.4.) Consider B’, a proxy object of B. B′ acts to provide A with the illusion
that it is directly communicating with B. And—in the other direction—A’ provides the same
illusion for B. Having the proxy objects keeps the original objects simple, because the proxies
provide them with an illusion that they communicated directly with each other. Figure 5-27(a)
shows this. In this way, proxies provide location transparency for the objects—objects remain
(almost) the same whether they reside in the same or different address spaces / machines.
Objects A' and B' comprise the network middleware.
Because it is not likely that we will develop middleware for only two specific objects
communicating, further division of responsibilities results in the Broker pattern – discussed next.
389
(a)
Broker
Knowing Responsibilities:
• Registry of name-to-reference
mappings
(b)
Doing Responsibilities:
(c)
Figure 5-28: (a) The Broker component of middleware represents the intersection of common
proxy functions, along with middleware services. (b) Broker’s employee card. (c) The Broker
pattern class diagram. The server proxy, called Stub, resides on the same host as the client and
the client proxy, called Skeleton, resides on the same host as the server.
390
5.4.1 Broker Pattern
Take a look at Figure 5-28. The Broker pattern is an architectural pattern. It is used to structure
distributed software systems with components that interact by remote method calls. The proxies
are responsible only for the functions specific to the individual they substitute.
In a distributed system, the functions that are common to all/most of the proxies are delegated
to the Broker component (Figure 5-28b). Figure 5-28(c) shows the objects constituting the Broker
pattern and their relationships. The proxies act as representatives of their corresponding objects
in the foreign address space. The proxies contain all the network-related code. The broker
component is responsible for coordinating communication and providing links to various
middleware services. Although Broker is shown as a single class in Figure 5-28(c), actual brokers
consist of many classes and provide many services.
391
Figure 5-29: Sequence diagram for a client call to the server (remote method invocation).
To use a remote object, a client first finds the object through the Broker, which returns a proxy
object or Stub. As far as the client is concerned, the Stub appears and works like any other local
object, because they both implement the same interface. But, in reality, the broker only arranges
the method call and associated parameters into a stream of bytes using the method marshal().
Figure 5-29 shows the sequence diagram for remote method invocation (also called remote
procedure call— RPC) via a Broker. The Stub “marshals” the method call into a stream of bytes
and invokes the Broker. The Broker forwards the byte stream to the client’s proxy, Skeleton, at
the remote host. Upon receiving the byte stream, the Skeleton uses the method unmarshal() to
rearrange this stream into the original method call and associated parameters. It also invokes this
method on the server containing the actual implementation. Finally, the server performs the
requested operation and sends back any return value(s).
The Broker pattern has proven an effective tool in distributed computing, because it leads to
greater flexibility, maintainability, and adaptability of the resulting system. By introducing new
components and delegating responsibility for communication details, the system becomes
potentially distributable and scalable. Java Remote Method Invocation (RMI), which is presented
next, is an example of elaborating and implementing the Broker pattern.
392
5.4.2 Java Remote Method Invocation (RMI)
The above analysis indicates that the Broker component is common to all remotely
communicating objects, so it needs to be implemented only once. The proxies, however, are
object-specific and need to be implemented for every new object. Fortunately, the process of
implementing the proxy objects can be made automatic. It is important to observe that the
original object shares the interface with its Stub and Skeleton (even if the object acts as a client).
Given the object’s interface, there are tools to automatically generate source code for proxy
objects. In the general case, the interface is defined in an Interface Definition Language (IDL). Java
RMI also uses plain Java for interface definition. The basic steps for using RMI are:
1. Define the server object interface
2. Define a class that implements this interface
3. Create the server process
4. Create the client process
Using our case-study example of SMART-home access control, I will show how a tenant could
remotely connect and download the list of recent accesses.
This interface represents a contract between the server and its clients. Our interface must extend
java.rmi.Remote, which is a “tagging” interface that contains no methods. Any parameters of the
methods of our interface--such as the return type Vector, in our case--must be serializable (i.e.,
implement java.io.Serializable).
In the general case, an IDL compiler would generate a Stub and Skeleton files for the Informant
interface. With Java RMI, we just use the Java compiler, javac:
% javac Informant.java
393
Remote UnicastRemoteObject
Client Informant
Informant
Client
+ getAccessHistory()
+ getAccessHistory()
InformantImpl_Stub
InformantImpl_Stub InformantImpl
InformantImpl
RemoteRef
++ getAccessHistory()
getAccessHistory() ++ getAccessHistory()
getAccessHistory()
Figure 5-30: Class diagram for the Java RMI example. See text for details.
Listing 5-9: The class InformantImpl implements the actual remote server object.
/* file InformantImpl.java (Informant server implementation) */ import
java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject; import
java.util.Vector;
394
return result;
}
}
Here, we first use the Java compiler, javac, then the RMI compiler, rmic:
% javac InformantImpl.java
% rmic –v1.2 InformantImpl
The first statement compiles the Java file. The second generates the stub and skeleton proxies,
called InformantImpl_Stub.class and InformantImpl_Skel.class. It might appear strange that the
RMI compiler operates on a .class file, rather than on a source file. In JDK version 1.2 or higher
(Java 2), only the stub proxy is used; the skeleton is incorporated into the server itself, so that
there are no separate entities as skeletons. Server programs now communicate directly with the
remote reference layer. This is why—if you are working with JDK 1.2 or higher--the command line
option -v1.2 should be employed; so that only the stub file is generated.
As Figure 5-30 shows, the stub is associated with a RemoteRef, which is a class in the RMI Broker
that represents the handle for a remote object. The stub uses a remote reference to carry out a
remote method invocation to a remote object via the Broker. It is worth looking inside the
InformantImpl_Stub.java, which is obtained if the RMI compiler is run with the option -keep.
Below is the stub file. (The server proxy resides on client host.)
Listing 5-10: Proxy classes (Stub and Skeleton) are automatically generated by the Java
rmic compiler from the Informantinterface (Listing 5-8).
// Stub class generated by rmic, do not edit.
// Contents subject to change without notice.
1 public final class InformantImpl_Stub
2 extends java.rmi.server.RemoteStub
3 implements Informant, java.rmi.Remote
4{
5 private static final long serialVersionUID = 2;
6
7 private static java.lang.reflect.Method $method_getAccessHistory_0;
8
9 static {
10 try {
11 $method_getAccessHistory_0 = Informant.class.getMethod("getAccessHistory",
new java.lang.Class[] {long.class, long.class});
12 } catch (java.lang.NoSuchMethodException e) {
13 throw new java.lang.NoSuchMethodError(
14 "stub class initialization failed");
15 }
16 }
17
18 // constructors
19 public InformantImpl_Stub(java.rmi.server.RemoteRef ref) {
20 super(ref);
21 }
22
395
23 // methods from remote interfaces
24
25 // implementation of getAccessHistory(long, long)
26 public java.util.Vector getAccessHistory(long $param_long_1, long $param_long_2)
27 throws java.rmi.RemoteException
28 {
29 try {
30 Object $result = ref.invoke(this,
$method_getAccessHistory_0, new java.lang.Object[] {new java.lang.Long($param_long_1), new
java.lang.Long($param_long_2)}, - 7208692514216622197L);
31 return ((java.util.Vector) $result);
32 } catch (java.lang.RuntimeException e) {
33 throw e;
34 } catch (java.rmi.RemoteException e) {
35 throw e;
36 } catch (java.lang.Exception e) {
37 throw new java.rmi.UnexpectedException("undeclared checked exception", e);
38 }
39 }
40 }
396
Step 3: Create the server process
The server process instantiates object(s) of the above implementation class. This server accepts
remote requests. The first problem is: how does a client get handle of such an object to be able
to invoke a method on it? The solution is for the server to register the implementation objects
with a naming service known as a registry. A naming registry is like a telephone directory. The
RMI Registry is a simple name repository which acts as a central management point for Java RMI.
It is also an RMI server itself. The process of registration is called binding.
The registry must be run before the server and client processes, using the following command
line:
% rmiregistry
It can run on any host, including the server’s or client’s hosts, and there can be several RMI
registries running at the same time. For every server object, the registry contains a mapping
between the well-known object’s name and its reference. Thus, the client object can get handle
of a server object by looking up its name in the registry. The lookup is performed by supplying a
URL with protocol “rmi”:
rmi://host_name:port_number/object_name
Here, host_name is the name/IP address of the host on which the RMI registry is running.
port_number is the port number of the RMI registry. object_name is the name bound to the server
implementation object. If the host name is not provided, the default is assumed as localhost. The
default port number of RMI registry is 1099, although this can be changed as desired. The server
object, on the other hand, listens to a port on the server machine. This port is usually an
anonymous port that is chosen at runtime by the JVM or the underlying operating system.
Alternatively, you may request the server to listen on a specific port on the server machine.
397
Let’s use the class “HomeAccessControlSystem”--defined in Listing 2-1, Section 2.7--as the main
server class. The class remains the same with several modifications:
398
Lines 11–12: These create the string URL. The URL includes the object’s name, which will be
bound with its remote reference in the next step.
Line 13: This binds the object’s name to the remote reference at the RMI naming
registry.
Line 14: This displays a message for our information--so we know when the binding is
completed.
After compilation, the server is run on the computer located at home by invoking the Java
interpreter on the command line:
% java HomeAccessControlSystem
If Java 2 is used, the skeleton is not necessary. Otherwise, the skeleton
class(InformantImpl_Skel.class) must be located on the server’s host. This is because, although
not explicitly used by the server, the skeleton will be invoked by Java runtime.
400
The code description is as follows:
Line 4: Specifies the host name on which the RMI naming registry is running.
Line 8: Looks up the RMI naming registry to get handle of the server object. The lookup returns
a java.rmi.Remote reference, so this must be typecast into an Informant reference (not
InformantImpl reference).
Lines 11–12: Invoke the service method on the stub, which in turn invokes this method on the
remote server object. The result is returned as a java.util.Vector object named accessHistory.
Lines 14–18: Display the retrieved history list.
Lines 19–25: Handle possible communication exceptions.
401
5.5 Information Security
“There is nothing special about security; it’s just part of getting the job done.” —Rob Short
402
Padlock and
Content
shared key Shared
copy key copy
Message
Sender Intermediary Receiver
Figure 5-31 illustrates the problem of transmitting a confidential message by analogy with
transporting a physical document via untrusted carrier. The figure also lists the security needs of
the communicating parties and the potential threats posed by intruders. The sender secures the
briefcase, and then sends it on. The receiver must use a correct key in order to unlock the briefcase
and read the document. Analogously, a sending computer encrypts the original data using an
encryption algorithm to make it unintelligible to any intruder. The data in the original form is
known as plaintext or cleartext. The encrypted message is known as ciphertext. Without a
corresponding “decoder,” the transmitted information (ciphertext) would remain scrambled and
be unusable. The receiving computer must regenerate the original plaintext from the ciphertext
with the correct decryption algorithm in order to read it. This pair of data transformations,
encryption and decryption, together forms a cryptosystem.
There are two basic types of cryptosystems: (i) symmetric systems, where both parties use the
same (secret) key in encryption and decryption transformations; and, (ii) public-key systems, also
known as asymmetric systems, where the parties use two related keys, one of which is secret and
the other one can be publicly disclosed. I first review the logistics of how the two types of
cryptosystems work, while leaving the details of encryption algorithms for the next section.
403
Sender’s Receiver’s
padlock padlock
Sender Receiver
2. Receiver additionally secures
the briefcase with his/her
padlock and returns
Figure 5-32: Secure transmission via untrustworthy carrier. Note that both sender and
receiver keep their own keys with them all the time—the keys are never exchanged.
practical purposes. A trapdoor function in this sense is not a very practical code, because
404
the legitimate user finds it just as hard to decode the message as the adversary does. The final
twist is
405
Receiver distributes his/her padlock (unlocked) to
“Public key” “Private key”
sender ahead of time, but keeps the key
Receiver’s key
Receiver’s padlock Receiver
(unlocked)
Sender
Receiver removes his/her
padlock to access the content
Figure 5-33: Public-key cryptosystem simplifies the procedure from Figure 5-32.
to define f in such a way that a single extra piece of information makes the computation of f −1
easy. This is the only bit of information that must be kept secret.
406
Symmetric Cryptography
The Advanced Encryption Standard has a fixed block size of 128 bits and a key size of 128, 192,
and 256 bits.
Public-Key Cryptography
As stated above, f is a trapdoor function if f is easy to compute, f −1 is very hard or
but
impossible for practical purposes. An example of such difficulty is factorizing a given number n
into prime numbers. An encryption algorithm that depends on this was invented by Ron Rivest,
Adi Shamir, and Leonard Adelman (RSA system) in 1978. Another example algorithm, designed by
Taher El Gamal in 1984, depends on the difficulty of the discrete logarithm problem.
In the RSA system, the receiver does as follows:
1. Randomly select two large prime numbers p and q, which always must be kept secret.
2. Select an integer number E, known as the public exponent, such that (p − 1) and E have
no common divisors, and (q − 1) and E have no common divisors.
3. Determine the product n = p⋅q, known as public modulus.
4. Determine the private exponent, D, such that (E⋅D − 1) is exactly divisible by both (p − 1)
and (q − 1). In other words, given E, we choose D such that the integer remainder when
E⋅D is divided by (p − 1)⋅(q − 1) is 1.
5. Release publicly the public key, which is the pair of numbers n and E, K + = (n, E). Keep
secret the private key, K − = (n, D).
Because a digital message is a sequence of digits, break it into blocks which, when considered as
numbers, are each less than n. Then it is enough to encode block by block.
Encryption works so that the sender substitutes each plaintext block B by the ciphertext C = BE %
n, where % symbolizes the modulus operation. (The modulus of two integer numbers x and y,
denoted as x % y, is the integer remainder when x is divided by y.)
Then the encrypted message C (ciphertext) is transmitted. To decode, the receiver uses the
decoding key D, to compute B = CD % n, that is, to obtain the plaintext B from the ciphertext C.
Now, suppose that the sender wants to send the plaintext “hello world.” The following table shows the
encoding of “hello.” I assign to letters a numeric representation, so that a=1, b=2, …, y=25, and z=26,
and I assume that block B is one letter long. In an actual implementation, letters are represented as
binary numbers, and the blocks B are not necessarily aligned with letters, so that plaintext “l” will not
always be represented as ciphertext “12.”
407
Plaintext numeric
Plaintext letter BE Ciphertext BE % n
representation
h 8 85 = 32768 85 % 35 = 8
e 5 55 = 3125 55 % 35 = 10
l 12 125 = 248832 125 % 35 = 17
l 12 248832 17
o 15 155 = 759375 155 % 35 = 15
The sender transmits this ciphertext to the receiver: 8, 10, 17, 17, 15. Upon the receipt, the
receiver decrypts the message as shown in the following table.
Plaintext
Ciphertext CD B = CD % n letter
8 829 = 154742504910672534362390528 829 % 35 = 8 h
10 100000000000000000000000000000 5 e
17 48196857210675091509141182522307169 12 l
7
17 48196857210675091509141182522307169 12 l
7
15 12783403948858939111232757568359375 15 o
We can see that even this toy example produces some extremely large numbers.
The point is that while the adversary knows n and E, he or she does not know p and q, so they
cannot work out (p − 1)⋅(q − 1) and thereby find D. The designer of the code, on the other hand,
knows p and q because those are what he started from. So does any legitimate receiver: the
designer will have told them. The security of this system depends on exactly one thing: the
notoriously difficulty of factorizing a given number n into primes. For example, given n = 267 − 1 it
took three years working on Sundays for F. N. Cole to find by hand in 1903 p and q for n = p⋅q
= 193707721 × 761838257287. It would be waste of time (and often combinatorially self-
defeating) for the program to grind through all possible options.
Encryption strength is measured by the length of its “key,” which is expressed in bits. The larger
the key, the greater the strength of the encryption. Using 112 computers, a graduate student
decrypted one 40-bit encrypted message in a little over 7 days. In contrast, data encrypted with a
128-bit key is 309,485,009,821,345,068,724,781,056 times stronger than data encrypted with a
40-bit key. RSA Laboratories recommends that the product of p and q be on the order of 1024
bits long for corporate use and 768 bits for use with less valuable information.
5.5.3 Authentication
408
Windows did not even provide something as simple as maintain memory space separation. Once
on a computer, a virus is executed when its carrier file is “opened” in some meaningful way by
software on that system. When the virus executes, it does something unwanted, such as causing
software on the host system to send more copies of infected files to other computers over the
network, infecting more files, and so on. In other words, a virus typically maximizes its likelihood
of being passed on, making itself contagious.
Viral behavior relies on security vulnerabilities that exist in software running on the host system.
For example, in the past, viruses have often exploited security vulnerabilities in Microsoft Office
macro scripting capabilities. Macro viruses are no longer among the most common virus types.
Many viruses take advantage of Trident, the rendering engine behind Internet Explorer and
Windows Explorer that is also used by almost every piece of Microsoft software. Windows viruses
often take advantage of image-rendering libraries, SQL Server’s underlying database engine, and
other components of a complete Windows operating system environment as well.
Viruses are typically addressed by antivirus software vendors. These vendors produce virus
definitions used by their antivirus software to recognize viruses on the system. Once a specific
virus is detected, the software attempts to quarantine or remove the virus—or at least inform the
user of the infection so that some action may be taken to protect the system from the virus.
This method of protection relies on knowledge of the existence of a virus, however, which means
that most of the time a virus against which you are protected has, by definition, already infected
someone else’s computer and done its damage. The question you should be asking yourself at this
point is how long it will be until you are the lucky soul who gets to be the discoverer of a new virus
by way of getting infected by it.
It’s worse than that, though. Each virus exploits a vulnerability — but they don’t all have to exploit
different vulnerabilities. In fact, it’s common for hundreds or even thousands of viruses to be
circulating “in the wild” that, between them, only exploit a handful of vulnerabilities. This is
because the vulnerabilities exist in the software and are not addressed by virus definitions
produced by antivirus software vendors.
These antivirus software vendors’ definitions match the signature of a given virus — and if they’re
really well-designed might even match similar, but slightly altered, variations on the virus design.
Sufficiently modified viruses that exploit the same vulnerability are safe from recognition through
the use of virus definitions, however. You can have a photo of a known bank robber on the cork
bulletin board at the bank so your tellers will be able to recognize him if he comes in — but that
won’t change the fact that if his modus operandi is effective, others can use the same tactics to
steal a lot of money.
By the same principle, another virus can exploit the same vulnerability without being recognized
by a virus definition, as long as the vulnerability itself isn’t addressed by the vendor of the
vulnerable software. This is a key difference between open source operating system projects and
Microsoft Windows: Microsoft leaves dealing with viruses to the antivirus software vendors, but
open source operating system projects generally fix such vulnerabilities immediately when
they’re discovered.
Thus, the main reason you don’t tend to need antivirus software on an open source system, unless
running a mail server or other software that relays potentially virus-laden files between other
systems, isn’t that nobody’s targeting your open source OS; it’s that any time someone targets it,
409
chances are good that the vulnerability the virus attempts to exploit has been closed up — even
if it’s a brand-new virus that nobody has ever seen before. Any half-baked script-kiddie has the
potential to produce a new virus that will slip past antivirus software vendor virus definitions, but
in the open source software world one tends to need to discover a whole new vulnerability to
exploit before the “good guys” discover and patch it.
Viruses need not simply be a “fact of life” for anyone using a computer. Antivirus software is
basically just a dirty hack used to fill a gap in your system’s defenses left by the negligence of
software vendors who are unwilling to invest the resources to correct certain classes of security
vulnerabilities.
The truth about viruses is simple, but it’s not pleasant. The truth is that you’re being taken to the
cleaners — and until enough software users realize this, and do something about it, the software
vendors will continue to leave you in this vulnerable state where additional money must be paid
regularly to achieve what protection you can get from a dirty hack that simply isn’t as effective as
solving the problem at the source would be.
Design patterns are heuristics for structuring the software modules and their interactions that are
proven in practice. They yield in design for change, so the change of the computing environment
has as minimal and as local effect on the code as possible.
Key Points:
• Pattern use must be need-driven: use a pattern only when you need it to improve your
software design, not because it can be used, or you simply like hitting nails with your new
hammer.
410
• Using the Broker pattern, a client object invokes methods of a remote server object,
passing arguments and receiving a return value with each call, using syntax similar to local
method calls. Each side requires a proxy that interacts with the system’s runtime.
There are many known design patterns and I have reviewed above only few of the major ones.
The text that most contributed to the popularity of patterns is [Gamma et al., 1995]. Many books
are available, perhaps the best known are [Gamma et al., 1995] and [Buschmann et al., 1996].
The reader can also find a great amount of useful information on the web. In particular, a great
deal of information is available in Hillside.net’s Patterns Library: http://hillside.net/patterns/ .
R. J. Wirfs-Brock, “Refreshing patterns,” IEEE Software, vol. 23, no. 3, pp. 45-47, May/June 2006.
411
Although Java RMI works only if both client and server processes are coded in the Java
programming language, there are other systems, such as CORBA (Common Object Request Broker
Architecture), which work with arbitrary programming languages, including Java. A readable
appraisal of the state of affairs with CORBA is available in [Henning, 2006].
412
Class iaik.security.dh.DHKeyAgreement
http://www.cs.utexas.edu/users/chris/cs378/f98/resources/iaikdocs/iaik.security.dh.DHKeyAgree
ment.html
Bill Steele, “‘Fabric’ would tighten the weave of online security,” Cornell Chronicle (09/30/10):
Fabric’s programming language, which is based on Java, builds in security as the program is
written. Myers says most of what Fabric does is transparent to the programmer.
http://www.news.cornell.edu/stories/Sept10/Fabric.html
P. Dourish, R. E. Grinter, J. Delgado de la Flor, and M. Joseph, “Security in the wild: user strategies
for managing security as an everyday, practical problem,” Personal and Ubiquitous Computing
(ACM/Springer), vol. 8, no. 6, pp. 391-401, November 2004.
M. J. Ranum, “Security: The root of the problem,” ACM Queue (Special Issue: Surviving Network
Attacks), vol. 2, no. 4, pp. 44-49, June 2004.
H. H. Thompson and R. Ford, “Perfect storm: The insider, naivety, and hostility,” ACM Queue
(Special Issue: Surviving Network Attacks), vol. 2, no. 4, pp. 58-65, June 2004.
introducing trust and its pervasiveness in information technology
Microsoft offers integrated hardware-level security such as data execution prevention,
kernel patch protection and its free Security Essentials software:
http://www.microsoft.com/security_essentials/
Microsoft's 'PassPort' Out, Federation Services In
In 2004 Microsoft issued any official pronouncements on "TrustBridge," its collection of federated
identity-management technologies slated to go head-to-head with competing technologies
backed by the Liberty Alliance.
http://www.eweek.com/c/a/Windows/Microsofts-Passport-Out-Federated-Services-In/
Practice Problems
Problem 5.1
Problem 5.2
Consider the online auction site described in Problem 2.31 (Chapter 2). Suppose you want to
employ the Publish-Subscribe (also known as Observer) design pattern in your design solution for
Problem 2.31. Which classes should implement the Publisher interface? Which classes should
413
implement the Subscriber interface? Explain your answer. (Note: You can introduce new classes
or additional methods on the existing classes if you feel it necessary for solution.)
Problem 5.3
In the patient-monitoring scenario of Problem 2.35 (Chapter 2), assume that multiple recipients
must be notified about the patient condition. Suppose that your software is to use the Publish-
Subscribe design pattern. Identify the key software objects and draw a UML interaction diagram
to represent how software objects in the system could accomplish the notification problem.
Problem 5.4
Problem 5.5
Consider the elevator control problem defined in Problem 3.7 (Chapter 3). Your task is
to determine whether the Publisher-Subscriber design pattern can be applied in this
design. Explain clearly your answer. If the answer is yes, identify which classes are
suitable for the publisher role and which ones are suitable for the subscriber role.
Explain your choices, list the events generated by the Publishers, and state explicitly for
each Subscriber to which events it is subscribed to.
Problem 5.7
Problem 5.8
Problem 5.9
Consider the automatic patient monitoring system described in Problem 2.35. Carefully
examine the draft UML sequence diagram in Figure 2-45. Check if the given design already
uses some patterns and explain your claim. Identify as many opportunities as you can to
improve the design by applying design patterns. Consider how an unnecessary application
of some design patterns would make this design worse. Draw UML sequence
diagrams or write pseudo-code to describe the proposed design. Always describe your motivation
for adopting or rejecting design modifications.
414
Problem 5.10
Consider the system for inventory management grocery supermarket from Problem 2.15. Suppose
you are provided with an initial software design as follows. This design is based on a basic version
of the inventory system, but the reader should be aware of extensions that are discussed in the
solution of Problem 2.15(c) and Problem 2.16. The software consists of the following classes:
ReaderIface:
This class receives messages from RFID readers that specific tags moved in or out of
coverage.
DBaseConn:
This class provides a connection to a relational database that contains data about shelf
stock and inventory tasks. The database contains several tables, including
ProductsInfo[key = tagID], PendingTasks[key = userID], CompletedTasks, and
Statistics[key = infoType] for various information types, such as the count of erroneous
messages from RFID readers and the count of reminders sent for individual pending tasks.
Dispatcher:
This class manages inventory tasks by opening new tasks when needed and
generates notifications to the concerned store employees.
Monitor:
This class periodically keeps track of potentially overdue tasks. It retrieves the list of
pending tasks from the database and generates reminders to the concerned store
employees.
Messenger:
This class sends email notifications to the concerned store employees. (The notifications
are generated by other classes.)
Assume that email notifications are used as a supplementary tool, but the system must keep an
internal record of sent notifications and pending tasks, so it can take appropriate actions.
Notice that the current design has a single timer for the whole system. The software designer
noticed that sending notifications for overdue tasks does not need to be exactly timed in this
system. Delays up to a certain period (e.g., hour or even day) are tolerable. Maintaining many
timers would be overkill and would significantly slow down the system. It would not be able to do
important activities, such as processing RFID events in a timely fashion. Therefore, the software
is designed so that, when a new pending task is created, there is no explicit activation of an
associated timer. Instead, the task is simply added to the list of pending tasks. The Monitor object
periodically retrieves this list and checks for overdue tasks, as seen below in the design for the
use case UC-5 SendReminder.
Another simplification is to check only for “out-of-stock” events and not for “low-stock” events. If
the customer demands that “low-stock” events be included, then the design of the software-to-
be will become somewhat more complex.
415
The UML sequence diagrams for all the use cases are shown in the following figures. Notice that
use cases UC-3, UC-4, and UC-6 «include» UC-7: Login (user authentication), which is not shown
to avoid clutter.
UC1: RemoveItem
rfid :
ReaderIface : Messenger : Dispatcher : DBaseConn
receive(event)
prodInfo := getProductInfo( tagID )
return
decrement( prodCount )
return
[prodCount ≤ Threshold]
recordProductInfo( updated product count )
createTask( "out-of-stock" )
recordPendingTask( task info )
[else]
recordProductInfo( updated product count )
return
In the design for UC-1, the system may check if a pending task for the given product already
exists in the database; if yes, it should not generate a new pending task for the same product.
416
UC2: AddItem
rfid :
ReaderIface : DBaseConn
receive(event)
prodInfo := getProductInfo( tagID )
[else]
increment( prodCount )
return
UC3: ViewPendingWork
user interface : : Dispatcher : DBaseConn
view pending
getPendingTasks( userID )
[userID == associate]
tasksList := getPendingTasks( associateID )
[else]
return error
show error
return tasksList
show tasks
Extension:
ref Store Manager may
optionally run UC4
UC4: AssignReplenishTask
to assign a pending task
417
UC4: AssignReplenishTask
user interface : : Dispatcher : Messenger : DBaseConn
view pending
assign task
assignTask( taskID, associateID, … )
taskInfo := getPendingTaskInfo( taskID )
removePendingTask( taskID )
Pending Task Info:
Task-type = "replenish-shelf"
Assigned-time = current time
Assigned-to = Store Associate send( "alert: task assigned" )
return result
show result
The [seq] interaction fragment specifies that the interactions contained within the fragment box
must occur exactly in the given order. The reason for using this constraint in UC-4 is that the
system may crash while the task is being converted from unassigned to pending. If
removePendingTask() were called first, and the system crashed after it but before
recordPendingTask(), then all information about this task would be lost! Depending on the
database implementation, it may be possible to perform these operations as atomic for they
update the same table in the database. To deal with crash scenarios where a task ends up in both
tables, the Monitor object in UC-5 SendReminder should be extended to perform a database
clean-up after a crash. It should remove those tasks from the PendingTasks table that are marked
both as unassigned and pending.
418
UC5: SendReminder
: Monitor : Dispatcher : Messenger : DBaseConn
wakeup
pendingList := getPendingTasks( )
Note that the Monitor discards the list of pending tasks before going to sleep, so it starts every
cycle with a fresh list of pending tasks, retrieved from the database, because our assumption is
that the database contains the most current information (possibly updated by other objects).
By examining the design for the use case UC-5 SendReminder, we see that the Monitor has to do
a lot of subtractions and comparisons every time it wakes up, but this can be done at leisure
because seconds or minutes are not critical for this activity. The computing power should be
better used for other use cases. Of course, we must ensure that the Monitor still works fast
enough not to introduce delays on the order of hours or days during each cycle!
In addition, we need to handle the case where the time values are not incrementing constantly
upwards, such as when a full 24 hours passes and a new day starts, the time resets to zero. In
Java, using java.lang.System.currentTimeMillis() returns the current time in milliseconds as a long
integer.
419
In the solution of Problem 2.16 we discussed using adaptive timeout calculation to adjust the
frequency of reminders for busy periods. Another option is to have shorter sleep periods, but
during each wakeup, process only part of the list of pending tasks, and leave the rest for
subsequent wakeups. Then cycle again from the head of the list. This way, the reminders will be
spread over time and not all reminders will be generated at once (avoid generating one “bulk”
notification each period).
UC6: ReplenishCompleted
: Dispatcher : Messenger : DBaseConn
close(taskID)
taskInfo := getPendingTask( taskID )
return error
return error
[else]
removePendingTask( taskID )
return
The logic of UC-6 is that it first retrieves the task, checks if such a task exists, makes sure it is really
done, and finally marks it as completed. The [seq] interaction fragment specifies that the
interactions contained within the fragment box must occur exactly in the given order. Similar to
UC-4 AssignReplenishTask, this constraint is needed in case the system crashes while the task is
being closed. If removePendingTask() were called first, and the system crashed after it but before
recordCompletedTask(), then all information about this task would be lost! These operations
cannot be performed as atomic, because they work on different tables in the database. To deal
with crash scenarios where a task ends up in both tables, the Monitor object in UC-5 should be
modified to perform a database clean-up after a crash. It should remove those tasks from the
PendingTaskstable that are already in the CompletedTaskstable.
420
Notice also that the Monitor runs in a separate thread, so while UC-6 is in the process of closing a
task, the Monitor may send an unnecessary reminder about this task (in UC-5).
arefully examine the existing design and identify as many opportunities as you can to
C improve the design by applying design patterns. Note that the existing design ignores the
issue of concurrency, but we will leave the multithreading issue aside for now and focus only on
the patterns that improve the quality of software design. (The concurrency issues will
be
considered later in Problem 5.20.)
(a) If you introduce a pattern, first provide arguments why the existing design may
be problematic.
(b) Provide as much details as possible about how the pattern will be implemented and how the
new design will work (draw UML sequence diagrams or write pseudo-code).
(c) Explain how the pattern improved the design (i.e., what are the expected benefits compared
to the original design).
If considering future evolution and extensions of the system when proposing a modification, then
describe explicitly what new features will likely be added and how the existing design would be
inadequate to cope with resulting changes. Then introduce a design pattern and explain how the
modified version is better.
If you believe that the existing design (or some parts of it) is sufficiently good then explain how
the application of some design patterns would make the design worse. Use concrete examples
and UML diagrams or pseudo-code to illustrate and refer to specific qualities of software design.
Problem 5.11
Problem 5.12
Problem 5.13
Problem 5.14
Problem 5.15
In Section 5.3, it was stated that the standard Java idiom for condition synchronization is
the statement:
while (condition) sharedObject.wait();
(a) Is it correct to substitute the yield()method call for wait()? Explain your answer and
discuss any issues arising from the substitution.
421
(b) Suppose that ifsubstitutes for while, so we have:
if (condition) sharedObject.wait()
Is this correct? Explain your answer.
Problem 5.16
Parking lot occupancy monitoring, see Figure 5-34. Consider a parking lot with the total number
of spaces equal to capacity. There is a single barrier gate with two poles, one for the entrance and
the other for the exit. A computer in the barrier gate runs a single program which controls both
poles. The program counts the current number of free spaces, denoted by occupancy, such that
0 ≤ occupancy≤ capacity
When a new car enters, the occupancy is incremented by one; conversely, when a car exits, the
occupancy is decremented by one. If occupancy equals capacity, the red light should turn on to
indicate that the parking is full.
In order to be able to serve an entering and an exiting patron in parallel, you should design a
system which runs in two threads. EnterThread controls the entrance gate and ExitThread controls
the exit gate. The threads share the occupancy counter so to correctly indicate the parking-full
state. Complete the UML sequence diagram in Figure 5-35 that shows how the two threads
update the shared variable, i.e., occupancy.
422
: SharedState
Figure 5-35: UML diagram template for parking lot occupancy monitoring, Problem 5.16.
Cook 2
Pickup Waiter
Supplier counter
Egg tray
Cook 1
Hint: Your key concern is to maintain the consistent shared state (occupancy) and indicate when
the parking-full sign should be posted. Extraneous actions, such as issuing the ticket for an
entering patron and processing the payment for an exiting patron, should not be paid attention—
only make a high-level remark where appropriate.
Problem 5.17
Consider a restaurant scenario shown in Figure 5-36. You are to write a simulation in Java such
that each person runs in a different thread. Assume that each person takes different amount of
time to complete their task. The egg tray and the pickup counter have limited capacities, Neggs and
Nplates, respectively. The supplier stocks the egg tray but must wait if there are no free slots.
Likewise, the cooks must hold the prepared meal if the pickup counter is full.
Problem 5.18
A priority inversion occurs when a higher-priority thread is waiting for a lower-priority thread to
finish processing of a critical region that is shared by both. Although higher-priority threads
normally preempt lower-priority threads, this is not possible when both share the same critical
region. While the higher-priority thread is waiting, a third thread, whose priority is between the
first two, but it does not share the critical region, preempts the low-priority thread. Now
the
423
higher-priority thread is waiting for more than one lower-priority thread. Search the literature
and describe precisely a possible mechanism to avoid priority inversion.
Problem 5.19
Assume that the patient device described in Problem 2.3 () runs in a multi- threaded mode, where
different threads acquire and process data from different sensors. (See also Problem 2.35 and its
solution on the back of this book.) What do you believe is the optimal number of threads? When
designing this system, what kind of race conditions or other concurrency issues can you think of?
Propose a specific solution for each issue that you identify (draw UML sequence diagrams or write
pseudo-code).
Problem 5.20
Consider the supermarket inventory management system from Problem 5.10. A first observation
is that the existing design ignores the issue of concurrency—there will be many users
simultaneously removing items, and/or several associates may be simultaneously restocking the
shelves. Also, it is possible that several employees may simultaneously wish to view pending tasks,
assign replenishment tasks, or report replenishment completed. Clearly, it is necessary to
introduce multithreading even if the present system will never be extended with new features.
Modify the existing design and introduce multithreading.
Problem 5.21
Problem 5.22
Use Java RMI to implement a distributed Publisher-Subscriber design pattern.
Requirements: The publisher and subscribers are to be run on different machines. The naming
server should be used for rendezvous only; after the first query to the naming server, the publisher
should cache the contact information locally.
Handle sudden (unannounced) departures of subscribers by implementing a heartbeat protocol.
Problem 5.23
Suppose that you are designing an online grocery store. The only supported payment method will
be using credit cards. The information exchanges between the parties are shown in Figure 5-37.
After making the selection of items for purchase, the customer will be prompted to enter
information about his/her credit card account. The grocery store (merchant) should obtain this
information and relay it to the bank for the transaction authorization.
In order to provide secure communication, you should design a public-key cryptosystem as
follows. All messages between the involved parties must be encrypted for confidentiality, so that
only the appropriate parties can read the messages. Even the information about the purchased
items, payment amount, and the outcome of credit-card authorization request should be kept
confidential. Only the initial catalog information is not confidential.
424
Customer Merchant Bank
Figure 5-37: Information exchanges between the relevant parties. The quoted variables in the
parentheses represent the parameters that are passed on when the operation is invoked.
The credit card information must be encrypted by the customer so that only the bank can read
it— the merchant should relay it without being able to view the credit card information. For the
sake of simplicity, assume that all credit cards are issued by a single bank.
The message from the bank containing binary decision (“approved” or “rejected”) will be sent to
the merchant, who will forward it securely to the customer. Both the merchant and customer
should be able to read it.
Answer the following questions about the cryptosystem that is to be developed:
(a) What is the (minimum) total number of public-private key pairs ( K + , K − ) that must be
i i
issued? In other words, which actors need to possess a key pair, or perhaps some actors
need more than one pair?
(b) For each key pair i, specify which actor should issue this pair, to whom the public key
K +i should be distributed, and at what time (prior to each shopping session or once for
multiple sessions). Provide an explanation for your answer!
(c) For each key pair i, show which actor holds the public key K +i and which actor holds the
private key K −i .
(d) For every message in Figure 5-37, show exactly which key K + / K − should be used in the
i i
encryption of the message and which key should be used in its decryption.
425
Problem 5.24
In the Model-View-Controller design pattern, discuss the merits of having Model subscribe to the
Controller using the Publish-Subscribe design pattern. Argue whether Controller should subscribe
to the View.
426
Book Website (2012 Draft)
Our thanks go to Dr. Marsic for the original two versions of this book – the second of which can
be found at the following link. Solutions to select Practice Problems from the current version are
also found at the end of the linked version.
- Shahpur, Peter, and Dennis
http://www.ece.rutgers.edu/~marsic/books/SE/
427