PYTH-290 · Lesson 2 of 12 · 28 min

Requirements and user stories

▶ Video coming soon A video version of this lesson is on the way.

The charter says what Ridgeline Ops is for. It does not say what the software must do. Between the two sits the work of requirements: turning goals like customers can subscribe into precise statements that a developer can build and a tester can check. Vague requirements are the single most expensive problem in software, because a misunderstanding found after the code is written costs far more to fix than one found in a conversation.

In the second half of the first week, the team turned the interview notes into a backlog of user stories, wrote acceptance criteria for the most important ones, listed the quality requirements that apply to the whole system, and agreed with Rosa which stories had to be finished for demo day. This lesson follows that work and builds small tools that keep a backlog honest.

Functional and non-functional requirements

Requirements come in two broad kinds. Functional requirements describe what the system does: a subscriber can skip the next delivery; the system creates renewal orders each morning; the daily report includes subscription sales. Non-functional requirements, often called quality attributes, describe how well it does it: pages load in under two seconds on a phone; customer data stays in the country; the system can be restored from backup within four hours; staff pages are usable with a keyboard and a screen reader.

Teams often write plenty of functional requirements and forget the non-functional ones until something goes wrong. At Ridgeline the important quality attributes were easy to find once the team asked. Anita's report must be correct to the cent, so money uses integer cents and is rounded once, as the team learned the hard way in PYTH-230. Subscriber emails and addresses are personal information, so privacy law applies. And the system runs on a small budget, so whatever architecture the team chooses must cost tens of dollars a month, not hundreds.

Non-functional requirements should be measurable, just like success measures. Fast is not a requirement; the renewal job finishes in under one minute for 500 subscriptions is. Secure is not a requirement; staff accounts require multi-factor sign-in and passwords are stored only as salted hashes is.

User stories

Agile teams usually capture functional requirements as user stories. A user story is a short description of something a user wants, written from their point of view. The most common template has three parts: As a role, I want a goal, so that a benefit. For example: As a subscriber, I want to skip my next delivery, so that coffee does not arrive while I am away.

The template is not magic, but each part does useful work. The role reminds the team who the story is for, which matters when different users want different things. The goal says what they want to do, without saying how the software does it. The benefit explains why, which helps the team find simpler solutions and helps the product owner decide priority. A story with no clear benefit is often a feature someone imagined rather than a need someone has.

A user story is famously described as a placeholder for a conversation. The card itself is short; the details live in the conversation between the developers and the user, and in the acceptance criteria written down as a result. Writing a two-page specification on the card misses the point. Writing a three-word card with no conversation misses it too.

Bill Wake's INVEST checklist describes a good story. It should be Independent, so it can be built in any order; Negotiable, so the details can be discussed; Valuable to a user; Estimable, so the team can judge its size; Small enough to finish in a few days; and Testable, so everyone can agree when it is done. Stories that fail the checklist usually need splitting or rewriting.

Checking a backlog against the user story template with a regular expression
import re

STORY = re.compile(
    r"As an? (?P<role>.+?), I want (?P<goal>.+?)(?:,? so that (?P<benefit>.+?))?\.?",
    re.IGNORECASE)

BACKLOG = [
    "As a subscriber, I want to skip my next delivery, so that coffee does not arrive while I am away.",
    "As an operations manager, I want a packing list each morning so that I can ship every subscription order on time.",
    "Add a database table for subscriptions.",
    "As an owner, I want a dashboard.",
]

for text in BACKLOG:
    m = STORY.fullmatch(text)
    if not m:
        print("NOT A STORY:", text)
    elif not m.group("benefit"):
        print("NO BENEFIT: ", m.group("role"), "->", m.group("goal"))
    else:
        print("OK:         ", m.group("role"), "->", m.group("goal"))
Output
OK:          subscriber -> to skip my next delivery
OK:          operations manager -> a packing list each morning
NOT A STORY: Add a database table for subscriptions.
NO BENEFIT:  owner -> a dashboard

