0% found this document useful (0 votes)
21 views381 pages

Marsic Revised With Solutions 0923

Uploaded by

splendiferousbee
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views381 pages

Marsic Revised With Solutions 0923

Uploaded by

splendiferousbee
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 381

SOFTWARE ENGINEERING

Ivan Marsic

Revised in 2023 by:


Shahpur Khan, Peter Rodeffer, Dennis Egen
Rutgers University-Camden

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]

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/

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.

Approach and Organization


The first part (Chapters 1–5) is intended to accompany a semester-long hands-on team project in
software engineering. In the spirit of agile methods, the project consists of two iterations. The
first iteration focuses on developing some key functions of the proposed software product. It is
also exploratory to help with sizing the effort and setting realistic goals for the second iteration.
In the second iteration the students perform the necessary adjustments, based on what they have
learned in the first iteration. Appendix G provides a worked example of a full software engineering
project.
The second part (Chapters 6–8 and most Appendices) is intended for a semester-long course on
software engineering of Web applications. It also assumes a hands-on student team project. The
focus is on Web applications and communication between clients and servers. Appendix F briefly
surveys user interface design issues because I feel that proper treatment of this topic requires a
book on its own. I tried to make every chapter self-contained, so that entire chapters can
be skipped if necessary. But you will not benefit the most by reading it that way. This book takes
an evolutionary approach, where new topics systematically build on previous topics.

Chapter 2 introduces object-oriented software engineering. It is short enough to be covered in


few weeks, yet it provides sufficient knowledge for students to start working on a first version of
their software product. Appendix G complements the material of Chapter 2 by showing a practical
application of the presented concepts. In general, this knowledge may be sufficient for amateur
3
software development, on relatively small and non-mission-critical projects.
Chapters 3 through 5 offer more detailed coverage of the topics introduced in Chapter 2. They are
intended to provide the foundation for iterative development of high-quality software products.
Chapters 6 – 8 provide advanced topics which can be covered selectively, if time permits, or in a
follow-up course dedicated to software engineering of Web applications.
This is not a programming text. Despite that, the original versions of the text (see beginning or
end of this version for a link) include several appendices as reference material for special topics
that will inevitably arise in many software projects.

Examples, Code, and Solved Problems


I tried to make this book as practical as possible by using realistic examples and working through
their solutions. I usually find it difficult to bridge the gap between an abstract design and coding.
Hence, I include a great deal of code. The code is in the Java programming language, which brings
me to another point.
Different authors favor different languages and students often complain about having to learn yet
another language on not having learned enough languages. I feel that the issue of selecting a
programming language for a software engineering textbook is artificial. Programming language is
a tool and the software engineer should master a “toolbox” of languages so to be able to choose
the tool that best suits the task at hand.
Every chapter (except for Chapters 1 and 9) is accompanied by a set of problems. Solutions to
most problems can be found in the back of this book.
Design problems are open-ended, without a unique or “correct” solution, so the reader is welcome
to question all the designs offered in the back of this book. I have myself gone through many
versions of each design, and will probably change them again in the future, as I learn more and
think more. At the least, the designs in the back of this book represent a starting point to critique
and improve.
Additional information about team projects and online links to related topics may be found at
the book website linked in the beginning of the book. Currently, some information may be
outdated or no longer accessible.

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.

Further, software needs to account for all the myriad user


behaviors, sometimes known, but often unknown at design
time. This ‘discovery’ process of trying to understand how the
software will be used, is near infinite in its complexity, so we
must find many ‘good enough’ or heuristic solutions. Then,
over time, there is a feedback loop to continuously improve
the software.
This chapter first discusses what software engineering is about
and why it is difficult. Then we give a brief preview of software
development. Next, cases studies are introduced that will be
used throughout the book to illustrate the theoretical
concepts and tools. Software object model forms the
foundation for concepts and techniques of modern software
engineering. Finally, the chapter ends by discussing hands-on
projects designed for student teams.

1
2
1.1 What is Software Engineering?

“Computer science is no more about computers than astronomy is about telescopes.”


—Edsger W. Dijkstra

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

• Formulating a solution that works for the customer’s business


• The ability to design a software system that will realize the proposed solution and
gracefully evolve with the evolving business needs for many years in the future.
Software engineering is often confused with programming. Proper programming, or
development, is simply one aspect of Software Engineering. Software engineering is the creative
activity of understanding the business problem, proposing a solution, and designing the solution.
Programming is the craft of implementing the given blueprints (Figure 1-1).

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

Software Engineer’s task:


To understand how the system-to-be needs to interact with
the user or the environment so that customer’s requirement is met
and design the software-to-be

May be the Programmer’s task:


same person To implement the software-to-be
designed by the software engineer

Figure 1-1: The role for software engineering.

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

Figure 1-2: Illustration of complexity on the problem of scheduling construction tasks.

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

E How may F Release


Release G Record
Record
I help $60 $60
$60 less
less
you?

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.

1.1.1 Why Software Engineering Is Difficult (1)


“Software is like entropy. It is difficult to grasp, weighs nothing, and obeys the second law of
thermodynamics; i.e., it always increases.” —Norman R. Augustine

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.

1.1.2 Book Organization


Chapter 2 offers a quick tour of software engineering that is based on software objects, known as
Object-Oriented Software Engineering (OOSE). Our focus will be on tools, not methodology, for
solving software engineering problems. Chapter 3 elaborates on techniques for problem
understanding and specification. Chapter 4 describes metrics for measuring the software process
and product quality. Chapter 5 elaborates on techniques for problem solution, but unlike Chapter
2 it focuses on advanced tools for software design. Chapter 6 describes structured data
representation using XML. Chapter 7 presents software components as building blocks for
complex software. Chapter 8 introduces service-oriented architectures and Web services.
I adopt an incremental and iterative refinement approach to presenting the material. For every
new topic, we will scratch the surface and move on, only to revisit later and dig deeper.
The hope with metaphors and analogies is that they will evoke understanding much faster and
allow a more relatable approach, based on the existing knowledge we have from such examples.

9
1.2 Software Engineering Lifecycle

The Feynman Problem-Solving Algorithm:


(i) Write down the problem (ii) think very hard, (iii) write down the answer.

Any product development can be expected to proceed as an organized process that


usually includes the following phases:
• Planning / Specification
• Design
• Implementation
• Evaluation

The common software development phases are as follows:


1. Requirements Specification
- Understanding the usage scenarios and deriving the static domain model
2. Design
- Assigning responsibilities to objects and specifying detailed dynamics of their
interactions under different usage scenarios
3. Implementation
- Encoding the design in a programming language
4. Testing
- Individual classes/components (unit testing) and the entire system
(integration testing)
5. Operation and Maintenance
- Running the system; Fixing bugs and adding new features
The lifecycle usually comprises many other activities, some of which precede the above ones, such
as marketing surveys to determine the market need for the planned product. This text is restricted
to engineering activities, usually undertaken by the software developer.

10
Requirements

Design

Implementation

Testing
Waterfall
method Deployment &
Maintenance

Figure 1-6: Waterfall process for software development.

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:

• Reaching a concensus on what should be built

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

1.2.1 Symbol Language


“Without images we can neither think nor understand anything.” —Martin Luther (1483-1546)

As part of a design process, it is essential to communicate your ideas. However, communicating


such ideas can be riddled with issues such as memory, and language used (just to name a few).
George Miller found in the 1950s that human short-term memory can store about seven items at
a time [Miller, 1957]. The short-term memory is what we use, for instance, to remember a
telephone number just long enough to look away from the paper on which it is written to dial the
number. It is also known as working memory because information is assumed to be processed
when first perceived. It has been likened to the RAM (random access memory) of a computer.
Recall how many times you had to look back in the middle of dialing, particularly if you are not
familiar with the area code, which makes the number a difficult 10 digits! It turns out that the
Miller’s hypothesis is valid for any seven “items,” which could be anything, such as numbers, faces,
people, or communities.

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

instance1 : Class1 instance5 : Class2 instance8 : Class3

doSomething()
doSomethingElse()

Interaction Diagram
doSomethingYetElse()

Figure 1-7: Example UML symbols for software concepts.

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

Speakerphone Telephone Transaction Safe Cash


record

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.

1.2.2 Requirements Analysis and System Specification


We start with the customer statement of work (also known as customer statement of
requirements), if the project is sponsored by a specific customer, or the vision statement, if the
project does not have a sponsor. The statement of work describes what the envisioned system-
to- be is about, followed by a list of features/services it will provide or tasks/activities it will
support.

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?

Typing in XYZ valid. Withdraw


PIN number Balance: $60
… in
Typing $100
XYZ valid. Withdraw
PIN number Balance: $60
… $100

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.

1.2.4 Object-Oriented Design


“Design is not just what it looks like and feels like. Design is how it works.”—Steve Jobs

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

Pressing ofofa abutton


Pressing buttononon
thethe
remote control
remote transmitter
control (1) (1)
transmitter
authenticates the
authenticates thedevice
device && activates thethe
activates motor in the
motor operator
in the (2). (2).
operator
The motor
The motorpulls
pullsthe
thechain
chain (or(or
belt) along
belt) thethe
along rail rail
(3) and windswinds
(3) and the
the torsion
torsion spring
spring (4). (4).
GGarage gdoor
a r a ge e The torsionspring
springwinds
winds
The torsion thethe cable
cable on the
on the pulleys
pulleys (or drums)
(or drums) (5) on(5)
do
door on both sides ofdoor.
the door.
both sides of the
The cables lift the door, pushing the different sections of the door
The cables lift the door, pushing the different sections of the door into the
into the horizontal tracks (6)
horizontal
At the sametracks (6).trolley (or traveler) (7) moves along the rail (3)
time, the
and controls how far the door opens (or closes),
as well as the force the garage door exerts by way of the curved door arm (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.

1.2.5 Project Effort Estimation and Product Quality


Measurement
Let’s look at an example of pruning a customer’s hedges to begin understanding how project
effort estimation and product quality measurements work hand-in-hand with incremental and
iterative development, particularly where agile methodologies are used. Imagine that you want
to earn some extra cash this summer and you respond to an advertisement by a certain Mr.
McMansion to prune the hedges around his property (Figure 1-12). You have never done hedge
pruning before, so you will need to learn as you go. The first task is to negotiate the compensation
and completion date. The simplest way is to make a guess that you can complete the job in two
weeks, and you ask for a certain hourly wage. Suppose that Mr. McMansion agrees and happily
leaves for vacation. After one week, you realize how far behind the schedule you have become,
so to catch up you lower the quality of your work. After the two weeks elapse, Mr. McMansion
returns from their vacation and you have pruned the hedges, but the quality of work reduced
after the lapse in the schedule. Meaning Mr. McMansion will likely find many problems with your
work and may balk at paying for the work done.

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)

Figure 1-12: Example for project estimation: Formal hedge pruning.

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%

Estimation accuracy Estimation cost


Figure 1-13: Exponential cost of estimation. Improving accuracy of estimation beyond a certain
point requires huge cost and effort (known as the law of diminishing returns).

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)

2) Prune Section 7 2 days (4pts)


3) Prune Section 6 1 day (2pts)
Items pulled by the team into an iteration
4) Prune Section 5 2 days (4pts)

5) Prune Section 4 1.5 days (3p)

6) Prune Section 1

7) Prune Section 2 3.5 days


8) Prune Section 3 5 days

Work items

21 days
1st iteration 2nd iteration n-th iteration

List prioritized by the customer Estimated completion date


5 days
Time

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

Rounded forms, which


Snow accumulates Straight lines require more Peaked and rounded tops
follow nature’s tendency,
on broad flat tops frequent trimming hinder snow accumulation
require less trimming

Figure 1-15: Quality metrics for hedge pruning.

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.

1.3 Case Studies

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.

1.3.1 Case Study 1: Smart Home Access Control --


Adaptive Homes
Figures 1-16 through 1-18 illustrate our case-study system that is used in the rest of the text to
illustrate many software engineering methods. A written explanation follows.

30
Central
Computer

Alarm bell

Backyard doors:
External &
Light bulb Internal lock

Lock Photosensor Switch


Front doors:
External &
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

burglar launches dictionary attack

Figure 1-17: Concept map representing home access control.

5 This is an architectural decision (see Section 2.3 about software architecture).

32
IF validKey AND holdOpenInterval THEN unlock

IF validKey THEN unlock

locked unlocked

IF pushLockButton THEN lock

IF timeAfterUnlock = max{ autoLockInterval, holdOpenInterval } THEN lock

Figure 1-18: System states and transition rules.

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.

Simply put, this system offers house access control. The


system could be required to authenticate (“Are you who you claim to be?”) and validate (“Are
you supposed to be entering this building?”) people attempting to enter a building. Along with
controlling the locks, the system may also control other household devices, such as the lighting,
air conditioning, heating, alarms, etc.
Many times in software engineering, a seemingly simple problem actually hides
many complexities that do not appear until later in development. Figure
1-16 already indicates some of those. For example, houses usually have more than one lock.
Two locks are shown, but there could be additional ones—one for a garage entrance, for example.
Additional features, such as intrusion detection, further complicate the system. The
house could provide you with an email report on security status while you are away on vacation.
Maybe the police will also attend when they receive notification that a monitored system has
been activated. False alarms require at least two officers to check in, wasting police resources.
Many cities now fine residents for excessive false alarms.

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.

Maintaining a centralized database of people’s access privileges is difficult. One key


