Use Cases
Object-Oriented Design with UML
Course 1 · Chapter 3 · From Requirements to Use Cases
Analysis starts with a question that has nothing to do with classes: who wants what from this system, and what has to happen for them to get it? A use case answers it for one goal, as a short story with a happy path and the ways it can go wrong. Use cases come before the domain model (chapter 4) because the concepts you need are the ones the stories mention, and they come before any class design because you cannot decide who is responsible for something nobody has described.
include and extend.
The Words
| Term | Meaning | Library example |
|---|---|---|
| Actor | Someone or something outside the system that interacts with it, in a role (a role, not a job title or a person) | Member, Librarian, the card payment service |
| Primary actor | The actor whose goal the use case serves | Member, for "Reserve a book" |
| Supporting actor | An actor the system calls on to reach the goal | The card payment service, for "Pay a fine" |
| Goal | What the primary actor wants to achieve; the name of the use case | "Borrow a book" |
| Scope | The system being designed, treated as a black box; everything outside it is an actor | The library lending system, not the whole library |
| Use case | A goal-directed set of interactions between actors and the system, including failures along the way | "Borrow a Book", with its flows |
| Scenario | One path through a use case | Borrow succeeds; borrow refused because of fines |
Two points that prevent most confusion. Roles, not people: a person can play both Member and Librarian (a librarian borrowing a book); the use case cares about the role. Scope decides who is an actor: inside the lending system the librarian is an actor, because the system is the software. If your scope were "the library as an organisation", the librarian would be inside the boundary. Always state the scope.
Finding Actors and Goals
A reliable method is to list actors first and then ask each one the same question: what do you want to get done with this system? Ask "what else?" until the answers dry up, then ask what happens on a schedule, with no person involved (a nightly job is an actor too). For the library:
| Actor | Goals |
|---|---|
| Member | Search the catalogue; borrow a book; return a book; reserve a book; renew a loan; pay a fine |
| Librarian | Borrow and return books at the desk for a member; join a new member; add books to stock |
| Card payment service (supporting) | Takes the payment for a fine |
| A nightly timer (exercise 2) | Send overdue notices |
Goals have a level. Cockburn's names (from memory) are cloud, kite, sea level, fish and clam. The useful one is sea level, or "user goal": something a person does in one sitting that is worth doing for its own sake, such as "Borrow a book". Above it ("Run the library") is too big to describe; below it ("Enter the card number") is a step, not a goal. A quick test: after finishing it, would the actor consider the job done, and could they walk away?
The Use Case Diagram
PlantUML, rendered here. The rectangle is the scope; actors stand outside it; each line says "this actor takes part in this use case".
That is the whole use case diagram, and it is useful for exactly one thing: showing the scope and the cast on one page, so a customer can say "you forgot that members can renew". Its limits are as important:
- It says nothing about what happens. "Borrow a book" is an ellipse; whether a member with fines can borrow, and what the librarian sees if not, is in the description below, not the picture.
- It has no order and no flow. Neither arrows nor position mean "first".
- It does not scale. Past a dozen ellipses it becomes wallpaper. Group by actor or by area into several diagrams.
- The arrowheads are a drawing convention (from memory of the UML notation, where the line between an actor and a use case is an association). Here the arrow runs from the primary actor to the use case, and from a use case to a supporting actor it needs.
Many teams keep only a list of use case names and put the effort into the descriptions. That is a fair choice.
The Use Case Description
The description is the real content. A common layout (Cockburn's, as I remember it) has these parts:
| Part | What goes in it |
|---|---|
| Name, scope, level, primary actor | Name it as a verb phrase for the goal; say what the system is and who wants this |
| Stakeholders and interests | Everyone who cares about the outcome, even if they never touch the system, and what they want (this finds rules that the actors would not mention) |
| Precondition | What must already be true for the use case to start (not a check the use case performs) |
| Trigger | The event that starts it |
| Main success scenario | Numbered steps of the simplest path in which everything goes right |
| Extensions | What can go differently, attached to the step where it happens (3a, 3b...), each ending with either "use case ends" or "resume at step N" |
| Guarantees | Success guarantee: what is true afterwards if it worked. Minimal guarantee: what is true at least, however it ends |
The library's "Borrow a Book", written out in full:
How It Reads
- The steps are about the actor and the system, not the screen. "The Librarian enters the card number and the ISBN" does not say whether that is a form, a scanner or a command line; deciding that is design.
- Each check is its own step (3 to 6), because each can fail differently, and each failure is an extension with the same number. This is where the value is: the main scenario is short and obvious, and the extensions list contains the decisions a developer would otherwise make silently.
- Extensions say how the use case ends, so no flow is left hanging. 4a even links to another use case (
Pay a fine) and says whether to resume. - The minimal guarantee is a promise about failure: a refused loan changes nothing. That sentence is cheap to write and expensive to forget, because a half-recorded loan is a bug nobody notices until the data is wrong.
- The open questions are part of the document. A use case written by someone who does not run the library will contain guesses; marking them is honest and tells you what to ask.
From Description to Check
A use case that cannot be turned into a test is probably vague. Here the description above became a Python function, with each numbered step and each extension as a comment in the code at the place it happens, and one test per flow. This is not the design you will end up with (who owns these checks is chapter 6's question); it is a way to make the description execute.
Two observations from doing this. First, writing the code found a missing extension. My first version of the description had no extension 2a: the code looked the ISBN up in the catalogue, and a book that was not there raised a Python KeyError, not a refusal the Librarian could read:
The description was incomplete; the code was merely the first thing to ask the awkward question. I added extension 2a, the test for it, and ran again. Second, I tested the tests. The minimal guarantee ("a refusal changes nothing") is checked by comparing a deep copy of the data before and after every refusal. To see whether that test could fail, I ran the suite against a version of borrow with a deliberate bug that changes the copy's state before the checks that may refuse:
Six tests failed, so the guarantee is really being checked. (Test code that never fails on broken code proves nothing; Build a Chatbot: From API Call to Deployed Bot 11 made the same point with simulations.) Note what the tests do not establish: that 21 days and a limit of five are right. Only the librarian can say.
Use Cases and User Stories
The Software Development Lifecycle 5 covers requirements and user stories; this section only relates the two. A story such as "As a member, I want to borrow a book so that I can read it at home" is a short reminder of a need, meant to start a conversation. A use case is what that conversation produces when the goal has real flows: the steps, the checks and the failures. They are not rivals.
| User story | Use case | |
|---|---|---|
| Size | One or two sentences | One to two pages |
| Strength | Planning, prioritising, small slices of value | Complete flows, rules and failures for one goal |
| Weakness | Detail lives in conversations and tests that can be lost | Heavier to write and to keep current |
| Use when | The goal is simple or the team talks daily | A goal has many rules and failure modes, or the people deciding are not in the room |
A practical way to use both: let the use case supply the stories. "Borrow a book" gives one story for the main scenario and one for each extension that is worth building separately ("As a member with fines, I want to be told why I was refused and how to fix it"); the extensions double as acceptance criteria.
Include and Extend, and Why to Be Sparing
Use cases can relate to each other in two ways (from memory of the UML standard and its usual explanations). Include: the base use case always performs the included one, as a step; use it to share a piece of behaviour that several use cases need. Extend: the extending use case optionally adds behaviour to the base at a named point, under a condition; the arrow runs from the extension to the base. A small example, rendered:
Borrowing and renewing both include "Check eligibility". "Pay a fine" can extend "Return a book" when the return is late.
Read the arrows: an include arrow points to the included use case; an extend arrow points to the base. People draw them backwards constantly. And a warning: the temptation is to overuse both. Splitting use cases into include-pieces turns the description into a functional decomposition (a flowchart in disguise), and the readers lose the story. Reach for them only when the sharing is real (the same several steps in several use cases) or the optional behaviour genuinely belongs elsewhere. Writing the extension inline ("4a. The Member has unpaid fines...") is usually clearer. In the Borrow description above I did exactly that, and referred to Pay a fine by name instead of drawing an extend relationship.
Common Mistakes
| Mistake | Example | Better |
|---|---|---|
| A use case that is a screen or a data operation | "Manage books" (create, read, update, delete) | Name the goals: "Add books to stock", "Withdraw a damaged book" |
| A step dressed as a use case | "Enter card number" | Make it a step inside "Borrow a book" |
| The user interface in the flow | "The Librarian clicks the green Borrow button" | "The Librarian asks to borrow the book" (the interface is a later decision) |
| The system as an actor, or an actor inside the system | "The Database" as an actor | Inside the scope it is design. An actor is outside it: a payment service, a timer |
| Only the happy path | A one-page description with no extensions | Walk each step and ask "what if this fails?"; write it down |
| Too many levels at once | A diagram mixing "Run the library" and "Scan a barcode" | Keep to sea level; mention the others in prose |
| Include and extend everywhere | Twenty ellipses joined by dotted arrows | Inline the extensions; share only what is truly shared |
| Never taking it to the people who know | A design finished without asking the librarian | Read the description aloud to them; it is written in their language, which is its point |
- I identified a physical book by its ISBN. The description and the code say "the book's ISBN" and look the copy up by it. That conflates two things: an ISBN identifies a title, and a library may own several physical copies of one title. Chapter 4's domain model exposes it (its demonstration shows two copies of one ISBN collapsing to one in this chapter's code, so a second member is refused a copy that is on the shelf). The fix, not made here, is to say "copy barcode" in the steps and add a Title/Copy distinction. Takeaway: a use case written before the domain model is a draft of its vocabulary; expect the model to correct it.
- A broken test helper. My first run of the use case tests failed two tests with
TypeError: Member.__init__() got multiple values for argument 'expires_on'. What went wrong: the test helper passedexpires_onby position and then again through the overrides. How to avoid it: build test fixtures from a dictionary of defaults that overrides replace, never from a positional argument plus overrides. - An incomplete use case, found late. The description had no extension for an unknown ISBN until the code raised
KeyError(shown above). The takeaway is the practice, not the apology: turn each description into a runnable check as early as is cheap, because code is the strictest reader a requirement will ever have. - A demonstration that printed the wrong thing. My first "gap" demo printed "not a Refusal" on every line, even after the fix, because the message was written as a constant instead of depending on the exception type. It said the opposite of what happened. Takeaway: make a demonstration's commentary depend on what it observes.
- A command that did not do what I thought. A
sedcommand to remove a label from a diagram silently matched nothing (the file held a literal backslash-n, and my pattern assumed a newline), and the render script still said "rendered". I only noticed because I looked at the picture. Takeaway: after an automated edit, check the file or the picture, not just the exit status.
Hands-On Exercises
Write the use case description for "Return a Book" in the full layout: scope, level, primary actor, stakeholders, precondition, trigger, a main success scenario, extensions (no open loan for the copy, an overdue book with a fine, another member waiting in the reservation queue, a damaged book), and the two guarantees. List any rule you had to assume.
📄 View solutionIn PlantUML, draw a use case diagram for the library with Member, Librarian and a nightly timer as actors (the timer triggers "Send overdue notice"). Draw the scope boundary. Say whether the Member is a primary or supporting actor for the notice, and why.
📄 View solutionTurn your "Return a Book" description into Python with a test for the main scenario and each extension, and run it. Look for a case your description did not decide, and write down the question for the librarian.
📄 View solutionChapter 3 Quick Reference
- Actor = a role outside the system; scope = the system as a black box; goal = what the primary actor wants; state the scope
- Find use cases by asking each actor "what do you want to get done?" and "what else?"; include scheduled jobs; aim for sea-level goals
- The use case diagram shows scope and cast on one page and nothing more; keep it small; many teams use a list instead
- The description is the content: precondition, trigger, numbered main scenario, extensions numbered by the step they attach to, each ending or resuming; success and minimal guarantees
- One check per step, so each failure gets its own extension; keep the user interface out of the steps
- Make the description executable early: it finds missing extensions, and test the tests by breaking the code on purpose
- Story = reminder for a conversation; use case = the detail it produces; extensions double as acceptance criteria
includearrow points to the included use case (always performed);extendarrow points to the base (optional, conditional); use sparingly, inline extensions first