The checker finds two problems in four items. Add a database table is a task, not a story: it describes work for the developers, not something a user wants. Tasks belong under the story they support. As an owner, I want a dashboard has no benefit, and it is too vague to build. When Jordan asked Rosa why she wanted a dashboard, the answer was specific: she wanted to know each Monday whether subscriptions were growing or shrinking. That became a much smaller and clearer story.

Try it yourself

Parse a user story

Write parse_story(text) that splits a story written as "As a ROLE, I want GOAL, so that BENEFIT." into a dictionary with the keys role, goal and benefit, each with surrounding spaces removed.

Accept "As a" or "As an" in any capitals, an optional comma before "so that", and an optional full stop at the end. Return None if the text does not have all three parts.

def parse_story(text):
    parts = text.split(",")
    return {"role": parts[0], "goal": parts[1], "benefit": parts[2]}

print(parse_story("As a subscriber, I want to skip my next delivery, so that coffee does not arrive while I am away."))

Acceptance criteria

A story says what the user wants; acceptance criteria say exactly when it is done. They are the conditions that must be true for the product owner to accept the story, and they become the team's tests. Without them, a developer finishes the skip feature, the product owner tries it and says that is not what I meant, and a week is lost.

A popular format comes from behavior-driven development: Given some starting situation, When the user does something, Then some result should follow. The format forces you to state the starting conditions, which is where most misunderstandings hide. For the skip story, the team and Rosa wrote:

  • Given an active fortnightly subscription with the next delivery on 1 December, when the subscriber skips the next delivery, then the next delivery becomes 15 December and no order is created on 1 December.
  • Given a subscription whose next delivery is tomorrow, when the subscriber tries to skip it, then the skip is refused because the order has already been packed.
  • Given a subscription that has already been skipped twice in a row, when the subscriber tries to skip again, then the system offers to pause the subscription instead.

The second and third criteria came from asking what could go wrong. Theo pointed out that orders are packed the afternoon before, so a skip after that point would waste a packed bag. Rosa wanted repeated skipping to lead to a pause, which keeps the subscriber count honest. Neither rule was in anyone's head until the conversation forced it out, and both changed the design.

A user story card with three parts: As a subscriber, I want to skip my next delivery, so that coffee does not arrive while I am away. Below it are three acceptance criteria in Given, When, Then form, each linked by an arrow to an automated test name.
A story card, its acceptance criteria, and the automated tests they become

Try it yourself

Read Given, When, Then

Write parse_criteria(text) that takes acceptance criteria written one step per line and returns a list of (given, when, then) tuples, one per scenario.

Each scenario starts with a line beginning "Given", followed by a "When" line and a "Then" line. A line starting with "And" adds to the step above it, joined with " and ". Ignore blank lines and keep the text after the keyword, stripped of spaces.

def parse_criteria(text):
    result = []
    for line in text.splitlines():
        if line.startswith("Given"):
            result.append((line, "", ""))
    return result

TEXT = """Given an active fortnightly subscription
When the subscriber skips the next delivery
Then the next delivery moves two weeks later
And no order is created

Given a delivery tomorrow
When the subscriber tries to skip it
Then the skip is refused"""
print(parse_criteria(TEXT))

Splitting stories

The first version of the backlog had a story that read: As a subscriber, I want to manage my subscription, so that it fits my life. It is a fine summary and a terrible story, because it cannot be estimated or finished in a few days. Large stories like this are called epics, and the team split it into smaller stories, each delivering something on its own.

There are several reliable ways to split a story. Split by workflow step: sign up, change coffee, skip, pause, cancel. Split by business rule: skip with no restrictions first, then add the packing cut-off, then the repeated-skip rule. Split by data variation: fortnightly subscriptions first, then four-weekly. Split by interface: a staff page first, a subscriber page later. Or split simple from complex: one coffee per subscription first, mixed boxes later. Each slice should still cut through every layer of the system, from data to screen, so that it can be shown working. A story that delivers only the database table is a task in disguise.