problem is having a permanent, hard-wired connection to the central computer. This sort of
network is very expensive, mainly due to the cost of human labor involved in network wiring and
maintenance. This is why, even in the most secure settings, a very tiny fraction of locks tend to be
connected. If interested, you can read about an interesting decentralized solution proposed by a
software company formerly known as CoreStreet (http://www.actividentity.com/). In their proposed
solution, the freshest list of access privileges spreads by “viral propagation” [Economist, 2004].
Figure 1-16 shows the locks connected by wire-lines to a central
computer. We need this computer to manage the user database, and other big data, in a
convenient and comfortable way. The connections would likely be wireless today, and the PC may
reside in a remote office instead of in the house.5

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.

Starting Simple: Locking and Unlocking


Our initial goal is only to support the basic door unlocking and locking functions, and automatic
light switching upon a lock or unlock. Each of these actions comes with difficulties not obvious at
first.

In unlocking, the difficulty is in handling failed password entries


(Figure 1-17). The system must allow a valid user to make some mistakes without penalty. At the
35
same time, it needs to be wary of “dictionary attacks”. Dictionary attacks consist of burglars
attempting to discover an identification key by systematically attempting wrong passwords.

Locking, coupled with light controls, has difficulties with


detection of daylight. In dark weather, for example, the photosensor could wrongly assume that
it is night—resulting in incorrect light switching. A solution to this could be the wall clock time.
This would mean that the light is always turned on between 7:30 P.M. and 7:30 A.M. In this case,
the limits should be adjusted for the seasons, assuming that the clock is automatically adjusted
for daylight saving time shift. Note also that we must specify which light should be turned on/off.
The living room light? The hallway light? All of them?
We describe all of these issues to emphasize the various questions and considerations that must be taken in software
engineering. What if the door needs to be locked after the tenant enters the house—should the light stay on or should
different lights turn on as the tenant moves to different rooms? Also, what if the user is not happy with the system’s decision
and does opposite of what the system did, e.g., the user turns off the light when the system turned it on? How do these
events affect the system functioning, i.e., how to avoid that the system becomes “confused” after such an event?

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.

CASE STUDY 2: WEB-BASED CLASS REGISTRATION

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.

Happy path examples:


Ecommerce: Logging in, browsing an online store, adding the item to. Your shopping cart, then
checking out without issues.
Food ordering site: Ordering 1 pizza on Grubhub
Online banking: Log in, check balance, log out.

Examples of use cases that are not ‘happy path’:


Logging in with the wrong password, getting locked out
Adding 3 items to your shopping cart, starting the checkout process, then trying to remove one
item from your cart before proceeding.

Let us first consider the ‘happy path’ of “SchoolReg”:


1. A student logs on to their school’s online portal and navigates to the administration tools section.
2. The student is required to provide additional authentication to access this site, possibly as secure as two-factor
authentication (2FA).
3. After a successful authentication the student is presented with various options such as department or semester to
select.
4. Once these selections are set the student will be redirected to the registration portal. The portal presents the user
with a listing of all available courses provided by the university that meet the chosen criteria. Within this view, the
user can sort courses by department (English, Math, Philosophy), by time of day, and so on, using this information
to plan their semester accordingly. It may be obvious that this system has many connections, from the varying pages
of navigation to the levels of authentication, but these are the most visible aspects of what is at work. There exist
many deeper connections that we are not considering, but we will discover them soon enough. [Figure]

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.

• Land on College portal


o The student has many choices but scrolling around the student finds “SchoolReg”.

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

• Land on “SchoolReg” portal


o The student lands on the “SchoolReg” start page.

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

• Submit courses for registration


o A simple clicking of buttons tells “SchoolReg” to register for those classes.

• Behind the scenes work


o Things are happening out of view of the user…I wonder what they could be?

• Given success message -> you are now registered


o Time to get back to studying! We are diligent students, after all.

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.

1.4 The Object Model

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

Example 1.1 Procedural approach versus Object-oriented approach


The process-based or procedural approach represents the solution as a sequence of steps to be
followed when the program is executed, Figure 1-28(a). It is a global view of the problem as seen by
the single agent advancing in a stepwise fashion towards the solution. The step-by-step approach is
easier to understand when the whole problem is relatively simple and there are few alternative choices
along the path. The problem with this approach becomes most clear when the number of steps and
alternatives becomes overwhelming.
The Object-oriented (OO) approach adopts a local view of the problem. Each object specializes only in
a relatively small subproblem and performs its task upon receiving a message from another object,
Figure 1-28(b). Unlike a single agent travelling over the entire process, we can think of the OO approach
as organizing many tiny agents into a “bucket brigade,” each carrying its task when called upon, Figure
1-28(c). When an object completes its task, it sends a message to another object saying, “that does it
for me; over to you—here’s what I did; now it’s your turn!” Here are pseudo-Java code snippets for
two objects, KeyChecker and LockCtrl:

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_;
... ...

/** Constructor */ public /** Constructor */ public


KeyChecker( LockCtrl(
LockCtrl lc, ...) { lock_ = lc; LightCtrl sw, ...) { switch_ = sw;
... ...
} }

/** 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) }
){

lock_.unlock(id); public lock(boolean light) {


} ... operate the physical lock device

} else { locked_ = true; if (light) {


// deny access switch_.turnOff();
// & sound alarm bell? }
}

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)

(a) unlock() turnOn()

Key
Lock
(c) Checker
Ctrl
Light
Ctrl

Figure 1-28: Comparison of process-oriented (procedural) and object-oriented methods on the


safe home access case study. (a) A flowchart for a procedural solution; (b) An object- oriented
solution. (c) An object can be thought of as a person with expertise and responsibilities.

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.

SIDEBAR 1.1: Object Orientation


♦ Object orientation is a worldview that emerged in response to real-world problems faced by
software developers. Although it has had many successes and is achieving wide adoption, as with
any other worldview, you may question its soundness in the changing landscape of software
development. OO stipulates that data and processing be packaged together, data being
encapsulated and unreachable for external manipulation other than through object’s methods. It
may be likened to disposable cameras where film roll (data) is encapsulated within the camera
mechanics (processing), or early digital gadgets with a built-in memory. People have not really
liked this model, and most devices now come with a replaceable memory card. This would speak
against the data hiding and for separation of data and processing. As we will see in Chapter 8,
web services are challenging the object-oriented worldview in this sense.

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

1.4.1 Controlling Access to Object Elements


Modular software design provides means for breaking software into meaningful components,
Figure 1-29. However, modules are only loose groupings of subprograms and data. Because there
is no strict ownership of data, subprograms can infringe on each other’s data and make it difficult
to track who did what and when. The object oriented approach goes a step further by emphasizing
state encapsulation, which means hiding the object state, so that it can be observed or modified
only via object’s methods. This approach enables better control over interactions among the
modules of an application. Traditional software modules, unlike software objects, are more
“porous;” encapsulation helps prevent “leaking” of the object state and responsibilities.
In object-orientation, an object’s data is more than just the program’s data—such data is the
object’s attributes, representing its individual characteristics or properties. When we design a

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)

Software Module 1 Software Module 2 Software Module 3

Methods
(behavior) Attributes
/data
(state)

(b)

Software Object 2 Software Object 3


Software Object 1

Figure 1-29: Software modules (a) vs. software objects (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.

anymathematician . areCoprimes( [ some correct


905, 1988
computation ]

Result:
YES!

Stu Any Mathematician

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.

1.4.2 Object Responsibilities and Relationships


The key characteristic of object-orientation is the concept of responsibility that an object has
towards other objects. Careful assignment of responsibilities to objects makes possible the
division of labor, so that each object is focused on its specialty. Other characteristics of object
orientation, such as polymorphism, encapsulation, etc., are characteristics local to the object
itself. Responsibilities characterize the design of the whole system. To understand how, you need
to read Chapters 2, 4, and 5. Because objects work together you would expect that the entities
have defined roles and responsibilities. The process of determining what the object should know
and what it should do (its state and behavior respectively) is known as ‘assigning the
responsibilities’. What are an object’s responsibilities? Some key object responsibilities are:
1. Knowing something (memorization of data or object attributes)
2. Doing something on its own (computation programmed in a “method”)
3. Calling methods of other objects (communication by sending messages)
We will additionally distinguish a special type of doing/computational responsibilities:
2.a) Business rules for implementing business policies and procedures
Business rules are important to distinguish because, unlike algorithms for data processing and
calculating functions, they require knowledge of the customer’s business context, and they often
change. We will also distinguish communication responsibilities:
3.a) Calling constructor methods; this is special because the caller must know the
appropriate parameters for initialization of the new object.
Assigning responsibilities essentially means deciding what methods an object gets and who
invokes those methods. Large parts of this book deal with assigning object responsibilities,
particularly Section 2.6 and Chapter 5.
The basic types of class relationships are inheritance, where a class inherits elements of a base
class, and composition, where a class contains a reference to another class. These relationships
can be further refined as: Base Class

• Is-a relationship (UML symbol ∆): A class “inherits”


from another class, known as base class, or parent class, or superclass
• Has-a relationship: A class “contains” another class
Container

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)

“Has-a” and “Uses-a” relationships can be seen as types of composition.

1.4.3 Reuse and Extension by Inheritance and


Composition
One of the most powerful characteristics of object-orientation is code reuse. Procedural
programming provides code reuse to a certain degree—you can write a procedure and then reuse
it many times. However, object-oriented programming goes an important step further, allowing
you to define relationships between classes that facilitate not only code reuse, but also better
overall design, by organizing classes and factoring in commonalities of various classes.
Two important types of relationships in the object model enable reuse and extension: inheritance
and composition. Inheritance relations are static—they are defined at the compile time and
cannot change for the object’s lifetime. Composition is dynamic, it is defined at run time (during
the participating object’s lifetimes) and it can change.
When a message is sent to an object, the object must have a method defined to respond to that
message. The object may have its own method defined as part of its interface, or it may inherit a
method from its parent class. In an inheritance hierarchy, all subclasses inherit the interfaces from
their superclass. However, because each subclass is a separate entity, each might require a
separate response to the same message. For example, in Figure 1-31 subclasses Lock Controller
and Light Controller inherit the three public methods that constitute the interface of the
superclass Household Device Controller. The private method is private to the superclass and not
available to the derived subclasses. Light Controller overrides the method activate() that it inherits
from its superclass, because it needs to adjust the light intensity after turning on the light. The
method deactivate() is adopted unmodified. On the other hand, Lock Controller overrides both
activate() and deactivate() because it requires additional behavior. For example, in addition to
disarming the lock, Lock Controller’s method deactivate() needs to start the timer that counts
down how long time the lock has remained unlocked, so it can be automatically locked. The
method activate() needs to clear the timer, in addition to arming the lock. This property that the
same method behaves differently on different subclasses of the same class is called
polymorphism.
Inheritance applies if several objects have some responsibilities in common. The key idea is to
place the generic algorithms in a base class and inherit them into various derived classes. With
inheritance, we can start with a ‘base case’ of common functions and attributes. Then, after that
foundation is established, we focus on what is ‘different from the base case in each subclass or
derived class. Inheritance is a strong relationship, in that the derivatives are inextricably bound to
their base classes. Methods from the base class can be used only in its own hierarchy and cannot

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

Figure 1-31: Example of object inheritance.

1.5 Student Team Projects

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

1.5.1 Stock Market Investment Fantasy League

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

Advertisers (e.g., PayPal) (e.g., Google/Yahoo! Finance)

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

Player Management Real-World Market Advertiser Management


Observation & Analysis • Account balance
• Account balance
• Trading support • Retrieval of stock prices • Uploading new banners
• Transactions archiving - On-demand vs. periodic • Banner placement selection
• Portfolio valuation • Analysis
• Periodic reporting (email) - Technical & fundamental
•?
League Management
• Player ranking
• Awards disbursement control
• Trading performance analysis
• Coaching of underperformers

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.

8 An introductory description is available here: http://en.wikipedia.org/wiki/Delphi_method . An in-depth


review is available here: http://web.njit.edu/~turoff/Papers/delphi3.html (M. Turoff and S. R. Hiltz:
“Computer Based Delphi Processes,” in M. Adler and E. Ziglio (Editors), Gazing Into the Oracle: The Delphi
Method and Its Application to Social Policy and Public Health, London, UK: Kingsley Publishers, 1995.)

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.

1.5.3 Remarks about the Projects


My criteria in the selection of these projects was that they are sufficiently complex so to urge the
students to enrich their essential skills (creativity, teamwork, communication) and professional
skills (administration, leadership, decision, and management abilities when facing risk or
uncertainty). In addition, they expose the students to at least one discipline or problem domain
in addition to software engineering, as demanded by a labor market of growing complexity,
change, and interdisciplinarity.
The reader should observe that each project requires some knowledge of the problem domain.
Each of the domains has myriads of details and selecting the few that are relevant requires a
major effort. Creating a good model of any domain requires skills and expertise and this is
characteristic of almost all software engineering projects—in addition to software development
skills, you must almost always learn something new to build a software product.
The above projects are somewhat deceptive insofar as the reader may get the impression that all
software engineering projects are well defined and the discovery of what needs to be developed
is done by someone else, so the developer’s job is just software development. Unfortunately, that
is rarely the case. In most cases the customer has a very vague idea of what they would like to be
developed and the discovery process requires a major effort. That was the case for all the above
projects—it took me a great deal of fieldwork and help from many people to arrive at the project
descriptions presented above. In the worst case you may not even know who your customer will
be, as is the case for traffic monitoring (described at the book website, given in the Preface) and
the investment fantasy league (Section 1.5.1). In such cases, you need to invent your own
customers—you need to identify who might benefit from your product and try and interest them
in participating in the development.

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.

Example 1.2 RFID tags in retail


The following example illustrates of how
a typical idea for a software engineering
project might evolve. The management
of a grocery supermarket (our customer)
contacted us with an idea for a more
effective product promotion. Their plan
is to use a computer system to track and
influence people’s buying habits. A set of
logical rules would define the conditions
for generating promotional offers for
customers, based on the products the
customer has already chosen. For
example, if a customer removed a
product A from the shelf, then they may
be offered a discount coupon on
product B. Alternatively, the customer
may be asked if they also need product
C. This last feature serves as a reminder,
rather than for offering discount
coupons. For example, if a customer
removes a soda bottle from a shelf, they
may be prompted to buy potato chips,
as well.
To implement this idea, the store will
use Radio Frequency Identification
(RFID) tags on all store items. Each tag carries a 96-bit EPC (Electronic Product Code). The RFID tag
readers will be installed on each shelf on the sales floor, as well as in the cashier registers at the sales
point. When a tag is removed from the region of a reader’s coverage, the reader will notify the
computer system that the given tag disappeared from its coverage area. In turn, the system will apply
the logical rules and show a promotional offer on a nearest display. We assume that each shelf will
have an “offers display” that will show promotional offers or reminders related to the last item that
was removed from this shelf.
As we consider the details of the idea, we realize that the system will not be able to identify individual
customers and tailor promotional offers based on the customer identity. In addition to privacy
concerns, identifying individual customers is a difficult technological problem and the store
management ruled out potential solutions as too expensive. We do not care as much to know who the
customer is; rather, we want to know the historic information about other items that this customer
placed in her cart previously during the current shopping episode to customize the offer.

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

• requirements gathering and elicitation

• problem framing

• user testing and feedback

1.6 Summary and Bibliographical Notes

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

Section 1.2.1: Symbol Language


Most people agree that symbols are useful, even if some authors invent their own favorite
symbols. UML is the most widely accepted graphical notation for software design, although it is
sometimes criticized for not being consistent. Even in mathematics, the ultimate language of
symbols, there are controversies about symbols even for such established subjects as calculus (cf.,
Newton’s vs. Leibnitz’s symbols for calculus), lest to bring up more recent subjects. To sum up,
you can invent your own symbols if you feel it is necessary, but before using them, explain their
meaning/semantics and ensure that it is always easy to look-up the meanings of your symbols.
UML is not ideal, but it is the best currently available and most widely adopted.
Arguably, symbol language has a greater importance than just being a way of describing one’s
designs. Every language comes with a theory behind it, and every theory comes with a language.
Symbol language (and its theory) helps you articulate your thoughts. Einstein knew about the
general relativity theory for a long time, but only when he employed tensors was he able to
articulate the theory (http://en.wikipedia.org/wiki/History_of_general_relativity).

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

Regarding Case Study 1: Home Access Control


The Case Study #1 Project (Section 1.3.1) – Literature about the home access problem domain:
A path to the future may lead this project to an “adaptive house” [Mozer, 2004]. See also:
Intel: Home sensors could monitor seniors, aid diagnosis (ComputerWorld)
http://www.computerworld.com/networkingtopics/networking/story/0,10801,98801,00.html
Another place to look is: University of Florida’s Gator Tech Smart House [Helal et al., 2005],
online at: http://www.harris.cise.ufl.edu/gt.htm
For the reader who would like to know more about home access control, a comprehensive, 1400-
pages two-volume set [Tobias, 2000] discusses all aspects of locks, protective devices, and the
methods used to overcome them. For those who like to tinker with electronic gadgets, a

great companion is [O’Sullivan & T. Igoe, 2004].


66
Further reading on Biometrics:
Wired START: “Keystroke biometrics: That doesn’t even look like my typing,” Wired, p. 42,
June 2005. Online at: http://www.wired.com/wired/archive/13.06/start.html?pg=9
Researchers snoop on keyboard sounds; Computer eavesdropping yields 96 percent accuracy rate.
Doug Tygar, a Berkeley computer science professor and the study's principal investigator
http://www.cnn.com/2005/TECH/internet/09/21/keyboard.sniffing.ap/index.html
Keystroke Biometric Password; Wednesday, March 28, 2007 2:27 PM/EST
BioPassword purchased the rights to keystroke biometric technology held by the Stanford
Research Institute. On March 26, 2007, the company announced BioPassword Enterprise Edition
3.0 now with optional knowledge-based authentication factors, integration with Citrix Access
Gateway Advanced Edition, OWA (Microsoft Outlook Web Access) and Windows XP embedded
thin clients.
http://blogs.eweek.com/permit_deny/content001/seen_and_heard/keystroke_biometric_password.
html?kc=EWPRDEMNL040407EOAD
See also [Chellappa et al., 2006] for a recent review on the state-of-the-art in biometrics.

67
Regarding the Object Model
The concept of information hiding originates from David Parnas [1972].

D. Coppit, “Implementing large projects in software engineering courses,” Computer Science


Education, vol. 16, no. 1, pp. 53-73, March 2006. Publisher: Routledge, part of the Taylor & Francis
Group

J. S. Prichard, L. A. Bizo, and R. J. Stratford, “The educational impact of team-skills training:


Preparing students to work in groups,” British Journal of Educational Psychology, vol. 76, no. 1,
pp. 119-140, March 2006.
(downloaded: NIH/Randall/MATERIALS2/)
M. Murray and B. Lonne, “An innovative use of the web to build graduate team skills,” Teaching
in Higher Education, vol. 11, no. 1, pp. 63-77, January 2006. Publisher: Routledge, part of the
Taylor & Francis Group

68
Chapter 2
Object-Oriented Software Engineering

Contents
Software Development Methods

This chapter describes concepts and techniques for object-


Requirements Engineering
oriented software development. The first chapter introduced
the stages of software engineering lifecycle (Section 1.2). Now,
Software Architecture
the tools and techniques for each stage are gradually detailed
and will be elaborated in later chapters.
Use Case Modeling
We start with the methodology and project management
issues, which is a first concern faced with large-scale product Analysis: Building the Domain Model
development. Next we review elements of requirements
engineering: how system requirements are gathered, Design: Assigning Responsibilities
analyzed, and documented. Real-world projects rarely follow
exclusive “bottom-up” approach, from requirements through Test-driven Implementation

objects to program code. For architectural reasons, a “top-


Summary and Bibliographical Notes
down” approach is taken instead.
Use case modeling is a popular approach to requirements
engineering. As the name suggests, use case modeling
Practice Problems
elaborates usage scenarios or user stories of the system-to-be.
Requirements engineering is followed by domain modeling.
Here, we model the problem domain, focusing mainly on the
“objects” of our system-to-be. “Objects” are internal
elements.
The design stage comes after analysis. This stage specifies how
objects interact to produce desired behaviors of the system-
to-be.
This chapter concludes with the techniques for software
implementation and testing. While studying this chapter, you
may find it useful to check Appendix G and see how the
concepts are applied in an example project.

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.

2.1.1 Agile Development


“People forget how fast you did a job—but they remember how well you did it.” —An advertising executive “Why do we
never have time to do it right, but always have time to do it over?” —Anonymous

An agile approach to development is essentially a results-focused method that iteratively


manages changes and risks. It also actively engages customers in providing feedback on
successive implementations, in effect making them part of the development team. Unlike
process-driven documentation, It promotes outcome-driven (instead of process-driven)
documentation. Agile development emphasizes traveling lightweight, producing only those
artifacts (documentation) that are absolutely necessary. The philosophy of the agile approach is
formulated by the Manifesto for Agile Software Development (http://agilemanifesto.org/).
Agile development supporters recommend that development be incremental and iterative, with
quick turnover, and light on documentation. They believe perfection is the enemy of innovation.
Agile methods are not meant to entirely replace methodologies such as structured analysis and
design, or object-oriented analysis and design. Rather, agile methods are often focused on how
to manage a project. They may still use the tools for software development inherited from other
methods, but in a different way. A popular

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.

2.1.2 Decisive Methodological Factors


Software quality can be greatly improved by paying attention to factors such as traceability,
testing, measurement, and security.
Traceability
Software development process starts with an initial artifact, such as customer statement of work,
and ends with source code. As the development progresses, being able to trace the links among
successive artifacts is key. If you do not make explicit how an entity in the current phase evolved
from a previous-phase entity, then it is unclear what was the purpose of doing all that previous
work. Lack of traceability renders the past creations irrelevant and we might as well have started
with this phase. It makes it difficult for testers to show that the system complies with its
requirements and maintainers to assess the impact of a change. Therefore, it is essential that a
precise link is made from use cases back to requirements, from design diagrams back to use cases,
and from source code back to design diagrams. Traceability refers to the property of a software
artifact, such as a use case or a class, of being traceable to the original requirement or

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

UC-1 CO-1 Code-1


Req-1
CO-2 Code-2
UC-2

CO-3 Code-3

UC-M CO-S Code-W


Req-K

UC-N CO-T Code-X


Requirements
Engineering Use Cases (Section OOA/OOD Implementation
(Section 2.2) 2.3) (Sections 2.4 & 2.5) (Section (2.7)

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.

2.2 Requirements Engineering

“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

Figure 2-1: Requirements process in different methodologies.

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.

2.2.1 Requirements and User Stories


“The best performance improvement is the transition from the nonworking state to the working state.”
—John Ousterhout

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.

Statement of Requirements, Case Study 1: SMART Home Access


Table 2-1 enumerates initial requirements extracted from the home access control problem
described in Section 1.3.1. Each requirement is assigned a unique identifier. The middle
column shows the priority weight (PW) of each requirement, with a greater number
indicating a higher priority. The range of priority weights is decided arbitrarily, in our
example it is 1–5. It is preferable to have a small range (10 or less), because the priorities
are assigned subjectively and it is difficult to discern finely-grained priorities. Larger projects
with numerous requirements may need larger range of priorities.

88
Table 2-1: Requirements for Case Study 1: SMART home access system (1.3.1).

Identifier Priority Requirement


REQ1 5 The system shall keep the door locked at all times, unless commanded otherwise
by authorized user. When the lock is disarmed, a countdown shall be initiated at
the end of which the lock shall be automatically armed (if still disarmed).
REQ2 2 The system shall lock the door when commanded by pressing a dedicated button.
REQ3 5 The system shall, given a valid key code, unlock the door and activate other devices.
REQ4 4 The system should allow mistakes while entering the key code. However, to resist
“dictionary attacks,” the number of allowed failed attempts shall be small, say three,
after which the system will block and the alarm bell shall be sounded.
REQ5 2 The system shall maintain a history log of all attempted accesses for later review.
REQ6 2 The system should allow adding new authorized persons at runtime or removing
existing ones.
REQ7 2 The system shall allow configuring the preferences for device activation when the
user provides a valid key code, as well as when a burglary attempt is detected.
REQ8 1 The system should allow searching the history log by specifying one or more of
these parameters: the time frame, the actor role, the door location, or the event
type (unlock, lock, power failure, etc.). This function shall be available over the
Web by pointing a browser to a specified URL.
REQ9 1 The system should allow filing inquiries about “suspicious” accesses. This function
shall be available over the Web.

An important issue is the granularity of requirements. Some of the requirements in Table


2-1 are relatively complex or compound requirements. Test-Driven Development (TDD)
stipulates writing requirements so that they are individually testable. In the end, a report is
created that says what requirements passed and what requirements failed. For this purpose, no
requirement should be written such that there are several “tests” or things to verify
simultaneously. If something fails, it may not be clear what part of the requirement has failed.
For example, if we were to test requirement REQ1 in Table 2-1, and the door was found unlocked
when it should have been locked, the entire requirement would fail Verification. It would be
impossible to tell from the report if the system accidentally disarmed the lock, or the autolock
feature failed. To avoid this, we can split up REQ1 like this:
REQ1a: The system shall keep the doors locked at all times, unless commanded otherwise by
authorized user.
REQ1b: When the lock is disarmed, a countdown shall be initiated at the end of which the lock
shall be automatically armed (if still disarmed).
However, there may be reasons besides testing to have some requirements combined. Some
requirements unfortunately don’t describe a stand-alone, meaningful unit of functionality. They
only make sense when combined with others. From customer’s viewpoint, good requirements
should describe the smallest possible meaningful units of functionality.
Although the choice of requirements granularity is subject to judgment and experience and there
is no clear metrics, the best approach is to organize one’s requirements hierarchically.

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

Activate for valid key Activate for burglary attempt

Lights Air-conditioning Alarm bell Send SMS


Music Heating Police …

Apply Close

Figure 2-2: Envisioning the preference configuration for the control of household devices.

Statement of Requirements, Case Study 2: Investment Assistant


Here we extract initial requirements for the personal investment assistant system based on the
description given in Section 1.3.2. The requirements are shown in Table 2-2.
The statement of requirements is only a digest, and the reader should keep in mind that it must
be accompanied with a detailed description of customer’s business practices and rules, such as
the market functioning described earlier.
The stock trading ticket in REQ2 is a form containing the client’s instructions to the broker or
dealer. A stock trading ticket contains four parts: the client’s information, the security
information, the order information and any special instructions. The ticket specifies the action
(buy/sell), the order type (market/limit/stop), the symbol of the stock to trade, the number of
shares, and additional parameters in case of limit and stop orders. If the action is to buy, the
system shall check that the investor has sufficient funds in his/her account.
The order management window lists working, filled, cancelled, and parked orders, as well as
exceptions and all orders. In the working window, an order can be cancelled, replaced, and
designed as “go to market” for immediate execution, as well as be chained for order-cancels-
order status.
Similar to Table 2-1, Table 2-2 contains only a few requirements that appear to be clear at the
outset of the project. Other requirements may be discovered or the existing ones enhanced or
altered as the development lifecycle progresses.

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.

user-role capability business-value

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

Identifier User Story Size


ST-1 As an authorized person (tenant or landlord), I can keep the doors locked at 4
all times. points
ST-2 As an authorized person (tenant or landlord), I can lock the doors on demand. 3 pts
ST-3 The lock should be automatically locked after a defined period of time. 6 pts
ST-4 As an authorized person (tenant or landlord), I can unlock the doors. 9
(Test: Allow a small number of mistakes, say three.) points
ST-5 As a landlord, I can at runtime manage authorized persons. 10 pts
ST-6 As an authorized person (tenant or landlord), I can view past accesses. 6 pts
ST-7 As a tenant, I can configure the preferences for activation of various devices. 6 pts
ST-8 As a tenant, I can file complaint about “suspicious” accesses. 6 pts

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.

2.2.2 Requirements Gathering Strategies


“Everything true is based on need.” —George Bernard Shaw
“Well, as the new Hummer H2 ads observe, ‘need’ is a highly subjective word.” —Peter Coffee (in 2003)

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.

2.2.3 Effort Estimation


Requirements and user stories can be used to estimate effort and plan software
releases. The estimation process works very similarly to the hedge-pruning
example described in 1.2.5. Similar to “hedge pruning points”, user-story points
are assigned to each user story to measure the stories’ relative size. Table 2-3
(right column) shows my initial estimates of the relative user story sizes on a 1-
10 scale.
It's worth noting that we do not have much confidence in these estimates. You may not always
have much confidence in yours, either—which is normal. While making the estimates in Table 2-
3, we are trying to make a holistic guess, which requires a great deal of subjectivity. It is impractical
to hold all those experiences in one’s head at once and combine them in a systematic manner.
The resulting estimates simply reflect a general feeling about the size of each user story. This may
be sufficient to start the project.
Still, when possible, I do prefer using more structured methods for software size estimation. One
such method is based on use case points, described later in Chapter 4. However, more structured
methods come at a cost—they require time to derive the design details. I recommend that the
reader should always be mindful about which part of the exponential curve in Figure 1-13 he is
operating on. When estimating, you should only pursue a level of accuracy that requires a
reasonable amount of effort.
As previously mentioned (link), to estimate the effort (duration) needed to develop the system,
we also need to know the development team’s speed. If the size of a project estimation is
measured in story points, then the development team’s speed is the number of user-story points
that the team can complete per iteration (unit of time). For example, a team may be able to
complete 20 user-points over two weeks. That is, the speed represents the team’s productivity.
Simply summing up story sizes is rarely appropriate. Code tends to be reused or shared, and some
functionality will be shared by several stories. Let me illustrate on an analogy. Consider you are
charged to build highways from city A to cities B and C (Figure 2-3).

96
(b) C
City C

City B
B

City A (a) (c)

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

Methods lowest level


Figure 2-4: Hierarchy of software system scope levels. At the highest scope level is a
product line—a family of products.

2.3 Software Architecture

“Conceptual integrity is the most important consideration in system design.”


—Fred Brooks, The Mythical Man-Month

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

Product/system A scope Product B 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

On embedded On office On tenant’s Decision on software-to-


computer desktop smartphone
hardware mapping

Figure 2-6: Architectural decisions for safe home access system.

Keypad and Light bulb


Embedded

RS-232
Interface cable

Computer

Figure 2-7: Hardware components for the system implementation.

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.

2.3.1 Problem Architecture


When faced with a difficult software engineering problem, it helps to recognize if it resembles to
known typical problems. If it does, we employ known solutions.

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)

IN doc System OUT doc

1.b) User edits information stored in a repository


User System System

User

2. Computer system controls the environment


(user not involved)

System Environment

3. Computer system intermediates between the 3.a) System observes the environment and displays information
user and the environment

User System Environment

User System Environment 3.b) System controls the environment as commanded by the user

User System Environment

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

Figure 2-11: Problem architectures of typical software engineering problems.

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.

2.3.2 Software Architectural Styles


So far the development process was presented as a derivation of a software design from system
requirements. Although this process is iterative, every iteration presumably starts with (possibly
revised) requirements and progresses towards an implementation. However, in reality such
“bottom-up” design approaches at the local level of objects are insufficient to achieve optimal
designs, particularly for large systems. There are many contextual constraints and influences
other than requirements that determine the software architecture. Examples include expertise-
based design preferences, the actual progress compared to the plan; currently prevailing
practices; or available assets, such as lack of expertise in some areas.

105
User Interaction User Authentication Archiving

Management of Communication w.
Sensors and Devices Police Station

User Interface Layer Domain Layer Technical Services Layer


(Application Logic)

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

Program Flow Control


One can also set up “daemons” that spend their lifetime on the lookout for a certain type of event,
and do what they have to do whenever a happening of that type occurs. A more flexible IF- THEN-
ELSE is to say, “If this happens, use that method to choose an appropriate procedure from this list
of procedures,” where the contents of the list in question can vary as the program runs.
IF-THEN-ELSE partitions the set of all possible situations in two or more cases. The partition may
turn out to be too rigid. There may be some exception cases that were not anticipated and now
need to be accounted for. Possibly even by the user! How can we let the user “rewire” the
paths/flows within the program if they need to? This is a key issue.
The program code that implements software classes and subsystems is usually organized into
software packages. Each package contains a set of logically related classes (Figure 2-12).

2.3.3 Recombination of Subsystems


After decomposition, different subsystems are usually developed and tested independently. At
some point, all subsystems need to be recombined and integrated into the whole system-to-be.
The recombination (or composition) problem is unsolved and very tricky. Key issues:
• Cross-platform compatibility, particularly trust and privilege issues
• Concurrent data access in multithreaded systems

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.

2.4 Use Case Modeling


A use case is a description of how a user will use the planned system to accomplish business goals.
As any description, it can be sketchy or it can be very detailed. Both versions (and many degrees
of detail in between) have important uses in requirements engineering. It is natural to start with
summary descriptions of use cases and gradually progress towards detailed descriptions that
thoroughly specify the planned system.
Use cases were already introduced in Section 1.2.2 . This section presents details of use case
modeling. We start with summary descriptions of use cases and end with detailed descriptions
that represent the specification of the planned system.
A brief note: from here on out, we may use the terms “system” and “system-to-be” quite
interchangeably. A “system-to-be” is simply a system that is to be built. For most of software
engineering, you are considering or planning out a system that has not been built yet, so it can be
assumed that when we mention a system, we are discussing a system-to-be. Unless it is clear
otherwise, assume that both terms refer to a system that is to be built.

2.4.1 Actors, Goals, and Sketchy Use Cases


In system development, we are mainly concerned with the actors that interact directly with the
system, including end users and other systems. However, all stakeholders have certain goals for
the system. Occasionally, it may be appropriate to list those goals. Identifying the actors for the
system is the first step of system requirements consideration.

Actors and Their Goals


An actor is any entity external to the system that interacts with it . An actor can be
a person or another system which interacts with our system-to-be. There are two
main categories of actors, defined relative to a particular use case:

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

We could distinguish the Maintenance actor who can do


everything as the Landlord, except to manage users. On the
other hand, what if we wanted to include a Maintenance actor
with the exact same use cases as the Tenant? Well, then then Tenant Landlord
there would be no reason to distinguish them at all! We should
keep things simple and just devise an actor name that covers Actor generalization.
both.
Note that the last row contains a to-be-identified
actor, whose goal is to automatically arm the lock
after a certain period of time expires, to account
for forgetful persons. Obviously, this is not a
person’s goal, but neither is it the system’s goal X

because system-to-be does nothing on its own—it Y

must receive an external stimulus to take action.


We will see later how this can be solved.
Actors may be defined in generalization hierarchies. Generalization hierarchies
consist of one abstract actor description, which is shared and augmented by
more specific actor descriptions. It may even be shared and augmented by just
one specific actor description.

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.

Summary Use Cases


We’ve introduced use cases before. Putting them in context with what we just learned, though, a
use case is a usage scenario for an external entity, known as actor, and the system-to-be. A use
case represents an activity that an actor can perform on the system and what the system does in
response. Use cases describe what happens when an actor disturbs our system from its
“stationary state”, as the system goes through its motions until a new stationary state. It is
important to keep in mind that the system is reactive, not proactive. If left undisturbed, the
system would remain forever in the equilibrium state.
Table 2-4 names the preliminary use cases for our case-study example. The reader may observe
that the summary use cases are similar to user stories (Table 2-3). Like user stories, summary use

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.

Use Case Diagram


Figure 2-13 sums up the actors, use cases, and their relationships in a so-called use case diagram.
There are two use-case categories distinguished: “first-” vs. “second tier.” The “first tier” use cases
represent meaningful services provided by the system to an actor. The “second tier” use cases
represent elaborations or sub-services of the main services. In a sense, they are equivalent of
subroutines in programs because they capture some repetitive activity and can be reused in
multiple locations. The figure also shows the relationship of use cases in different tiers. The two
stereotypical- or cliché types of relationship are:
• «extend» – optional extensions of the main case
• «include» – required subtasks of the main case
The developer can introduce new stereotypes or clichés for representing the use case
relationships. Note also that the labels on communication lines («initiate» and «participate») are
often omitted to avoid cluttering.
The AuthenticateUser use case is not a good candidate for first tier use cases, because it does not
represent a meaningful stand-alone goal for an initiating actor. It is, however, useful to show it
explicitly as a second tier use case. This is because it reveals which use cases require user
authentication. For example, one could argue that Lock does not need authentication, because
performing it without authentication does not represent a security threat. Similarly, Disable
should not require authentication because that would defeat the purpose of this case. Design
decisions like these may need further consideration and justification. The reader should not take
these lightly, because each one can have serious consequences. The reader is advised to try to
come up with scenarios where the above design decisions may not be appropriate.

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

SIDEBAR 2.4: Is Login a Use Case?

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

2.4.2 System Boundary and Subsystems

Determining the System Boundary

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

UC1: Unlock LockDevice


Tenant «p
art «inc
ici l
p
UC7: AuthenticateUser

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

Subsystems and Software Architecture

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

2.4.3 Detailed Use Case Specification


A detailed use case description represents a use case of the system as a sequence of interactions
between actors and the system-to-be. Detailed use cases are usually written as usage scenarios
or scripts, listing a specific sequence of actions and interactions between the actors and system.
For use case scenarios, we will use a stepwise, “recipe-like” description. A scenario describes in a
step-by-step manner activities that an actor does and how the system responds. A scenario is also
called a use case instance. It represents only one of several possible courses of action for a given
use case. Use cases specify what information must pass the boundary of a system when a user or
another system interacts with it.
We usually first elaborate the “normal” scenario, also called main success scenario or “happy
path”. The happy path assumes that everything goes perfect. Because everything flows
straightforward, this scenario usually does not include any conditions or branching—it flows
linearly. It is just a causal sequence of action/reaction or stimulus/response pairs. Figure 2-19
shows the use case schema.9
Alternate scenarios or extensions in a use case can result from:
• Inappropriate data entry, such as the actor making a wrong menu-item choice (other than
the one he/she originally intended), or the actor supplies an invalid identification.
• System’s inability to respond as desired, which can be a temporary condition, or the
information necessary to formulate the response may never become available.
For each of the alternate cases, we must create an event flow that describes what exactly happens
in such a case and lists the participating actors. Alternate scenarios are even more important than
the main success scenario, because they often deal with security issues (or other issues).

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

Figure 2-19: A general schema for UML use cases.

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.

Detailed Use Cases


Detailed use cases elaborate the summary use cases (Table 2-4). For example, for the use case
Unlock, the main success scenario in an abbreviated form may look something like this:

119
In step 5, the activity of locking the door is in
brackets, because this is covered under the use

Use Case UC-1: Unlock


Related Requirem’ts: REQ1, REQ3, REQ4, and REQ5 stated in Table 2-1
Initiating Actor: Any of: Tenant, Landlord
Actor’s Goal: To disarm the lock and enter, and get space lighted up automatically.
Participating Actors: LockDevice, LightSwitch, Timer
Preconditions: • There is a non-empty set of valid keys stored in the system database.
• The system displays the menu of available functions; at the door
keypad the menu choices are “Lock” and “Unlock.”
Postconditions: The auto-lock timer has started countdown from integer
autoLockInterval.
Flow of Events for Main Success Scenario:
→ 1. Tenant/Landlord arrives at the door and selects “Unlock” on their smart device.
2. include::AuthenticateUser (UC-7)
← 3. System (a) signals to the Tenant/Landlord the lock status, e.g., “disarmed,” (b) signals
to LockDevice to disarm the lock, and (c) signals to LightSwitch to turn the light on
← 4. System signals to the Timer to start the auto-lock timer countdown
→ 5. Tenant/Landlord opens the door, enters the home [and shuts the door and locks]
case Lock, and does not concern the use case
Unlock. Of course, we want to ensure that this
indeed happens, which is the role of an auto-
lock timer, as explained later. An extension
scenario for the above use case may specify
how the system will behave if the door is
unlocked manually, using a physical key.
Extensions or alternate scenarios are not listed
in the description of UC-1, but we must
consider what could go wrong for each
of the steps in the main success scenario. For example:
• In Step 1, the actor may make a wrong menu selection
• Exceptions during the actor authentication are considered related to UC-7
• In Step 5, the actor may be held outside for a while, e.g., greeting a neighbor
For instance, to address the exceptions in Step 5, we may consider installing an infrared beam in
the doorway that detects when the person crosses it. Example alternate scenarios are given next
for the AuthenticateUser and Lock use cases.
In step 2 of UC-1, I reuse a “subroutine” use case, AuthenticateUser, by keyword “include,”
because I anticipate this will be useful in other use cases, as well. Subroutines can be thought of
as functions in programming. They are defined because they can be reused to make life easier.
Here is the main scenario for AuthenticateUser as well as the exceptions, in case something goes

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:

← 1. System prompts the actor to verify their entry


→ 2. Tenant/Landlord verifies the entry
← 3. System (a) verifies that the verification occurred, and (b) signals to the actor the
occurrence of verification
Flow of Events for Extensions (Alternate Scenarios):
2a. Tenant/Landlord enters an invalid identification key
System (a) detects error, (b) marks a failed attempt, and (c) signals to the actor
1a. System (a) detects that the count of failed attempts exceeds the maximum allowed number, (b)
signals to sound AlarmBell, and (c) notifies the Police actor of a possible break-in

Tenant/Landlord -verifies their entry


3. Same as in Step 3 above

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:

Use Case UC-3: AddUser


Related Requirements: REQ6 stated in Table 2-1

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:

Use Case UC-5: Inspect Access History


Related Requirements: REQ8 and REQ9 stated in Table 2-1
Initiating Actor: Any of: Reisdnet, Landlord
Actor’s Goal: To examine the access history for a particular door.
Participating Actors: Database, Landlord
Preconditions: Resident/Landlord is currently logged in the system and is shown
a hyperlink “View Access History.”
Postconditions: None.
Flow of Events for Main Success Scenario:
Resident/Landlord clicks the hyperlink “View Access History”
System prompts for the search criteria (e.g., time frame, door location, actor role, event
type, etc.) or “Show all”
Resident/Landlord specifies the search criteria and submits
System prepares a database query that best matches the actor’s search criteria and

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.

Example 2.1 Restaurant Automation, terminating a worker employment


Consider the restaurant automation project described on the book website (given in Preface). One of
the requirements states that the restaurant manager will be able to manage the employment status:
REQ8: The manager shall be able to manage employee records for newly hired employees, job
reassignments and promotions, and employment termination.
Based on the requirement, a student team derived a use case for employment termination, as shown:
Use Case UC-10: Terminate Employee (FIRST VERSION)
Related Requirem’ts: REQ8
Initiating Actor: Manager
Actor’s Goal: To fire or layoff an employee.
Participating Actors:
Preconditions:
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
(a) System removes the employee from Employee List; (b) updates Employee List
Flow of Events for Extensions (Alternate Scenarios):
3a. Manager selects Cancel option
System does not delete the employee

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.

System Sequence Diagrams


A system sequence diagram represents in a visual form a usage scenario that an actor experiences
while trying to obtain a service from the system. In a way, they summarize textual description of
the use case scenarios. As noted, a use case may have different scenarios, the main success
scenario and the alternate ones. A system sequence diagram may represent one of those
scenarios in entirety, or a subpart of a scenario. Figure 2-20 shows two examples for the Unlock
use case.

127
(a)

: System
User AlarmBell Police
«initiating actor» «supporting actor» «offstage actor»

select function(“unlock")

loop prompt for the


press
(b) Press
Verify
enter key
press

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.

2.4.4 Security and Risk Management


A business process risk is the chance of something happening that will have an impact on the
process objectives, such as financial or reputational damage, and is measured in terms of
likelihood and consequence. Identifying and preempting the serious risks that will be faced by the
system is important for any software system, not only for the ones that work in critical settings,
such as hospitals, etc. Potential risks depend on the environment in which the system is to be
used. The root causes and potential consequences of the risks should be analyzed, and reduction
or elimination strategies devised. Some risks are intolerable while others may be acceptable and
should be reduced only if economically viable. Between these extremes are risks that have to be
tolerated only because their reduction is impractical or grossly expensive. In such cases the
probability of an accident arising because of the hazard should be made as low as reasonably
practical (ALARP).

Risk Identification Risk Type Risk Reduction Strategy


Lock left disarmed (when it should be armed) Intolerable Auto-lock after autoLockInterval
Lock does not disarm (faulty mechanism) ALARP Allow physical key use as alternative
To address the intolerable risk, we can design an automatic locking system which observes the
lock state and auto-locks it after autoLockInterval seconds elapses. The auto-locking system could
be made stand-alone, so its failure probability is independent of the main system. For example, it
could run in a different runtime environment, such as separate Java virtual machines, or even on
a separate hardware and energy source.

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.

2.4.5 A Challenge in Software Engineering (2)


“It’s really hard to design products by focus groups. A lot of times, people don’t know what they want until
you show it to them.” —Steve Jobs, BusinessWeek, May 25 1998

A key challenge, probably the most important one, is that


we usually know only approximately what we are to Disciplined development
do. But, a general understanding of the problem is not
enough for success. We must know exactly what to do
Idea Product
because programming does not admit vagueness—it is SOFTWARE ENGINEERING
a very explicit and precise activity.

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.

2.5 Analysis: Building the Domain Model

“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

Boundary concepts Concept 3 Concept 3

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

We begin with analysis. In Section 1.2.3 I likened object-oriented analysis to setting up an


enterprise. The analysis phase is concerned with the “what” aspect—identifying what workers
need to be hired and what things acquired. Design (Section 2.6) deals with the “how” aspect—
how these workers interact with each other and with the things at their workplace to do their
part of the job. Of course, as any manager would tell you, it is difficult to make a clear boundary
between the “what” and the “how.” We should not be purists about this—the distinction between
Analysis and Design is primarily to indicate where the emphasis should be during each stage of
development.
We already encountered concepts and their relations in Section 1.3. We described concept maps
as diagrams of knowledge about problem domains. Domain models are similar, but somewhat
more complex, as we will see soon.

2.5.1 Identifying Concepts


Back to our setting-up-an-enterprise approach, we need to hire workers with appropriate
expertise and acquire things they will work with. To announce the openings on an employment
websiten, we start by listing the positions---or, better responsibilities--for which we are hiring. We
identify the responsibilities by examining the use case scenarios and system sequence diagrams.
For example, we need a worker to verify whether or not the key entered by the user is valid, so
we title this position KeyChecker. We also need a worker to keep track of the collection of valid
keys, so we advertise an opening for KeyStorage. Further, to operate the lock and the light/switch,
we come up with LockOperator and LightOperator positions, respectively. Note that concept
name is always a noun phrase.
In building the domain model, a useful strategy is to start from the “periphery” (or “boundary”) of
the system, as illustrated in Figure 2-22. That is, we start by assigning concepts that handle
interactions between the actors and the system. Each actor interacts with at least one boundary
object. The boundary object collects the information from the actor and translates it into a form
that can be used by “internal” objects. Also,the boundary object may translate the information in
the other direction, from “internal” objects to a format suitable for an actor.

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:

• «boundary», - between an actor and the system


• «control», - ensures that a use case and its business logic get all the processing they need
• and «entity» - essentially long-term information that is useful to stakeholders.

This is also shown in Figure 2-23.

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

«boundary» «boundary» «boundary» «boundary»


LockOperator LightOperator MusicPlayerOperator AlarmOperator

Figure 2-24: Generalization of the concept HouseholdDeviceOperator (Figure 2-23) as a


conceptual superclass obtained by identifying commonality among the concepts that operate
different household devices.

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.

(from UC-5, Step 2).


Rs3. Render the retrieved records into an HTML document for sending
D Page Maker
to actor’s Web browser for display.
Rs4. HTML document that shows the actor the current context, what
actions can be done, and outcomes of the previous actions. K Interface Page
Rs5. Prepare a database query that best matches the actor’s search Database
D
criteria and retrieve the records from the database (from UC-5, Step 4). Connection
Rs6. Filter the retrieved records to match the actor’s search criteria (from
D Postprocessor
UC-5, Step 6).
Rs7. List of “interesting” records for further investigation, complaint Investigation
K
description, and the tracking number. Request
Rs8. Archive the request in the database and assign it a tracking number
(from UC-5, Step 8). D Archiver
Rs9. Notify Landlord about the request (from UC-5, Step 8). D Notifier
Note that upon careful examination we may conclude that responsibility Rs6 is relatively simple
and it should be assigned to the Page Maker (Postprocessor concept would be rejected). Similarly,
responsibilities Rs8 and Rs9 may be deemed relatively simple and assigned to a single concept
Archiver (Notifier concept would be rejected).
Let us assume that we reject Postprocessor and keep Notifier because it may need to send follow-
up notifications.
The partial domain model corresponding to the subsystem that implements UC-5 is shown later
in Figure 2-26, completed with attributes and associations.

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.

Concept Pair Association Description


Association Name
Controller ↔ Page Controller passes requests to Page Maker and
conveys requests
Maker receives back pages prepared for displaying
Page Maker ↔ Database Connection passes the retrieved data to
provides data
Database Connection Page Maker to render them for display
Page Maker ↔
Page Maker prepares the Interface Page prepares
Interface Page
Controller ↔ Controller passes search requests to Database
conveys requests
Database Connection Connection
Controller passes a list of “interesting” records and
Controller ↔
complaint description to Archiver, which assigns the conveys requests
Archiver
tracking number and creates Investigation Request
Archiver ↔
Archiver generates Investigation Request generates
Investigation Request
Archiver ↔ Database Archiver requests Database Connection to store
requests save
Connection investigation requests into the database
Archiver requests Notifier to notify Landlord about
Archiver ↔ Notifier requests notify
investigation requests

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

Resident “Reading direction arrow.”


Has no meaning; it only helps reading
the association label, and is often left out.

(b)

«boundary» «boundary» «boundary» «boundary»


LockOperator LightOperator MusicPlayerOperator AlarmOperator
autoLockInterval
holdOpenInterval
acceptingInterval asks-alarm-time

«boundary» «entity» «entity»


IlluminationDetector PlayList NotificationList

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

C URRENT USER Accidental user


Does not speak
Chance-driven
Expected
Expected commands:
<meaningless - ignore>

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.

2.5.3 Domain Analysis


“Scientists should always state the opinions upon which their facts are based.”—Author unknown

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.

2.5.4 Contracts: Preconditions and Postconditions


Contracts express any important conditions about the attributes in the domain model. In addition
to attributes, contracts may include facts like the following: forming or breaking relations between
concepts, and the time-points at which instances of concepts are created/destroyed. You can
think of a software contract as equivalent to a rental contract. A rental contract spells out the
condition of an item prior to renting, and will spell out its condition subsequent to renting. For
example, for the operations Unlock and Lock, the possible contracts are:
Operation
Unlock

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.

2.6 Design: Assigning Responsibilities

“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

Controller : Checker : KeyStorage : LockCtrl

checkKey()
User
«initiating actor»
: System tem Timer
«offstage actor»
sk := getNext()
select function(“unlock")

prompt for the key


alt val != null setOpen(true)
enter key
verify key

signal: valid key, lock open [else] val == null : setLit(true)


open the lock,
turn on the light

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?

SIDEBAR 2.5: Interaction Diagrams


♦ Interaction diagrams display protocols—permitted dynamic relations among objects in the
course of a given activity. This sidebar highlights the main points and the reader should check
the details in a UML reference. You read a UML sequence diagram from the top down:
• At the top, each box represents an object, which may be named or not. If an object is
named, the name is shown in the box to the left of the colon. The class to which the
object belongs is shown to the right of the colon.
• Each timeline (dashed vertical line) describes the world from the vantage point of the
object depicted at the top of the timeline. As a convention, time proceeds downward,
although in a concurrent program the activities at the same level in the diagram do not
necessarily occur at the same time (see Section 5.3).
• Thin elongated boxes on a timeline represent the activities of the particular object (the
boxes/bars are optional and can be omitted)
• Links (solid horizontal lines with arrows) between the timelines indicate the followed-
by relation (not necessarily the immediately-followed-by relation). The link is annotated
with a message being sent from one object to another or to itself.
• Normally, all “messages” are method calls and, as such, must return. The return action
is denoted by a dashed horizontal link at the bottom of an activity box, oriented opposite
of the message arrow. Although this link is often omitted if the method has no return
value, the call returns nonetheless. Some novices just keep drawing message arrows in
one direction and forget that these must return at some point and the caller cannot
proceed (send new messages) before the callee returns.

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.

2.6.1 Design Principles for Assigning Responsibilities


A popular approach to micro-level design is known as responsibility-driven design (RDD). We
know the three types of responsibilities objects can have (Section 1.4.2):

1. Knowing responsibility: Memorizing data or references, such as data values, data


collections, or references to other objects, represented as a property

2. Doing responsibility: Performing computations, such as data processing, control of physical


devices, etc., represented as a method

3. Communicating responsibility: Communicating with other objects, represented as message


sending (method invocation)

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

Tight coupling Loose coupling

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

get( queryRequest : string )


specify
query accessList := retrieve(params : string)
request retrieve records

result

interfacePage := render(accessList : string)

alt access List != NULL


page :=
renderList()

[else] page :=List()


warning()
ing()
result «post page»
displayed

Figure 2-32: Sequence diagram for part of use case UC-5: Inspect Access History.

Example of Assigning Responsibilities


Let us go back to the problem of assigning responsibilities for UC-1 and UC-5 of the safe home
access case study, presented in Figure 2-31. We first consider use case UC-5: Inspect Access
History, and design the interactions only for its Steps 4 – 6. We identified responsibilities, and now
we describe alternative option for assigning those responsibilities with Figure 2-31(a).
Assigning responsibility R1--for retrieving records from the Database Connection--is relatively
straightforward. The object making the call must know the query parameters. This information
is first given to the Controller- - so, by t h e Expert Doer design principle,
r e s p o n s i b i l i t y R 1 s h o u l d b e a s s i g n e d t o the Controller.
As for R2 (rendering the retrieved list), there are alternative options. The object making the call
must know the access list as retrieved from the Database. The feasible alternatives are:

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

We also have the Logger to maintain the history log of accesses.


Note that there is a data-processing rule hidden in our design. This is also known as “business
rule”, because it specifies the business policy for dealing with a given situation:

IF key ∈ ValidKeys THEN disarm lock and turn lights on ELSE


increment failed-attempts-counter
IF failed-attempts-counter equals maximum number allowed THEN
block further attempts and raise alarm

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»

loop [for all stored keys]


val := checkKey( k )
sk := getNext()

compare(k, sk)

logTransaction( k, val )
«destroy»

alt val == true activate( "lock" )

dl := isDaylight()

opt dl == false activate( "bulb" )

[else] numOfAttempts++

alt numOfAttempts == maxNumOfAttempts


denyMoreAttempts()

activate( "alarm" )

[else]

prompt: "try again"

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

checkKey(k) : DeviceCtrl : PhotoSObs


b
checkIfDaylightAndIfNotThenSetLit()
controlLock(k) dl := isDaylight()

The caller dl == false


ok == true
could be
Controller or
Checker

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.

2.6.2 Class Diagram


A class diagram is created simply by reading the class names and their operations off of the
interaction diagrams. The class diagram of our case-study system is shown in Figure 2-35. Note
the similarities and differences with the domain model (Figure 2-25). Unlike domain models, the
class diagram notation is standardized by UML.
Because class diagrams gather class operations and attributes in one place, it is easier to size up
the relative complexity of classes in the system. The number of operations in a class correlates
with the amount of responsibility handled by the class. Good object-oriented designs distribute
expertise and workload among many cooperating objects. If you observe that some classes have
considerably greater number of operations than others, you should examine the possibility that
there may be undiscovered class(es) or misplaced responsibilities. Look carefully at operation
names and ask yourself questions such as: Is this something I would expect this class to do? Or, Is
there a less obvious class that has not been defined?

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

(a) (b) (c)


Figure 2-37: Example object communication patterns. (a) One-to-one direct messages. (b) One-
to-many untargeted messages. (c) Via a shared data element.

Write Write Run Verify &


test code test validate

Figure 2-38: Test-driven implementation.

Object Communication Patterns


A communication pattern is a message-sending relation imposed on a set of objects. As with any
relation, it can be one-to-one or one-to-many, and it can be deterministic or random. Some of
these patterns are illustrated in Figure 2-37. For more information on one-to-one, one-to-many,
determinism, randomness, etc., see Section 3.1.1.
Object-oriented design, particularly design patterns, is further elaborated in Chapter 5.

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.

2.7.1 Overview of Software Testing


“Testing shows the presence, not the absence of bugs.” —Edsger W. Dijkstra

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

Ensure that each Function Quality Acceptance Installation


component works test test test test
as specified

Verifies that functional Verifies non-functional Customer verifies Testing in user environment
requirements are satisfied requirements all requirements

Figure 2-39:Logical organization of software tests.

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.

2.7.2 Test Coverage and Code Coverage


Because exhaustive testing often is not practically achievable, a key issue is to know when we
have done enough testing. Test coverage measures the degree to which the specification or code
of a software program has been exercised by tests. In this section we interested in a narrower
notion of code coverage, which measures the degree to which the source code of a program has
been tested. There are several code coverage criteria, including equivalence testing, boundary
testing, control-flow testing, and state-based testing.
To select the test inputs, one may make an arbitrary choice of what one “feels” should be
appropriate input values. A better approach is to select the inputs randomly by using a random
number generator. Yet another option is choosing the inputs systematically, by partitioning large
input space into a few representatives. Arbitrary choice usually works the worst; random choice
works well in many scenarios; systematic choice is the preferred approach.
Equivalence Testing
Equivalence testing is a black-box testing method that divides the space of all possible inputs into
equivalence groups such that the program “behaves the same” on each group. The goal is to
reduce the total number of test cases by selecting representative input values from each
equivalence group. The assumption is that the system will behave similarly for all inputs from an
equivalence group, so it suffices to test with only a single element of each group.

Equivalence testing has two steps:

(i) partitioning the values of input parameters into equivalence groups


and
(ii) choosing the test input values.
The trouble with this approach is that it is just as hard to find the equivalence classes of inputs as
it is to prove correctness. Therefore, we use heuristics to select a set of test cases. Heuristics are
rules of thumb that are generally useful, but do not guarantee correctness. We are essentially
guessing based on experience and domain knowledge, and hoping that at least one of the
selected test cases belongs to each of the true (unknown) equivalence classes.

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 [numOfAttemps ≤ maxNumOfAttempts] /


signal-failure
action

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.

2.7.3 Practical Aspects of Unit Testing


Executing tests on single components (or “units”) or a composition of components requires that
the tested thing be isolated from the rest of the system. Otherwise we will not be able to localize
the problem uncovered by the test. But system parts are usually interrelated and cannot work
without one another. To substitute for missing parts of the system, we use test drivers and test
stubs. A test driver simulates the part of the system that invokes operations on the tested
component. A test stub is a minimal implementation that simulates the components which are
called by the tested component. The thing to be tested is also known as the fixture.
A stub is a trivial implementation of an interface that exists for the purpose of performing a unit
test. For example, a stub may be hard-coded to return a fixed value, without any computation. By
using stubs, you can test the interfaces without writing any real code. The implementation is really
not necessary to verify that the interfaces are working properly (from the client’s perspective—
recall that interfaces are meant for the client object, Section 1.4). The driver and stub are also
known as mock objects, because they pretend to be the objects they are simulating.
Each testing method follows this cycle:
1. Create the thing to be tested (fixture), the test driver, and the test stub(s)
2. Have the test driver invoke an operation on the fixture
3. Evaluate that the results are as expected
More specifically, a unit test case comprises three steps performed by the test driver:
1. Setup objects: create an object to be tested and any objects it depends on, and set them up
2. Act on the tested object
3. Verify that the outcome is as expected
Suppose you want to test the Key Checker class of the safe-home-access case study that we
designed in Section 2.6. Figure 2-41(a) shows the relevant excerpt sequence diagram extracted
from Figure 2-33. Class Checker is the tested component and we need to implement a test driver
to substitute Controllerand test stubs to substitute KeyStorageand Keyclasses.

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

// 3. verify assertEqual(result, false);


}
}

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.

Listing 2-2: Example test case for the Controller class.


public class ControllerTest {
// test case to check that the state Blocked is visited @Test public void
enterKey_accepting_toBlocked() {

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

2.7.4 Integration and Security Testing


In traditional methods, testing takes place relatively late in the development lifecycle and follows
the logical order Figure 2-39. Unit testing is followed by integration testing, which in turn is
followed by system testing. Integration testing works in a step-by-step fashion by linking together
individual components (“units”) and testing the correctness of the combined component.
Components are combined in a horizontal fashion and integration processes in different direction,
depending on the horizontal integration testing strategy.
In agile methods, testing is incorporated throughout the development cycle. Components are
combined in a vertical fashion to implement an end-to-end functionality. Each vertical slice
corresponds to a user story (Section 2.2.3) and user stories are implemented and tested in parallel.

Horizontal Integration Testing Strategies


There are various ways to start by combining the tested units. The simplest, known as “big bang”
integration approach, tries linking all components at once and testing the combination.
Bottom-up integration starts by combining the units at the lowest level of hierarchy. The
“hierarchy” is formed by starting with the units that have no dependencies to other units. For
example, in the class diagram of Figure 2-35, classes PhotoSObsrv, Logger, and DeviceCtrl do not
have navigability arrow pointing to any other class—therefore, these three classes form the
bottommost level of the system hierarchy (Figure 2-42(a)). In bottom-up integration testing, the
bottommost units (“leaf units”) are tested first by unit testing (Figure 2-42(b)). Next, the units
that have navigability to the bottommost units are tested in combination with the leaf units. The
integration proceeds up the hierarchy until the topmost level is tested. There is no need to
develop test stubs: The bottommost units do not depend on any other units; for all other units,
the units on which the currently tested unit depends on are already tested. We do need to develop
test drivers for bottom-up testing, although these can be relatively simple. Note that in real-world
systems unit hierarchy may not necessarily form a “tree” structure, but rather may include cycles
making it difficult to decide the exact level of a unit.
Top-down integration starts by testing the units at the highest level of hierarchy that no other
unit depends on (Figure 2-42(c)). In this approach, we never need to develop test drivers, but we
do need test stubs.

174
Controller
Level-4

Level-3 KeyChecker

Level-2 KeyStorage Key

Level-1 Logger PhotoSObsrv DeviceCtrl


(a)

Test
Logger Test Controller &
KeyChecker & KeyStorage &
Test Key & Logger & PhotoSObsrv
PhotoSObsrv & DeviceCtrl

Test
DeviceCtrl Test KeyChecker
(b)
& KeyStorage &
Key
Test Key &
KeyStorage

Test Controller &


Test Test Controller &
Test KeyChecker & KeyStorage &
Controller & KeyChecker &
Controller
KeyChecker KeyStorage & Key
Key & Logger & PhotoSObsrv
& DeviceCtrl (c)

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.

2.7.5 Implementing Test-Driven Development


“Real programmers don’t comment their code. If it was hard to write, it should be hard to understand.”
—Unknown

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.

Listing 2-3: Implementation Java code of the main class, called


HomeAccessControlSystem, of the case-study home-access system.
import java.io.IOException; import
java.io.InputStream;
import java.util.TooManyListenersException; import
javax.comm.CommPortIdentifier;
import javax.comm.NoSuchPortException;

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;

public class HomeAccessControlSystem extends Thread implements


SerialPortEventListener {
protected Controller ctrler_; // entry point to the domain logic protected InputStream
inputStream_; // from the serial port protected StringBuffer key_ = new StringBuffer(); // user key
code public static final long keyCodeLen_ = 4; // key code of 4 chars

public HomeAccessControlSystem( KeyStorage ks, SerialPort


ctrlPort
){
try {
inputStream_ = ctrlPort.getInputStream();
} catch (IOException e) { e.printStackTrace(); }

LockCtrl lkc = new LockCtrl(ctrlPort); LightCtrl lic = new


LightCtrl(ctrlPort); PhotoObsrv sns = new PhotoObsrv(ctrlPort);
AlarmCtrl ac = new AlarmCtrl(ctrlPort);

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
}

/** The first argument is the handle (filename, IP address, ...)


* of the database of valid keys.
* The second arg is optional and, if present, names
* the serial port. */
public static void main(String[] args) { KeyStorage ks = new
KeyStorage(args[1]);

SerialPort ctrlPort; String portName =


"COM1";
if (args.length > 1) portName = args[1]; try { //
initialize
CommPortIdentifier cpi = CommPortIdentifier.getPortIdentifier(portName);
if (cpi.getPortType() == CommPortIdentifier.PORT_SERIAL) { ctrlPort = (SerialPort)
cpi.open();

// start the thread for reading from serial port new


HomeAccessControlSystem(ks, ctrlPort);
} catch (NoSuchPortException e) { System.err.println("Usage:
................................................................................... port_name");
}

178
try {
ctrlPort.setSerialPortParams(
9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE
);
} catch (UnsupportedCommOperationException e) { e.printStackTrace();
}
}

/** Thread method; does nothing, just waits to be interrupted


* by input from the serial port. */ public void run()
{
while (true) { // alternate between sleep/awake periods try { Thread.sleep(100); }
catch (InterruptedException e) { /* do nothing */ }
}
}

/** Serial port event handler


* Assume that the characters are sent one by one, as typed in. */ public void
serialEvent(SerialPortEvent evt) {
if (evt.getEventType() == SerialPortEvent.DATA_AVAILABLE) { byte[] readBuffer = new
byte[5]; // 5 chars, just in case

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

if (key_.length() >= keyCodeLen_) { // got the whole key?


// pass on to the Controller ctrler_.enterKey(key_.toString());
// get a fresh buffer for a new user key key_ = new
StringBuffer();
}
}
}
}

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;
}

public enterKey(String key_code) { Key user_key =


new Key(key_code) if
(checker_.checkKey(user_key)) {
lockCtrl_.setArmed(false);
if (!sensor_.isDaylight()) { lightCtrl_.setLit(true); } numOfAttempts_ = 0;
} else {
// we need to check the attempt period as well, but ... if (++numOfAttempts_ >=
maxNumOfAttempts_) {
alarmCtrl_.soundAlarm();
numOfAttempts_ = 0; // reset for the next user
}
}
}
}

import java.util.Iterator; public class

KeyChecker {
protected KeyStorage validKeys_;

public KeyChecker(KeyStorage ks) { validKeys_ = ks; } public boolean

checkKey(Key user_key) {
for (Iterator e = validKeys_.iterator(); e.hasNext(); ) { if (compare((Key)e.next(),
user_key) { return true; }
}
return false;
}

protected boolean compare(Key key1, Key key2) {

}
}

import javax.comm.SerialPort;

180
public class LockCtrl {
protected boolean armed_ = true;

public LockCtrl(SerialPort ctrlPort) {


}
}

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.

Using Polymorphism Instead of Conditional Logic


An important feature of programming languages is the conditional. This is a statement that causes
another statement to execute only if a particular condition is true. One can use simple “sentences”
to advise the computer, “Do these fifteen things one after the other; if by then you still haven’t
achieved such-and-such, start all over again at Step 5.” In the same way, one can symbolize a
complex conditional command like: “If at that particular point of runtime, this happens, then do
so-and-so; but if that happens, then do such-and-such; if anything else happens, whatever it is,
then do thus-and-so.” Using the language constructs such as IF-THEN-ELSE, DO-WHILE, or SWITCH,
the occasion for action is precisely specified. The problem with conditionals is that they may make
code difficult to understand and prone to errors.
Polymorphism allows avoiding explicit conditionals when you have objects whose behavior varies
depending on their types. As a result, you find that conditional statements are much less common
in an object- oriented program. Polymorphism can have many advantages. The biggest gain is
reusability. Imagine a program in which the same set of conditions appears in many places. If you
want to add a new type, you have to find and update all the conditionals. But with polymorphism,
you just create a new subclass and provide the appropriate methods. Clients of the class do not
need to know about the subclasses, which reduces the dependencies in your system and makes
it easier to update.
Still, some conditionals are needed, like checks for boundary conditions. But when you repeatedly
work with similar variables, applying different operations to them based on condition, you are in
an ideal scenario to use polymorphism and reduce the code complexity.

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)

enterKey(()) open tthe llock,,


tturn on tthe light
Landllord
addElement()
public class Hom
siignal:: vallid key,, llock open

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.

2.8 Summary and Bibliographical Notes

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

Section 2.1: Software Development Methods


[MacCormack, 2001; Larman & Basili, 2003; Ogawa & Piller, 2006]

Section 2.2: Requirements Engineering


IEEE Standard 830 was last revised in 1998 [IEEE 1998]. The IEEE recommendations cover such
topics as how to organize requirements specifications document, the role of prototyping, and the
characteristics of good requirements.
The cost-value approach for requirement prioritization was created by Karlsson and Ryan [1997].
A great introduction to user stories is [Cohn, 2004]. It describes how user stories can be used to
plan, manage, and test software development projects. It is also a very readable introduction to
agile methodology.
More powerful requirements engineering techniques, such as Jackson’s “problem frames”
[Jackson, 2001], are described in the next chapter.

Section 2.3: Software Architecture

Section 2.4: Use Case Modeling


An excellent source on methodology for writing use cases is [Cockburn, 2001].
System sequence diagrams were introduced by [Coleman et al., 1994; Malan et al., 1996] as part
of their Fusion Method.

Section 2.5: Analysis: Building the Domain Model


The approach to domain model construction presented in Section 2.5 is different from, e.g., the
approach in [Larman, 2005]. Larman’s approach can be summarized as making an inventory of the
problem domain concepts. Things, terminology, and abstract concepts already in use in the
problem domain are catalogued and incorporated in the domain model diagram. A more inclusive
and complex model of the business is called Business Object Model (BOM) and it is also part of
the Unified Process.
An entrepreneurial reader may wish to apply some of the analysis patterns described by Fowler
[1997] during the analysis stage. However, the main focus at this stage should be to come up with
any idea of how to solve the problem, rather than finding an optimal solution. Optimizing should
185
be the focus of subsequent iterations, after a working version of the system is implemented.

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.

Section 2.7: Test-driven Implementation


[Raskin, 2005] [Malan & Halland, 2004] [Ostrand et al., 2004]
Useful information on Java programming is available at:
http://www.developer.com/ (Gamelan) and http://www.javaworld.com/ (magazine)
For serial port communication in Java, I found useful information here (last visited 18 January
2006):
http://www.lvr.com/serport.htm
http://www.cs.tufts.edu/~jacob/150tui/lecture/01_Handyboard.html
http://show.docjava.com:8086/book/cgij/exportToHTML/serialPorts/SimpleRead.java.html
Also informative is Wikibooks: Serial Data Communications, at:
http://en.wikibooks.org/wiki/Programming:Serial_Data_Communications

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

“A problem well stated is a problem half-solved.””


—Charles Kettering

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

Motion detection perimeter


Remote
Transmitter
Figure 2-45: Depiction of the problem domain for Problem 2.10.

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

wakeup vital := readVitalSign( )

ranges := getValues()
abnormal := isOutOfRange(vital)
check if in/out

opt [ abnormal == TRUE ] send( Hospital Alert Abnormal Vitals )

result :=
faulty := isFaulty() run tests

isFailed(result)

opt [ faulty == TRUE ] send( Hospital Alert Sensor Failure )


data :=
exrcs := isExercising() read sensor

classify(data)

opt [ exrcs == TRUE ] adjust( exercise-mode )

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.

3.1 What is a System?

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

3.1.1 World Phenomena and Their Abstractions


The key to solving a problem is in understanding the problem. Because problems are in the real
world, we need good abstractions (models) of world phenomena. Good abstractions will help us
to represent accurately the knowledge that we gather about the world (that is, the “application
domain,” as it relates to our problem at hand). In object-oriented approach, key abstractions are
objects and messages. These served us well in Chapter 2 in understanding the problem and
deriving the solution. We are not about to abandon them now; rather, we will broaden our
horizons and perhaps take a slightly different perspective.
Usually we partition the world in different parts and consider different phenomena.See Figure 3-
1. A phenomenon is a fact, object, or occurrence that appears or is perceived to appear, when
observing the world or a part of it.
We can distinguish world phenomena by different criteria. Structurally, we have two broad
categories of phenomena:
- Individuals
- relations among individuals.
Logically, we can distinguish:
- causal
- vs. symbolic phenomena.

In terms of behavior, we can distinguish:

227
- deterministic
- vs. stochastic phenomena.

Next, I describe each kind briefly.

228
WORLD

Phenomena in Part i

Phenomena in Part j
Shared
phenomena

Phenomena in Part k

Figure 3-1: World partitioning into domains and their phenomena.

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 love triangles

Set of neighbors

Figure 3-2: Example relations: Neighbors(Person_i, Person_j) and InLoveTriangle(Person_i,


Person_j, Person_k).

♦ An entity is an individual with distinct existence, as opposed to a quality or relation. An entity


persists over time and can change its properties and states from one point in time to another.
Some entities may initiate events; some may cause spontaneous changes to their own states;
some may be passive.
Software objects and abstract concepts modeled in Chapter 2 are entities. But entities also include
real-world objects. The entities are determined by what part of the world is being modeled. A
student in our school registration case study (Section 1.3.2) is an entity; so is her course-load for
a semester; a listed-course is also an entity. They belong to the entity classes ”student”,
”coursesRegistered”, and ”course”, respectively.
♦ A value is not subject to change. The values we are interested in are such things as numbers
and characters, represented by symbols. For example, a value could be the numerical measure of
a quantity or a number denoting amount on some conventional scale, such as 7 kilograms.
In our case study (Section 1.3.2), a particular course is represented by a number of credits meant
to signify the value of effort for that course. Examples of value classes include integer, character,
string, and so on.

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.

Causal vs. Symbolic Phenomena


♦ Causal phenomena are events, or roles, or states relating entities. These are causal phenomena
because they are directly produced or controlled by some entity, and because they can give rise
to other phenomena in turn.
♦ Symbolic phenomena are values, and truths and states relating only values. They are called
symbolic because they are used to symbolize other phenomena and relationships among them. A
symbolic state that relates values—for example, the data content of a disk record—can be
changed by external causation, but we do not think of it as causal because it can neither change
itself nor cause change elsewhere.

Deterministic vs. Stochastic Phenomena


♦ Deterministic phenomena are the causal phenomena for which the occurrence or non-
occurrence can be established with certainty.
♦ Stochastic phenomena are the causal phenomena that are governed by a random distribution
of probabilities.

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.

Object (System part) State relations


Power button {Off, On}
Disc tray {Empty, Loaded}
Play button {Off, On}
… …

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:

Variable State relations


Door lock {Disarmed, Armed}
Bulb {Lit, Unlit}
Counter of failed attempts {0, 1, …, maxNumOfAttempts}
Auto-lock timer {0, 1, …, autoLockInterval}

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.

Example 3.1 Identifying SchoolReg States (First Attempt)


Consider our second case study of a school registration system (Section 1.3.2), and suppose we want
to identify the states of the registration process. There are many things that we can say about this
process, but what are the properties that relate to our problem? Here are some candidates:
• When are courses accessible? Are there multiple registration periods per year?
• What courses are currently offered?
• For each course, what are the prerequisites, and how many credits is it worth?
• Have enough people registered? Can more students register for this course?
The state variables can be summarized like so:

Variable State relations


Operating condition (or gate condition) {Open, Closed}
ith course prerequisites course code(s)
ith course credits {1, 2, 3, …}
People registered for ith course {0, 1, 2, 3, …}

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

Stock_1_Price Stock_1_Shares Stock_2_Price Stock_2_Shares

( prices and num. of shares for


all listed stocks )

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.

Observables vs. Hidden Variables

States Defined from Observable Phenomena


A state is an abstraction, and as such it is subjective—it depends on who is making the
abstraction. There are no “objective states”—every categorization of states is relative
to the observer. Of course, the same observer can come up with different abstractions.
The observer can also define new states based on observable phenomena; such states
are directly observed. Consider, for example, a fruit states: “green,” “semiripe,” “ripe,”
“overripe,” and “rotten.” The state of “ripeness” of a fruit is defined based on observable
parameters such as its skin color and texture, size, scent, softness on touch, etc. Similarly, a
“moving” state of an elevator is defined by observing its position over subsequent time moments
and calculating the trend.
For the auto-lock timer discussed in Case Study 1, we can define the states “CountingDown” and
“Idle” like so:
Δ
CountingDown(Timer) = The relation Equals(Timer, τ) holds true for τ decreasing with time
Δ
Idle(Timer) = The relation Equals(Timer, τ) holds true for τ remaining constant with time
Δ
The symbol = means that this is a defined state.

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.

Example 3.2 Identifying school registration States (Second Attempt)


Upon closer examination of Example 3.1 you might realize that a student may not care about those
specific variables. A student might care about little else than knowing what courses are available and
then registering for those courses. Given this we can represent the states of two things:
• “Course availability” states (“offered”, “open”, “full”, “closed”) are defined based on considering
the time of year and number of registered students.
• “registration status” states (“unregistered”, “pending”, “registered”) are defined based on the
students actions within the program.
It is possible to discern further nuances in many states. For example, the “closed” state can be reached
by several conditions such as not meeting prerequisites, registering for the wrong college, or being on
hold. While these may not be considerations a student necessarily cares about, as an actor, they may
be required by the institution nevertheless. Further emphasizing the care you must take when planning
out a design for potential software and the flexibility of mind to be able to recognize when a state is
represented as overly general.

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

Figure 3-6: Events take place at transitions between the states.

Example states could be:


Open – The Course is open to sign up for
Closed – The course is full
Pre-open – The course is closed but has not been open yet to registration

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.

3.1.3 Events, Signals, and Messages


Consider an event as indivisible. By “indivisible”, we mean that there is a state before it and a
state after it, but you must treat the event as instantaneous. This means that it cannot be broken
down further. An event should represent a sharp boundary between two distinct states. We also
need to assume that no two events occur simultaneously. All events happen sequentially, and
between successive events there are intervals of time in which nothing happens. Events and
intervals alternate: each event ends one interval and begins another. Likewise, each interval
(state) is initiated and terminated by an event. Consider the example in Figure 3-6. By examining
the time diagram, we partition time into intervals (“states”) and identify what point (“event”)
separates two intervals. Then we name the resulting five phenomena as shown in Figure 3-

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.

Example 3.3 Identifying Course Registration Events


Consider Example 3.2, where the states ”unregistered”, “pending”, or “registered”, are defined based
on recent student actions. The events that directly lead to transitioning between these states are
registration requests from students. There may be many other requests processed until the transition
happens, but we view the transitioning as an indivisible event. The events can be summarized like so:

Event Description
Submit The student submits the request for course registration
Success The system allows the student to be registered for the course

The events marking a registration transition are shown in Figure 3-7.

3.1.4 Context Diagrams and Domains


Now that we have defined basic phenomena, we can start the problem domain analysis. We do
this by placing the planned system in a context—the environment in which it will work. For this
we use context diagrams, which are essentially a bit more than the commonplace “block
diagrams.” Context diagrams are not part of UML. They were introduced by Michael Jackson
[1995] based on the notation dating back to structured analysis in the 1970s. The context diagram
represents the context of the problem that the developer sets out to solve. The block diagram
we encountered in Figure 1-32 is essentially a context diagram. Based on the partitioning in Figure
3-1, we show different domains as rectangular boxes and connect them with lines to indicate that
they share certain phenomena. Figure 3-8 is a context diagram of our system-to-be, labeled
“machine,”. This subsumes the broker’s role and the figure also shows abstract concepts such as
course load, and student. Jackson uses the term “machine” to avoid the ambiguities of the word
“system,” some of which were discussed in Section 2.4.2. We use several terms: “system-to-be,”
“software-to-be,” and “machine.”

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.

♦ A lexical domain is a physical representation of data—that is, of symbolic phenomena.

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.

3.1.5 Systems and System Descriptions


Now that we have defined domains as distinguishable parts of the world, we can consider any
domain as a system. A system is an organized or complex whole, an assemblage of things or parts
interacting in a coordinated way. All systems are affected by events in their environment--either
internal and under the organization’s control, or external and out of its control.
Most real-world problems require a dynamical model to capture a process which changes over
time. Depending on the application, the particular choice of model may be: deterministic or
244
stochastic; continuous or discrete (using differential or difference equations); or a hybrid.

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.

3.2 Notations for System Specification

“… 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

3.2.1 Basic Formalisms for Specifications


“You can only find truth with logic if you have already found truth without it.”
—Gilbert Keith Chesterton, The Man who was Orthodox

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.

More Advanced Set Theory


We define Cartesian product or cross product of two sets A and B (denoted as A × B) as the set
of all ordered pairs
〈x, y〉 where x ∈ A and y ∈ B.

We can define the n-fold Cartesian product as A × A × … × A.

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.

Refresher on Boolean Logic


The rules of logic give precise meaning to statements and so they play a key role in specifications.,
if descriptions are expressed in a standard and predictable manner, not only they can be easily
understood, but also automated tools can be developed to understand such
descriptions. This allows automatic checking of descriptions. p q p⇒q
T T T
A proposition is a
T F F
declarative sentence (a sentence that declares a fact) that is either true or
F T T
false, but not both.
F F T
Truth table
for p ⇒ q.
Examples of declarative sentence are “Dogs are mammals” and “one plus one equals three.” The
first proposition is true and the second one is false. The sentence “Write this down” is not a
proposition because it is not a declarative sentence. Also, the sentence “x is smaller than five” is
not a proposition because it is neither true nor false (depends on what x is). If a proposition is
true, its truth value is denoted by T and, conversely, the truth value of a false proposition is
denoted by F.
Many statements are constructed by combining one or more propositions, using logical operators,
to form compound propositions. Some of these operators are shown on top of Table 3-1.
A conditional (statement) is obtained by combining two propositions p and q to a compound
proposition “if p, then q.” It is also written as p ⇒ q and can be read as “p implies q.”
In the conditional statement p ⇒ q, p is called the premise and q is called the conclusion.
The conditional statement p ⇒ q is false when the premise p is true and the conclusion q is false,
and true otherwise.
It is important to note that conditional statements should not be interpreted in terms of cause
and effect. Thus, when we say “if p, then q,” we do not mean that the premise p causes the
conclusion q, but only that when p is true, q must be true as well1.
247
1 This is different from many programming languages. Most programming languages contain statements
such as if p then S, where p is a proposition and S is a program segment of one or more statements to
be executed. When such an if-then statement is encountered during the execution of a program, S
is executed if p is true, but S is not executed if p is false.

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

The statement p ⇔ q is a biconditional, or bi-implication, which means that p ⇒ q and q ⇒


p. The biconditional statement p ⇔ q is true when p and q have the same truth value, and is
false otherwise.

Predicate Logic Refresher


So far, we have considered propositional logic; now let us briefly introduce predicate logic. We
saw earlier that the sentence “x is smaller than 5” is not a proposition because it is neither true
nor false. This sentence has two parts: the variable x, which is the subject, and the predicate, “is
smaller than 5,” which refers to a property that the subject of the sentence can have.
We can denote this statement by P(x), where P denotes the predicate “is smaller than 5” and x is
the variable. The sentence P(x) is also said to be the value of the propositional function P at x. Once
a specific value has been assigned to the variable x, the statement P(x) becomes a proposition and
has a truth value.
In our example, by setting x = 3, P(x) is true; conversely, by setting x = 7, P(x) is false2.
There is another way of creating a proposition from a propositional function, called
quantification. Quantification expresses to what extent predicate is true over a range of elements.
It does so using words such as all, some, many, none, and few. The most common types of
quantification are universal and existential, shown at the bottom of Table 3-1.
The universal quantification of P(x) is the proposition “P(x) is true for all values of x in the domain,”
denoted as ∀x P(x). The value of x for which P(x) is false is called a counterexample of
∀x P(x).
The existential quantification is the proposition “There exists a value of x in the domain such that
P(x) is true,” denoted as ∃x P(x).
In constructing valid arguments, a key elementary step is replacing a statement with another
statement of the same truth value. We are particularly interested in compound propositions
formed from propositional variables using logical operators, as given in Table 3-1.

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.

Example 3.4 Translating Requirements into Logical Expressions


Translate the following two requirements for a school registration system into logical expressions:

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

We have the following propositions derived from REQ1:


`a`
(∀password) [C(password) ∧ D(titleIX) ∧ E(honest) ∧ b ⇒ f]

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.

Finite State Machines


The behavior of complex objects and systems depends not only on their immediate input, but also
on the past history of inputs. This memory property, represented as a state, allows such systems
to change their actions with time. A simple but important formal notation for describing such
systems is called finite state machines (FSMs). FSMs are used extensively in computer science and
data networking. The UML standard extends the FSMs into UML state machine diagrams (Section
3.2.2).
There are various ways to represent a finite state machine. One way is to make a table showing
how each input affects the state the machine is in. Here is the state table for the door lock used
in our case-study example.

266
lock unlock unlock

lock

Figure 3-10: State transition diagram for a door lock.


Present state
Armed Disarmed
lock Armed Armed
Input unlock Disarmed Disarmed

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.

Start v = (input-key ∈ Valid-keys)


i = (input-key ∉ Valid-keys)
M = maxNumOfAttempts

Figure 3-11: State transition diagram for the counter of unsuccessful lock-opening attempts.

267
lock unlock / beep unlock

(a)

lock / beep

lock unlock [key ∈ Valid-keys] / beep unlock

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

3.2.2 UML State Machine Diagrams


One of the key weaknesses of the original finite-state-machines model (described in the preceding
section) in the context of system and software specification is the lack of modularization
mechanisms. When considering the definitions of states and state variables in Section 3.1.2, FSMs
are suitable for representing individual simple states (or microstates). UML state machine
diagrams provide a standardized diagram notation for state machines. They also incorporate
extensions, such as macrostates and concurrent behaviors.

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:

• Perform an activity upon entry of the state


• Do an activity while in the state
• Perform an activity upon exit of the
state.

An example is shown in Figure 3-14.

Composite States and Nested States


UML state diagrams define superstates (or macrostates). A superstate is a complex state that is
further refined by decomposition into a finite state machine. A superstate can also be obtained
by aggregation of elementary states, as already seen in Section 3.1.2.
Suppose now that we wish to extend the diagram in Figure 3-13 to show the states ”unregistered”,
”pending”, and ”registered”, which we defined in Example 3.2. These states are a refinement of
the “registration status” state within which they are nested, as shown in Figure 3-15. This nesting
is depicted with a surrounding boundary known as a region and the enclosing boundary is called
a composite state. Given the composite state Traded with its three substates, the semantics of
nesting implies an exclusive OR (XOR) relationship. If the course is in the ”registration status” state
(the composite state), it must also be in exactly one of the three substates: ”unregistered”,
”pending”, or ”registered”.

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.

3.2.3 UML Object Constraint Language (OCL)


“I can speak French but I cannot understand it.” —Mark Twain

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.

OCL Constraints and Contracts


Contracts are constraints on a class that enable the users of the class, implementers, and
extenders to share the same assumptions about the class. A contract specifies constraints on the
class state that must be valid always or at certain times, such as before or after an operation is
invoked. The contract is between the class implementer about the promises of what can
be

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.

SIDEBAR 3.1: The Dependent Delegate Dilemma


♦ The class invariant is a key concept of object-oriented programming, essential for reasoning
about classes and their instances. Unfortunately, the class invariant is, for all but non-trivial
examples, not always satisfied. During the execution of the method that client object called on
the server object (“dependent delegate”), the invariant may be temporarily violated. This is
considered acceptable because in such an intermediate state the server object is not directly
usable by the rest of the world—it is busy executing the method that client called—so it does not
matter that its state might be inconsistent. What counts is that the invariant will hold before and
after the execution of method calls.
However, if during the executing of the server’s method the server calls back a method on the
client, then the server may catch the client object in an inconsistent state. This is known as the
dependent delegate dilemma and is difficult to handle. The interested reader should check
[Meyer, 2005] for more details.

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.

3.3 Problem Frames

“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

Problem frames--proposed by Michael Jackson [1995; 2001]--are a way of understanding and


systematically describing the problem as a first step towards the solution. Problem frames
decompose the original complex problem into simple, known subproblems. Each frame captures
a problem class stylized enough to be solved by a standard method and simple enough to present
clearly separated concerns.
We have an intuitive feeling that a problem of data acquisition and display is different from a
problem of text editing, which in turn is different from writing a compiler that translates source
code to machine code. Some problems combine many of these simpler problems. The key idea of
problem frames is to identify the categories of simple problems, and to devise a methodology for
representing complex problems in terms of simple problems.
There are several issues to be solved for successful formulation of a problem frame methodology.
First, we need to identify the frame categories. One example is the information frame, which
represents the class of problems that are primarily about data acquisition and display.
We need to define the notation to be used in describing/representing the frames.
Then, given a complex problem, we need to determine how to decompose it into a set of problem
frames.

281
(a) The Problem The
Machine Domain Requirement

Domain properties
seen by the requirement
(b)

The Problem The


Machine Domain Requirement

Specification Domain properties Requirement


seen by the machine
a: specification interface phenomena b:
requirement interface phenomena

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.

3.3.1 Problem Frame Notation


Figure 3-19 shows one way to picture the relationship between the computer system to be
developed and the real world where the problem resides. The task of software development is to
construct the Machine by programming a general-purpose computer. The machine has an
interface “a”, consisting of a set of phenomena—typically events and states—shared with the
Problem Domain. Example phenomena are keystrokes on a computer keyboard, characters and
symbols shown on a computer screen, signals on the lines connecting the computer to an
instrument, etc.
The purpose of the machine is described by the Requirement, which specifies that the machine
must produce and maintain some relationship among the phenomena of the problem domain.
For example, to disarm the lock device when a correct code is presented, or to ensure that the
figures printed on a restaurant check correctly reflect the patron’s consumption.
Phenomena “a” shared by a problem domain and the machine are called specification
phenomena. Conversely, phenomena “b” articulate the requirements and are called the
requirement phenomena. Although “a” and “b” may be overlapping, they are generally distinct.
The requirement phenomena are the subject matter of the customer’s requirement, while the
282
specification phenomena describe the interface at which the machine can monitor and control
the problem domain.

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.

3.3.2 Problem Decomposition into Frames


Problem analysis relies on a strategy of problem
decomposition based on the type of problem domain and
the domain properties. The resulting subproblems are
treated independently of other subproblems, which is
the basis of effective separation of concerns. Each
subproblem has its own machine (specification), problem
domain(s), and requirement. Each subproblem is a
projection of the full problem, like color separation in
printing, where colors are separated independently and
then overlaid (superimposed) to form the full picture.
Jackson [2001] identifies five primitive problem frames,
which serve as the basic units of problem decomposition.
These are: (i) required behavior, (ii) commanded
behavior, (iii) information display, (iv) simple workpieces,
and (v) transformation. They differ in their
requirements, domain characteristics, domain involvement (whether the domain is controlled,
active, inert, etc.), and the frame concern. These problem frames correspond to the problem
types identified earlier in Figure 2-11.
Each frame has a particular concern, which is a set of generic issues that need to be solved when
solving the frame. Let’s take a look.

284
Control
CM!C1 Controlled
C3 Required
Machine Domain C Behavior
CD!C2

Figure 3-20: Required Behavior Frame: Problem frame diagram.

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

Basic Frame Type 1: Required Behavior


In this scenario, we need to build a machine which controls the behavior of a part of the physical
world according to given conditions.
Figure 3-20 shows the frame diagram for the required behavior frame. The control machine is the
system to be built. The controlled domain is the part of the world to be controlled. The
requirement, giving the condition to be satisfied by the behavior of the controlled domain, is
called the required behavior.
The controlled domain is a causal domain, as indicated by the C in the bottom right corner of its
box. Its interface with the machine consists of two sets of causal phenomena: C1, controlled by
the machine, and C2, controlled by the controlled domain. The machine imposes the behavior on
the controlled domain by the phenomena C1; the phenomena C2 provide feedback.

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

Figure 3-22: Simple Workpieces Frame: Problem frame diagram.

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.

Basic Frame Type 2: Commanded Behavior


In this scenario, we need to build a machine which allows an operator to control the behavior of
a part of the physical world by issuing commands.
286
Basic Frame Type 3: Information Display
In this scenario, we need to build a machine which acquires information about a part of the
physical world and presents it at a given place in a given form.

Basic Frame Type 4: Simple Workpieces


In this scenario, we need to build a machine which allows a user to create, edit, and store some
data representations, such as text or graphics. The lexical domain that will be edited may be
relatively simple to design, such as text document for taking notes. It may also be very complex,
such as creating and maintaining a “social graph” on a social networking website. A videogame is
another example of a very complex digital (lexical) domain that is edited as the users play and
issue different commands.
Figure 3-22 (above) shows the Simple Workpieces frame diagram. Figure 3-23 (below) shows an example.

287
Figure 3-23: Simple Workpieces Frame: Example. (course registration environment)

Basic Frame Type 5: Transformation


In this scenario, we need to build a machine takes an input document and produces an output
document according to certain rules, where both input and output documents may be formatted
differently. For example, given the records retrieved from a relational database, the task is to
render them into an HTML document for Web browser display.
A key concern for a transformation frame problem is to define the order in which the data
structures of the input data and output data will be traversed and their elements accessed. For
example, if the input data structure is a binary tree, then it can be traversed in pre-order, in-order,
or post-order manner.

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.

3.3.3 Combinations of Problem Frames


Real-world problems almost always consist of combinations of simple problem frames. Problem
frames help us achieve understanding of simple subproblems and derive solutions (machines) for
these problem frames. Once the solution is reached at the local level of specialized frames, the
integration—or composition--—is needed to make a coherent whole. Often, this requires
specialized understanding.
There are some standard composite frames, consisting of compositions of two or more simple
288
problem frames.

289
Domain
1
The
Requirements

Domain
5

(a) The Domain


Machine 2

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.

LL 22 33 44 Requests outstanding to visit the floors

L 2 3 4 5 6 7 Floor where the elevator currently is

Down Up Direction of travel

Button to summon the elevator to this floor

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.

4.1 Fundamentals of Measurement Theory


“It is better to be roughly right than precisely wrong.” —John Maynard Keynes

The Hawthorne effect is an increase in worker productivity produced by the psychological

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

4.1.1 Measurement Theory


Measurement theory is a branch of applied mathematics. 01
89

The specific theory we use is called the representational 5


4
3
2

theory of measurement. It formalizes our intuitions about


the way the world actually works.
Measurement theory allows us to use statistics and probability to understand quantitatively the
possible variances, ranges, and types of errors in the data.

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.

Some Basic Measures


Ratio
Proportion
Percentage
Rate
Six Sigma

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.

4.2 What to Measure?

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.

4.2.1 Use Case Points


Intuitively, the more complicated requirements a project has, the more effort it takes to design
and implement. In addition, the effort depends not only on inherent difficulty or complexity of the
problem, but also on what tools the developers employ and how skilled the developers are. The
factors that determine the time to complete a project include:
• Functional requirements: These are often represented with use cases (Section 2.3). The
complexity of use cases, in turn, depends on the number and complexity of the actors and
the number of steps (transactions) to execute each use case.
• Nonfunctional requirements: These describe the system’s nonfunctional properties,
known as FURPS+ (see Section 2.2.1), such as security, usability, and performance. These
are also known as the “technical complexity factors.”
• Environmental factors: Various factors such as the experience and knowledge of the
development team, and how sophisticated tools they will be using for the development.
An estimation method that takes into account the above factors early in a project’s life cycle, and
produces a reasonable accurate estimate--say within 20% of the actual completion time--would
be very helpful for project scheduling, cost, and resource allocation.
Because use cases are developed at the earliest or notional stages of system design, they afford
opportunities to understand the scope of a project early in the software life-cycle. The Use Case
Points (UCP) method provides the ability to estimate the hours of work a project requires based
on its use cases. The UCP method analyzes the use case actors, scenarios, nonfunctional
requirements, and environmental factors, and abstracts them into an equation. Detailed use case
descriptions (Section 2.3.3) must be derived before the UCP method can be applied. The UCP
method cannot be applied to “sketchy” (or poorly-defined) use cases. As discussed in Section
2.3.1, we can apply user story points for project effort estimation at this very early stage. User
story points were described in Section 2.2.3.
The formula for calculating UCP is composed of three variables:
1. Unadjusted Use Case Points (UUCP), which measures the complexity of the functional
requirements
2. The Technical Complexity Factor (TCF), which measures the complexity of the
nonfunctional requirements
3. The Environment Complexity Factor (ECF), which assesses the development team’s
experience and their development environment

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!

Unadjusted Actor Weight (UAW)


An actor in a use case might be a person, another program, a piece of hardware, etc. The weight
for each actor depends on how sophisticated the interface between the actor and the system is.
Some actors, such as a user working with a text-based command-line interface, have very simple
needs and increase the complexity of a use case only slightly. Other actors, such as a user working
with a highly interactive graphical user interface, have a much more significant impact on the
effort to develop a use case. To capture these differences, each actor in the system is classified as
simple, average, or complex, and is assigned a weight as shown in Table 4-1. This scale for rating
actor complexity was devised by expert developers based on their experience. Notice that this is
an ordinal scale (Section 4.1.1). You can think of this as a scale for “star rating,” similar to “star
ratings” of books (Amazon.com), films (IMDb.com), or restaurants (yelp.com). Your task is, using
this scale, to assign “star ratings” to all actors in your system. In our case, we can assign one, two,
or three “stars” to actors, corresponding to “Simple,” “Average,” or “Complex” actors,
respectively. Table 4-2 shows my ratings for the actors in the case study of home access control,
for which the actors are described in Section 2.3.1.

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:

UAW(home access) = 5 × Simple + 2 × Average + 1 × Complex = 5×1 + 2×2 + 1×3 = 12

Unadjusted Use Case Weight (UUCW)


The UUCW is derived from the number of use cases in three categories: simple, average,
and complex (see Table 4-3). Each use case is categorized based on the number of steps
(or, transactions) within its event flow, including both the main success scenario and
alternative scenarios (extensions).
The number of steps in a scenario affects the estimate. A large number of steps in a use case
scenario will bias the UUCW toward complexity and increase the UCPs. A small number of steps
will bias the UUCW toward simplicity and decrease the UCPs. Sometimes, a large number of steps
can be reduced without affecting the business process.
The UUCW is calculated by tallying the number of use cases in each category, multiplying each
total by its specified weighting factor, and then adding the products. For example, Table 4-4
computes the UUCW for the sample case study.
How to count alternate scenarios—or “extensions”—is a debated topic. Initially, it was suggested
to ignore all scenarios except the main success scenario. However, extensions represent a
significant amount of work. Therefore, we recommend that they be included in effort estimation.
It is left to the Software Engineer ‘on the job’ as to how much weight to give to an alternate scenario
– an example approach could be to give each extension/alternate scenario a discounted weight - say
50% of the main success scenario. This accounts for some level of ‘copy and paste’ from (or
redundancy with) the main scenario.

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.

Technical Complexity Factor (TCF)—Nonfunctional


Requirements
Expert developers have identified 13 standard technical factors to estimate the impact of
nonfunctional requirements on project productivity. See Table 4-5. Each factor is weighted
according to its relative impact.
The development team should assess the perceived complexity of each technical factor from
Table 4-5 in the context of their project. Based on their assessment, they should assign another
“star rating,”--a perceived complexity value between 0 and 5. The perceived complexity value
reflects the team’s subjective perception of how much effort will be needed to satisfy a given
nonfunctional requirement. For example, if they are developing a distributed system (factor T1 in
Table 4-5), it will require more skill and time than if developing a system that will run on a single
computer. A perceived complexity value of 0 means that a technical factor is irrelevant for this
project, 3 corresponds to average effort, and 5 corresponds to major effort. When in doubt, use
3.
Each factor’s weight (Table 4-5) is multiplied by its perceived complexity factor to produce the
calculated factor. The calculated factors are summed to produce the Technical Total Factor. Table
4-6 calculates the technical complexity for the case study.
Two constants are used with the Technical Total Factor to produce the TCF. The constants limit
the impact the TCF has on the UCP equation (4.1) from a range of 0.6 (when perceived
complexities are all zero) to a maximum of 1.3 (when perceived complexities are all five).See
Figure 4-2(a).

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.4 (70, 1.3) 1.4 (0,


(0, 1.4)
1.4)

1.2 1.2

1 1
TCF

0.8 0.8
ECF

0.6 (0, 0.6) 0.6


(0, 0.6)
0.4 0.4 (32.5,
(32.5, 0.425)
0.425)
0.2 0.2
0 00 10 20 30 40
0 10 20 30 40 50 60 70 80

Technical Factor Total Environmental Factor Total

(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

TCF = Constant-1 + Constant-2 × Technical Factor Total = C1 + C2 ⋅∑Wi ⋅ Fi (4.2)


i=1

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

Environment Complexity Factor (ECF)


The environmental factors measure the experience level of the people on the project and the
stability of the project. See Table 4-7. Greater experience will reduce the UCP count, while lower
experience will increase it.. One might wish to consider other external factors, such as the
available budget, company’s market position, the state of the economy, etc.
The development team determines each factor’s perceived impact based on their perception the
factor has on the project’s success. A value of 1 means the factor has a strong, negative impact for
the project; 3 is average; and 5 means it has a strong, positive impact. A value of zero has no
impact on the project’s success.
Let’s apply this to Table 4-7 down below.
For factors E1-E4, 0 means no experience in the subject, 3 means average, and 5 means expert.
For E5, 0 means no motivation for the project, 3 means average, and 5 means high motivation.
For E6, 0 means unchanging requirements, 3 means average amount of change expected, and 5
means extremely unstable requirements.
For E7, 0 means no part-time technical staff, 3 means on average half of the team is part-time,
and 5 means all of the team is part-time.

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

Putting it All Together: Calculating the Use Case Points (UCP)


As a reminder, the UCP equation (4.1) is:
UCP = UUCP × TCF × ECF
From the above calculations, the UCP variables have the following values:
UUCP = 97
TCF = 0.91
ECF = 0.92
For the sample case study, the final UCP is the following:
UCP = 97 × 0.91 × 0.92 = 81.21 or 81 use case points.
Note for the sample case study, the combined effect of TCF and ECF was to increase the UUCP by
approximately 3 percent (94/97×100 − 100 = +3%). This is a minor adjustment, and can be
overlooked, given that many other inputs into the calculation are subjective estimates.

Discussion of the UCP Metric


Notice that the UCP equation (4.1) is not consistent with measurement theory, because the
counts are on a ratio scale and the scores for the adjustment factors are on an ordinal scale (see
Section 4.1.1). However, such formulas are often used in practice.
It is worth noticing that UUCW (Unadjusted Use Case Weight) is calculated simply by adding up
the perceived weights of individual use cases (Table 4-3). This assumes that all use cases are
completely independent, which usually is not the case. The merit of linear summation of size
measures was already discussed in Sections 1.2.5 and 2.2.3.

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.

4.2.2 Cyclomatic Complexity


One of the most common areas of complexity in a program lies in complex conditional logic (or,
control flow). Thomas McCabe [1974] devised a measure of cyclomatic complexity, intended to
capture the complexity of a program’s conditional logic. A program with no branches is the least
complex; a program with a loop is more complex; and a program with two crossed loops is more
complex still. Cyclomatic complexity corresponds roughly to an intuitive idea of the number of
different paths through the program—the greater the number of different paths through a
program, the higher the complexity.
McCabe’s metric is based on graph theory, in which you calculate the cyclomatic number of a
graph G, denoted by V(G), by counting the number of linearly independent paths within a
program. Cyclomatic complexity is
V(G) = e − n + 2 (4.4)
where e is the number of edges, n is the number of nodes.
Converting a program into a graph is illustrated in Figure 4-3. It follows that cyclomatic complexity
is also equal to the number of binary decisions in a program plus 1. If all decisions are not binary,
a three-way decision is counted as two binary decisions and an n-way case (select or switch)
statement is counted as n − 1 binary decisions. The iteration test in a looping statement is counted
as one binary decision.

317
CODE FLOWCHART GRAPH

(a) statement3

(b)

(c)

Figure 4-3: Converting software code into an abstract graph.

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.

Cyclomatic complexity appeals to software professionals because it is based on decisions and


branches, which is consistent with the logic pattern of design and programming. But it is not
without its drawbacks. Cyclomatic complexity ignores the complexity of sequential statements. In
other words, any program with no conditional branching has zero cyclomatic complexity! Also, it
does not distinguish different kinds of control flow complexity, such as loops vs. IF-THEN-ELSE
statements or selection statements vs. nested IF-THEN-ELSE statements.
Cyclomatic complexity metric was originally designed to indicate a program’s testability and
understandability. It allows you to also determine the minimum number of unique tests that must
be run to execute every executable statement in the program. One can expect programs with
higher cyclomatic complexity to be more difficult to test and maintain, due to their higher
complexity, and vice versa. To have good testability and maintainability, McCabe recommends
that no program module should exceed a cyclomatic complexity of 10. Many software

318
refactorings are aimed at reducing the complexity of a program’s conditional logic [Fowler, 2000;
Kerievsky, 2005].

4.3 Measuring Module Cohesion

Cohesion is defined as a measure of relatedness or consistency in the functionality of a software


unit. It identifies how much the parts within a unit belong together or are related. In an object-
oriented paradigm, a class can be a unit, the data can be attributes, and the methods can be parts.
Modules with high cohesion are usually robust, reliable, reusable, and easy to understand.
Modules with low cohesion are associated with undesirable traits such as being difficult to
understand, test, maintain, and reuse. Cohesion is an ordinal type of measurement, so it is usually
expressed as “high cohesion” or “low cohesion.”
We already encountered the term cohesion in Chapter 2, where it was argued that each unit of
design, whether it is at the modular level or class level, should be focused on a single purpose.
This means that it should have very few responsibilities that are logically related. Terms such as
“intramodular functional relatedness” or “modular strength” have been used to address the
notion of design cohesion.

4.3.1 Internal Cohesion or Syntactic Cohesion


Internal cohesion can best be understood as syntactic cohesion, which is evaluated by examining
the code of each individual module. It is thus closely related to the way in which large programs
are modularized. Modularization can be accomplished for a variety of reasons and in a variety of
ways.
A very crude modularization is to require that each module should not exceed certain size, e.g.,
50 lines of code. This would arbitrarily divide the program into blocks of about 50 lines each.
Alternatively, we may require that each unit of design has certain prescribed size. For example, a
package is required to have certain number of classes, or each class a certain number of attributes
and operations. We may well end up with the unit of design or code which is performing unrelated
tasks. Any cohesion here would be accidental or coincidental cohesion.
Coincidental cohesion does not usually occur in an initial design. However, as the design goes
through multiple changes and modifications, e.g., due to requirements changes or bug fixes, and
is under schedule pressures, the original design may evolve into a coincidental one. The original
design may be patched to meet new requirements, or a related design may be adopted and
modified instead of a fresh start. This will easily result in multiple unrelated elements in a design
unit.

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

4.3.2 Interface-based Cohesion Metrics


Interface-based cohesion metrics help evaluate cohesion among methods of a class early in the
analysis and design phase. These metrics evaluate the consistency of methods in a class’s interface
using the lists of parameters of the methods. They can be applied on class declarations that only
contain method prototypes (method types and parameter types). This means they do not require
the class implementation code. One such metric is Cohesion Among Methods of Classes (CAMC).
The CAMC metric is based on the assumption that the parameters of a method reasonably define
the types of interaction that method may implement.

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

Computing the CAMC Metric Value


To compute the CAMC metric value, we determine the following:
Determine a union of all parameters of all the methods of a class. Let this union set be (T).
A set of parameter object types for each method is also determined. Let this set be Mi.
For all methods in the class, we compute an intersection set of Mi with the union set T. Let this
intersection be set Pi. .
For all methods, we also compute the ratio of the size of the intersection set to the size of the
union set..
We take the summation of all intersection sets Pi.
We take the product of the number of methods and the size of the union set.
We divide the summation by the product.

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

4.3.3 Cohesion Metrics using Disjoint Sets of Elements


An early metric of this type is the Lack of Cohesion of Methods (LCOM1). This metric counts the
number of pairs of methods that do not share their class attributes. It considers how many disjoint
sets are formed by the intersection of the sets of the class attributes used by each method.

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.3.4 Semantic Cohesion


Although cohesion, in a sense, can be regarded as a system design concept, we can more properly
regard cohesion as a semantic concern.
Semantic cohesion is an externally discernable concept. It assesses whether the abstraction
represented by the module/class can be semantically considered a “whole”. Semantic complexity
metrics evaluate whether an individual class is really an abstract data type—in the sense of being
both complete and coherent. That is: to be semantically cohesive, a class should contain
everything that one would expect a class with those responsibilities to possess. No more, no less.
It is possible to have a class with high internal, syntactic cohesion, but little semantic cohesion.
Individual semantically cohesive classes may be merged, perhaps resulting in a class with poor
external semantic cohesion—while retaining internal syntactic cohesion.
For example, imagine a class that includes features of both a person and the car the person owns.
Let us assume that each person can own only one car, and that each car can only be owned by
one person. We have a one-to-one association. Then, person_id ↔ car_id, which would be
equivalent to data normalization. However, classes have not only data, but operations to perform
various actions. They provide behavior patterns for both aspects of our proposed class (person
and car). Assume there is no intersecting behavior between PERSON and CAR. What, then, is the
meaning of our class, presumably named CAR_PERSON? Such a class could be very internally
cohesive--but semantically, as a whole class seen from outside, the notion of a thing called a
“person-car” is nonsense.

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.

4.5.1 Algorithmic Information Content


I already mentioned that our abstractions are unavoidably approximate. The term often used is
“coarse graining,” which means that we are blurring detail in the world picture and single out only
the phenomena we believe are relevant to the problem at hand. Hence, when defining complexity
it is always necessary to specify the level of detail up to which the system is described, with finer
details being ignored.
One way of defining the complexity of a program or system is by means of its description, that is,
the length of the description. I discussed above the merits of using size metrics as a complexity

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

Logical Depth and Crypticity

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

Figure 4-6: A model of a limited working memory.

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.

4.6 Effort Estimation

“Adding manpower to a late software project makes it later.”


—Frederick P. Brooks, Jr., The Mythical Man-Month
“A carelessly planned project will take only twice as long.”
—The law of computerdom according to Golub
“The first 90 percent of the tasks takes 10 percent of the time and the last 10 percent takes the other 90
percent.” —The ninety-ninety rule of project schedules

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.

4.7 Summary and Bibliographical Notes

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.

Section 4.2: What to Measure?


[Henderson-Sellers, 1996] provides a condensed review of software metrics up to the publication
date, so it is somewhat outdated. It is technical and focuses on metrics of structural complexity.
Horst Zuse, History of Software Measurement, Technische Universität Berlin, Online at:
http://irb.cs.tu-berlin.de/~zuse/sme.html
[Halstead, 1977] distinguishes software science from computer science. The premise of software
science is that any programming task consists of selecting and arranging a finite number of
program “tokens,” which are basic syntactic units distinguishable by a compiler: operators and
operands. He defined several software metrics based on these tokens. However, software science

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.

Section 4.3: Measuring Module Cohesion


The ordinal scale for cohesion measurement with seven levels of cohesion was proposed
by Yourdon and Constantine [1979].
[Constantine et al., 1974; Eder et al., 1992; Allen & Khoshgoftaar, 1999; Henry & Gotterbarn,
1996; Mitchell & Power, 2005]
See also: http://c2.com/cgi/wiki?CouplingAndCohesion
B. Henderson-Sellers, L. L. Constantine, and I. M. Graham, “Coupling and cohesion: Towards a
valid suite of object-oriented metrics,” Object-Oriented Systems, vol. 3, no. 3, 143-158, 1996.
[Joshi & Joshi, 2010; Al Dallal, 2011] investigated the discriminative power of object-oriented
class cohesion metrics.

Section 4.4: Coupling

331
Section 4.5: Psychological Complexity
[Bennett, 1986; 1987; 1990] discusses definition of complexity for physical systems and defines
logical depth.

Section 4.6: Effort Estimation

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

(a) Draw the flowchart of the above algorithm.


(b) Draw the corresponding graph and label the nodes as n1, n2, … and edges as e1, e2, …
(c) Calculate the cyclomatic complexity of the above algorithm.

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

capturing the existing best practices, rather than inventing


untried procedures. Patterns are used primarily to improve
existing designs or code by rearranging it according to a Problems

“pattern.” By reusing a pattern, the developer gains efficiency,


by avoiding a lengthy process of trials and errors in search of a
solution, and predictability because this solution is known to
work for a given problem.
Design patterns can be of different complexities and for
different purposes. In terms of complexity, the design pattern
may be as simple as a naming convention for object methods
in the JavaBeans specification (see Chapter 7) or can be a
complex description of interactions between the multiple
classes, some of which will be reviewed in this chapter. In
terms of the purpose, a pattern may be intended to facilitate
component-based development and reusability, such as in the
JavaBeans specification, or its purpose may be to prescribe the
rules for responsibility assignment to the objects in a system,
as with the design principles described in Section 2.5.
As pointed earlier, finding effective representation(s) is a recurring theme of software

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

Figure 5-1: The players in a design pattern usage.

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

Figure 5-2: The concept of indirect communication in a Publisher/Subscriber system.

5.1 Indirect Communication: Publisher-Subscriber

“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:

Registers/Unregisters with publishers


Processes received event notifications

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

Info Info Info


Src Doer Src info Doer Src Doer

(2) event (info)

(a) (b) (c)


Figure 5-4: Request- vs. event-based communication among objects. (a) Direct request—
information source controls the activity of the doer. (b) Direct request—the doer controls its
own activity. Information source is only for lookup, but the doer must know when the
information is ready and available. (c) Indirect request—the doer controls its own activity and
does not need to worry about when the information will be ready and available. It receives a
prompt from the information source.

import java.util.ArrayList; public class


Content {
public Publisher source_; public
ArrayList data_;

public Content(Publisher src, ArrayList dat) { source_ = src;


data_ = (ArrayList) dat.clone(); // for write safety...
} // ...avoid aliasing and create a new copy
}

public interface Publisher {


public subscribe(Subscriber subscriber); public
unsubscribe(Subscriber subscriber);
}

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;

public class Checker implements Publisher { protected


KeyStorage validKeys_;
protected ArrayList subscribers_ = new ArrayList(); public Checker( ... ) { }

public subscribe(Subscriber subscriber) { subscribers_.add(subscriber); // could check whether this


} // subscriber already subscribed

public unsubscribe(Subscriber subscriber) { int idx =


subscribers_.indexOf(subscriber);
if (idx != -1) { subscribers_.remove(idx); }
}

public void checkKey(Key user_key) { boolean valid = false;


... // verify the user key against the "validKeys_" database

// notify the subscribers


Content cnt = new Content(this, new ArrayList());

if (valid) { // authorized user cnt.data.add("valid");


} else { // the lock is being tampered with
cnt.data.add("invalid");
}
cnt.data.add(key);

for (Iterator e = subscribers_.iterator(); e.hasNext(); ) { ((Subscriber) e.next()).receive(cnt);


}
}
}

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
}

public interface KeyIsInvalidSubscriber {


public void keyIsInvalid(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();

public Checker(KeyStorage ks) { validKeys_ = ks; }

public void subscribeKeyIsValid(KeyIsValidSubscriber sub) { keyValidSubscribers_.add(sub);


}

public void subscribeKeyIsInvalid(KeyIsInvalidSubscriber sub) { keyInvalidSubscribers_.add(sub);


}

public void checkKey(Key user_key) { boolean valid = false;


... // verify the key against the database

// notify the subscribers


LockEvent evt = new LockEvent(this, new ArrayList()); evt.data.add(key);

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);
}
}
}
}

public class DeviceCtrl implements KeyIsValidSubscriber { protected LightBulb bulb_;


protected PhotoSObs photoObserver_;

public DeviceCtrl(LockPublisher keyChecker, PhotoSObs sensor, .. )


{
photoObserver_ = sensor; // Code continues on next page

347
keyChecker.subscribeKeyIsValid(this);
...
}

public void keyIsValid(LockEvent event) {


if (!photoObserver_.isDaylight()) bulb_.setLit(true);
}
}

public class AlarmCtrl implements KeyIsInvalidSubscriber { public static final long


maxNumOfAttempts_ = 3;
public static final long interAttemptInterval_ =300000; //millisec protected long numOfAttempts_ =
0;
protected long lastTimeAtempt_ = 0;

public AlarmCtrl(LockPublisher keyChecker, ...) { keyChecker.subscribeKeyIsInvalid(this);


...
}

public void keyIsInvalid(LockEvent event) { long currTime =


System.currentTimeMillis();
if ((currTime – lastTimeAttempt_) < interAttemptInterval_) { if (++numOfAttempts_ >=
maxNumOfAttempts_) {
soundAlarm();
numOfAttempts_ = 0; // reset for the next user
}
} else { // this must be a new user's first mistake ... numOfAttempts_ = 1;
}
lastTimeAttempt_ = currTime;
}
}

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.

5.1.1 Applications of Publisher-Subscriber


The Publisher-Subscriber design pattern is used in the Java AWT and Swing toolkits for notification
of the GUI interface components about user generated events. (This pattern in Java is known as
Source-Listener or delegation event model -- see Chapter 7.)

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?

5.1.2 Control Flow


When it comes to control flow, direct and indirect communication types are different. Figure 5-6
ahead highlights these differences. In direct communication, , the control is centralized and all
flows emanate from the Controller. In indirect communication, the control is decentralized, and
it is passed as a token around, cascading from object to object. These diagrams also show the
dynamic architecture (behaviors) of the system.

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.

5.1.3 Pub-Sub Pattern Initialization


Figure 5-7 above shows an example of the “setup” part of the pattern. Note that this part plays a
major, but often ignored, role in the pattern. It essentially represents the master plan of solving
the problem using the publish-subscribe pattern and indirect communication.
Most programs are not equipped to split hard problems into parts and then use divide-and-
conquer methods. Also, few programs represent their goals, except perhaps as comments in their
source codes. However, a class of programs, called General Problem Solver (GPS), was developed
in the 1960s by Allen Newel, Herbert Simon, and collaborators. GPS did in fact have explicit goals
and subgoals—and it solved some significant problems [Newel & Simon, 1962].
I propose that goal representation in object-oriented programs should be implemented in the
setup part of the program. This way, the setup part can “rewire” the object relationships at any
time during the execution--instead of only at the initialization.

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

Knowing Responsibilities: + execute()


Knows receiver of action request
Optional: May know whether action is reversible
Doing Responsibilities:
Executes an action
Optional: May undo an action if it is reversible

(a) (b)

custodian invoker cmd : Command receiver : CommandHistory

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

next object + request()

Decorator RealSubject

+ request() + request()

(a)
ConcreteDecorator1 ConcreteDecorator2
(b)
+ request() + request()

client : : ConcreteDecorator1 : ConcreteDecorator2 : RealSubject

addedProcessing( )

addedProcessing( )

(c) ∗ and ‡ denote


added special-
case processing
moreAddedProcessing( )

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)

alt val == true activate()


activate()
dl := isDaylight()

opt dl == false
turnOnLight()

activate()
disarmLock()

turnOnMusicPlayer()

[else] numOfAttempts++

alt numOfAttempts == maxNumOfAttempts


AlarmCtrl
denyMoreAttempts() preceded by
suitable decorators
activate()

[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()

(b) ConcreteStateA ConcreteStateB

+ handle() + handle()

(a)

: Context currentState : ConcreteStateA

request( event-1 )
handle( event-1 ) nextState := this

result, nextState
event-1 event-2 [condition] / action-2
result

State-A State-B request( event-2 )

opt condition == true


handle( event-2 ) perform action-2

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

Proxy realSubject RealSubject

+ request() + request()

(a) (b)

client : : Proxy : RealSubject

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.

The causes of access complexity and the associated constraints include:


• The subject is located in a remote address space, e.g., on a remote host. In this case, the
invocation (sending messages to it) requires following complex networking protocols.
Solution: use the Remote Proxy pattern for crossing the barrier between different
memory spaces.
• Different access policies constrain the access to the subject. Security policies require that
access is provided only to the authorized clients, filtering out others. Safety policies may
impose an upper limit on the number of simultaneous accesses to the subject.

Solution: use the Protection Proxy pattern for additional housekeeping.


• Deferred instantiation of the subject, to speed up the performance (provided that its full
functionality may not be immediately necessary). For example, a graphics editor can be
started faster if the graphical elements outside the initial view are not loaded until they
are needed; only if the user changes the viewpoint, the missing graphics will be loaded.
Graphical proxies make this process transparent for the rest of the program.
Solution: use the Virtual Proxy pattern for optimization in object creation.

361
Obtain user role
and credentials
[ user == sys-admin ] Grant full access
to metadata and data
[else]

[ user == landlord ] Grant read/write access


to all data
[else]

[ user == tenant ] Grant read-only access to


personal data and activity
data for own apartment
[else]
Deny all access

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

dBase := getDbaseConnection( credentials )


dBc := java.sql.DriverManager.getConnection(…)

alt [credentls == "admin"]


proxyAD := create( dBc )
return proxyAD

[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;

public class WebDataAccessServlet extends HttpServlet { private String //


database access parameters
driverClassName = "com.mysql.jdbc.Driver",
dbURL = "jdbc:mysql://localhost/homeaccessrecords", dbUserID = null,
dbPassword = null;
private Connection dBase = null;

365
public void init(ServletConfig config) throws ServletException { super.init(config);
...

dbUserID = config.getInitParameter("userID"); dbPassword =


config.getInitParameter("password"); Factory factory = new
Factory(driverClassName, dbURL);
dBase = factory.getDbaseConnection(dbUserID, dbPassword);
}

public void service(


HttpServletRequest req, HttpServletResponse resp
) throws ServletException, java.io.IOException { Statement statm =
dBase.createStatement();
// process the request and prepare ...
String sql = // ... an SQL statement from the user's request boolean ok = statm.execute(sql);

ResultSet result = statm.getResultSet();


// print the result into the response (resp argument)
}
}

public class Factory { protected String


dbURL_;
protected Connection dBc_ = null;

public Factory(String driverClassName, String dbURL) {


// load the database driver class (the Driver class creates Class.forName(driverClassName);
// an instance of itself) dbURL_ = dbURL;
}

public Connection getDbaseConnection( String dbUserID,


String dbPassword
){
dBc_ = DriverManager.getConnection( dbURL_,
dbUserID, dbPassword
);

Connection proxy = null;

int userType = getUserType(dbUserID, dbPassword); switch (userType) {


case 1: // dbUserID is a system administrator proxy = new
DBConAdmin(dBc_, dbUserID, dbPassword);
case 2: // dbUserID is a landlord
proxy = new DBConLlord(dBc_, dbUserID, dbPassword); case 3: //
dbUserID is a tenant
proxy = new DBConTenant(dBc_, dbUserID, dbPassword); default: //
dbUserID cannot be identified
proxy = null;
}
return proxy;
}
}

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

public Statement createStatement() { statm =


dBc_.createStatement();

return createStatmProxy(statm, credentials_);


}

private Statement createStatmProxy( Statement statm,


credentials_
){
// create a proxy of java.sql.Statement that is appropriate
// for a user of the type "tenant"
}
}

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.

5.3 Concurrent Programming

“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

Step 1: Lock Step 2: Use Step 3: Unlock

Figure 5-20: Illustration of exclusion synchronization. The lock simply ensures that
concurrent accesses to the shared resource are serialized.

5.3.2 Exclusive Resource Access: Exclusion


Synchronization
If several threads attempt to access and manipulate the same data concurrently, a race condition
or race hazard exists. Here, the outcome of the execution depends on the particular order in
which the access takes place. Consider the following example of two threads simultaneously
accessing the same banking account (say, husband and wife interact with the account from
different branches):
Thread 1 Thread 2
oldBalance = account.getBalance(); ...
newBalance = oldBalance + deposit; oldBalance = account.getBalance();
account.setBalance(newBalance); newBalance =
oldBalance - withdrawal;
... account.setBalance(newBalance);
The final account balance is incorrect, and the value depends on the order of access. To avoid race
hazards, we need to control access to the common data (shared with other threads) and make
the access sequential instead of parallel.
A

D
ID
ID
N
D
NI
IN
N

373
thrd1 : Thread shared_obj : Object thrd2 : Thread

Critical region; the region


acquire lock
code fragment acquire lock
can have only one Successful: thrd1
thread executing it locks itself Unsuccessful:
at once. in & does the work thrd2 blocked and
waiting for the
shared object to
release lock become vacant
transfer lock

Lock transfer thrd2 acquires


controlled by the lock &
operating system does the work
and hardware

Figure 5-21: Exclusion synchronization pattern for concurrent threads.

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:

synchronized (this) { /* block of code statements */ }


When the lock is obtained, the statement is executed as if it were a synchronized method on the
locked object. Examples of exclusion synchronization in Java are given in Section 5.3.4.

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

region acquire lock


Lock
Lock transfer
transfer
controlled by
by Successful:
controlled
Does the work that can
operating system
operating system
affect the wait condition
and hardware
and hardware
notify()

transfer
transfer lock
lock release lock

Figure 5-23: Condition synchronization pattern for concurrent threads.

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

Total interaction time for both users

Average interaction time

Figure 5-24: Single-threaded, sequential servicing of users in Example 4.1.

5.3.4 Concurrent Programming Example


The following example illustrates cooperation between threads.

Example 5.1 Concurrent Home Access


In our SMART home access case-study, Figure 1-12 shows lock controls both on front and backyard doors.
Suppose two different tenants arrive (almost) simultaneously at the different doors and attempt the
access (see Figure 5-24). The single-threaded system designed in Section 2.7 would process them one-by-
one, which may cause the second user to wait a considerable amount of time. A user unfamiliar with the
system intricacies may perceive this as a serious glitch. Later, in Chapter 5, we also saw in Figure 5-24 that
the processor is idle most of the time- - such as between individual keystrokes or while the user tries
to recall the exact password after an unsuccessful attempt. Meanwhile, the second user is needlessly
waiting. To improve the user experience, let us now design a multithreaded solution.

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;

public class HomeAccessControlSystem_2x extends Thread


implements SerialPortEventListener {
protected ControllerThd contrlFront_; // front door controller protected ControllerThd
contrlBack_; // back door controller protected
InputStream inputStream_; // from the serial port protected StringBuffer keyFront_ = new
StringBuffer(); protected StringBuffer keyBack_ = new StringBuffer();
public static final long keyCodeLen_ = 4; // key code of 4 chars

public HomeAccessControlSystem_2x( KeyStorage ks,


SerialPort ctrlPort
){
try {
inputStream_ = ctrlPort.getInputStream();
} catch (IOException e) { e.printStackTrace(); }

LockCtrl lkc = new LockCtrl(ctrlPort); LightCtrl lic = new


LightCtrl(ctrlPort); PhotoObsrv sns = new PhotoObsrv(ctrlPort);
AlarmCtrl ac = new AlarmCtrl(ctrlPort);

contrlFront_ = new ControllerThd(


new KeyChecker(ks), lkc, lic, sns, ac, keyFront_
);
contrlBack_ = new ControllerThd(
new KeyChecker(ks), lkc, lic, sns, ac, keyBack_
);

try {
ctrlPort.addEventListener(this);
} catch (TooManyListenersException e) {

381
e.printStackTrace(); // limited to one listener per port
}
start(); // start the serial-port reader thread
}

/** The first argument is the handle (filename, IP address, ...)


* of the database of valid keys.
* The second arg is optional and, if present, names
* the serial port. */
public static void main(String[] args) {
...
// same as in Listing 2-1 above
}

/** Thread method; does nothing, just waits to be interrupted


* by input from the serial port. */ public void run()
{
while (true) {
try { Thread.sleep(100); }
catch (InterruptedException e) { /* do nothing */ }
}
}

/** Serial port event handler.


* Assume that the characters are sent one by one, as typed in.
* Every character is preceded by a lock identifier (front/back).
*/
public void serialEvent(SerialPortEvent evt) {
if (evt.getEventType() == SerialPortEvent.DATA_AVAILABLE) { byte[] readBuffer = new
byte[5]; // just in case, 5 chars

try {
while (inputStream_.available() > 0) {
int numBytes = inputStream_.read(readBuffer);
// could chk if numBytes == 2 (char + lockId) ...
}
} catch (IOException e) { e.printStackTrace(); }

// Append the new char to a user key, and if the key


// is complete, awaken the corresponding Controller thread if (inputStream_[0] == 'f') {
// from the front door
// If this key is already full, ignore the new chars if (keyFront_.length() <
keyCodeLen_) {
synchronized (keyFront_) { // CRITICAL REGION
keyFront_.append(new String(readBuffer, 1,1));
// If the key just got completed,
// signal the condition to others
if (keyFront_.length() >= keyCodeLen_) {
// awaken the Front door Controller keyFront_.notify(); //only
1 thrd waiting
}
} // END OF THE CRITICAL REGION
}
} else if (inputStream_[0] == 'b') { // from back door if (keyBack_.length() <
keyCodeLen_) {
synchronized (keyBack_) { // CRITICAL REGION
keyBack_.append(new String(readBuffer, 1, 1));

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 class ControllerThd implements Runnable { protected KeyChecker


checker_;
protected LockCtrl lockCtrl_; protected LightCtrl
lightCtrl_; protected PhotoObsrv sensor_;
protected AlarmCtrl alarmCtrl_; protected
StringBuffer key_;
public static final long maxNumOfAttempts_ = 3;
public static final long attemptPeriod_ = 600000; // msec [=10min] protected long
numOfAttempts_ = 0;

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;

Thread t = new Thread(this, getName()); t.start();


}

public void run() {


while(true) { // runs forever
synchronized (key_) { // CRITICAL REGION
// wait for the key to be completely typed in while(key_.length() <
HomeAccessControlSystem_2x.keyCodeLen_) { try {
key_.wait();
} catch(InterruptedException e) { throw new
RuntimeException(e);

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.

Hazards and Performance Penalties


Ideally, we would like for the processor to never be idle while there is a task waiting for execution.
Take a look at Figure 5-25(b). Even with threads, the processor may be idle while there are users
who are waiting for service. The key issue here is granularity--how to minimize the length (i.e.,
processing time) of the critical region.
The solution here ought to be to try to narrow down the critical region. This could be accomplished by one of the
following:

• 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

Total interaction time for both users

Average interaction time

Arrival Service
User 1
Time
(b) Waiting
Service
Arrival
User 2

Total interaction time for both users

Average interaction time

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.

5.4 Broker and Distributed Computing

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

Step 1: Define the server object interface


The server object will be running on the home computer. It currently offers a single method which
returns the list of home accesses for the specified time interval:

Listing 5-8: The Informant remote server object interface.


/* file Informant.java */ import
java.rmi.Remote;
import java.rmi.RemoteException; import
java.util.Vector;

public interface Informant extends Remote {


public Vector getAccessHistory(long fromTime, long toTime) throws RemoteException;
}

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.

Step 2: Define a class that implements this interface


The implementation class must extend class java.rmi.server.RemoteObject or one of its
subclasses. In practice, most implementations extend the subclass
java.rmi.server.UnicastRemoteObject, because this class supports point-to-point communication
using the TCP protocol. The class diagram for this example is shown in Figure 5-30. The
implementation class must implement the interface methods and a constructor (even if it has an
empty body). I adopt the common convention of adding “Impl” onto the name of our interface to
form the implementation class.

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;

public class InformantImpl extends UnicastRemoteObject implements Informant {


protected Vector accessHistory_ = new Vector();

/** Constructor; currently empty. */


public InformantImpl() throws RemoteException { }

/** Records all the home access attempts.


* Called from another class, e.g., from KeyChecker, Listing 2-2
* @param access Contains the entered key, timestamp, etc.
*/
public void recordAccess(String access) { accessHistory_.add(access);
}

/** Implements the "Informant" interface. */


public Vector getAccessHistory(long fromTime, long toTime) throws RemoteException {
Vector result = new Vector();
// Extract from accessHistory_ accesses in the
// interval [fromTime, toTime] into "result"

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 }

The code description is as follows:


Line 2 shows that our stub extends the RemoteStub class, which is the common superclass to
client stubs. It provides a wide range of remote reference semantics. It is similar to the broker
services in Figure 5-28(a).
Lines 7–15 perform part of the marshaling process of the getAccessHistory() method
invocation. Computational reflection—which is described in Section 7.3--is employed.
Lines 19–21 pass the remote server’s reference to the RemoteStub superclass.
Line 26 starts the definition of the stub’s version of the getAccessHistory()method.
Line 30 sends the marshaled arguments to the server and makes the actual call on the remote
object. It also gets the result back.
Line 31 returns the result to the client.
Remember that this is only the tip of the iceberg, in terms of how much of the Broker component
is revealed in a stub code. The Broker component, also known as Object Request Broker (ORB),
can provide very complex functionality and comprise many software objects.

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:

Listing 5-11: Refactored HomeAccessControlSystem class (from Listing 2-1) to instantiate


remote server objects of type Informant.
1 import java.rmi.Naming;
2
3 public class HomeAccessControlSystem extends Thread
4 implements SerialPortEventListener {
5 ...
6 private static final String RMI_REGISTRY_HOST = "localhost";
7
8 public static void main(String[] args) throws Exception {
9 ...
10 InformantImpl temp = new InformantImpl();
11 String rmiObjectName =
12 "rmi://" + RMI_REGISTRY_HOST + "/Informant";
13 Naming.rebind(rmiObjectName, temp);
14 System.out.println("Binding complete...");
15 ...
16 }
...
}

The code description is as follows:


Lines 3–5: The old HomeAccessControlSystem class as defined in Section 2.7.
Line 6: For simplicity’s sake, I use localhost as the host name. Since this is default, it could be
omitted.
Line 8: The main() method now throws Exception, to account for possible RMI-related
exceptions thrown by Naming.rebind().
Line 10: Creates an instance object of the server implementation class.

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.

Step 4: Create the client process


The client requests services from the server object. Because the client has no idea on which
machine and to which port the server is listening, the client looks up the RMI naming registry. In
return, it receives a stub object that knows all these things—however, but to the client, the stub
appears to behave the same as the actual server object. The client code is as follows:

Listing 5-12: Client class InformantClient invokes services on a remote Informant


object.
1 import java.rmi.Naming;
2
3 public class InformantClient {
4 private static final String RMI_REGISTRY_HOST = " ... ";
5
6 public static void main(String[] args) {
7 try {
8 Informant grass = (Informant) Naming.lookup(
9 "rmi://" + RMI_REGISTRY_HOST + "/Informant"
10 );
11 Vector accessHistory =
12 grass.getAccessHistory(fromTime, toTime);
13
14 System.out.println("The retrieved history follows:");
15 for (Iterator i = accessHistory; i.hasNext(); ) {
16 String record = (String) i.next();
17 System.out.println(record);
18 }
19 } catch (ConnectException conEx) {
20 System.err.println("Unable to connect to server!");
21 System.exit(1);
22 } catch (Exception ex) {
23 ex.printStackTrace();
24 System.exit(1);
25 }
26 ...
27 }
...
399
}

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.

SIDEBAR 5.2: How Transparent Should Object Distribution Be?


♦ If you experiment with Java RMI, such as by trying Problem 5.22, and then try to implement
the same with plain network sockets (see Appendix B), you will appreciate how easy it is to work
with distributed objects. I recall from using some CORBA object request brokers that some
provided even greater transparency than Java RMI. Although this certainly is an advantage, there
are perils of making object distribution so transparent that it becomes too easy.
The problem was that people often forgot that there is a network between distributed objects.
They built applications that relied on fine-grained communication across the network. Too many
round-trip communications led to poor performance and reputation problems for CORBA. An
interesting discussion of object distribution issues is available in [Waldo et al., 1994], from the
same developers who authored Java RMI [Wollrath et al., 1996].

401
5.5 Information Security

“There is nothing special about security; it’s just part of getting the job done.” —Rob Short

Information security is a nonfunctional property of the system, it is an emergent property. Owing


to different types of information use, there are two main security disciplines. Communication
security is concerned with protecting information when it is being transported between different
systems. Computer security is related to protecting information within a single system, where it
can be stored, accessed, and processed. Although both disciplines must work in accord to
successfully protect information, information transport faces greater challenges and so
communication security has received greater attention. Accordingly, this review is mainly
concerned with communication security. Notice that both communication- and computer security
must be complemented with physical (building) security as well as personnel security. Security
should be thought of as a chain that is as strong as its weakest link.
The main objectives of information security are:
• Confidentiality: ensuring that information is not disclosed or revealed to
unauthorized persons
• Integrity: ensuring consistency of data, in particular, preventing unauthorized
creation, modification, or destruction of data
• Availability: ensuring that legitimate users are not unduly denied access to
resources, including information resources, computing resources, and communication
resources
• Authorized use: ensuring that resources are not used by unauthorized persons or in
unauthorized ways
To achieve these objectives, we institute various safeguards, such as concealing (encryption)
confidential information so that its meaning is hidden from spying eyes; and key management
which involves secure distribution and handling of the “keys” used for encryption. Usually, the
complexity of one is inversely proportional to that of the other—we can afford relatively simple
encryption algorithm with a sophisticated key management method.

402
Padlock and
Content
shared key Shared
copy key copy

Message
Sender Intermediary Receiver

Sender needs: Threats posed by intruder/adversary: Receiver needs:


• receive securely a copy of the shared key • forge the key and view the content • receive securely a shared key copy
• positively identify the message receiver • damage/substitute the padlock • positively identify the message sender
• damage/destroy the message • detect any tampering with messages
• observe characteristics of messages
(statistical and/or metric properties)

Figure 5-31: Communication security problem: Sender needs to transport a confidential


document to Receiver over an untrusted intermediary.

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

1. Sender secures the briefcase


with his/her padlock and sends

Sender Receiver
2. Receiver additionally secures
the briefcase with his/her
padlock and returns

3. Sender removes his/her


padlock and sends again

4. Receiver removes his/her


padlock to access the content

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.

5.5.1 Symmetric and Public-Key Cryptosystems


In symmetric cryptosystems, both parties use the same key in encryption and decryption
transformations. The key must remain secret and this, of course, implies trust between the two
parties. This is how cryptography traditionally works and prior to the late 1970s, these were the
only algorithms available.
The system works as illustrated in Figure 5-31. In order to ensure the secrecy of the shared key,
the parties need to meet prior to communication. In this case, the fact that only the parties
involved know the secret key implicitly identifies one to the other.
Using encryption involves two basic steps: encoding a message, and decoding it again. More
formally, a code takes a message M and produces a coded form f(M). Decoding the message
requires an inverse function f −1 , such that f −1(f (M )) = M. For most codes, anyone who
knows how to perform the first step also knows how to perform the second, and it would be
unthinkable to release to the adversary the method whereby a message can be turned into code.
Merely by “undoing” the encoding procedures, the adversary would be able to break all
subsequent coded messages.
In the 1970s Ralph Merkle, Whitfield Diffie, and Martin Hellman realized that this need not be so.
The weasel word above is “merely.” Suppose that the encoding procedure is very hard to undo.
Then it does no harm to release its details. This led them to the idea of a trapdoor function.
We call f a trapdoor function if f is easy to compute, f −1 is very hard, indeed impossible for
but

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.

This alternative approach is known as public-key cryptosystems. To understand how it works, it is


helpful to examine the analogy illustrated in Figure 5-32. The process has three steps. In the first
step, the sender secures the briefcase with his or her padlock and sends. Second, upon receiving
the briefcase, the receiver secures it additionally with their own padlock and returns. Notice that
the briefcase is now doubly secured. Finally, the sender removes his padlock and re-sends. Hence,
sender manages to send a confidential document to the receiver without needing the receiver’s
key or surrendering his or her own key.
There is an inefficiency of sending the briefcase back and forth, which can be avoided as illustrated
in Figure 5-33. Here we can skip steps 1 and 2 if the receiver distributed his/her padlock (unlocked,
of course!) ahead of time. When the sender needs to send a document, i.e., message, he/she
simply uses the receiver’s padlock to secure the briefcase and sends. Notice that, once the
briefcase is secured, nobody else but receiver can open it, not even the sender. Next I describe
how these concepts can be implemented for electronic messages.

5.5.2 Cryptographic Algorithms


Encryption has three aims: keeping data confidential; authenticating who sends data; and,
ensuring data has not been tampered with. All cryptographic algorithms involve substituting one
thing for another, which means taking a piece of plaintext and then computing and substituting
the corresponding ciphertext to create the encrypted message.

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.

Example 5.2 RSA cryptographic system


As a simple example of RSA, suppose the receiver chooses p = 5 and q = 7. Obviously, these are too
small numbers to provide any security, but they make the presentation manageable. Next, the receiver
chooses E = 5, because 5 and (5 − 1)⋅(7 − 1) have no common factors. Also, n = p⋅q = 35. Finally, the
receiver chooses D = 29, because ( p E⋅D−1 = 5⋅29−1 = 144 = 6 , i.e., they are exactly divisible. The
−1)⋅(q−1) 4⋅6 24
receiver’s public key is K + = (n, E) = (35, 5), which is made public. The private key K − = (n, D) = (35, 29)
is kept secret.

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

5.5.4 Program Security


A virus is malicious code carried from one computer to another by some medium—often an
“infected” file. Any operating system that allows third-party programs to run can theoretically run
viruses. Some operating systems are more secure than others; earlier versions of Microsoft

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.

However, we should not forget that security comes at a cost.


In theory, application programs are supposed to access hardware of the computer only through
the interfaces provided by the operating system. But many application programmers who dealt
with small computer operating systems of the 1970s and early 1980s often bypassed the OS,
particularly in dealing with the video display. Programs that directly wrote bytes into video display
memory run faster than programs that didn't. Indeed, for some applications—such as those that
needed to display graphics on the video display—the operating system was totally inadequate.
What many programmers liked most about MS-DOS was that it “stayed out of the way” and let
programmers write programs as fast as the hardware allowed. For this reason, popular software
that ran on the IBM PC often relied upon idiosyncrasies of the IBM PC hardware.

5.6 Summary and Bibliographical Notes

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.

Section 5.1: Indirect Communication: Publisher-Subscriber

Section 5.2: More Patterns

Section 5.3: Concurrent Programming


Concurrent systems are a large research and practice filed and here I provide only the
introductory basics. Concurrency methods are usually not covered under design patterns and it is
only for the convenience sake that here they appear in the section on software design patterns. I
avoided delving into the intricacies of Java threads—by no means is this a reference manual for
Java threads. Concurrent programming in Java is extensively covered in [Lea, 2000] and a short
review is available in [Sandén, 2004].
[Whiddett, 1987]
Pthreads tutorial: http://www.cs.nmsu.edu/~jcook/Tools/pthreads/pthreads.html
Pthreads tutorial from CS6210 (by Phillip Hutto):
http://www.cc.gatech.edu/classes/AY2000/cs6210_spring/pthreads_tutorial.htm

Section 5.4: Broker and Distributed Computing


The Broker design pattern is described in [Buschmann et al., 1996; Völter et al., 2005].
Java RMI:
Sun Developer Network (SDN) jGuru: “Remote Method Invocation (RMI),” Sun Microsystems,
Inc., Online at: http://java.sun.com/developer/onlineTraining/rmi/RMI.html
http://www.javaworld.com/javaworld/jw-04-2005/jw-0404-rmi.html
http://www.developer.com/java/ent/article.php/10933_3455311_1

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

Section 5.5: Information Security


In an increasingly networked world, all computer users are at risk of having their personally
identifying information and private access data intercepted. Even if information is not stolen,
computing resources may be misused for criminal activities facilitated by unauthorized access to
others’ computer systems.
Kerckhoffs’ Principle states that a cryptosystem should remain secure even if everything about it
other than the key is public knowledge. The security of a system’s design is in no way dependent
upon the secrecy of the design, in and of itself. Because system designs can be intercepted, stolen,
sold, independently derived, reverse engineered by observations of the system’s behavior, or just
leaked by incompetent custodians, the secrecy of its design can never really be assumed to be
secure itself. Hence, the “security through obscurity” security model by attempting to keep
system design secret Open source movement even advocates widespread access to the design of
a system because more people can review the system’s design and detect potential problems.
Transparency ensures that the security problems tend to arise more quickly, and to be addressed
more quickly. Although an increased likelihood of security provides no guarantees of success, it is
beneficial nonetheless.
There is an entire class of software, known as “fuzzers,” that is used to quickly detect potential
security weaknesses by feeding abusive input at a target application and observing its behavior
under that stress. These are the tools that malicious security crackers use all the time to find ways
to exploit software systems. Therefore, it is not necessary to have access to software design (or
its source code) to be able to detect its security vulnerabilities. This should not be surprising, given
that software defects are rarely found by looking at source code. (Recall the software testing
techniques from Section 2.7.) Where access to source code becomes much more important is
when trying to determine why a particular weakness exists, and how to remove it. One might
conclude, then, that the open source transparency does not contribute as much to detecting
security problems as it does to fixing them.
Cryptography [Menezes et al., 1997], which is available for download, entirely, online at
http://www.cacr.math.uwaterloo.ca/hac/.
ICS 54: History of Public-Key Cryptography:
http://www.ics.uci.edu/~ics54/doc/security/pkhistory.html
http://www.netip.com/articles/keith/diffie-helman.htm
http://www.rsasecurity.com/rsalabs/node.asp?id=2248
http://www.scramdisk.clara.net/pgpfaq.html
http://postdiluvian.org/~seven/diffie.html
http://www.sans.org/rr/whitepapers/vpns/751.php
http://www.fors.com/eoug97/papers/0356.htm

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

Problem 5.6: Elevator Control


L 22 333 4 5 6 7

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 )

opt [prodInfo == nil] recordStatistics( "error: unknown tagID" )

return

decrement( prodCount )

alt [prodCount < 0]


recordStatistics( "error: negative prodCount" )

send( "error: negative prodCount" )


email to store manager

return

[prodCount ≤ Threshold]
recordProductInfo( updated product count )

createTask( "out-of-stock" )
recordPendingTask( task info )

send( “alert: out-of-stock" )


email to store manager Pending Task Info:
Task-type = "out-of-stock"
Assigned-time = current time
Assigned-to = Store Manager

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

alt [prodInfo == nil]


recordStatistics( "error: unknown tagID" )
return

[else]
increment( prodCount )

recordProductInfo( updated product count )

return

UC3: ViewPendingWork
user interface : : Dispatcher : DBaseConn

view pending
getPendingTasks( userID )

alt [userID == manager]


tasksList := getPendingTasks( ALL )

[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

Store Manager runs


ref UC3 to view pending tasks
UC3: ViewPendingWork and selects one to assign

assign task
assignTask( taskID, associateID, … )
taskInfo := getPendingTaskInfo( taskID )

opt [ taskInfo != nil && taskType == "out-of-stock" ]

seq recordPendingTask( task info )

removePendingTask( taskID )
Pending Task Info:
Task-type = "replenish-shelf"
Assigned-time = current time
Assigned-to = Store Associate send( "alert: task assigned" )

email to store associate

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

loop [ for every task in pendingList ]

opt [ (currentTime − task.assignTime) ≥ thresholdPendingInterval ]


increment task.remindAttempts

recordPendingTask( task info )

alt [ task.remindAttempts < maxAttempts ]


receivers := task.assignedTo

receivers := ALL employees (system-wide)

sendReminder( receivers, task )


increment task.remindAttempts

recordStatistics( task reminder alert info )

send( “alert:: " + task.ID + " overdue" )


email to receivers

set sleep period


return

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 )

alt [taskInfo == nil]

return error

[else] prodInfo := getProductInfo( taskInfo.getProductID( ) )

alt [prodCount ≤ Threshold]

return error

[else]

seq recordCompletedTask( taskInfo )

removePendingTask( taskID )

send( "alert: task completed" )


email to store manager

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.

Figure 5-34: Parking lot occupancy monitoring, Problem 5.16.

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

Figure 5-36: Concurrency problem in a restaurant scenario, Problem 5.17.

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

enter selection (“items catalog“)

place order (“selected items")

enter credit card info (“payment amount“)

process payment (“card info")

approve transaction (“card info“, “payment amount")

notify outcome (“result value“)

notify outcome (“result value“)

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

You might also like