After splitting, the subscription epic became nine stories and the whole backlog had twenty-three. That is too many for five weeks, which is exactly the point: now the team could see the size of the work and choose.

Prioritizing for the deadline

Rosa, as the product owner, decided priority; the developers estimated size. The team used MoSCoW, a technique from the DSDM agile method, which sorts every story into four groups. Must have stories are required for the release to be useful at all. Should have stories are important but the release still works without them. Could have stories are desirable extras. Won't have this time stories are explicitly deferred. The capital letters spell MoSCoW; the lower-case o's are only there to make it pronounceable.

The rule that makes MoSCoW work is that the Must group must fit comfortably inside the time available, usually no more than about sixty out of every hundred points of capacity. If everything is a Must, nothing is, and the team has no room to absorb surprises. Rosa found this hard at first: everything felt essential. Jordan's question helped: if this story were not finished on 18 December, would we cancel the launch? For most stories the honest answer was no.

Each story was estimated in story points, a relative measure of size and uncertainty rather than hours. The team used a short scale, 1, 2, 3, 5 and 8, and estimated by planning poker: everyone reveals a number at once, and large disagreements lead to a discussion that often uncovers a hidden requirement. Lesson 4 explains how points turn into a forecast.

Checking that the Must stories fit comfortably inside the team's capacity
CAPACITY = 60   # points the team can finish in three build sprints

BACKLOG = [
    ("Sign up for a subscription", "must", 5),
    ("Create renewal orders each morning", "must", 8),
    ("Packing list for operations", "must", 3),
    ("Skip next delivery", "must", 3),
    ("Daily report includes subscriptions", "must", 5),
    ("Stock check against the roast plan", "should", 5),
    ("Pause and resume", "should", 3),
    ("Change coffee", "should", 2),
    ("Weekly growth figure for the owner", "should", 3),
    ("Four-weekly frequency", "could", 2),
    ("Email reminder before each delivery", "could", 5),
    ("Mixed coffee boxes", "wont", 8),
]

totals = {}
for title, group, points in BACKLOG:
    totals[group] = totals.get(group, 0) + points
for group in ("must", "should", "could", "wont"):
    print(f"{group:6} {totals.get(group, 0):3} points")
must = totals["must"]
print(f"Must stories use {must} of {CAPACITY} points ({must * 100 // CAPACITY} in every 100)")
Output
must    24 points
should  13 points
could    7 points
wont     8 points
Must stories use 24 of 60 points (40 in every 100)

The Must group came to 24 points, well under the 60-point limit the team had agreed for Musts, leaving room for most of the Should stories and some contingency. On paper that looked almost too comfortable; in practice, as later lessons show, the renewal job alone grew from 8 points to 13 once the team understood its edge cases.

Try it yourself

Plan the minimum release

Write plan_release(backlog, capacity) where backlog is a list of (title, group, points) tuples and group is "must", "should", "could" or "wont". Return the list of titles to build, taking every Must story first, then Should, then Could, each group in backlog order, and adding a story only if it still fits in the remaining capacity. Never include "wont" stories.

If the Must stories alone do not fit, raise ValueError, because the release cannot be useful without them.

def plan_release(backlog, capacity):
    return [title for title, group, points in backlog if group != "wont"]

BACKLOG = [
    ("Sign up", "must", 5), ("Renewals", "must", 8), ("Pause", "should", 3),
    ("Reminder email", "could", 5), ("Change coffee", "should", 2), ("Mixed boxes", "wont", 8),
]
print(plan_release(BACKLOG, 20))

Keeping requirements alive

Requirements are not finished when the first week ends. The backlog is a living list: stories are added as people learn, split when they turn out larger than expected, and reprioritized when the business changes. What matters is that changes are visible and deliberate. At Ridgeline every new request went into the backlog with a note of who asked and why, and Rosa reviewed the order at the start of each sprint.

Two practices help. A definition of ready lists what a story needs before the team will start it: a clear benefit, acceptance criteria, an estimate and no unanswered questions. A definition of done lists what must be true before a story counts as finished: code reviewed and merged, tests passing, acceptance criteria demonstrated, documentation updated. Both definitions are short, agreed by the whole team, and written in the repository so nobody has to remember them. Lesson 4 shows Ridgeline's versions.

Common mistakes

  • MistakeWriting only functional requirements and discovering performance or privacy needs late.

    InsteadList the quality attributes early and make each one measurable.

  • MistakePutting tasks such as "add a database table" in the backlog as if they were stories.

    InsteadWrite stories from a user's point of view and list technical tasks under the story they support.

  • MistakeWriting stories without a benefit.

    InsteadAsk why until the benefit is clear; it guides both priority and design.

  • MistakeTreating the story card as the full specification, or skipping the conversation.

    InsteadUse the card as a reminder, talk with the user, and record the result as acceptance criteria.

  • MistakeSplitting stories by layer, such as database first and screens later.

    InsteadSplit into thin slices that each work end to end, by workflow step, rule, data variation or interface.

  • MistakeMarking almost everything as Must have.

    InsteadKeep Musts to about sixty out of every hundred points of capacity so there is room for surprises.

Summary and key terms

  • Requirements turn goals into precise statements developers can build and testers can check.
  • Functional requirements say what the system does; non-functional requirements say how well, and must be measurable.
  • A user story names a role, a goal and a benefit, and is a placeholder for a conversation.
  • INVEST describes good stories: independent, negotiable, valuable, estimable, small and testable.
  • Acceptance criteria in Given, When, Then form state exactly when a story is done and become tests.
  • Epics are split into thin, end-to-end stories by workflow step, rule, data, interface or complexity.
  • MoSCoW sorts stories into Must, Should, Could and Won't; Musts must fit well inside capacity.
  • Definitions of ready and done keep the backlog honest and are agreed by the whole team.
functional requirement
A statement of something the system must do.
non-functional requirement
A measurable statement of how well the system must perform, such as speed, privacy or availability.
user story
A short description of something a user wants, in the form As a role, I want a goal, so that a benefit.
acceptance criteria
The conditions that must be true for a story to be accepted as done.
epic
A story too large to finish in one sprint, which must be split into smaller stories.
INVEST
A checklist for good stories: independent, negotiable, valuable, estimable, small, testable.
MoSCoW
A prioritization method sorting work into Must, Should, Could and Won't have this time.
story point
A relative unit for estimating the size and uncertainty of a story.
product owner
The person who decides the priority of the backlog on behalf of the users and the business.
definition of done
The team's agreed checklist for when a story counts as finished.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Rewrite "the system should be fast" as a measurable requirement for the renewal job.

    Show answer

    For example: the morning renewal job creates orders for 500 subscriptions in under 60 seconds.

  2. What is wrong with the backlog item "Create the orders table"?

    Show answer

    It is a technical task, not a user story; it belongs under a story such as creating renewal orders.

  3. Write a Given, When, Then criterion for pausing a subscription.

    Show answer

    For example: Given an active subscription, when the subscriber pauses it, then no renewal orders are created until it is resumed.

  4. How would you split "As a subscriber, I want to manage my subscription" by workflow step?

    Show answer

    Into sign up, change coffee, skip, pause and cancel stories, each delivering something on its own.

  5. The Must stories total 45 points and the team can finish 60. Is this a healthy plan?

    Show answer

    It is risky: 45 is 75 out of every 100 points, above the usual limit of about 60, leaving little room for surprises.

  6. What is the difference between a definition of ready and a definition of done?

    Show answer

    Ready lists what a story needs before work starts; done lists what must be true before it counts as finished.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz