Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Lecture Slides

Slide decks for teaching Object Oriented Development and Agile Practices (SE506), one deck per chapter.

Fifteen lecture decks, one for each chapter of Object Oriented Development and Agile Practices. Each deck follows its chapter and companion section by section, so you can teach a chapter in as many sittings as you like. Pick a chapter below; each card gives the number of slides in the deck.

Part I: Foundations and Object-Oriented Principles

Part II: Testing, Creation and Behavioural Patterns

Part III: Architecture, Packages and Legacy Systems

Part IV: Flow Metrics, Delivery and AI Assistance

Using a deck

  1. Navigate: the arrow keys or the space bar move between slides.
  2. Present: press F for fullscreen.
  3. Print or export: decks format to one slide per page in landscape orientation, so Ctrl with P gives a handout or a PDF.

The chapter text each deck follows.

The learning outcomes and the chapter-by-chapter progression are on the Curriculum and topics page.

1 - Chapter 1: Developing Software That Can Change

Object Oriented Development and Agile Practices (SE506)

Chapter 1: Developing Software That Can Change

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 1.1 When a working program meets a new request
    • One small Java rule, and a change that the rule cannot absorb by editing numbers.
  • 1.2 What does “it works” actually mean?
    • What “it works” leaves out, and the kinds of change a team makes.
  • 1.3 How design and delivery fit together
    • Responsibilities, iterations, increments, and how design meets process.
  • 1.4 How do we know a change is right?
    • Acceptance examples, regressions and the evidence a reviewer can use.

Chapter Roadmap (2 of 3)

  • 1.5 Working with an AI assistant without giving up your judgement
    • Activity 1A: separating the claims a code explanation gets right from the one it invents.
  • 1.6 The project we will build, and the road ahead
    • The Campus Equipment Lending System, its vocabulary and how the chapters build on each other.
  • 1.7 How to read this book, and any other
    • Predicting before running, keeping a learning record, reading advice as a claim.
  • 1.8 Easy mistakes, and how to spot them
    • Five mistakes, each exposed by a small counterexample.

Chapter Roadmap (3 of 3)

  • 1.9 Try it yourself: a readiness check
    • Tasks A, B and C: predict, find a fault, plan the staff change.
  • 1.10 Questions to think with
    • Six questions, then a room-booking change in a different domain.
  • What to take away, and what comes next
    • What to carry into Chapter 2, which examines how objects protect their state.

Learning Outcomes

After working through this chapter, you should be able to:

  1. Explain why a functioning program may still be difficult to maintain.
  2. Describe how object-oriented design, iterative delivery, testing and review contribute to a controlled change.
  3. Read a small Java class and distinguish its actual behaviour from assumptions about the larger system.
  4. Identify evidence needed to evaluate a proposed change or an AI-generated explanation.
  5. Use the book’s learning route and a readiness diagnostic to plan your study.

How to Use This Chapter

  • Core reading: Sections 1.1 to 1.8.
  • What you must do: Task A, Task B (the paper part), Task C and Activity 1A.
    • Together they produce every item in “What to produce”.
    • Everything can be done by tracing the code on paper.
  • If you want more: run the program and the deliberate fault (needs a Java Development Kit), then answer the questions and the transfer challenge.

Prerequisites and Status of This Chapter

  • Prerequisites: basic familiarity with variables, conditions, methods and classes.
  • Not needed yet: design patterns, a testing framework or an Agile project-management tool.
  • If Java syntax is unfamiliar, read the explanation beside each example, then the Java reading in the sources. Appendix A has a short Java refresher and a readiness diagnostic.
  • This is an orientation chapter. It adds no teaching week and previews ideas that later chapters teach in full.
  • Its code is an orientation example for the book’s project: ReservationRules stays outside the running Campus Equipment Lending code, which starts with the Chapter 2 starter.

Opening Scenario: A Program That Works

  • A university lends cameras, microphones and other equipment to students.
  • Your team has written a small program that decides whether a student may request another reservation.
  • It checks two things: the number of active reservations and the requested duration.
  • The examples you tried produce the expected answers.
  • So far, nothing is wrong.

Then the Equipment Officer Asks for a Change

Staff should also be able to borrow equipment, but their reservation limits will be different. The existing student limits should stay the same.

  • The change sounds simple. Perhaps one more condition and two new numbers.

The Questions the Change Raises

  • Where do the current limits appear?
  • How will the program know whether a borrower is a student or a staff member?
  • Which existing behaviours must be preserved?
  • What evidence will show that the new rule works?
  • None of these can be answered by looking only at the new numbers.

Why This Book Starts Here

  • We will build software, and we will also examine how it is understood, tested, reviewed and changed.
  • The goal is to make decisions that:
    • another developer can inspect, and
    • the team can revise when requirements develop.
  • Every chapter returns to the same habit: state a claim, then name the check that could show it to be wrong.

1.1 When a Working Program Meets a New Request

One small rule and a change it cannot absorb


The Initial Rules

The equipment office gives us three rules:

  • A student may have at most three active reservations.
  • A requested loan lasts from one to seven days, inclusive.
  • A request is rejected if the supplied active-reservation count is negative.

These are fictional rule invented for this books for our project, not university policy.


What the Count Means

  • Active reservation: a confirmed reservation that has not been cancelled or completed.
  • The count passed to the method is the count before the proposed reservation is added.
  • The caller must obtain that count correctly.
  • The method receives two integers. It never sees a borrower, an item or a calendar.

The Class: ReservationRules

public final class ReservationRules {
    public boolean mayRequest(int activeReservations, int requestedDays) {
        return activeReservations >= 0
                && activeReservations < 3
                && requestedDays >= 1
                && requestedDays <= 7;
    }
}
  • A complete class: one method, two integer inputs, one Boolean result.

Reading the Class

PieceWhat it does
ReservationRulesGroups a rule calculation under one name
mayRequestReceives two integers, returns true or false
&&Requires all four conditions to hold
publicMakes the class and method accessible to other code
finalPrevents the class from being subclassed
  • Neither public nor final proves that the rule is correct.

One Boundary Deserves Attention

  • activeReservations < 3: a borrower with two active reservations may request a third.
  • A borrower who already has three may not request a fourth.
  • Writing <= 3 would allow one reservation too many if the successful request were then confirmed.
  • The count is measured before adding the new reservation. That is why the test is “less than three”.

Five Calls, Traced

ExistingDaysResultReason
27trueOne more reaches the limit; seven days is allowed
37falseAlready at the reservation limit
00falseA zero-day loan is outside the range
08falseEight days exceeds the duration limit
-12falseThe supplied count is invalid

What true Does and Does Not Mean

true establishestrue does not establish
The count is non-negative and below threeA borrower was identified
The duration is from one to seven daysThe camera is available on those dates
Nothing moreA reservation was stored
  • A larger application must perform the other checks before confirming a reservation.

The Risk of a Suggestive Name

  • Method names can suggest more than their implementations establish.
  • A developer who reads true as “the reservation is confirmed” may introduce a defect.
  • The class itself behaves exactly as written. The defect lives in the reader’s assumption.
  • Habit for the whole book: state what a result does and does not establish.

A Deliberate Simplification

  • Returning false for a negative count treats an impossible input like an ordinary refusal.
  • A caller bug can then look like a policy decision.
  • We accept this here to keep the example small.
  • Chapter 5 returns to contracts, and to how code should respond when a caller breaks them.

The Changed Requirement, Clarified

Students keep three active reservations and seven days. Staff may have up to five active reservations and request up to fourteen days.

  • Students: 3 reservations, 7 days (unchanged).
  • Staff: 5 reservations, 14 days (new).

Why Editing the Numbers Fails

  • Replacing 3 with 5 and 7 with 14 makes the method more permissive for everyone.
  • A student with zero reservations requesting eight days is now accepted. That is a regression.
  • Student with four reservations requesting one day: also accepted, and wrongly.
  • The method receives no borrower category, so it cannot tell students from staff by its inputs.

Before Editing: Three Questions

  1. How is the borrower category established?
  2. What should happen if the category is missing or unknown?
  3. Which student behaviours must be checked again after the change?
  • The answers come from the requirement, not from the original code.

Do Not Guess the Unknown Case

  • Do not silently decide that every unrecognised borrower receives the staff allowance.
  • That is a requirements decision, not a fact we can infer from the original code.
  • Related questions to raise with the equipment officer:
    • How is the category obtained?
    • What if someone holds both roles?
  • Ask, and record the answer.

Several Possible Implementations

ApproachWhat it asks of the code
An explicit category and a small conditionalDirect, but repeated category tests may spread
A separately selected limit passed into the calculationMoves selecting and validating limits to the caller
Give the policy its own responsibilityWorth considering as variations emerge
  • Each approach creates different obligations for callers and different places for mistakes.

A Proportionate Choice

  • At this stage we do not need to select a design pattern.
  • We need to:
    • understand the rule,
    • preserve student behaviour, and
    • choose a proportionate implementation.
  • Chapters 5 and 6 return to this kind of problem with more precise design tools.

Discussion: What Would You Ask the Equipment Officer?

  • Write down three questions before any code is changed.
  • Prompts:
    • Where does the category come from, and can a caller simply claim it?
    • What happens to a request whose category is unknown?
    • Is there anyone who is both student and staff?
  • Real callers must not grant privileges merely because a user claims a category.

1.2 What Does “It Works” Actually Mean?

Does it work, and how hard will it be to change?


“Does It Work?” Needs a Condition

  • To answer usefully we must say what behaviour is expected and under which conditions.
  • For the reservation rule: an expected result for a seven-day student request.
  • For the complete application we also need expectations about:
    • equipment availability,
    • recording reservations,
    • reporting failures.
  • A successful demonstration of one request does not answer all of those questions.

Maintainability

  • A further question: how difficult will the software be to change correctly?
  • Maintainability is our ability to understand, diagnose, test and modify software over its useful life.
  • In this book we look for practical evidence of that ability.
  • It is not a label attached to a diagram.

One Decision, Three Places

  • The seven-day limit appears in:
    • a command-line screen,
    • a reservation service,
    • a report generator.
  • A developer updates two locations and misses the third.
  • Each expression may be easy to read, yet the system is difficult to change consistently.
  • The problem lies in how one decision is represented across the codebase.

The Codebase Is More Than Source Code

  • A codebase is the body of source code and supporting material that the team maintains together.
  • Its tests, build instructions and configuration affect whether another person can work with it.
  • A clear method inside a project that no one else can build is only part of a maintainable solution.

Making Quality Claims Concrete (1 of 2)

ConcernQuestion to investigateUseful evidence
Functional behaviourAre the stated rules enforced?Examples and tests linked to those rules
ModifiabilityCan one policy change be made without unrelated edits?A reviewed change and an explanation of affected responsibilities
UnderstandabilityCan another developer say where the decision is made?A walkthrough backed by accurate names

Making Quality Claims Concrete (2 of 2)

ConcernQuestion to investigateUseful evidence
TestabilityCan the rule be checked with controlled inputs?Repeatable checks that need no live university service
Failure handlingWhat happens when required information is unavailable?Explicit expectations and checks for the failure path
  • These concerns interact.
  • Moving a rule into its own component can ease testing; many tiny components can make navigation harder.
  • Explain a design choice by the problem it solves and the cost it introduces.

Kinds of Change

ChangeNature
Correcting a wrong limitChanges behaviour to meet the requirement
Adding staff borrowingIntroduces new behaviour
Improving internal organisation, same observable behaviourRefactoring (the distinction follows Fowler)
  • Keeping these intentions separate makes review easier.
  • A reviewer should know whether a changed result is deliberate or accidental.

What Is Expected of You

  • You will not be expected to make the project ready for every possible future.
  • You will be expected to:
    • state its assumptions, and
    • respond thoughtfully when a relevant assumption changes.

1.3 How Design and Delivery Fit Together

Where design and process meet


Objects and Responsibilities

  • Object-oriented development organises software around objects with state, behaviour and responsibilities.
  • A responsibility is an obligation assigned to part of the system. For example:
    • representing a loan period,
    • deciding whether a borrower is eligible,
    • recording a confirmed reservation.

A Class Is Not Automatically a Good Design

  • Putting code inside a class does not create a useful design. A class can still:
    • contain unrelated decisions,
    • expose information that callers can misuse,
    • depend on details scattered through the application.
  • Chapter 2 examines these problems through encapsulation, cohesion and coupling.

Keeping Meanings Clear

  • In the equipment-lending domain, an equipment item and a reservation are different things.
  • The item is something the university owns.
  • The reservation is an arrangement concerning that item for a period.
  • Keeping these meanings clear helps the team discuss requirements before it decides which fields or methods are needed.

Iteration and Increment

  • An iteration is a repeated period of work and feedback.
  • An increment adds usable capability to what already exists.
  • A team can improve a reservation workflow over several iterations while delivering successive increments of functionality.
  • One iteration might refine a flow without adding a new feature, so the terms are not interchangeable.

Agile Principles in One Sentence

  • The Agile principles connect frequent delivery and adaptation with continuing attention to technical quality.
  • They also call for teams to reflect and adjust their way of working.
  • The implication for our project is practical:
    • feedback should influence what we build and how we build it.
  • This chapter does not yet teach a complete Agile framework (Chapter 3 does).

A Demonstration Reveals a Mismatch

  • Suppose an early demonstration shows that the equipment officer uses “reservation” to mean a confirmed allocation.
  • The team has used it for an unapproved request.
  • The feedback can change names, states and user messages.
  • Found in a small working increment, the mismatch gives a concrete example and a bounded amount of code to revise.

Design and Process Meet at the Change

  • Planning decides which behaviour the team intends to deliver.
  • Design decides where its decisions belong.
  • Tests and review help determine whether the implementation matches those intentions.
  • Feedback may revise the intentions, or reveal a weakness in the structure.

Figure 1.1: A Change Moves Through a Cycle

Clarify therequested changeChoose a smallimplementationRun checksand reviewEvidence supportsacceptance?Inspect the mismatchDemonstratethe incrementGatheruser feedbackNoYes

Reading the Figure

  • A mismatch may reveal a defect, a mistaken expectation or an unclear requirement.
  • User feedback can begin another cycle after the increment is accepted.
  • The diagram is a learning model, not a mandatory sequence where each activity happens once.
  • In practice a developer may clarify a detail while writing a test, or revisit design during review.

1.4 How Do We Know a Change Is Right?

What would make us willing to accept the change?


Acceptance Examples

  • Acceptance examples are concrete situations with expected outcomes, agreed from the requirement.
  • Examples are agreed before the change is trusted.
  • They make the distinction between categories visible.
  • They stop us validating the change using only staff cases and forgetting existing student behaviour.

Acceptance Examples for the Staff Change

BorrowerExisting countDaysEligible under the numeric rules?
Student27Yes
Student3anyNo
Staff414Yes
Staff5anyNo
  • They do not resolve an unknown borrower category. That still needs clarification.

Automated Tests and Regressions

  • An automated test performs a check repeatedly and compares observed behaviour with an expected result.
  • A regression is an unintended loss or alteration of behaviour that previously worked.
  • Regression checks help reveal such changes.
  • Their usefulness depends on what they examine and whether their expectations are correct.

A Test That Misses the Boundary

// original
&& requestedDays <= 7;

// accidental change
&& requestedDays < 7;
  • A test of a two-day request passes with both versions.
  • A seven-day request exposes the difference: true becomes false.
  • Both versions compile.

Compiling Is Not Checking Policy

  • The compiler checks language rules.
  • It does not know that the equipment office permits seven days.
  • So a successful compile tells us the source is well formed, nothing more.
  • Which brings us to the question: what evidence do we have?

Several Kinds of Evidence (1 of 2)

EvidenceCan help establishDoes not establish by itself
Successful compilationThe source satisfies the compiler’s checksThe program implements the requested policy
Passing behavioural testsThe tested cases match their expected resultsAll relevant cases were identified correctly
Code reviewAnother person examined decisions, changes and risksEvery defect was discovered

Several Kinds of Evidence (2 of 2)

EvidenceCan help establishDoes not establish by itself
User demonstrationThe workflow can be inspected against user expectationsUntested failure paths work correctly
Version historyThe team can see what changed and whyEach recorded change was necessary or correct
  • No row is a guarantee beyond its scope.
  • Strong decisions combine several kinds.

A Review Connects the Evidence

  • A reviewer might ask:
    • Why is the staff category trusted?
    • Does the student boundary remain covered?
    • Does the explanation match the actual diff?
  • A diff is a comparison showing changes between versions of files.
  • It gives the discussion a concrete object: precisely what is being proposed.

Useful Feedback

Weak feedbackUseful feedback
“This is bad design.”“The fourteen-day limit is repeated in two places, so the next change could leave them inconsistent.”
  • Feedback should lead to an action or a justified decision.
  • Good feedback names a problem and its consequence.

How the Evidence Will Grow

  • Chapter 7 develops automated testing and test-driven development.
  • Chapter 11 examines safe changes to unfamiliar code.
  • Chapter 13 checks interactions between components and introduces delivery pipelines.
  • Here the essential habit is: match each claim with a check that could reveal it to be wrong.

1.5 Working with an AI Assistant Without Giving Up Your Judgement

Candidate contributions to examine


AI Output Is a Candidate Contribution

  • An AI coding assistant may:
    • offer an explanation,
    • suggest an implementation,
    • propose test cases,
    • review a change.
  • These are useful candidates to examine.
  • They do not automatically settle what the system should do, or whether an answer is correct.

What a Vendor’s Own Documentation Says

  • GitHub’s documentation for Copilot inline suggestions warns that a suggestion can look valid and still be incorrect.
  • It says users are responsible for reviewing and validating suggestions before accepting them.
  • This is guidance about one tool, not evidence that every model behaves identically.
  • In this book we apply the same review discipline to any candidate output.

Activity 1A: Evaluate a Candidate Explanation (1 of 2)

For this activity
Your goalDecide which statements the code actually supports
You start withThe class in Section 1.1 and its three stated rules
What is in boundsJudge this one method; do not rewrite it

Activity 1A: Evaluate a Candidate Explanation (2 of 2)

For this activity
How to checkThe code, a hand trace of two inputs, the stated rules
How to decideAccept what evidence supports, reject what it contradicts, mark the rest undetermined
No AI account?The candidate output is all you need

What Kind of Output Is This?

  • The candidate output on the next slide is an author-written teaching example of a plausible AI response.
  • It was constructed for this exercise. It is not a recorded output from a particular model.
  • Do not present it as evidence about any real model’s performance.

The Candidate Output

The method permits a request only when the active-reservation count is non-negative and below three, and the requested duration is between one and seven days inclusive. It returns a Boolean result. Before returning true, it consults the equipment database to confirm that the requested item is available.


Break It into Claims

For each claim, record:

  1. Whether the code supports it, contradicts it, or provides insufficient information.
  2. The expression, input or operation that justifies your decision.
  3. Any further information needed to make a broader claim about the application.
  • Then write a corrected explanation in two or three sentences.
  • Preserve the accurate parts. Do not reject everything because one part is wrong.

Worked Answer: Claim Classification

ClaimVerdictEvidence
Count non-negative and below threeSupportedactiveReservations >= 0 && activeReservations < 3
Duration from one to seven days, inclusiveSupportedrequestedDays >= 1 && requestedDays <= 7
Returns a Boolean resultSupportedReturn type boolean
Consults the equipment databaseContradictedThe body has only comparisons and a return

Why the Database Claim Fails

  • The method receives neither an item identifier nor a period.
  • It performs no call to any database or availability service.
  • The larger application’s availability behaviour is unknown from this class.
  • Keep the scope precise:
    • “this method does not query a database” is true;
    • “the whole application never queries a database” is not established.

A Corrected Explanation

The method returns true when the supplied count is non-negative and below three, and the requested duration is from one to seven days inclusive. It does not change state or create a reservation. Availability and the accuracy of the supplied count must be handled elsewhere.


The Key Skill Is Locating Evidence

  • The presence of a database elsewhere in a real application would not establish that this method consults it.
  • A missing database call in this class would not prove that no other component ever checks availability.
  • Strong answers separate local behaviour from system assumptions.

Optional: Using a Live Assistant

  • The supplied example is sufficient. If live AI use is permitted and available, an optional prompt is:

Explain only the behaviour visible in this Java class. Identify its inputs, returned result and any state changes. Separate facts visible in the code from assumptions about its caller. Do not invent surrounding services or rewrite the program.


Checking a Live Response

  • Provide the class and compare the response with your own trace.
  • A confident explanation still needs checking.
  • A careful prompt does not guarantee a correct answer.
  • Record:
    • the tool used,
    • the claim you accepted or rejected,
    • the evidence behind that decision.

The Shared-Misunderstanding Danger

  • If the same misunderstood requirement influences both an implementation and its tests, they may agree with each other.
  • They can then both disagree with the intended policy.
  • Reviewing expected results against a clarified requirement is necessary even when all generated tests pass.

Rules for AI Use in This Book

  • Use synthetic project data.
  • Follow the module leader’s current rules for permitted AI use in assessments.
  • Every AI exercise has a route that does not require a paid service.
  • Chapter 15 collects the recurring workflows and is meant to be consulted alongside any chapter.
  • This chapter’s activity is self-contained.

1.6 The Project We Will Build, and the Road Ahead

The Campus Equipment Lending System


The Running Example

  • Throughout the book we develop the Campus Equipment Lending System in Java.
  • The first useful system supports a small set of activities:
    1. inspect availability,
    2. request a reservation,
    3. confirm an eligible request,
    4. issue the equipment,
    5. record its return.

Starting Simple

  • We begin with a simple interface and data held in memory.
  • This keeps early examples focused on domain behaviour.
  • Later chapters introduce the boundaries needed to work with storage and external services.
  • The opening ReservationRules is a small orientation example, not the completed application and not a promise that its structure will remain unchanged.

Orientation Code vs. the Running Project

  • ReservationRules is a separate teaching example.
  • The running project begins with the Chapter 2 starter.
  • Each later chapter states whether its code joins that project or stays on a separate teaching branch.
  • Appendix D lists, for every chapter:
    • where the project stands,
    • which count a build should report,
    • the rules of the domain.

Domain Vocabulary

TermMeaning in the book
BorrowerA person seeking permission to borrow equipment
Equipment itemOne individually identifiable piece of equipment
ReservationA recorded arrangement for an item over a period, whose status matters
LoanThe actual issue of an item to a borrower, followed by its return
Loan periodThe intended interval during which the item is borrowed

Reservation vs. Loan

  • A reservation and a loan are related but distinct.
  • A confirmed reservation can exist before the borrower collects the equipment.
  • Cancelling a reservation is different from returning an item already issued.
  • Confusing the two is the kind of mismatch a demonstration should expose early.

Initial Scope

  • One item per reservation.
  • Explicit periods.
  • Synthetic borrowers.
  • Once we represent actual dates and times, we must define:
    • when periods overlap, and
    • whether one period can begin exactly when another ends.
  • The integer duration in Section 1.1 does not settle those calendar rules.

Invariants

  • We will preserve invariants: conditions the domain requires to remain true during valid operation.
  • Examples:
    • a valid loan period,
    • no conflicting confirmed reservations for the same item and period,
    • no return operation on an inactive loan.
  • These guide the evolving implementation. The small rule class has not already implemented all of them.

How the Chapters Build on One Another (1 of 2)

ReadingMain development in your understanding
Chapters 2 to 4Objects and responsibilities; a backlog; a shared delivery workflow
Chapters 5 to 7Improve responsibilities and dependencies; develop behaviour with useful automated tests
Chapters 8 to 10Evaluate recurring design solutions; organise a modest application

How the Chapters Build on One Another (2 of 2)

ReadingMain development in your understanding
Chapters 11 to 13Improve unfamiliar code; interpret project evidence; implement a changed requirement across integration boundaries
Chapter 14Evaluate the result, document decisions, prepare a reproducible handover
Chapter 15Consult AI-assisted engineering methods alongside the relevant chapters

Project Versions

  • Chapters 2 to 14 follow the thirteen teaching weeks. This introduction and Chapter 15 add none.
  • Versions for the team project:
    • Week 3: kickoff.
    • Week 6: a minimum viable product.
    • Week 9: patterns and application structure.
    • Week 12: a verified response to changed requirements.
  • The later assessment change is introduced in Week 11.

The Book’s Project and Your Team’s Project

  • The book’s worked system provides shared examples.
  • Each practical activity says whether it runs on the book’s project or on your team’s project.
  • Teams choose a domain the module provides. Equipment lending and an ML model-serving tool are examples.
  • If your team uses another domain its change requests will differ, so each chapter ends with a transfer note.

Transfer, Not Copying

  • Learn the reasoning well enough to apply it elsewhere.
  • Copying an equipment-related class name into another project does not establish that the responsibility belongs there.
  • The question to ask each time: what problem made this structure useful?

AI and Data Components

  • The module aims to prepare you to build reliable software around AI and data components.
  • The running project reflects that. It does not require training a model.
  • The book takes the data-backed route. Its collaborator is the equipment directory.
  • The directory answers: “who is this borrower, and which category are they in?” from outside the application.
  • The domain code depends on it only through a contract.

Meeting the Equipment Directory in Steps

ChapterWhat happens to the directory
6Previews the contract
7Shows how a test substitutes it
8Builds the adapter that translates the provider’s codes
10Places it in the application structure
13Adds what a real provider needs: timeouts and an explicit fallback-or-failure policy

What the Book Is Not

  • If your project uses a model-serving domain, the same questions apply to the component that calls the model.
  • Using AI to help write software is a different topic. Chapter 15 covers it.
  • Neither turns this book into a machine-learning implementation course.

1.7 How to Read This Book, and Any Other

Predict, record, evaluate


Predict Before You Execute

  1. Select one ordinary case and one case near a limit.
  2. Trace the decisions.
  3. Write the expected result.
  4. Only then compare it with the program’s output.
  • A mismatch is an opportunity to find out whether your understanding, the code or the requirement needs correction.

Reading a Worked Design

  • Ask what problem made the additional structure useful.
  • If the chapter introduces a new class, identify the responsibility it receives.
  • If it introduces an interface, identify the caller that benefits from it.
  • If you cannot name the benefit, reread the problem before copying the solution.

Keep a Learning Record

For each practical session, three entries:

  1. The decision you made.
  2. The evidence you used.
  3. The uncertainty that remains.
Describes an actionExplains intent and evidence
“I changed the condition.”“I retained the student limit and checked requests at two and three existing reservations.”

Reading Software-Engineering Advice

  • Technical books recommend practices for particular circumstances.
  • Read recommendations as claims to understand and evaluate.
  • Hypothetical statement: “Give every class an interface.” Ask:
    • Which callers need interchangeable behaviour?
    • What coupling does the interface reduce?
    • Does the extra file make this example easier to understand?
  • Criticise the strongest reasonable interpretation, not an exaggerated version.

The Reading Record (1 of 2)

EntryQuestion
Claim and locationWhat exactly does the author recommend, and where?
ContextWhat problem and assumptions surround the advice?
Supporting exampleHow does the example demonstrate the claimed benefit?

The Reading Record (2 of 2)

EntryQuestion
LimitationUnder what conditions might the cost outweigh the benefit?
Project applicationWhat would change if we applied it to our system?
Further evidenceWhich independently checked source or experiment helps evaluate it?

The Critical Review Assignment

  • The module includes an individual critical review of a book from the approved software-design and Agile-engineering list, due in Week 8.
  • It asks you to:
    • evaluate the book’s principles and examples,
    • discuss their trade-offs and limits,
    • consider how they apply to engineering AI-enabled systems, supported by additional sources.
  • Follow the current assignment brief for exact requirements. Appendix E develops the review method.

What This Textbook Does Not Replace

  • This textbook does not replace the book you select.
  • An AI summary of a book you have not read is not evidence about it.
  • Begin reading early.
  • The reading record above is enough to start a useful record now.

1.8 Easy Mistakes, and How to Spot Them

Each exposed by a small counterexample


Mistake 1: Reading true as “Confirmed”

  • Mistake: reading true as “the reservation is confirmed”.
  • Counterexample: mayRequest(0, 1) returns true even if the camera is already reserved for that day.
  • Better habit: state what a result does and does not establish.

Mistake 2: Changing the Numbers

  • Mistake: changing the numbers to add the new behaviour.
  • Counterexample: replacing 3 and 7 with 5 and 14 accepts a student request for eight days.
  • Better habit: preserve existing cases while adding the new ones.

Mistake 3: Treating Passing Tests as Proof

  • Mistake: treating passing tests as proof.
  • Counterexample: a test that expects students to borrow for fourteen days passes beside an implementation with the same error.
  • Better habit: check expected results against the clarified requirement.

Mistake 4: Trusting a Fluent Explanation

  • Mistake: trusting a fluent explanation.
  • Counterexample: Activity 1A’s explanation mixes accurate claims with an invented database call.
  • Better habit: locate the code or requirement behind each claim.

Mistake 5: Assuming the Edit Is Local

  • Mistake: assuming the edit is local.
  • Counterexample: the seven-day limit may also appear in a screen and a report.
  • Better habit: search for where each decision is represented before editing.

1.9 Try It Yourself

A readiness check


What the Diagnostic Is

  • It helps identify what you need to practise.
  • It is not a new graded assessment.
  • It runs on the book’s project: the Java class in Section 1.1, its stated rules and the staff change are your materials.
  • No team code is needed.

Task A: Predict the Results

Without running the code, predict each result and name the condition that explains it.

CallYour predicted resultYour reason
mayRequest(0, 1)
mayRequest(2, 7)
mayRequest(3, 1)
mayRequest(1, 8)
mayRequest(-1, 1)
mayRequest(1, 0)

Task A: Also Identify

  • The class name, method name, parameter types and return type.
  • Whether a call modifies a reservation count or records a new reservation.
  • If a keyword is unfamiliar, distinguish that syntax gap from a difficulty understanding the rule.

Task A: Answers

CallResultReason
mayRequest(0, 1)trueCount and duration are within range
mayRequest(2, 7)trueOne more is allowed; seven days is inclusive
mayRequest(3, 1)falseThree existing already reach the limit
mayRequest(1, 8)falseEight days exceeds the student limit
mayRequest(-1, 1)falseA negative count is explicitly rejected
mayRequest(1, 0)falseDuration must be at least one day

Task A: Reading the Class

  • Class: ReservationRules. Method: mayRequest.
  • Both parameters are int; the return type is boolean.
  • There are no fields, no assignments to shared state, no storage calls and no reservation creation.
  • Calling the method neither increments a count nor reserves equipment.
  • Its result is narrower than overall eligibility or confirmation.

A Caution About final

  • Do not teach that final makes all Java objects immutable.
  • Here it only prevents subclassing.
  • The relevant absence of state changes is explained by the absence of mutable state and by the body of the method.

Task B: Examine One Small Fault

  • A developer changes requestedDays <= 7 to requestedDays < 7.
  • Provide one input that distinguishes the original and the modified version.
  • State both results.
  • Explain which result agrees with the initial requirement.

Task B: Answer

  • mayRequest(2, 7) distinguishes them.
  • Original: true. Faulty < 7 version: false.
  • The original agrees with the explicit inclusive maximum of seven days.
  • Any input with seven days and a valid count from zero to two works as a counterexample.

Optional Execution: The Demo Class

public class Chapter01Demo {
    public static void main(String[] args) {
        ReservationRules rules = new ReservationRules();
        System.out.println(rules.mayRequest(2, 7));
        System.out.println(rules.mayRequest(3, 7));
    }
}
  • Save the rule as ReservationRules.java and this as Chapter01Demo.java.
  • new ReservationRules() creates the object on which the method is called.

Compile and Run

javac ReservationRules.java Chapter01Demo.java
java Chapter01Demo
true
false
  • Run from the directory containing both files, using a Java Development Kit.
  • Appendix B gives a full, reproducible development setup. Installing tools is not required for the paper diagnostic.

Each Call Evaluates Only Its Arguments

  • The first call does not increase any count.
  • Calling mayRequest(2, 7) repeatedly returns the same result.
  • The complete application would have to update its reservation records separately.
  • Printing the returned values makes the difference visible without building a user interface.

Repeatable Checks: The Runner

for (int[] example : cases) {
    boolean expected = example[2] == 1;
    boolean actual = rules.mayRequest(example[0], example[1]);
    if (actual != expected) {
        throw new AssertionError("mayRequest(" + example[0]
            + ", " + example[1] + ") expected " + expected
            + " but got " + actual);
    }
}

Repeatable Checks: The Data

int[][] cases = {
    {0, 1, 1}, {2, 7, 1}, {3, 1, 0}, {1, 8, 0},
    {-1, 1, 0}, {1, 0, 0}, {3, 7, 0}, {0, 7, 1},
    {2, 1, 1}, {4, 2, 0}, {1, -1, 0}, {0, 14, 0}
};
  • Each row: existing count, requested days, expected result (1 means true).
  • The conversion to Boolean is a convenience for the example, not a modelling recommendation.
  • Expected output: All 12 chapter checks passed.

Running the Checks Needs No Library

  • AssertionError is thrown explicitly, so it works without enabling Java’s optional assert statements.
  • The runner stops at the first mismatch.
  • It is not meant to teach the full testing approach. That is Chapter 7.
  • The 12 checks cover the diagnostic cases and several nearby boundaries.

Two Deliberate Faults, Two Failure Messages

EditFirst failing check
requestedDays <= 7 becomes requestedDays < 7mayRequest(2, 7) expected true but got false
activeReservations < 3 becomes activeReservations <= 3mayRequest(3, 1) expected false but got true
  • The (3, 7) case would also fail, but (3, 1) appears earlier in the table.
  • Make a temporary copy first, and restore the original expression afterwards.

Task C: Plan the Staff Change

Write a short change note (no complete redesign). Include:

  • The behaviour that must remain unchanged for students.
  • The new staff behaviour and the extra information needed to select it.
  • One unresolved question to clarify rather than guess.
  • At least four acceptance examples covering both categories and their limits.
  • One simple implementation approach and one trade-off.
  • An evidence statement of two or three sentences.

Task C: Suggested Acceptance Examples

CategoryExisting countDaysDecision
Student27Allow
Student31Reject
Student08Reject
Staff414Allow
Staff51Reject
Staff015Reject

Task C: What the Examples Leave Open

  • Four well-chosen examples are enough. They must cover both categories and meaningful limits.
  • Negative counts and non-positive durations stay invalid for both.
  • None of these decisions establishes availability.
  • Counts are measured before the new reservation is added.

Task C: A Strong Evidence Statement

I would need the six acceptance examples to give the expected results, the original student checks to still pass unchanged, and a reviewer to confirm from the diff that student limits were not edited. This would not show that the category is trustworthy, that availability is checked, or that the examples match the equipment officer’s intent.


Task C: What Makes a Weak Answer

  • Weak answers say only that “the code works” or that “all tests pass”.
  • A good evidence statement names checks that could fail.
  • It also says what the evidence would still not show.

Task C: Choosing an Implementation Approach

  • A small conditional with an explicit category is an acceptable initial approach.
    • Advantage: directness.
    • Cost: repeated category conditions may spread if more policy decisions appear.
  • Passing limits into a shared calculation can also be reasonable.
    • It moves the obligation to select and validate limits to a caller.
  • Accept either with a coherent rationale. Do not reward an early interface hierarchy just for using patterns.

What to Produce

Submit or retain, as your instructor directs:

  • your completed prediction table,
  • your fault-revealing input,
  • the short change note with its evidence statement,
  • the AI claim analysis,
  • the readiness checklist.

If you run the program, include the observed result and say which version produced it.


The Task Is Complete When

  • Your predictions follow the code.
  • Your proposed change preserves the original student rule.
  • Your acceptance examples could reveal a boundary error.
  • Your evidence statement names checks that could show the change to be wrong.
  • You can explain why the method alone cannot confirm equipment availability.

Readiness Checklist

Readiness questionConfidentNeed practice
I can trace a Boolean expression with several conditions.☐☐
I can distinguish a method’s inputs, returned result and state changes.☐☐
I can identify a case at the edge of an allowed range.☐☐
I can separate a confirmed requirement from an assumption.☐☐
I can explain what evidence supports a code explanation.☐☐
I can describe a small change and the behaviour it must preserve.☐☐

Choose Your Next Action

Pick one item marked “Need practice” and state your next action:

  • Syntax: use the relevant Java tutorial (Appendix A has a refresher).
  • Tracing: work through another input with a partner.
  • Requirements: rewrite the rule as concrete examples and identify what remains unanswered.
  • If you want more: once your team has chosen its domain, repeat Task C for one numeric limit in that domain.

1.10 Questions to Think with

Concept, diagnosis, transfer


Concept Questions

  1. Give an example of software that produces the expected result today but is difficult to maintain. What makes the next change risky?
  2. Explain the difference between an iteration and an increment using the equipment-lending project.
  3. Why does passing a set of tests provide useful but limited evidence? Include an example of an incorrect expectation.
  4. Distinguish a behaviour change from a refactoring. Why should a reviewer know which one is intended?

Diagnosis Questions

  1. A developer replaces the student limits of three reservations and seven days with five and fourteen. Staff examples now pass. Explain why this is insufficient evidence that the requested change is correct, and give a student example that exposes the problem.
  2. An AI assistant writes a method and tests for it. Every test passes, so the team accepts the change without comparing it with the equipment officer’s rules. Identify the gap in the reasoning and propose a check that addresses it.

Guidance: Questions 1 and 2

  1. A rule duplicated across several screens and services may pass today’s examples yet need coordinated edits tomorrow. Name the risk of inconsistent copies, not just “ugly code”.
  2. An iteration is a repeated period of development and feedback. An increment adds usable capability, such as a reservation flow. An iteration might refine a flow without adding a feature, so the terms are not interchangeable.

Guidance: Questions 3 and 4

  1. Tests cover selected expectations. A test that wrongly expects students to borrow for fourteen days can pass beside an equally wrong implementation. Compare expectations with the clarified policy.
  2. Allowing staff fourteen days adds behaviour. Reorganising the same rule without changing outcomes is refactoring. Knowing the intention helps a reviewer decide whether a changed result is a defect or a feature.

Guidance: Questions 5 and 6

  1. A student with zero active reservations requesting eight days should still be rejected. Replacing all limits with staff limits would accept it. Staff-only checks do not protect student behaviour.
  2. Tests and implementation may share the same misunderstood requirement. Review expected results independently against agreed examples, especially category distinctions and boundaries, then execute the relevant checks.

Transfer Challenge: Room Booking

A room-booking application allows each student society up to two active bookings, each lasting at most two hours. University departments now need up to four active bookings, each at most three hours. The society rules stay unchanged.

  • Prepare a change note using the method from Task C.
  • State assumptions about duration values and booking status.
  • Give one example that must remain rejected for a society but is allowed for a department.
  • Identify one fact about room availability that these numeric rules cannot establish.

Transfer Challenge: Guidance

  • One acceptable assumption: durations are positive whole hours, and counts refer to active confirmed bookings before adding another.
  • Raise the policy for unknown groups and define what “active” means.
  • A society with two active bookings requesting one hour is rejected. A department with the same count and duration may be allowed.
  • Checking a room needs a room identity and a requested time interval. Count and duration alone cannot establish that a room is free.

What to Take Away, and What Comes Next

What to carry forward


Summary: A Working Program Is a Starting Point

  • To change a program responsibly we need to:
    • understand the requested behaviour,
    • locate the decisions that implement it,
    • identify what must remain true.
  • In the reservation example, the new requirement could not be met by editing numbers.

Summary: Design, Delivery and Evidence

  • Object-oriented design helps organise responsibilities.
  • Iterative delivery creates opportunities to inspect useful increments and revise understanding.
  • Tests, reviews, demonstrations and version history each contribute a different kind of evidence.
  • None should be mistaken for a guarantee beyond its scope.

Summary: AI-Assisted Work Needs Judgement

  • A plausible explanation can combine accurate observations with an invented operation.
  • Evaluate individual claims against the code and the requirements.
  • Record why a contribution was accepted or rejected.

Next: Chapter 2

  • Chapter 2: Object-Oriented Development Beyond Syntax.
  • How objects protect their state.
  • How responsibilities are assigned.
  • How cohesion and coupling affect the changes we make.
  • The equipment-lending project gives those ideas a concrete setting, starting from the Chapter 2 starter.

Sources and Further Reading

  1. Dev.java. Classes and Objects. Official Java learning material on classes, methods, constructors and objects.
  2. Authors of the Agile Manifesto. Principles behind the Agile Manifesto, 2001.
  3. Fowler, Martin. Definition of Refactoring, 2004.
  4. GitHub Docs. Application card: GitHub Copilot inline suggestions.
  • Full references and links are in the chapter text.

2 - Chapter 2: Object-Oriented Development Beyond Syntax

Object Oriented Development and Agile Practices (SE506)

Chapter 2: Object-Oriented Development Beyond Syntax

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 2.1 Objects: their knowledge, behaviour and identity
    • Four small classes, three kinds of identity, mutable and immutable objects.
  • 2.2 Who is responsible for keeping things true?
    • The half-open period convention and who protects which condition.
  • 2.3 Keeping state safe: encapsulation and information hiding
    • Why private is not enough, and what List.copyOf does.
  • 2.4 Is a long method the problem? Cohesion and coupling
    • Diagnosing a method by its reasons to change, then one modest refactoring.

Chapter Roadmap (2 of 3)

  • 2.5 Has-a or is-a? Composition and inheritance
    • Use versus subtype, and why a hierarchy is not the default.
  • 2.6 Drawing the design: UML class diagrams to read, write and check
    • The notation the midterm assesses, so it gets the most time.
  • 2.7 Making a change someone else can review
    • A small, reviewable change and the commands that inspect it.
  • 2.8 Checking an AI’s explanation of the coordinator
    • Classifying the claims of a plausible but flawed explanation.

Chapter Roadmap (3 of 3)

  • 2.9 Mistakes that look like good design
    • Eight misconceptions, each with a better question.
  • 2.10 Try it yourself: improve the design, with evidence
    • Parts A to D: trace, diagnose, change, organise the team.
  • 2.11 Questions to think with
    • Room-booking coordinator in a different domain.
  • What to take away, and what comes next
    • What carries into Chapter 3 (Agile development and the nature of change).

Learning Outcomes (1 of 2)

By the end of the chapter, you should be able to:

  1. Assign responsibilities to objects and explain the state they protect.
  2. Distinguish encapsulation and information hiding from mechanically adding getters and setters.
  3. Diagnose cohesion and coupling problems using concrete change scenarios.

Learning Outcomes (2 of 2)

  1. Compare composition with inheritance and justify a modest design choice.
  2. Draw and interpret a small UML class diagram that matches the code, using associations, multiplicity, aggregation, composition, inheritance and interface realisation appropriately.
  3. Make a behaviour-preserving improvement and present it as a reviewed Git change.

How to Use This Chapter

  • Core reading: Sections 2.1 to 2.9.
    • Section 2.6 (UML class diagrams) is the notation the midterm assesses, so give it the most time.
  • What you must do: the Section 2.8 AI activity and Parts A to D of Section 2.10.
    • Parts A to C run on the book’s project; Part D concerns your team’s project.
  • If you want more: the optional investigation at the end of 2.10, then the questions and the transfer challenge.

Build on Chapter 1, Use the Companion

  • This chapter builds on Chapter 1’s reservation-rule example.
  • You should be able to trace conditions, call methods and explain a returned value.
  • Java’s official class tutorial gives targeted syntax revision. Appendix A has a short refresher.
  • The companion holds every complete source file, a starting version, a completed refactoring, checks and suggested answers.
  • Use its starting listings first. Consult solutions after attempting the activity.

Toolchain Baseline

  • Java 17 language and API baseline. No preview features, no external dependencies.
  • A JDK is required to compile the examples.
  • JUnit, Maven and CI configuration are not prerequisites for this first design exercise.
  • They are introduced with the relevant later work. Appendix B records the toolchain and commands for the whole book.

Weekly Reading and the Project Version

  • Reading: Martin, Agile Software Development: Principles, Patterns, and Practices. The Part I introduction and the object-oriented design fundamentals. Sections 2.1 to 2.5 are the practical counterpart.
  • Starting version: the Chapter 2 starter in the companion.
  • Resulting version: the same code after the confirmation extraction. It becomes the running baseline for later chapters.
  • The fault demonstrations and the notation examples in 2.6 are separate teaching material. They do not join the project.

Opening Scenario: A Demonstration That Works

  • The equipment officer can now try a small reservation application.
  • It checks a borrower’s limit, looks for a conflicting reservation, records a successful request and returns a confirmation message.
  • The demonstration works.
  • But the developer who wrote it is absent when another team member is asked to change the message.

The New Developer Opens ReservationCoordinator

  • One method does all of this:
    • counts reservations,
    • applies a borrowing rule,
    • searches for date conflicts,
    • creates a record,
    • assembles the confirmation text.
  • Which lines may be changed safely?

Three Questions to Carry Through the Chapter

  1. Why is the conflict check concerned with an equipment item while the count is concerned with a borrower?
  2. Could the list of stored reservations be changed from outside the class?
  3. Which responsibilities belong together, and how should other code be allowed to use them?
  • Knowing how to declare a class is necessary. It does not answer these.

2.1 Objects

Their knowledge, behaviour and identity


Class and Object

  • A class defines a type and its implementation.
  • An object is an instance created at run time.
  • Two objects from the same class can represent different things and hold different state.
  • Java gives us fields, constructors and methods to express these distinctions.

The Reservation Record

  • A Reservation records an arrangement involving a borrower, an equipment item and a period.
  • Its state:
    • an identifier such as R1,
    • a borrower identifier such as B1,
    • an item identifier such as CAM1,
    • a LoanPeriod.

Identifiers Are Different Kinds of Identity

  • Two physical cameras may share a model name but need different item identifiers.
  • Two reservations for the same camera on different dates are still separate reservations.
  • A borrower’s name is a poor identifier: names can change or be shared by different people.
  • The identifiers stand in for borrower and equipment records. A non-blank identifier does not prove a real person or item exists.

Java Object Identity

  • Two references can point to the same instance, or to distinct instances that represent the same domain identifier.
  • == on references checks whether they refer to the same object.
  • Domain equality needs a deliberate definition. It is not automatically established by matching fields.
  • Our Reservation does not override equals. Do not assume identifier-based equality.

State and Behaviour

  • State is information an object holds.
  • Behaviour is what it can do through its operations.
  • A reservation can expose its identifier and period without letting callers replace them.
  • A coordinator can add a new record after checking the request.
  • A period can answer whether it overlaps another period.

Not Everything Needs to Be Mutable

  • In the starter, Reservation and LoanPeriod are immutable after construction:
    • their fields cannot be reassigned,
    • their referenced values are immutable,
    • their operations do not modify state.
  • The coordinator is different. It accumulates reservations in a private list.

Why the Same Request Can Give a Different Answer

  • Chapter 1’s mayRequest only examined supplied numbers.
  • A call to the coordinator may change the state that the next call examines.
  • After one successful reservation, the same request can be rejected as unavailable.

From the Numeric Rule to a Small System

  • ReservationRules stays unchanged: at most three active reservations, one to seven days.
  • The staff variation remains a future design exercise.
  • This version applies the student policy to every borrower identifier.
  • The starter has four domain and application classes.

The Four Starter Classes

ClassMain roleState held
ReservationRulesEvaluate the numeric borrowing policyNo instance fields
LoanPeriodRepresent a valid period; answer overlap questionsStart date and exclusive end date
ReservationRecord a confirmed reservationThree identifiers; a period
ReservationCoordinatorProcess requests using policy and existing recordsIn-memory reservations; a rules object

The Starter Files

FileRole
ReservationRules.javaThe unchanged numeric policy from Chapter 1
LoanPeriod.javaValid dates and exclusive-end overlap semantics
Reservation.javaImmutable reservation details
ReservationCoordinator.javaThe starting request workflow
Chapter02Demo.javaThree requests: success, overlap, adjacency
Chapter02Checks.javaSupplied behavioural checks (34)

2.2 Who Is Responsible for Keeping Things True?

Who protects which condition?


Responsibility vs. Data

  • A responsibility is an obligation assigned to part of the system.
  • “Hold two dates” describes data.
  • “Represent a period whose end follows its start” describes an obligation that gives the data meaning.

Invariants Belong at Different Boundaries

  • An invariant is a condition that must hold for valid objects or valid system states.
  • One LoanPeriod can ensure its end is after its start.
  • It cannot, by itself, ensure that another reservation has not already claimed the same equipment.
  • Local invariants and system-level conditions need different enforcement points.

A Period Convention We Can Test

  • Dates are calendar dates with no time of day.
  • A period is start inclusive, end exclusive: [start, end).
  • 20 October to 22 October covers the 20th and 21st. Another period may begin on the 22nd.
  • This is an explicit teaching policy. A real service might require a cleaning or inspection interval, which would need another rule.

LocalDate and the Convention

  • Java’s LocalDate is immutable and represents a date without a time or time zone.
  • We use isBefore and plusDays to express the convention.
  • We do not interpret these dates as UTC instants or fixed numbers of elapsed hours.
  • The exclusive-end convention is a project decision, not a rule LocalDate imposes.

The LoanPeriod Constructor

public LoanPeriod(LocalDate start, LocalDate endExclusive) {
    this.start = Objects.requireNonNull(start, "start");
    this.endExclusive =
        Objects.requireNonNull(endExclusive, "endExclusive");
    if (!start.isBefore(endExclusive)) {
        throw new IllegalArgumentException("End must be after start.");
    }
}
  • Missing date: NullPointerException. Zero-length or reversed interval: IllegalArgumentException.

The overlaps Operation

public boolean overlaps(LoanPeriod other) {
    Objects.requireNonNull(other, "other");
    return start.isBefore(other.endExclusive)
            && other.start.isBefore(endExclusive);
}
  • Two periods overlap when each begins before the other ends.
  • Both comparisons are strict, so adjacent periods do not overlap.
  • requireNonNull throws for a null argument. It does not fabricate a replacement period.

Four Overlap Examples on a Timeline

19 Oct20 Oct21 Oct22 Oct23 Oct24 Oct[20, 22) and [21, 23)Overlap: Yes. Both include 21 Oct[20, 22) and [22, 23)Overlap: No. Second starts at the first's end[20, 22) and [20, 22)Overlap: Yes. Identical periods overlap[20, 22) and [19, 24)Overlap: Yes. The second contains the firstfirst period, always [20, 22)second period

The Same Examples as a Table

FirstSecondOverlap?Why
[20 Oct, 22 Oct)[21 Oct, 23 Oct)YesBoth include 21 October
[20 Oct, 22 Oct)[22 Oct, 23 Oct)NoSecond begins at the first’s exclusive end
[20 Oct, 22 Oct)[20 Oct, 22 Oct)YesIdentical periods overlap
[20 Oct, 22 Oct)[19 Oct, 24 Oct)YesThe second contains the first
  • Comparing start dates alone would miss some of these relationships.

Who Protects Which Condition?

  • The constructor protects the local validity of a period.
  • ReservationRules protects the numeric policy, only when given an accurate count.
  • The coordinator counts its records and checks item conflicts before storing.
  • No one class can enforce all of these without information from the others.

Operating Assumptions of the Starter (1 of 2)

  • All stored records are confirmed and remain active: no cancellation, completion or expiry.
  • Calls occur sequentially in one process.
  • Data disappear when the process ends.
  • Identifiers are matched exactly, with no normalisation or authentication.

Operating Assumptions of the Starter (2 of 2)

  • The coordinator checks availability only against its own stored records.
  • Maintenance schedules and other coordinators are outside this version.
  • Within one sequential coordinator, checking conflicts before adding prevents the demonstrated overlap.
  • It does not establish safe concurrent booking by multiple processes. Later integration work revisits that boundary.

2.3 Keeping State Safe

Encapsulation and information hiding


  • Encapsulation groups state with the operations that govern its use, and controls access to that state.
  • Information hiding concerns which implementation decisions callers should not need to know.
  • Abstraction presents what is relevant to a caller and leaves out unnecessary detail.
  • None is achieved simply by writing private before every field.

Abstraction Through an Ordinary Method

  • A caller can ask a period whether it overlaps another without repeating the date comparisons.
  • That abstraction exists through an ordinary public method.
  • It does not need Java’s abstract keyword or a separate interface.
  • A useful abstraction still needs a clear contract, including our decision that adjacent periods do not overlap.

Setters Can Defeat Private Fields

  • Suppose a class exposes separate setters for a period’s start and end.
  • A caller could move the start beyond the current end.
  • If each setter merely assigns a field, making the fields private has not protected the relationship between them.

How LoanPeriod Avoids This

  • The constructor requires both dates together and validates the relationship.
  • It provides readers but no setters.
  • To express another period, a caller creates another valid object.
  • No invalid intermediate state enters the public protocol.
  • In another domain a controlled mutation operation could be right. Immutability is a design choice, not a rule.

A Getter Can Expose a Mutation Path

// Unsafe alternative for diagnosis; not the starter implementation.
public List<Reservation> reservations() {
    return reservations;
}
  • The field is private. The caller still receives a reference to the mutable list.

The Attack: clear()

  • A caller can invoke coordinator.reservations().clear().
  • That erases records without passing through the coordinator’s request checks.
  • The next request finds no conflicts.
  • “My fields are private” does not mean the state is protected.

The Starter’s Protection

public List<Reservation> reservations() {
    return List.copyOf(reservations);
}
  • List.copyOf returns an unmodifiable list whose contents are not updated by later changes to the source.
  • It protects the list structure and provides a snapshot.

What List.copyOf Does Not Do

  • It does not deep-copy the elements.
  • It does not make mutable elements immutable.
  • Here, Reservation, LoanPeriod, String and LocalDate already provide the immutability needed for the values we expose.
  • Be able to explain the protection without claiming all copies are deep copies.

final Does Not Help

  • private final List<Reservation> alone would be insufficient.
  • final prevents reassignment of the field to another list.
  • It does not prohibit add or clear on that list.
  • Reference fixed, object immutable, or both? Always ask which.

What Remains Exposed

  • The coordinator still exposes more representation than a mature system might require: callers know stored reservations can be inspected as a list.
  • That is acceptable for a small teaching version.
  • If a caller later needs only a count or an availability answer, a narrower operation may reduce what the caller must understand.

2.4 Is a Long Method the Problem?

Cohesion and coupling


Cohesion

  • Cohesion concerns how closely the responsibilities within a component belong together.
  • LoanPeriod has a focused purpose: its validation and overlap calculation concern the same period concept.
  • A coordinator that also knows message wording, storage details and borrowing policy has more varied responsibilities.

Coupling

  • Coupling concerns the dependencies between components.
  • A component depends on another when it relies on its type, operations, data format or behaviour.
  • Some dependency is necessary: software works because components collaborate.
  • The question: does the dependency express a useful relationship, or let unrelated changes spread?

The reserve Method: Validate and Count

public String reserve(String borrowerId, String itemId,
                      LocalDate start, int requestedDays) {
    if (borrowerId == null || borrowerId.isBlank()
            || itemId == null || itemId.isBlank()) {
        throw new IllegalArgumentException(
            "Identifiers must not be blank.");
    }
    Objects.requireNonNull(start, "start");
    int activeCount = 0;
    for (Reservation existing : reservations) {
        if (existing.borrowerId().equals(borrowerId)) { activeCount++; }
    }

The reserve Method: Policy and Conflict

    if (!rules.mayRequest(activeCount, requestedDays)) {
        return "Rejected: borrower limit or duration.";
    }
    LoanPeriod requested =
            new LoanPeriod(start, start.plusDays(requestedDays));
    for (Reservation existing : reservations) {
        if (existing.itemId().equals(itemId)
                && existing.period().overlaps(requested)) {
            return "Rejected: item unavailable.";
        }
    }
  • Policy is rejected before availability; keep that order.

The reserve Method: Store and Confirm

    Reservation reservation = new Reservation(
            "R" + (reservations.size() + 1), borrowerId, itemId, requested);
    reservations.add(reservation);
    return "Confirmed: " + reservation.id()
            + " for " + reservation.borrowerId()
            + " on " + reservation.itemId()
            + " [" + reservation.period().start()
            + ", " + reservation.period().endExclusive() + ").";
}
  • It creates and stores a reservation, then assembles its confirmation. It does not send it.

The Request as a Flow

Validateidentifiersand startCount thisborrower'srecordsrules.mayRequest(count, days)?Overlaps a recordfor this item?Create, addand returnconfirmationRejected: borrowerlimit or durationRejected:item unavailableallowedfreenoyesNeither rejection adds a record or uses an identifier.

Not Wrong Merely Because It Has Several Steps

  • Coordination often involves several collaborators.
  • “The method is long” is not a diagnosis.
  • The useful question is: which decisions have independent reasons to change?

Diagnosis by Change Scenario (1 of 2)

Possible requestDecision it affectsCurrent location
Change a borrowing limitNumeric policyReservationRules
Add a maintenance gap between reservationsAvailability policy and period useCoordinator conflict check, possibly period rules

Diagnosis by Change Scenario (2 of 2)

Possible requestDecision it affectsCurrent location
Store records beyond process lifetimeStorage mechanismCoordinator’s list and storage operations
Translate a confirmation messagePresentation wordingCoordinator’s final return expression
  • This identifies which changes currently require editing the coordinator, and why.
  • A future database is a possible boundary. It is not a reason to build a repository framework now.

A Small Improvement with a Limited Claim

  • Extract the confirmation expression into a private method confirmationFor.
  • The request method still adds the record first, then returns the helper’s result.
  • The helper receives the existing reservation and returns exactly the same text.
  • This is a modest refactoring: it gives the formatting a name.

What the Extraction Does Not Do

  • It does not create a separate presentation component.
  • It does not remove storage coupling.
  • It does not make the coordinator thread-safe.
  • A private helper changes readability inside the class. A new component would change the collaboration between classes.
  • If translation later needs collaborators, a separate formatting responsibility may become justified.

Preserving Behaviour: Text and State

  • The supplied checks examine both returned messages and stored state.
  • After the extraction both must remain the same.
  • Preserving the success string while forgetting to store the reservation would not preserve behaviour.

2.5 Has-A or Is-A?

Composition and inheritance


Composition

  • Composition, in the broad programming sense: building behaviour by giving an object collaborators or component objects.
  • A Reservation has a LoanPeriod and delegates the overlap question through it.
  • A coordinator has a ReservationRules object. It does not inherit from it.

It Fits the Domain Meanings

  • A coordinator is not a kind of borrowing rule.
  • A reservation is not a kind of period.
  • Their relationships concern what they use or contain, not a subtype relationship.

Inheritance

  • Inheritance defines a derived class in relation to a superclass.
  • Java supports inherited members, method overriding and run-time selection of overridden instance methods.
  • These mechanisms can express a useful family of related behaviours.
  • They also establish expectations that callers may rely on.

A Hypothetical SpecialReservation

  • Suppose a subclass is introduced only because some borrowers have longer limits.
  • Is the difference really a new kind of reservation, or a policy governing whether one may be created?
  • If staff status can change while an existing reservation keeps its identity, forcing that distinction into a hierarchy may be awkward.

Not a Verdict Against Inheritance

  • This does not prove inheritance is always unsuitable.
  • Ask: what varies, what remains stable, and what a caller expects from the base type.
  • Chapter 5 develops behavioural substitutability more formally.
  • For this version, constructors, fields and collaborators already express the relationships without a custom hierarchy.

Composition vs. Inheritance: Questions

QuestionComposition can help whenInheritance deserves consideration when
What is the relationship?One object uses or contains anotherOne type has a meaningful subtype relationship
What varies?A collaborator or delegated responsibilityBehaviour shared through a stable base contract
What must be reviewed?How collaborators are selected and calledWhether derived behaviour preserves caller expectations

Composition Has Costs Too

  • Too many small collaborators make a request harder to follow.
  • Avoid replacing one readable expression with several classes unless that organisation solves an actual problem.
  • The design goal is understandable responsibility boundaries, not the maximum number of objects.

2.6 Drawing the Design

UML class diagrams to read, write and check


What a Class Diagram Is

  • A class diagram is a selective explanation of a design.
  • It answers one question for one audience: for example, “which objects take part in a reservation request, and how are they related?”
  • It is not a copy of the source code. A diagram showing every field and method answers no question well.
  • Class diagrams are the only UML notation used in this chapter.

Three Skills in This Section

  1. Read a diagram.
  2. Draw one from a written description.
  3. Check one against code.
  • Hand-drawn diagrams use the same symbols as the ones in this book.
  • The book’s diagrams are written as text (Mermaid) so they can be kept beside the code. Appendix A shows the syntax.

Figure 2.1: The Starter’s Collaborators

ReservationCoordinator+reserve(String borrowerId, String itemId,LocalDate start, int requestedDays) String+reservations() List<Reservation>ReservationRules+mayRequest(int activeReservations,int requestedDays) booleanReservation-String id-String borrowerId-String itemId+period() LoanPeriodLoanPeriod-LocalDate start-LocalDate endExclusive+overlaps(LoanPeriod other) boolean11uses10..*stores0..*1refers to

Reading the Boxes

  • Each box is a class with up to three compartments:
    • top: the class name,
    • middle: the attributes (data the class keeps),
    • bottom: the operations (methods other code can call).
  • A label in double angle brackets above the name, such as <<interface>>, says what kind of class it is.
  • + marks a public member and - a private one.

A Box and the Java It Describes

// Excerpt from LoanPeriod.java; method bodies omitted.
public final class LoanPeriod {
    private final LocalDate start;
    private final LocalDate endExclusive;

    public LoanPeriod(LocalDate start, LocalDate endExclusive) { ... }
    public LocalDate start() { ... }
    public LocalDate endExclusive() { ... }
    public boolean overlaps(LoanPeriod other) { ... }
}

Mapping Box Parts to Source

Part of the boxIn the Java sourceHow to read it
LoanPeriodclass LoanPeriodThe class name
-LocalDate startprivate final LocalDate start;A private attribute start of type LocalDate
+overlaps(LoanPeriod other) booleanpublic boolean overlaps(LoanPeriod other)A public operation taking a LoanPeriod, returning boolean
  • Type comes before the name, as in Java. The return type comes after the parameter list.

What a Box Leaves Out

  • The constructor and the accessors start() and endExclusive() exist in the code but not in the box.
  • The explanation does not need them.
  • You can trust a diagram for what it shows.
  • You cannot assume that what it omits does not exist.

Attribute or Line, Not Both

  • A field whose type is a plain value (String, int, LocalDate) is normally an attribute.
  • A field whose type is another class of your design is normally a line to that class’s box.
  • Do not show it both ways. A -LoanPeriod period attribute beside a line to LoanPeriod says one thing twice.

Reading Multiplicity

  • The number at each end of a line is a multiplicity: how many objects take part.
  • Stand at one class and read the number at the far end.
  • That is how many objects of the far class one object of your class is linked to.
WrittenMeans
1exactly one
0..1none or one
0..*zero or more
1..*one or more

Reading One Line in Both Directions

ReservationCoordinator "1" --> "0..*" Reservation : stores

  • From the coordinator: “each coordinator stores zero or more reservations.”
  • From a reservation: “each reservation is stored by exactly one coordinator.”
  • The arrowhead shows which object holds the reference.

Try It 1: The Other Two Lines

  • Write one sentence for each of the other two lines in Figure 2.1, reading from both ends.
  • For each number, find where the Java code enforces it and where the model merely assumes it.
  • A strong answer separates “enforced by the code” from “true of how the code happens to be used”.

Try It 1: Answers (1 of 2)

LineReadingEnforced by
Coordinator 1 to 1 RulesEach coordinator uses one rules object; each rules object is used by one coordinatorprivate final ReservationRules rules = new ReservationRules();
Coordinator 1 to 0..* ReservationEach coordinator stores zero or more; each reservation is stored by one coordinatorThe list starts empty and has no upper limit

Try It 1: Answers (2 of 2)

LineReadingEnforced by
Reservation 0..* to 1 LoanPeriodEach reservation refers to exactly one period; a period may be referred to by any number of reservationsrequireNonNull(period, "period") and the final field
  • Only assumed:
    • that a rules object is never shared,
    • that a reservation is held by only one coordinator (the constructor is public; snapshots hold the same objects),
    • the 0..* on periods (nothing prevents sharing, though each reservation currently gets its own).

The Relationships You Need to Read and Draw

AssociationABLasting link; arrowhead shows who holds itDependencyABUses another class without keeping itAggregationABWhole groups parts that exist on their ownCompositionABWhole owns parts that need it to existInheritanceABSubclass is a kind of the superclassRealisationABClass implements the interface's contract

Relationships: When to Use Each (1 of 2)

RelationshipRead it asUse it when
AssociationOne object is linked to anotherThe link is structural and lasting
DependencyOne class uses another without keeping itA parameter or brief call is the only link
AggregationA whole groups parts that can exist without itParts can be shared or removed and still exist

Relationships: When to Use Each (2 of 2)

RelationshipRead it asUse it when
CompositionA whole owns parts with no meaning without itA part’s lifetime is tied to its owner
InheritanceThe subclass is a kind of the superclassA real subtype relationship holds
Interface realisationThe class implements the interface’s contractSeveral classes satisfy one contract callers rely on

Figure 2.2: A Notation Sampler

Pack-String title+print(Printer printer) String«interface»Printer+render(List<String> lines) StringPdfPrinterPageArticleResearchArticleAuthor11..*contains0..*1..*collects1..*1..*written byextendsusesimplements

Reading the Sampler

  • A pack owns its pages: a page has no meaning without its pack (composition).
  • A pack only collects articles, which exist independently and can appear in many packs (aggregation).
  • Articles and authors: a plain association, no arrowheads, because the description does not say who holds the reference.
  • This is a notation sampler for a course-pack printing service. It is not part of the reservation project.

Reading the Sampler, Continued

  • A research article is a kind of article (inheritance).
  • A pack uses a printer only while printing (dependency).
  • PdfPrinter implements the Printer interface (realisation).

Choosing a Relationship by Meaning

Choose by what it means, not how it looks. For each pair of classes, ask in order:

  1. Does one class implement a contract that other classes rely on? Realisation.
  2. Is A a kind of B, so a client of B could be handed an A without noticing? Inheritance (Chapter 5 tests this with the Liskov Substitution Principle).
  3. Does A keep a lasting reference to B in a field? If not, and A only uses B as a parameter, local variable or short-lived object: dependency.

Choosing a Relationship, Continued

  1. If A keeps the reference, does B belong to A so completely that it has no meaning without A and is discarded with it? Composition.
  2. Does A group Bs that exist independently and could be shared or removed? Aggregation, but only if you can say what the grouping adds. Otherwise a plain association.

Aggregation and Composition Are Claims

  • Aggregation is often indistinguishable from association in code.
  • Composition describes a lifetime the Java types do not enforce.
  • Both are claims about meaning.
  • Figure 2.1 uses plain associations: periods can be shared by reference, and the code enforces no lifetime.
  • Do not confuse “composition” as a programming idea (building from collaborators) with the filled diamond, a stronger ownership claim.

From Code to Diagram (1 of 2)

In the Java sourceIn the diagram
private final LocalDate start;Attribute -LocalDate start
private final List<Reservation> reservationsAssociation to Reservation, 0..* at that end
private final ReservationRules rulesAssociation to ReservationRules, 1 at that end
A parameter, local variable or new X() used only inside a methodDependency on X

From Code to Diagram (2 of 2)

In the Java sourceIn the diagram
class A extends BInheritance, triangle at B
class A implements IRealisation, triangle at I
public and private+ and -
  • Caution 1: the Java type does not settle the meaning. A List<Reservation> might be an association or a composition. Only the design decides.
  • Caution 2: a field that can be null is 0..1, not 1.

A Worked Example: From Scenario to Diagram

The workshop keeps tools in numbered lockers.

  • A locker holds at most one tool, and a tool sits in at most one locker.
  • A technician looks after any number of lockers; each locker has exactly one technician at a time, and can be reassigned.
  • Every inspection of a locker is logged as an entry with a date and a note; an entry is meaningless without its locker.
  • Reports are printed as plain text now; other formats are expected later.

Steps 1 and 2: Nouns, Then Classes

  • Step 1: find the nouns. Workshop, tools, lockers, technician, inspection, entry, date, note, report, plain text, formats.
  • Step 2: decide which become classes. Keep a noun as a class when it has data or rules the description needs.
  • The table on the next slides records each decision and its reason.

Step 2: Candidates and Decisions (1 of 2)

CandidateDecisionReason
WorkshopLeave outThe whole system; no behaviour described for it
LockerClassHas a number, a log and a report
ToolClassA locker holds one
TechnicianClassLooks after lockers

Step 2: Candidates and Decisions (2 of 2)

CandidateDecisionReason
Inspection entryClassHas a date and a note; belongs to a locker
Date, note, number, nameAttributesPlain values describing a class
ReportOperation of LockerSomething a locker does, not a thing with its own data
Plain text, other formatsReportPrinter interface and PlainTextPrinter classThe format is expected to vary

Step 3: Attributes and Operations

  • Separate what the description states from what you assume.
  • Stated: a locker’s number and report; an entry’s date and note.
  • Assumptions, which you should label as assumptions:
    • a technician’s name and a tool’s name as identifying attributes,
    • a single print operation on the printer, one reasonable way to give the report a format-independent contract.
  • A different, justified choice is acceptable.

Step 4: Choose a Relationship for Each Pair

PairWhat decides itResult
Technician and lockerNeither owns the other; a locker can be reassignedPlain association
Locker and toolNeither is part of the otherPlain association
Locker and inspection entryAn entry is meaningless without its lockerComposition, diamond at Locker
Locker and ReportPrinterA locker uses a printer only while reportingDependency
PlainTextPrinter and ReportPrinterThe class implements the contractRealisation

Step 5: Write Each Multiplicity as a Sentence

  • Each locker is looked after by exactly one technician: 1 at the Technician end.
  • A technician looks after any number of lockers: 0..* at the Locker end.
  • A locker holds at most one tool and a tool sits in at most one locker: 0..1 at both ends.
  • A locker has any number of entries; an entry belongs to exactly one locker.

Step 6: Read It Aloud and Delete

  • Read every line in both directions against the description.
  • Remove anything the description does not say.
  • Then compare with the model on the next slide: ask why each line differs, not whether it matches.

Figure 2.3: The Resulting Model

Technician-String nameLocker-int number+report(ReportPrinterprinter) StringTool-String nameInspectionEntry-LocalDate date-String note«interface»ReportPrinter+print(List<String> lines) StringPlainTextPrinter10..*looks after0..10..1holds10..*logsusesimplements

Reading Figure 2.3

  • The two plain associations have no arrowheads: the description does not say which object holds the reference.
  • A model should record what the description says and no more.
  • Other answers are defensible: Tool only as an attribute, or a Workshop class if the task were scheduling.

Checking a Diagram Against the Code

A diagram that disagrees with its code is worse than no diagram, because readers trust it.

  • For every box, find the class. For every attribute, find the field with that name and type.
  • For every line, find the field, parameter or extends/implements clause that creates it. If you cannot, remove the line or fix the code.

Checking Multiplicities and Omissions

  • For every multiplicity, find where the code enforces it: a constructor check, a final field, a collection type. If nothing does, mark it as an assumption.
  • List what the diagram leaves out, and decide whether each omission is deliberate.

Try It 2: What Figure 2.1 Leaves Out

  • Find two details present in the Java but absent from Figure 2.1.
  • Say why leaving each out is acceptable.
  • Examples of acceptable answers: the constructors and their validation; the accessors id(), borrowerId(), itemId(); the period field (the association line already says it); the private reservations and rules fields; the order of the checks and the exceptions thrown.

What the Diagram Cannot Tell You

  • It omits standard library classes and some accessors.
  • It does not establish the order of operations, display every validation, or prove overlapping bookings are rejected.
  • Read the method body and the period convention for those questions.
  • After extracting a private formatting method, the relationships in Figure 2.1 are unchanged: useful evidence about the scale of the edit.

Spot the Errors: Figure 2.4

Technician-String name-Locker lockerLocker-int number+report(PlainTextPrinterprinter) StringToolInspectionEntryPlainTextPrinter«interface»ReportPrinter+print(List<String> lines) String0..*1looks after0..10..1holdslogsuses

Try It 3

  • Another student’s first attempt at the workshop model (Figure 2.4) contains at least five mistakes.
  • List every mistake you can find.
  • For each, say why it is wrong using the description.
  • Say what the correct notation is.
  • Then redraw the diagram.

Try It 3: Answers (1 of 2)

#MistakeWhy it is wrong
1-Locker locker attribute in TechnicianRepeats the line; implies one locker per technician
2Multiplicities reversed on Technician to LockerSays each locker has many technicians and each technician one locker
3Filled diamond at TechnicianClaims lockers die with their technician, but a locker can be reassigned

Try It 3: Answers (2 of 2)

#MistakeCorrect notation
4Locker <|-- InspectionEntryComposition: filled diamond at Locker, 1 and 0..*
5Locker ..> PlainTextPrinter and report(PlainTextPrinter printer)Depend on ReportPrinter; type the parameter as ReportPrinter
6ReportPrinter <|-- PlainTextPrinterDashed line, hollow triangle at ReportPrinter
  • Mistakes 2 and 3 sit on the same line. Count them as two ideas if both are explained.

Common Drawing Mistakes (1 of 3)

MistakeWhy it is wrongBetter habit
Multiplicity at the wrong endThe far number counts the far class; reversed numbers say the oppositeRead every line aloud in both directions
Arrowhead as ownershipAn arrowhead shows who holds the reference, not who owns whomUse diamonds, with a reason, for ownership
Inheritance for “uses” or “has”A locker is not a kind of technician; an entry is not a kind of lockerTest “A is a kind of B” and its promises

Common Drawing Mistakes (2 of 3)

MistakeWhy it is wrongBetter habit
Solid line for realisationRealisation is dashed; solid with a triangle is inheritanceCheck the line style of every triangle
Filled diamond because the field is finalComposition claims a lifetime; final only fixes a referenceAsk whether the part can exist without the owner
Showing a relationship twice-Locker locker plus a line to Locker repeats one factShow a field of a design class as a line only

Common Drawing Mistakes (3 of 3)

MistakeWhy it is wrongBetter habit
Depending on a concrete class when an interface existsA client naming PlainTextPrinter cannot accept another formatPoint the dependency at the interface
Drawing every class and memberThe design question disappears among the detailOne diagram, one question, only the members it needs
A diagram that disagrees with the codeReaders trust the diagramRun the checking steps before sharing it

2.7 Making a Change Someone Else Can Review

A change a team can inspect


Version Control Is Not Evidence of Good Design

  • A codebase becomes a shared piece of work when the team can inspect its changes, reproduce its behaviour and explain its decisions.
  • Version control supports that work.
  • A commit alone is not evidence of a good design.

Working Files, Staging, Commits

Working files(what you edit)Staging area(the next commit)Recorded commits(history)git addgit commitgit diffgit diff --stagedEdit, stage the exact change, inspect it, then record it.

Why the Staging Area Matters

  • You edit working files, select content for the next commit, and record that selected state.
  • The staging area matters when unrelated edits are present.
  • The next commit should contain the change you intend to explain.

Branches and Pull Requests

  • A branch names a line of development. For the lab, use a short-lived branch containing the confirmation extraction.
  • A pull request (or merge request) is a hosting-platform review proposal. It is not the same as a local commit.
  • A local peer review can still serve the learning objective when no hosting account is available.

Trunk-Based or Feature Branches?

  • Trunk-based development: integrate small changes frequently into a main line, sometimes through very short-lived branches.
  • Feature-branch workflow: group a feature’s work on a branch before integration.
  • The approaches overlap: a short reviewed branch can be part of frequent integration.
  • For this course: keep changes small, reviewable and integrated regularly.

A Reviewable Change: The Sequence

  1. Begin with the supplied starting files and a recorded baseline.
  2. Confirm that the checks pass before editing.
  3. Create a branch.
  4. Perform only the extraction.
  5. Rerun the checks and inspect the diff.
  • The companion contains the complete setup commands.

The Inspection Commands

git status
git diff
git diff --staged
  • git status reports file state.
  • git diff normally shows tracked changes not yet staged.
  • git diff --staged shows staged changes against the current commit.

Reading the Output Correctly

  • An empty git diff after staging does not mean there is nothing to commit.
  • Untracked files also need attention in git status.
  • Keep compiled class files out of commits: the companion’s .gitignore handles generated files.
  • Appendix B collects the Git commands and the pull-request workflow (B.8).

Commit Messages

  • A useful message: Extract reservation confirmation formatting.
  • Many teams adopt a written convention. Conventional Commits puts a type before a short description: refactor: extract reservation confirmation formatting
  • Use whichever convention your team agrees, and apply it consistently.

A Useful Review Description

  • State the problem, the limited change, and the checks performed.
  • Example: “The request method interleaves storage and confirmation text. This change moves the text assembly into a private helper. The existing messages and stored records remain unchanged in the supplied checks.”
  • Add limits: persistence, concurrent booking, lifecycle operations and message delivery remain outside this version.

What the Reviewer Inspects

  • Inspect the diff.
  • Verify that the helper adds no state changes.
  • Check that the record is still added before confirmation is returned.
  • “Looks cleaner” is weaker than an explanation of what was inspected and why it matters.

A Diff With Only Additions Still Moves Behaviour

  • In Git, the extraction appears as four added lines and no removed lines.
  • The existing return "Confirmed: "... expression is kept and now sits inside the new method.
  • The reviewer must read where the retained lines ended up.

One Change at a Time

  • Do not combine this refactoring with changed limits, renamed identifiers or new message punctuation.
  • Each may be legitimate work.
  • Combining them makes it harder to determine why a result changed.

2.8 Checking an AI’s Explanation of the Coordinator

Classifying claims against the code


Ground Rules for the Activity (1 of 2)

For this activity
Your goalCompare an explanation of the coordinator with its actual control flow, state changes and side effects
You start withThe complete starting ReservationCoordinator and collaborators
What is in boundsJudge the explanation only; do not change the code

Ground Rules for the Activity (2 of 2)

For this activity
How to checkTracing the code, the three-request demonstration, the supplied checks
How to decideKeep supported claims, reject contradicted ones, mark claims the code does not establish
No AI account?The ready-made example is enough
  • Chapter 15 collects the recurring AI workflows. This activity is self-contained.

The Ready-Made Example (1 of 2)

An ready-made example written by the author imitating a plausible AI explanation. Not a captured response from a real model.

The coordinator validates identifiers, counts the borrower’s stored reservations and checks the numeric rule. It tests availability using existing reservations for the same item. On success, it adds a reservation before returning a confirmation.


The Ready-Made Example (2 of 2)

It sends that confirmation by email and persists the reservation to a database. Because the list field is final, callers receive a deeply immutable copy and booking is safe under concurrent requests.

  • Classify each claim as supported, contradicted or not established.
  • Cite the relevant operation, or the absence of one.

Claim Classification (1 of 2)

ClaimJudgementEvidence
Validates identifiersSupported within scopeRejects null or blank identifiers; no catalogue check
Counts the borrower’s stored reservationsSupportedLoops over records, compares borrower identifiers
Applies the numeric ruleSupportedCalls ReservationRules.mayRequest
Checks conflicts for the same itemSupportedMatching item and overlapping period

Claim Classification (2 of 2)

ClaimJudgementEvidence
Adds before returning a confirmationSupportedadd is called before the message is built
Sends emailContradictedNo sender or delivery call; the method returns a string
Persists to a databaseContradictedAn in-memory ArrayList; no database operation
Final list: callers get a deeply immutable copyIncorrectfinal stops reassignment; List.copyOf is shallow
Concurrent booking is safeNot establishedCheck and add are not one synchronised operation

A Corrected Four-Sentence Explanation

The coordinator rejects missing or blank identifiers and requires a start date, then counts the borrower’s stored records and applies the numeric rule. It checks overlapping periods for the same item before recording a successful reservation and returning confirmation text.


A Corrected Four-Sentence Explanation, Continued

Policy and availability rejections do not add a record, and the returned reservation list is an unmodifiable snapshot containing immutable elements. The implementation has in-memory storage only, sends no email, and is intended for sequential use rather than concurrent booking.


Optional Live Assistant

Trace this coordinator and its supplied collaborators. Identify inputs, returned values and state changes on success and rejection. State what is established about storage, notifications and concurrency. Separate facts visible in the code from assumptions. Do not change the code.

  • Record one accepted claim and one rejected or qualified claim with evidence.
  • A second model’s agreement is not a substitute for tracing the operations.

2.9 Mistakes That Look Like Good Design

Eight misconceptions, eight better questions


Mistakes 1 and 2

  • “Private fields mean the object is safely encapsulated.”
    • A returned mutable list can still expose a modification path.
    • Better question: what can callers change through the references they receive?
  • “A final field makes the referenced object immutable.”
    • The coordinator can still add to its final list.
    • Better question: is the reference fixed, the object immutable, or both?

Mistakes 3 and 4

  • “Every noun should immediately become a class.”
    • Several concepts have no behaviour or state we need yet.
    • Better question: what responsibility would that class own now?
  • “More interfaces automatically mean less harmful coupling.”
    • An interface can repeat an unstable contract without solving a caller’s problem.
    • Better question: which dependency would this interface improve?

Mistakes 5 and 6

  • “Calling another class means inheriting from it.”
    • The coordinator uses a policy; it is not a policy subtype.
    • Better question: is the relationship use, containment or substitution?
  • “A filled diamond just means ‘has a’.”
    • Composition claims the part cannot outlive its owner; LoanPeriod objects can be shared by reference.
    • Better question: does the code enforce this lifetime, and would the domain agree?

Mistakes 7 and 8

  • “A formatting refactoring fixes the whole design.”
    • The helper leaves storage and other responsibilities in the coordinator.
    • Better question: which improvement did this edit actually make?
  • “Green checks prove the application is production-ready.”
    • The starter omits lifecycle, persistence, catalogue validation and concurrency.
    • Better question: which claims have we tested, under which assumptions?

2.10 Try It Yourself

Improve the design, with evidence


Inputs and Starting Point

  • Parts A to C use the book’s project; Part D concerns your team’s project.
  • Use the companion’s common source files and the starting ReservationCoordinator.java.
  • Save all six Java files in one directory; compile and run them using the companion’s instructions.
  • The check runner should report 34 passing checks before you begin.
  • Read the checks as provided evidence. You need not master the runner yet.

Compile and Run

javac --release 17 -d out ReservationRules.java LoanPeriod.java \
    Reservation.java ReservationCoordinator.java \
    Chapter02Demo.java Chapter02Checks.java
java -cp out Chapter02Demo
java -cp out Chapter02Checks
  • Recompile after every source change. Otherwise the JVM may run old class files and give misleading evidence.

Expected Output

Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Rejected: item unavailable.
Confirmed: R2 for B2 on CAM1 [2026-10-22, 2026-10-23).
Stored: 2
All 34 chapter checks passed.

Part A: Understand the Request

Trace the three requests in Chapter02Demo.

  • Record the returned message and the number of stored records after each call.
  • Identify the first condition that rejects the overlapping request.
  • Explain why the adjacent request is accepted.
  • Explain why the rejected request does not consume a reservation identifier.

Part A: The Trace

CallOutcomeStoredImportant step
B1 requests CAM1 from 20 Oct for two daysConfirmed as R11Empty history has no conflict
B2 requests CAM1 from 21 Oct for one dayRejected: unavailable1Overlaps R1 for the same item
B2 requests CAM1 from 22 Oct for one dayConfirmed as R22Equality with R1’s exclusive end is permitted
  • Identifiers derive from the number of stored records, so a rejection consumes none. That may not suit deletion, persistence or several coordinators.

Part A: Diagram and Invariants

  • Draw a small class diagram of the starter without copying Figure 2.1.
    • Use the “From code to diagram” table, then run the four checking steps on your own drawing.
  • Annotate one local invariant and one condition that needs information from multiple objects.
  • A local invariant: a period’s end follows its start (the LoanPeriod constructor).
  • A multi-object condition: confirmed reservations for the same item do not overlap (the coordinator checks it).

Part A: A Separate Modelling Exercise

Equipment kits. It does not extend the project.

  • A kit has a name and holds one or more equipment items.
  • Removing a kit does not delete its items; an item belongs to at most one kit.
  • Each item keeps any number of maintenance records; a record belongs to exactly one item and has no meaning without it.
  • A kit summary is produced through an exporter: CSV now, other formats later.

Part A: What to Draw

  • A class diagram with the classes, their key attributes and multiplicities.
  • Include at least one aggregation, one composition, one interface realisation and one dependency.
  • State one assumption you had to make.
  • For each relationship, write the Section 2.6 question that decided it.
  • Then read every line aloud in both directions against the scenario.

One Suitable Model: Equipment Kits

EquipmentKit-String name+summary(Exporter exporter) StringEquipmentItem-String itemIdMaintenanceRecord-LocalDate performedOn-String note«interface»Exporter+format(List<String> lines) StringCsvExporter0..11..*groups10..*keepsusesimplements

What a Strong Kit Answer Shows

RequirementModel decision
A kit holds one or more items; removing it does not delete themAggregation, 1..* at the items end
An item belongs to at most one kit0..1 at the kit end
A record belongs to exactly one item and has no meaning without itComposition from item to record: 1 at the item, 0..* at the records
A summary is produced through an exporter; other formats expectedExporter interface realised by CsvExporter; the kit depends on the interface

Common Kit Errors

  • Interface realisation drawn as inheritance with a solid line.
  • The diamond reversed.
  • 1..* at the wrong end.
  • The kit depending directly on CsvExporter.
  • An assumption can be anything stated and consistent: for example, that an item with no kit is allowed.
  • Do not credit extra classes for their own sake.

Part B: Diagnose the Design

  • Write a short responsibility inventory for the coordinator.
  • Identify two distinct reasons it might change.
  • Explain one cohesive collaborator and one dependency that deserves attention.
  • Avoid judging quality by method length alone.

Part B: A Model Answer

  • The coordinator validates inputs, counts records, invokes policy, checks availability, allocates an identifier, stores a record and formats a message.
  • Two reasons to change: a new storage mechanism and changed confirmation wording.
  • LoanPeriod is cohesive: construction rules and overlap concern one concept.
  • The coordinator depends on both reservation representation and message wording.

Part C: Make the Change

  • On a short-lived branch, move only the confirmation-string expression into a private confirmationFor(Reservation reservation) method.
  • Keep unchanged: output, validation order, identifier allocation, storage effects.
  • Do not add a new interface or change public method signatures.
  • Run the checks again, inspect the diff, ask a peer to review.

Part C: The Completed Method

        reservations.add(reservation);
        return confirmationFor(reservation);
    }

    private String confirmationFor(Reservation reservation) {
        return "Confirmed: " + reservation.id()
                + " for " + reservation.borrowerId()
                + " on " + reservation.itemId()
                + " [" + reservation.period().start()
                + ", " + reservation.period().endExclusive() + ").";
    }

Part C: Git Baseline Commands

git init -b main
git add .gitignore ReservationRules.java LoanPeriod.java \
    Reservation.java ReservationCoordinator.java \
    Chapter02Demo.java Chapter02Checks.java
git diff --staged
git commit -m "Add chapter 2 reservation starter"
git switch -c refactor/confirmation-text
  • .gitignore contains out/ and *.class.

Part C: Commit the Extraction

git status
git diff
git add ReservationCoordinator.java
git diff --staged
git commit -m "Extract reservation confirmation formatting"
git log --oneline -2
git status
  • Produce one focused commit after addressing relevant feedback.
  • In independent study, perform a self-review and label it honestly as such.

Part C: A Review Record

  • Problem: the request method contains message formatting alongside the request workflow.
  • Change: move only the confirmation-string construction into confirmationFor, keeping record creation and storage order.
  • Evidence: identical demonstration output; the same 34 checks pass; the diff changes only internal organisation.
  • Limits: persistence, concurrent booking, lifecycle operations and message delivery remain outside this version.

Part C: What the Review Record Needs

  • The change or commit identifier.
  • The reviewer.
  • The evidence inspected.
  • The findings and the response.
  • Creating a commit does not create a hosted pull request or establish that anyone reviewed it.
  • Record your own observed results. Do not copy a claim you did not verify.

Part D: Organise Collaboration

  • Form teams of three or four as directed by the module leader.
  • Confirm which provided starter domain your assessed project uses. If it is not equipment lending, use each chapter’s transfer note.
  • Set up an issue board with columns that match how your team will work.
  • Add three cards with concrete completion conditions.

Part D: Cards and Branch Policy

  • A good card: “extract confirmation formatting with unchanged output and checks”. Not: “improve OO”.
  • Agree a branch policy:
    • very short-lived branches integrated into a main line often, or
    • one feature branch per story.
  • Write down your reason. The reason matters more than the choice.
  • Agree who prepares the next demonstration and who reviews the next change. Rotate roles.

Evidence to Retain

  • The annotated diagram and the scenario model.
  • The responsibility diagnosis.
  • Before-and-after check results.
  • The commit identifier and the review note.
  • Your AI claim analysis.
  • Your team’s issue board and branch-policy note.
  • A short limitation statement: which risks remain after the extraction?

The Activity Is Complete When

  • The diagram matches the code.
  • The extraction preserves the checked behaviour.
  • Your review explains both the improvement and its limits.
  • You can explain the returned list’s protection without claiming all copies are deep copies.

Optional Investigation: Break It on Purpose

  • In a temporary copy, replace List.copyOf(reservations) with reservations.
  • Predict the effect of an external clear() call, then run the checks.
  • Result: snapshot::clear succeeds; the runner reports that snapshot unmodifiable expected an UnsupportedOperationException.
  • Restore the original before continuing. This is a fault demonstration, not an improvement.

A Second Fault: Omit the Storage

  • In another disposable copy, remove reservations.add(reservation);.
  • The first confirmation can still look correct.
  • The checks fail with one success stored.
  • A demonstration that inspects only printed text would miss the changed state.

2.11 Questions to Think with

Concept, diagnosis, transfer


Concept Questions

  1. Distinguish a Java object reference from a reservation’s domain identifier. Why are equal identifier strings not sufficient to make two Reservation instances equal through equals in this starter?
  2. Explain one local invariant and one system-level condition in the project. Why do they need different enforcement points?
  3. Explain cohesion and coupling using one likely change to the coordinator. Why is reducing the total number of dependencies not a sufficient design goal?
  4. Compare composition and inheritance for a reservation and its loan period. What would inheritance incorrectly suggest?

Diagnosis Questions

  1. A developer adds a getter returning the private mutable list and claims the coordinator is still encapsulated because the field is private. Give a concrete counterexample and explain the limits of the proposed correction using List.copyOf.
  2. A refactoring returns the same confirmation text but omits reservations.add(reservation). The developer demonstrates the printed text and declares success. Explain the behavioural change and identify evidence that would expose it.

Guidance: Questions 1 and 2

  1. A reference identifies a Java object instance; id represents domain identity in the teaching model. Two separately constructed reservations can hold the same identifier text yet remain distinct under the inherited equals. Identifier-based equality needs an explicit contract and implementation.
  2. LoanPeriod protects its own date relationship. Conflict prevention needs other reservations and the requested item, so it belongs at a boundary with that information.

Guidance: Questions 3 and 4

  1. Changed message wording affects presentation but currently requires editing the coordinator. A private helper makes the purpose easier to locate; it does not remove the class-level dependency. Zero dependencies would prevent collaboration; extra classes can add indirection without solving a problem.
  2. A reservation has a period. It is not a period subtype. Inheriting would conflate reservation identity and borrower/item details with an interval, and suggest inappropriate substitutability. LoanPeriod is also final.

Guidance: Questions 5 and 6

  1. Returning the mutable internal list allows clear or add outside the coordinator’s validation. List.copyOf blocks structural mutation and isolates later additions, but does not deep-copy mutable elements. This starter’s elements are separately immutable.
  2. A success string is returned for a reservation the coordinator does not retain. The next request can wrongly find no conflict. The one success stored check exposes the fault immediately.

Transfer Challenge: Room Booking

A university room-booking coordinator stores bookings, checks room conflicts and constructs confirmation text. Its users now request a wording change only.

  • Propose a responsibility diagram with at most four domain or application classes.
  • State a period convention.
  • Identify the state that callers must not modify directly.
  • Describe one small behaviour-preserving improvement and two before-and-after observations to accept it.
  • Include a brief review description and one remaining limitation.

Transfer Challenge: Guidance

  • One reasonable model: RoomBookingCoordinator, RoomBooking, BookingPeriod, BookingRules.
  • State an explicit convention, for example start-inclusive, end-exclusive periods in local building time.
  • Extract first, change the wording second: preserve the old output while extracting; then change the text as a separate behaviour change with updated expectations.
  • Two observations: identical confirmation text and identical stored records; a conflicting request still adds nothing.
  • A limitation: one process’s list cannot coordinate several application instances.

What to Take Away, and What Comes Next

What to carry forward


Summary: Objects as Design Units

  • Objects become useful design units when their responsibilities and permitted interactions are clear.
  • Constructors and controlled operations protect invariants.
  • Immutable values and careful collection access stop callers bypassing intended boundaries.

Summary: Cohesion, Coupling and the Limited Claim

  • Cohesion and coupling help us discuss the consequences of change.
  • The formatting extraction makes one operation easier to follow without resolving every responsibility the coordinator holds.
  • Explaining that limited improvement is part of sound design reasoning.

Summary: Evidence Should Agree

  • Diagrams, checks, diffs and review notes describe different aspects of the same codebase.
  • They should agree with the implementation.
  • An AI explanation is another candidate description to check against that evidence.

Next: Chapter 3

  • Chapter 3: Agile Development and the Nature of Change.
  • Turn project needs into an ordered backlog.
  • Write useful stories and acceptance criteria.
  • Examine how a team plans and learns through feedback.

Sources and Further Reading

  1. Dev.java: Classes and Objects; Inheritance.
  2. Oracle: Java SE 17 API for LocalDate, Objects and List.
  3. Chacon and Straub: Pro Git: Recording Changes; Branching Workflows.
  4. Git documentation: git diff and git switch.
  5. Conventional Commits 1.0.0.
  6. Martin, Robert C.: Agile Software Development: Principles, Patterns, and Practices, 2002.
  • Full references and links are in the chapter text.

3 - Chapter 3: Agile Development and the Nature of Change

Object Oriented Development and Agile Practices (SE506)

Chapter 3: Agile Development and the Nature of Change

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 3.1 What do we not know yet? Uncertainty and feedback
    • Four kinds of uncertainty, and the evidence that reduces each.
  • 3.2 What the Agile values mean for real project decisions
    • Preferences, not prohibitions, applied to project decisions.
  • 3.3 Iteration or increment? Two kinds of progress
    • Related but different, and neither is a public release.
  • 3.4 Scrum: who is accountable, what happens when, what it produces
    • The November 2020 Scrum Guide, and what a classroom adapts.

Chapter Roadmap (2 of 3)

  • 3.5 Kanban: making the workflow visible
    • A five-column board, a combined WIP limit, a provisional expectation.
  • 3.6 From “manage equipment” to a story: epics, user stories and INVEST
    • From “manage equipment” to a small, testable story and an ordered backlog.
  • 3.7 Is the story finished? Acceptance criteria and Definition of Done
    • Behaviour of one item versus the standard for every increment.
  • 3.8 Estimating without pretending to know: relative estimation
    • Story points as a conversation, not a guarantee.

Chapter Roadmap (3 of 3)

  • 3.9 Challenging a story with AI without letting it invent policy
    • Seven suggestions: accept, reject for this story, or clarify.
  • 3.10 Try it yourself: build a draft backlog
    • Parts A to C on your team’s project.
  • 3.11 Habits that sound agile but are not and 3.12 Questions to think with
  • What to take away, and what comes next
    • Into Chapter 4: from requirements to an iteration.

Learning Outcomes (1 of 2)

After completing this chapter, you should be able to:

  1. Distinguish iterative development from incremental delivery.
  2. Explain Agile values through concrete project decisions.
  3. Compare Scrum’s framework with Kanban’s attention to workflow and flow.

Learning Outcomes (2 of 2)

  1. Develop small user stories and evaluate them using INVEST.
  2. Write acceptance criteria and distinguish them from a Definition of Done.
  3. Discuss relative estimates and uncertainty without treating estimates as guarantees.

How to Use This Chapter

  • Core reading: Sections 3.1 to 3.9.
  • What you must do: Parts A to C of Section 3.10, on your team’s project.
    • The equipment-lending backlog in this chapter is the worked model, not your evidence.
    • Evidence needs no code or tools beyond a text editor or a board.
  • If you want more: the plain-Java acceptance check at the end of 3.10, then the questions and the transfer challenge.

Readings and Scope

  • Readings: the Agile practices and planning chapters of Martin, the Scrum Guide, and selected chapters of Rubin.
  • The chapter assumes Chapter 2’s objects, date convention and basic review workflow.
  • It needs no project-management platform and no new Java framework.
  • The companion holds worked backlog items, board policies, exercise solutions and an optional executable bridge from acceptance examples to the existing Java.
  • The running application stays at Chapter 2’s completed version. This chapter plans future work; it does not claim features already exist.

Opening Scenario: The Demonstration

  • The team demonstrates the reservation coordinator from Chapter 2.
    • A student request succeeds.
    • A conflicting request is rejected.
    • A booking can begin on the exclusive end date of another.
  • The program behaves as its developers expected.

The Officer’s Question

Can I check whether a camera is available before I create a reservation?

  • The team can inspect the stored list, but there is no dedicated availability query.
  • The demonstration revealed a distinction: checking a rule during a request versus supporting the officer’s actual workflow.

More Requests Follow

  • The next conversation raises:
    • cancellation,
    • displaying one borrower’s reservations,
    • rejecting an equipment identifier that does not exist.
  • The team cannot implement every suggestion immediately.
  • It needs a way to clarify requests, compare their value, expose uncertainty and decide what useful work to finish next.

What This Chapter Produces

  • An ordered backlog.
  • Testable stories.
  • Shared completion conditions.
  • A visible workflow.
  • All developed on the same small codebase from Chapter 2.

3.1 What Do We Not Know Yet?

Uncertainty and feedback


Uncertainty Takes Several Forms

  • We may not understand:
    • what a user needs,
    • how a rule should behave,
    • whether an integration is feasible,
    • how much work a change will require.
  • Treating all of these as “requirements problems” hides useful distinctions.

Four Uncertainties and Their Next Actions

UncertaintyExample in the equipment projectA useful next action
User needDoes the officer need an availability enquiry without reserving?Discuss the workflow; demonstrate a small example
Domain ruleCan a borrower cancel after equipment has been issued?Ask the responsible stakeholder; record the decision
Technical feasibilityCan the institution’s catalogue be accessed?A bounded investigation with a concrete result
DeliveryCan the team finish a reviewed query workflow next iteration?Examine remaining work, capacity, assumptions

Different Questions Need Different Evidence

  • A larger estimate will not determine the cancellation policy.
  • More conversation will not prove that an unavailable service can be accessed.
  • Naming the uncertainty helps the team choose an action that can actually reduce it.

Feedback Also Varies

  • A user can expose a missing workflow.
  • A compiler can identify an invalid program.
  • A test can show that an observed result differs from an expectation.
  • A reviewer can identify a dependency that makes the next change harder.
  • None of these observers has complete authority over every question.

A Discovery Is Not Automatically a Defect

  • The implementation currently allows adjacent bookings. That matches our rule invented for this book.
  • If the officer asks for an inspection day between loans, the team has found a possible policy change, not a defect in the original comparison.
  • Record: who proposed it, why it matters, and which examples would differ if it were adopted.

What Short Feedback Cycles Do

  • They make discoveries while there is still a manageable amount of work to revise.
  • They do not make change free: tests, documentation and stored data may also need to change.
  • The team should expose those consequences rather than accept every request without discussion.

3.2 What the Agile Values Mean for Real Project Decisions

Preferences, not prohibitions


The Manifesto, Paraphrased

  • The Agile Manifesto gives greater emphasis to:
    • human collaboration,
    • usable software,
    • ongoing customer involvement,
    • adaptation,
  • than to relying primarily on tools, exhaustive documents, contractual positions or a fixed plan.
  • It explicitly retains value in those latter activities.

Applying the Values to Our Project

Project decisionApplying the values thoughtfullyA mistaken interpretation
Choosing a boardSelect a tool that makes coordination easierAssume installing a board creates collaboration
Recording the period conventionKeep a short, clear decision with examplesDiscard documentation because the code exists
Handling a new borrowing ruleDiscuss its purpose with the equipment officerLet the team invent policy privately
Planning the next incrementUse current information; revise when neededStop planning because requirements may change

Principles: Delivery and Technical Quality

  • The accompanying principles connect regular delivery with technical quality, a sustainable pace and reflection on team effectiveness.
  • In this course the connection matters because poorly understood code makes a small request expensive.
  • The design and testing work from earlier chapters supports the team’s ability to respond.

“Check Availability” Is Two Different Requests

  • Option 1: merely inspect the coordinator’s current in-memory records. A small teaching increment.
  • Option 2: include an authoritative inventory and maintenance schedule. Substantially more work.
  • Calling both “check availability” does not make them equivalent.
  • Clarify which one is meant.

Agile Still Needs Commitments

  • Agile work still needs commitments, boundaries and evidence.
  • What changes is the expectation that every detail can be settled correctly at the beginning.
  • We maintain an explicit record of:
    • what we know,
    • what we intend to deliver,
    • what still needs a decision.

3.3 Iteration or Increment?

Two kinds of progress


Definitions

  • An iteration is a repeated cycle of work and feedback.
  • An increment adds integrated, usable capability.
  • The terms describe related but different aspects of development.

One Example of Each

  • The team revises its reservation messages after a demonstration: it has iterated on the implementation.
  • It then adds an availability enquiry that works with the existing records and can be used through the agreed interface: it has added an increment of capability.

Effort Is Not an Increment

  • An isolated database table or a collection of unfinished screens may represent effort without a usable increment.
  • Technical tasks can be necessary.
  • The team should explain how they contribute to a result someone can inspect and use.

A Small Route Through the Project

  1. Make the current reservation flow reproducible.
  2. Add an enquiry that does not change records.
  3. Support a borrower’s list of active reservations.
  4. Cancellation follows once its policy and state transitions are understood.
  • A proposed learning route, not a new assessment schedule.

Each Addition Must Still Work With What Came Before

  • If the enquiry answers correctly but creates a reservation as a side effect, it has not delivered the intended capability.
  • If cancellation removes an unrelated record, a successful cancellation message is insufficient evidence.
  • Check the whole relevant state, not only the message.

An Increment Is Not a Public Release

  • A bounded teaching application can be demonstrated to its intended reviewers while remaining unsuitable for institutional deployment.
  • Its scope and limitations must be explicit.
  • Releasing to real users involves further considerations beyond completing a classroom exercise.
  • Chapter 4 turns the selected work into an iteration plan.

3.4 Scrum

Who is accountable, what happens when, what it produces


Scrum Is a Framework With Defined Elements

  • The November 2020 Scrum Guide is the formal reference used here.
  • Basic structure: accountabilities, events, artefacts.
  • Terminology should clarify responsibilities and decisions.
  • User-story templates and story points are optional practices, not required Scrum elements.

Accountabilities

ElementMeaning
Product OwnerAccountable for product value and effective Product Backlog management
DevelopersCreate usable increments and plan their work
Scrum MasterEstablishes Scrum and supports team effectiveness

The Sprint and Its Events (1 of 2)

ElementMeaning
SprintA fixed-length event of one month or less containing the other events
Sprint PlanningEstablishes why the Sprint matters, the selected work and an approach
Daily ScrumDevelopers inspect progress toward the Sprint Goal and adapt their plan

The Sprint and Its Events (2 of 2)

ElementMeaning
Sprint ReviewInspect the outcome with stakeholders and consider adaptations
Sprint RetrospectiveExamine how the team worked and choose improvements

The Sprint Contains All the Events

Sprint: a fixed length of one month or less, containing all the eventsSprintPlanningwhy the Sprint matters,what is selected,and an approachDailyScrumDevelopers inspectprogress toward theSprint Goal and adaptSprintReviewinspect the outcomewith stakeholders;consider adaptationsSprintRetrospectiveexamine how the teamworked; chooseimprovementsTogether they inspect progress and adapt: the Sprint Goal ties the selected work to one outcome.

Artefacts and Their Commitments

Product Backlogan ordered, emergent listProduct Goalthe future state to work towardcommitmentSprint Backloggoal, selected work, planSprint Goalwhy this Sprint's work belongs togethercommitmentIncrementa usable step toward the goalDefinition of Donethe shared quality standardcommitment

The Sprint Backlog

  • The Sprint Backlog includes the goal, the selected work and the delivery plan.
  • Each artefact is tied to a commitment that makes its purpose explicit.
  • The Definition of Done is the commitment for the Increment.

A Product Goal

Enable the equipment office to manage a small set of student reservations with visible rules and reliable records.

  • It expresses a useful future state.
  • It does not specify every screen or method.

A Sprint Goal

  • A possible Sprint Goal is narrower: “Let the officer check availability without changing reservations.”
  • It tells the team why its selected work belongs together.
  • Compare: “Finish US-02, update the README and edit three classes” lists work but does not explain the outcome that connects it.

A Daily Scrum Contribution

  • A developer might say: “The enquiry works, but its result depends on a date convention we have not documented. I will pair with the reviewer to resolve that before adding another feature.”
  • This focuses on the team’s progress.
  • The Scrum Guide defines the Daily Scrum as a 15-minute event for the Developers to inspect progress toward the Sprint Goal and adapt their plan.
  • It is not a status report to a supervisor.

A Sprint Review

  • The equipment officer tries the enquiry and asks whether a damaged item should ever appear available.
  • That discussion concerns the product and possible next work.
  • The Scrum Guide describes the Sprint Review as a working session, not limited to a presentation.

A Sprint Retrospective

  • The team notices that reviews consistently arrived too late.
  • It agrees to request review when a small behaviour first works, then inspects whether that agreement helps.
  • The retrospective concerns increasing quality and effectiveness, including how the team collaborated and verified work.

Classroom Adaptations Must Be Named

  • The module leader may provide stakeholder answers; teams may rotate facilitation; reviews may happen within teaching sessions.
  • These are course arrangements. They do not redefine Scrum’s accountabilities or turn every classroom meeting into a formal Scrum event.
  • An academic submission date is not automatically a Sprint boundary.

“Sprint 0”

  • The descriptor’s later “Sprint 0” label refers to kickoff work.
  • Treat it as a local preparation convention, not another formal Scrum event.
  • Keep the distinction visible when you write reports about the process your team actually followed.

3.5 Kanban

Making the workflow visible


What Kanban Focuses On

  • Kanban focuses on the flow of value through work.
  • The May 2025 Kanban Guide describes:
    • defining and visualising a workflow,
    • managing work within it,
    • improving that workflow.

What a Workflow Definition Includes

  • Work items.
  • Start and finish points.
  • States.
  • Work-in-progress controls.
  • Movement policies.
  • A service-level expectation: elapsed time combined with a probability. Historical evidence should inform it when available.

Our Five Columns

ColumnLocal meaning
BacklogCandidate work, not yet selected to start
ReadySmall enough and clear enough to begin under the team’s local policy
BuildingImplementation and associated checks are under way
ReviewWork is awaiting or receiving review and verification
DoneThe agreed completion conditions have been met
  • These columns are our example, not mandatory names.

Start, Finish and the Combined Limit

  • Start point: entry into Building.
  • Finish point: entry into Done.
  • Limit the combined number of items in Building and Review to two.
  • Blocked work stays within that count. An uncounted “Blocked” column would hide unfinished work rather than finish it.

A Hypothetical Snapshot

BacklogReadyUS-03US-04BuildingUS-02ReviewEN-01blockedDoneUS-01start pointfinish pointWIP limit: 2 items in Building and Review combinedHypothetical snapshot

The Limit Is Reached: What Now?

  • One item is in Building and another is waiting in Review.
  • Before starting a third, investigate what would help an existing item finish:
    • review together,
    • resolve a question,
    • make a smaller change.
  • A limit is useful only when it affects decisions.

A Board Alone Is Incomplete

  • A team also needs workflow policies, active management and evidence for improvement.
  • For practice: provisionally expect 85% of small items to finish within five working days after starting.
  • Without historical data that is an explicitly unvalidated hypothesis, not a measured promise.
  • Chapter 12 develops flow measures and forecasting. We do not derive a delivery guarantee from an invented example.

Comparing Scrum and Kanban (1 of 2)

QuestionScrum emphasisKanban emphasis
What structures the work?Goals, Sprints, defined accountabilities and eventsExplicit workflow and control of work in progress
What is inspected?Product outcomes and progress toward goalsHow work moves, waits and finishes

Comparing Scrum and Kanban (2 of 2)

QuestionScrum emphasisKanban emphasis
Can a board help?Yes, to make selected work visibleYes, to visualise the workflow and its policies
Can they be used together?A Scrum team can also manage flow explicitlyFlow practices can operate within a Sprint cadence
  • Not: “Scrum has meetings; Kanban has none”. Not: “Scrum is planned; Kanban is unplanned.”
  • The chosen approach should help the team make decisions, not merely supply labels.

3.6 From “Manage Equipment” to a Story

Epics, user stories and INVEST


“Manage Equipment” Is Too Broad

  • It may refer to stock records, borrowing, maintenance, losses, reporting or purchasing.
  • To guide the next implementation we must identify a particular actor, outcome and reason.

Epic and User Story

  • An epic is a label for a large area of work that needs further division.
    • Here: “Manage student equipment reservations”.
  • A user story expresses a smaller user-centred need.

As an equipment officer, I want to record an eligible, non-conflicting reservation so that the selected item is allocated for the borrower’s requested period.


A Story Opens a Discussion

  • The sentence does not define eligibility, conflicts, dates or failure behaviour by itself.
  • Ron Jeffries connects a short written reminder with conversation and confirmation.
  • We keep a card, record the relevant decisions and attach observable examples.

Stories and Implementation Tasks

  • “Create a HashMap” names an implementation task. It does not explain the outcome a user needs.
  • A story can lead to tasks (data structures, checks, interface work), but they should remain connected to the outcome.
  • Not all backlog work needs a fictional user-story sentence.

Technical Enablers

  • A build-check task can be labelled a technical enabler, justified by the delivery problem it addresses.
  • Our convention: US- for stories and EN- for enablers.
  • These prefixes are local identifiers, not Agile rules.

INVEST as Questions (1 of 2)

QualityQuestion to ask about this story
IndependentCan it provide an identifiable outcome without another unfinished story? If not, what is the dependency?
NegotiableWhich implementation details can still be discussed while preserving the need?
ValuableWho benefits from this behaviour, and how?

INVEST as Questions (2 of 2)

QualityQuestion to ask about this story
EstimableIs the team clear enough about scope and uncertainty to discuss its size?
SmallCan the team implement and verify a useful version within its intended work period?
TestableCan we describe observations that distinguish success from failure?
  • Bill Wake introduced INVEST to prompt useful qualities in stories.

INVEST Is a Reasoning Aid

  • It is not a six-point score that certifies a story.
  • Real dependencies should be recorded. “Independent” should not make us pretend cancellation needs no existing reservation.
  • “Negotiable” should not mean ignoring an agreed safety or domain rule.

A Small Version of the Availability Story

  • Inspect the coordinator’s current records for one known item and one period, leaving records unchanged.
  • Adding live inventory, maintenance calendars, permissions and email delivery at the same time would make it harder to identify what is being delivered.

A Backlog Is Ordered Work

  • The Scrum Guide describes the Product Backlog as an emergent, ordered list.
  • Refinement is the ongoing work of breaking items into smaller, more precise ones.
  • Ordering and refinement together make upcoming work understandable.
  • We record current support as well as remaining work, because the team inherits code from Chapter 2.

The Beginning of a Draft Backlog

OrderIDOutcome or purposeRelationship to existing code
1US-01Record an eligible, non-conflicting student reservationCore behaviour exists; delivery and completion evidence must be reviewed
2US-02Inspect an item’s availability without reserving itNo dedicated query operation exists yet
3EN-01Make the existing checks reproducible in an automated build workflowPlanned enabler for the kickoff work in Chapter 4

Not a Delivery Report

  • This is the beginning of a draft backlog, not a report that three items have been delivered.
  • Review inherited capability instead of pretending to implement it again.
  • When a story’s conditions are met, retain the evidence in completed history rather than counting it again as new work.

3.7 Is the Story Finished?

Acceptance criteria and Definition of Done


Two Different Questions

  • Acceptance criteria describe the behaviour expected for a particular item.
    • Specific enough that a second team can tell whether an implementation meets them.
  • Definition of Done describes the shared quality conditions required for an increment.
    • Scrum makes that definition the commitment associated with the Increment.

US-01 Criteria: Conventions

  • All counts are measured before adding the new reservation.
  • All stored reservations are currently active.
  • Dates use the exclusive-end convention from Chapter 2.
  • The next slides list AC-01 to AC-05.

US-01 Acceptance Criteria

CriterionRequired observable behaviour
AC-01An eligible request for an available item creates one stored reservation and returns its confirmation
AC-02A request overlapping an existing reservation for the same item is rejected without changing stored records
AC-03A request that begins at an existing reservation’s exclusive end, or ends exactly where one begins, is permitted when the other rules hold
AC-04A borrower with three active reservations cannot add a fourth, even for another item
AC-05One through seven whole days are allowed; zero, negative and eight-day requests are rejected without adding records

What the Criteria Do Not Establish

  • They describe a controlled demonstration using non-blank, agreed sample identifiers and representable dates.
  • They do not establish catalogue membership, authentication, persistence or concurrency safety.
  • Those omissions must remain visible when judging what kind of product the story represents.

Turn AC-02 into an Example

Given CAM1 is reserved for B1 from 20 October to 22 October,
using a start-inclusive, end-exclusive period,
when B2 requests CAM1 from 21 October for one day,
then the request is rejected as unavailable,
and the original reservation remains the only stored record.
  • The format separates context, action and observations.
  • It is plain text here, not an executable framework.

Both Observations Matter

  • The rejection, and
  • the unchanged state.
  • A program that prints the correct rejection but still adds a reservation has not met the criterion.
  • This introduces traceability between an example and behaviour. It is not yet a complete TDD or BDD method.

A Working Definition of Done (1 of 2)

DoD version 0.1 for the controlled teaching increment:

  1. Relevant acceptance examples have been checked, including important rejection paths.
  2. The code compiles and runs using the documented commands in the agreed environment.
  3. The change has been reviewed and acceptance-blocking findings resolved.

A Working Definition of Done (2 of 2)

  1. The work is integrated with the team’s shared codebase and existing checks still pass.
  2. Necessary usage notes, domain decisions and known limitations are current.
  3. The increment can be reproduced and demonstrated within its explicitly stated scope.
  • A proposed team agreement, not a university grading policy.

Do Not Pretend a Pipeline Exists

  • The DoD uses checks available now. Automated CI gates can be added when the infrastructure is introduced.
  • Do not claim a pipeline has passed before it exists.
  • Do not weaken the agreed standard merely to move a card to Done.

Story Criteria and Shared Completion

  • AC-03 belongs to the reservation story because it defines date behaviour.
  • Reproducible setup belongs to the shared completion agreement.
  • Passing a story’s examples while leaving an unreviewed, unintegrated change does not meet this DoD.
  • Appendix C has blank forms, each with a worked example, for a story card (C.2), an acceptance example (C.3) and a Definition of Done (C.4).

“Ready” Has a Different Purpose

  • “Ready” records whether a team considers an item sufficiently understood to start.
  • Our Ready-column policy is a local agreement, not an additional formal Scrum artefact or event.
  • It should help a conversation, not become a ritual that demands every detail months in advance.

Distinguishing the Three Conditions

Kind of conditionExampleQuestion answered
Acceptance criterionAn enquiry leaves reservations unchangedDoes this behaviour satisfy the story?
Definition of DoneThe integrated change is reviewed and its checks passDoes the increment meet the shared standard?
Local Ready policyKnown scope, example inputs, identified blocking questionsIs the item understood enough to begin?

From Criterion to Code: A Plain-Java Bridge

// AC-02: an overlapping request changes no records.
String conflict = coordinator.reserve("B2", "CAM1", day.plusDays(1), 1);
require(conflict.equals("Rejected: item unavailable."), "AC-02 rejection");
require(coordinator.reservations().size() == 1, "AC-02 unchanged count");
require(coordinator.reservations().get(0).borrowerId().equals("B1"),
        "AC-02 original borrower preserved");
System.out.println("AC-02 passed");
  • The companion’s Chapter03AcceptanceExamples runs AC-01 to AC-05 against the completed Chapter 2 coordinator.

Running the Bridge

javac --release 17 -d out ReservationRules.java LoanPeriod.java \
    Reservation.java ReservationCoordinator.java \
    Chapter03AcceptanceExamples.java
java -cp out Chapter03AcceptanceExamples
AC-01 passed
AC-02 passed
AC-03 passed
AC-04 passed
AC-05 passed
All 5 acceptance examples passed.

What the Bridge Shows

  • Each labelled group corresponds to a criterion: setup, action, observations.
  • AC-04 uses different item identifiers, so the fourth refusal comes from the borrower’s total count, not an item conflict.
  • AC-05 stays below the count limit, so the duration rule is the reason for rejection.
  • A passing run does not satisfy the Definition of Done by itself: review, integration and reproducibility still matter.

Do the Examples Detect Faults?

Change to LoanPeriod or ReservationRulesChapter 3 runner
OriginalAll 5 acceptance examples passed
First overlap comparison to !start.isAfter(other.endExclusive)Failed at AC-03 request ending at an existing start
Second overlap comparison to !other.start.isAfter(endExclusive)Failed at AC-03 adjacent confirmation
activeReservations < 3 to <= 3Failed at AC-04 fourth rejected
requestedDays <= 7 to < 7Failed at AC-05 seven days

Why AC-03 Has Two Requests

  • If AC-03 contained only a request starting at an existing end, the first overlap change would pass every Chapter 3 example.
  • The extra request, one that ends exactly where an existing reservation begins, closes that gap.
  • Chapter 2’s adjacency symmetric check also caught that change.
  • Lesson: choose example sets to expose specific faults, not merely to be numerous.

3.8 Estimating Without Pretending to Know

Relative estimation


What an Estimate Is For

  • An estimate helps a team discuss and forecast work.
  • It is not a guarantee that a specific person will finish at a precise time.
  • Before estimating, establish what is included: implementation alone, or also review, checking and documentation needed to meet Done?

Story Points as a Local Scale

  • Story points are an optional local relative scale.
  • A team might use 1, 2, 3, 5 and 8 to compare work considering effort, complexity and uncertainty.
  • These numbers are not hours.
  • The same number in two teams need not represent equivalent work.

Two Estimates That Disagree

  • A familiar display adjustment is the reference item at two points.
  • A small availability query may seem larger: a non-mutating operation, several date examples and a new interaction.
  • One learner estimates three, another eight.
  • Averaging them immediately would discard the most useful information: why they disagree.

Expose the Assumption

  • The second learner may be assuming the query includes maintenance schedules.
  • Once exposed, the team can narrow the story or record a separate requirement.
  • If the uncertainty is an external catalogue with an unknown interface, a short investigation may be more useful than a larger number.

A Bounded Investigation

  • Good: “Determine whether a read-only sample catalogue can be accessed and record the fields it provides.”
  • Bad: an unlimited task called “research the system”.
  • A bounded investigation has a question and an output, including the result “access not obtained”.
  • Do not probe systems without authorisation.

Practising Estimation

  • Estimate only clarified items and write the assumption behind each estimate.
  • Leave an item unestimated when a material question remains.
  • Different reasoned estimates are acceptable. Claiming certainty without evidence is not.

Estimates Are Not Productivity Measures

  • Do not rank students by points completed.
  • Do not convert points into individual productivity targets.
  • Do not assume that doubling team size doubles delivery.
  • Chapter 4 combines selected work with actual capacity; Chapter 12 examines measured progress and flow.

An Example of Estimates With Assumptions

ItemEstimateAssumption or uncertainty
US-02 local availability enquiry3 pointsKnown sample identifiers, existing date convention, no external catalogue
US-03 one borrower’s active records2 points“Active” unchanged; officer-operated controlled demonstration
US-04 cancellationNot yet estimatedAuthority, permitted lifecycle states and effect on counts unresolved
  • Invented numbers for discussion. No velocity or forecast is claimed.

3.9 Challenging a Story with AI Without Letting It Invent Policy

Without inventing policy


What AI Can and Cannot Do Here

  • An AI assistant can suggest missing assumptions and candidate criteria.
  • It has no authority to decide the equipment office’s rules.
  • The exercise is to evaluate its proposals against the agreed context.
  • Chapter 15 collects the recurring AI workflows. This activity is self-contained.

Ground Rules for the Activity (1 of 2)

For this activity
Your goalDecide which suggestions belong in the story, which do not and which need a stakeholder
You start withUS-01, AC-01 through AC-05, and Chapter 2’s scope
What is in boundsClassify the suggestions; do not change the policy, the code or the story’s scope on your own

Ground Rules for the Activity (2 of 2)

For this activity
How to checkThe stated rules, the Chapter 2 code and checks, the story’s scope
How to decideAccept, reject for this story, or clarify with a named person or decision
No AI account?The ready-made example is enough

The Ready-Made Example (1 of 2)

An ready-made example written by the author, not a captured response from a real model.

  1. A request for the same item and an identical period must be refused.
  2. A request that begins exactly when an existing period ends must be refused.
  3. Every confirmation must be emailed within thirty seconds.
  4. A refused request must leave stored reservations unchanged.

The Ready-Made Example (2 of 2)

  1. Specify how start dates that are already in the past are treated.
  2. A deposit payment must be taken before any reservation can be confirmed.
  3. Specify whether cancelled reservations count toward the borrower’s limit.
  • Classify each suggestion with a reason.
  • “Reject” can mean the proposal contradicts a rule or introduces unapproved scope. It need not mean the idea could never be useful.

Decision Log (1 of 2)

SuggestionDispositionReason
1. Refuse the same item and an identical periodAcceptAn identical period overlaps; a useful special case of AC-02
2. Refuse a request beginning exactly when another endsReject for this storyContradicts the exclusive-end convention and AC-03
3. Email every confirmation within thirty secondsReject for this storyAdds delivery infrastructure and a timing requirement not agreed
4. A refused request leaves records unchangedAcceptMatches the state-preservation expectations of AC-02 and AC-05

Decision Log (2 of 2)

SuggestionDispositionReason
5. Specify how past start dates are treatedClarifyThe policy sets no past-date restriction; do not invent one
6. Take a deposit before confirmingReject for this storyA new business process and unapproved scope
7. Do cancelled reservations count toward the limit?Clarify, link to US-04Cancellation is not implemented; it matters when that lifecycle arrives

What a Sufficient Record Contains

  • The suggestion.
  • The classification.
  • The source of authority (rule, code or stakeholder).
  • The next action.
  • “AI is wrong” without naming the conflicting rule is not enough.
  • No suggestion becomes a rule merely because AI produced it.

Optional Live Prompt

Review this story against the supplied policy and current project scope. Suggest missing assumptions, edge cases and candidate acceptance criteria. Label each suggestion as derived from a stated rule, an open question or a possible scope extension. Do not silently change the policy or claim a feature is already implemented.

  • Refer to the rule or code evidence, not the assistant’s confidence.

3.10 Try It Yourself

Build a draft backlog


Context

  • The activity runs on your team’s project.
  • Equipment lending teams start from the Chapter 2 coordinator, the three backlog entries in 3.6 and the rule invented for this books.
  • Another provided domain? Apply the same steps to that domain’s starter codebase and its first user-visible capability.
  • For an ML model-serving tool, for example: “as an analyst, I want a prediction for a supplied record so that I can check the result”.
  • It is preparation for the Week 3 kickoff and does not change assessment dates or weights.

Part A: Define and Refine

  • Write a short proposed Product Goal.
  • Extend the draft backlog to six items, considering a borrower’s active-reservation list, cancellation and catalogue-identifier validation.
  • Use stable IDs; identify dependencies and unresolved decisions.
  • Order the items and explain the first two positions.

Part A: Be Honest About the Baseline

  • Record which behaviours are already supported in code, which need new implementation, and which cannot yet be judged.
  • Do not label an inherited feature Done solely because a demonstration prints the desired text.
  • Refine US-02 into a small story with at least four acceptance examples.
  • State that asking about availability must not create or remove reservations.

A Proposed Product Goal

Enable an equipment officer, in a controlled demonstration, to create and inspect student reservations with clear eligibility decisions and no overlapping reservations for the same item within one coordinator.

  • A future outcome, explicitly bounded; not a claim that it is achieved.
  • A next-iteration candidate: inspect availability without changing reservations.

A Worked Six-Item Backlog (1 of 2)

OrderIDItemType
1US-01Record an eligible, non-conflicting student reservationUser story
2US-02Inspect availability without reserving an itemUser story
3EN-01Automate the existing build and checksTechnical enabler

A Worked Six-Item Backlog (2 of 2)

OrderIDItemType
4US-03Inspect one borrower’s active reservationsUser story
5US-04Cancel a confirmed reservation under an agreed policyUser story
6US-05Reject unknown equipment identifiers using an agreed catalogueUser story
  • The order is one defensible teaching choice, not a calendar and not a promise that all six fit one Sprint.

Rationale and Dependencies

  • US-01 prevents building on a falsely reported baseline. Verify, then move to completed history with evidence.
  • US-02 answers the workflow gap found at demonstration. It need not wait for a production database.
  • EN-01 supports reliable repeated work; coordinate with Chapter 4’s kickoff.
  • US-04 changes the meaning of “active”, so it affects counts and availability.
  • US-05 sits lower because early demonstrations use controlled sample identifiers. Reorder if real users or uncontrolled input enter scope.

US-02 Refined: The Card

  • Actor and outcome: as an equipment officer, I want to inspect whether a known item is available for a proposed period without reserving it, so that I can discuss options with a borrower.
  • Scope: the current coordinator’s records, controlled sample identifiers.
  • It does not promise the item is operational, not under maintenance, or available in another application instance.
  • A successful enquiry never confirms a reservation and never guarantees a later request will succeed.

US-02: Acceptance Examples (1 of 2)

IDGiven and whenExpected observations
AQ-01No stored reservations; inspect CAM1 for [20 Oct, 22 Oct)Available within this record set; records unchanged
AQ-02CAM1 reserved for [20 Oct, 22 Oct); inspect [21 Oct, 23 Oct)Unavailable; existing record unchanged
AQ-03Same reservation; inspect CAM1 for [22 Oct, 23 Oct)Available under the adjacency convention; records unchanged

US-02: Acceptance Examples (2 of 2)

IDGiven and whenExpected observations
AQ-04CAM1 reserved; inspect a different known item MIC1 for the same periodAvailable if MIC1 has no conflict; records unchanged
AQ-05Inspect an invalid period with equal or reversed datesAn identifiable invalid-input outcome, not a valid availability answer; no records changed
  • AQ-05 states the user-observable meaning without prescribing an exception class. It must not silently return “available”.

US-02: Unknowns and Status

  • Unknowns: catalogue identifiers outside the controlled examples; maintenance rules; restrictions on past dates. Record them as questions.
  • Do not add a hidden maintenance buffer: the interval convention allows adjacency.
  • Status: the query is not implemented by the Chapter 2 code.
  • reserve cannot be reused by calling it and undoing the result: that introduces a mutation and fails the story.

INVEST Applied to US-02

  • Independent: demonstrable with existing records, without cancellation. Depends on the period convention.
  • Negotiable: input mechanism and presentation, while preserving non-mutation.
  • Valuable: compare periods without speculative reservations.
  • Estimable: a query against a controlled local list is bounded.
  • Small: one item and one period.
  • Testable: compare the result and the complete relevant state before and after.

Part B: Agree Completion and Workflow

  • Propose a Definition of Done and explain how it differs from your story criteria.
  • Draw a board: the five example columns or a justified alternative.
  • Define start and finish points, movement policies, WIP control and handling of blocked work.
  • Record the service-level expectation as provisional if you lack data.

Part B: The Process Note

  • Write a short process note naming:
    • which Scrum elements your team uses as the Guide defines them,
    • which it adapts for the course,
    • which optional practices (stories, points, a board) it adopts.
  • Any honest statement is acceptable. Using stories, points and a board but no designated Scrum Master is a legitimate adaptation.
  • Calling an academic deadline a Sprint without naming that adaptation is not.

A Complete Example Workflow Policy (1 of 2)

  • Work item: a small user story or an explicitly identified enabler.
  • States: Backlog, Ready, Building, Review, Done.
  • Start and finish: start on entering Building; finish on entering Done. Building and Review both count toward WIP.
  • WIP control: at most two items across Building and Review combined (invented for the classroom).

A Complete Example Workflow Policy (2 of 2)

  • Movement: Ready when outcome, scope and examples are understood; Building when capacity allows; Review when change and evidence can be inspected; Done only when acceptance conditions and the DoD are met. A failed review returns to Building and stays within WIP.
  • Blocked work: keep it in its actual state, mark the blocker and who is investigating it, keep it within WIP. If abandoned, record that separately.
  • Inspection: first look at blocked or waiting active work and agree a concrete next action.

Board Exercise (Hypothetical)

ReadyBuildingReviewDone
US-03, US-04US-02EN-01, blocked by a setup problemUS-01
  • The combined Building-plus-Review limit is two.
  • Decide what to investigate next, and explain why moving the blocked card outside the counted area misrepresents its work.

Board Exercise: Solution

  • US-02 is in Building and EN-01 is blocked in Review: two items started, none finished, limit reached.
  • Investigate the setup blocker, help review or complete the existing feature before pulling another ordinary item from Ready.
  • If the blocker truly prevents progress, discuss and record the trade-off rather than silently changing the count.
  • A WIP policy can be revised deliberately. An invisible exception destroys the information needed to evaluate it.
  • US-01’s Done position belongs only to the simulation.

Part C: Estimate and Critique

  • Choose two clarified items and propose relative estimates with assumptions.
  • Identify one item that needs clarification or investigation before sizing.
  • Complete the AI activity and record the disposition of each suggestion.

Compare Two Invented Recommendations

Advice A: Freeze every detailed story before any implementation begins.

Advice B: Start implementation immediately and leave acceptance conditions until the demonstration.

  • For each: the problem it tries to solve, its risk in this project, and a more defensible working agreement.

Critique of Advice A and B

  • Advice A avoids ambiguity but assumes all detail is known up front. The availability enquiry itself emerged from a demonstration.
  • Advice B encourages fast feedback but cannot tell success from a persuasive demonstration: a query that mutates state might look acceptable.
  • Stronger agreement: clarify the next small outcome and its key examples before implementation; revise as evidence appears; keep later work less detailed until it nears selection.

Evidence and Completion Check

  • Retain: the proposed goal, ordered six-item backlog, two refined story cards, acceptance examples, Definition of Done, workflow policy, process note, estimates with assumptions, AI decision log.
  • Exchange the top story with another team.
  • Complete when the other team can explain the outcome, select meaningful inputs and predict observable results without inventing essential policy.
  • Record any question they could not answer, then revise the story or explicitly defer the decision.

If You Want More

  • Write a plain-Java check for your top story’s examples, in the style of Chapter03AcceptanceExamples.
  • Confirm that it fails when you introduce a deliberate fault.
  • Peer story review: ask another team to describe the outcome, pick an ordinary and a failure case, and state the expected state afterwards.

3.11 Habits That Sound Agile but Are Not

Seven mistakes and their corrections


Mistakes 1 to 3

MistakeConsequenceCorrection
Treating Agile as the absence of planningDependencies and deadlines remain invisiblePlan at useful detail; revise from evidence
Treating a story template as the full requirementEligibility and failure behaviour remain ambiguousAdd discussion, decisions and examples
Calling every item highest priorityThe team has no usable orderExplain why one outcome precedes another

Mistakes 4 and 5

MistakeConsequenceCorrection
Treating acceptance criteria as the whole DoDCorrect examples hide unreviewed or unintegrated workCheck behaviour and shared quality conditions
Starting new work whenever review is blockedUnfinished work accumulatesMake the blockage visible; help existing work finish

Mistakes 6 and 7

MistakeConsequenceCorrection
Treating AI suggestions as stakeholder decisionsScope or policy changes without agreementClassify each proposal and justify its disposition
Counting inherited working code as newly delivered workProgress becomes misleadingRecord the baseline and the remaining work honestly

3.12 Questions to Think with

Concept, diagnosis, transfer


Concept Questions

  1. Explain how an iteration can improve an existing feature and how an increment adds usable capability. Give an equipment-lending example of each.
  2. Choose one Agile value and explain a decision that respects both sides of its preference rather than discarding one side.
  3. Distinguish Sprint Review, Sprint Retrospective and Daily Scrum using the decisions each helps the team make.
  4. Explain the difference between acceptance criteria, Definition of Done and the local Ready-column policy.

Diagnosis Questions

  1. A team marks an availability story Done because its method returns a correct answer for one date. The enquiry also adds a reservation, and the change has not been reviewed. Identify two separate completion failures and the evidence needed to investigate them.
  2. Two items already occupy the team’s limited active workflow. One is blocked in Review. A developer moves it to an uncounted column and starts a third item, claiming the WIP limit is still respected. Explain what the board now hides and propose a useful next action.

Guidance: Questions 1 and 2

  1. Revising the reservation messages after feedback is an iteration on an existing capability. Adding an integrated, usable enquiry that leaves records untouched is an increment. A cycle can produce an increment, but the words do not mean the same.
  2. Favouring usable software over exhaustive documents still permits a short written period convention. The documentation supports the outcome; it does not substitute for examining it.

Guidance: Questions 3 and 4

  1. A Review examines outcomes with stakeholders and influences product choices. A Retrospective selects improvements in how the team works. A Daily Scrum helps Developers adapt their near-term plan toward the Sprint Goal.
  2. Acceptance criteria are item-specific behavioural expectations. The DoD is the shared quality standard for the increment. Ready is this team’s local policy for starting work, not a formal Scrum artefact.

Guidance: Questions 5 and 6

  1. The enquiry violates its non-mutation acceptance condition, and missing review violates the example DoD. Inspect records before and after, repeat the relevant examples, and obtain an actual review. One correct date is insufficient evidence for either.
  2. The blocked item remains unfinished, so a third item exceeds the combined limit. Investigate or resolve the blockage and finish existing work. If policy must change, record the change and its reason.

Transfer Challenge: Laboratory Room Booking

A laboratory room-booking team receives the request “Make booking easier.”

  • Develop two small user stories and one justified technical enabler.
  • Give one story four acceptance examples, including a failure case and an observation about unchanged state.
  • Propose a shared Definition of Done and a simple WIP policy.
  • Identify one question for a stakeholder and one technical uncertainty that might need an investigation.
  • Explain how to obtain feedback without pretending every rule is known.

Transfer Challenge: Sample Answer

  • RB-01: as a laboratory coordinator, I want to inspect availability for one known room and period without booking it, so that I can offer an appropriate slot.
  • RB-02: confirm an eligible, non-conflicting booking.
  • RE-01 (enabler): make the existing build and checks reproducible in the shared workflow.
  • RB-01 examples: no booking, available, nothing created; booking 10:00 to 11:00 and enquiry 10:30 to 11:30, unavailable and unchanged; enquiry starting at 11:00 may be available; end before start gives an invalid-input result.

Transfer Challenge: Completion and Uncertainty

  • A reasonable DoD: checked examples, reproducible build, actual review, integration, updated decisions or notes.
  • WIP: combined Building and Review limited to two, blocked work counted.
  • Stakeholder question: does room capacity or organiser authority restrict booking?
  • Technical uncertainty: can an approved read-only room catalogue be obtained? Investigate with a bounded task. Do not invent an API.
  • Confirm whether a cleaning buffer is required before accepting adjacency for a real system.

What to Take Away, and What Comes Next

What to carry forward


Summary: Learning and Quality

  • Agile combines a willingness to learn with visible work and explicit quality expectations.
  • Short cycles help when feedback changes what the team understands.
  • Careful design and checks help it make the resulting changes safely.

Summary: Scrum, Kanban and Our Result

  • Scrum and Kanban are different ways to organise work.
  • A board, meeting or estimate has little value if the team cannot explain how it affects the next action.
  • Our result: a draft backlog with clear stories, observable examples, shared completion conditions and a workflow.
  • It distinguishes inherited capability from new work and records open decisions.

Next: Chapter 4

  • Chapter 4: From Requirements to an Iteration.
  • Use these documents to define project scope and slice features.
  • Prepare the kickoff milestone.
  • Establish a reproducible repository and an initial CI workflow.

Sources and Further Reading

  1. Agile Manifesto authors: Manifesto for Agile Software Development and Principles behind the Agile Manifesto, 2001.
  2. Schwaber and Sutherland: The Scrum Guide, November 2020.
  3. Kanban Guides contributors: The Kanban Guide, May 2025.
  4. Jeffries: Essential XP: Card, Conversation, Confirmation, 2001.
  5. Wake: INVEST in Good Stories, and SMART Tasks, 2003.
  6. Martin: Agile Software Development, 2002. Rubin: Essential Scrum, 2012.
  • Full references and links are in the chapter text.

4 - Chapter 4: From Requirements to an Iteration

Object Oriented Development and Agile Practices (SE506)

Chapter 4: From Requirements to an Iteration

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 4.1 What are we building? Vision, scope and constraints
    • A bounded outcome, a visible boundary, small decision records.
  • 4.2 Sketching the screens and walking through examples
    • A wireframe is a question made visible.
  • 4.3 Prioritisation and vertical slicing: what first, how thin?
    • Slice by a user outcome, not by technical layer.
  • 4.4 What can we really finish? Release outlook and iteration planning
    • Capacity in person-hours, and why the total is not enough.

Chapter Roadmap (2 of 3)

  • 4.5 How the team works together: agreements and review
    • How work moves, who reviews, what evidence a reviewer needs.
  • 4.6 A starter build and CI you can run
    • One meaningful command, Arrange-Act-Assert, a hosted workflow, a failure you can observe.
  • 4.7 Getting ready for Milestone 1
    • An evidence trail, rehearsed from another person’s starting point.
  • 4.8 Challenging an overambitious plan with AI

Chapter Roadmap (3 of 3)

  • 4.9 Planning mistakes that look reasonable
  • 4.10 Try it yourself: prepare for the kickoff
    • Eight steps on your team’s project.
  • 4.11 Questions to think with and 4.12 A different setting: the transfer challenge
    • A clinic appointment-slot system.
  • What to take away, and what comes next
    • Chapter 5 examines responsibilities, extension and substitutability.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Define a project vision with explicit scope, assumptions and constraints.
  2. Slice a feature into a small increment that supports an observable user outcome.
  3. Form an iteration plan that includes implementation, review, verification and realistic capacity.

Learning Outcomes (2 of 2)

  1. Apply a shared repository workflow with clear review responsibilities.
  2. Run a starter build and checks, and demonstrate that the checking process detects a deliberate failure.
  3. Assemble traceable evidence for the kickoff milestone.

Module Connection

  • The chapter prepares you for Milestone 1 (the Week 3 kickoff). It supports LO5 and LO6.
  • The descriptor’s term Sprint 0 is the local kickoff label. It is not a formal Scrum event.
  • It supplies preparation guidance, not new assessment weights, submission rules or approved module wording.
  • Readings: the planning and testing chapters of Martin, and the sprint-planning chapters of Rubin.

Prerequisites and Route

  • Prerequisites: the Chapter 2 Java version; Chapter 3’s ordered backlog and Definition of Done; basic Git operations.
  • Tools: a Java 17 JDK, Git and Bash. On Windows, use Git Bash or WSL.
  • Core reading: 4.1 to 4.7, running the supplied checks while reading 4.6.
  • What you must do: steps 1 to 8 of Section 4.10 on your team’s project. Start steps 6 and 7 (hosted CI) early.
  • If you want more: the AI activity, review questions, transfer challenge and extension.

Opening Scenario: A Kickoff With Ideas but No Boundary

  • The team arrives at kickoff with six backlog items and a working Java reservation coordinator.
  • One member wants a mobile interface. Another proposes a database. A third wants to complete every story before inviting feedback.
  • Everyone is busy.
  • Yet nobody can describe what an equipment officer will be able to demonstrate at the end of the next iteration.

The Difficulty Is Not a Shortage of Ideas

  • It is the absence of a shared boundary and a credible route from an idea to evidence.
  • A backlog tells the team what might be valuable.
  • An iteration plan must also explain:
    • what it will try to achieve,
    • what it can reasonably finish,
    • how another person will know that it worked.

What This Chapter Builds

  • A bounded project vision.
  • A thin user journey.
  • A capacity-aware plan.
  • A repository that runs supplied checks.
  • The existing reservation behaviour is preserved. The availability enquiry (US-02) remains proposed work.
  • Preparing delivery evidence is progress, but it must not be reported as newly implementing behaviour already in the starter.

4.1 What Are We Building?

Vision, scope and constraints


A Vision Explains Whose Problem and What Improvement

  • It should guide choices when time is limited.
  • “Build a complete equipment platform” provides little help: almost any proposed feature can be justified under it.
  • A useful vision identifies an actor, an observable improvement and the setting where the claim can be evaluated.

The Working Vision

Help an equipment officer demonstrate clear reservation decisions for a small, controlled set of student borrowers and equipment, so that accepted requests can be inspected and conflicting requests do not create overlapping records within one running coordinator.


What the Vision Does Not Promise

  • It is deliberately narrower than a production service.
  • It does not promise:
    • authentication,
    • a maintained inventory,
    • safe operation by several officers at once.
  • The broader direction can include borrowing and returns later. The first evidence stays small enough to inspect.

Product Goal, Iteration Goal, Sprint Backlog

  • A Product Goal describes a future product state toward which the team works.
  • An iteration goal gives the next period of work coherence.
  • Neither is a list of every task.
  • In Scrum, the Sprint Backlog combines the Sprint Goal, selected backlog items and an actionable delivery plan.
  • Our worked plan uses that distinction without assuming a classroom team has adopted every element of Scrum.

Make the Boundary Visible

  • Scope is most useful when it makes decisions easier.
  • Use a short table that distinguishes:
    • present capability,
    • selected work,
    • deferred questions.
  • The next three slides show the table for the Chapter 4 version.

Scope at the Chapter 4 Version (1 of 3)

AreaAt the versionPlanning consequence
Reservation requestExisting Java accepts eligible, non-conflicting requestsVerify and demonstrate; do not rebuild it for credit
Availability enquiryProposed US-02; no query method or interfaceRefine and plan a thin next slice
StorageIn-memory records in one coordinatorRestart loses records; demonstrate in one process

Scope at the Chapter 4 Version (2 of 3)

AreaAt the versionPlanning consequence
Identity and equipmentNonblank identifiers; synthetic examplesNo claim of directory or catalogue validation
Loan periodWhole days; start inclusive, end exclusiveLabel dates and test adjacency
Cancellation and completed loansNot implemented; all stored reservations count as activeClarify lifecycle before adding those stories

Scope at the Chapter 4 Version (3 of 3)

AreaAt the versionPlanning consequence
User interfaceScripted console demonstration; proposed wireframesNo web or mobile implementation required for kickoff
Concurrent useSequential teaching exampleNo claim that a check prevents races across processes

Constraints, Assumptions, Open Questions

  • Constraint: Java 17 compatibility is a chosen technical constraint for this starter.
  • Teaching boundary: using synthetic borrower identifiers.
  • Assumption: an officer can recognise a sample item (an assumption about the demonstration).
  • Open question: the rule about past dates.
  • Labelling all four as “requirements” hides their different owners and consequences.

An Assumption Needs a Trigger

  • Example: “For the controlled demo, the operator uses CAM1 and MIC1 from a supplied list of sample items.”
  • Trigger: “Before opening input to arbitrary equipment, clarify the catalogue authority and unknown-item behaviour.”
  • The trigger connects a temporary simplification to the point at which it becomes inadequate.

A Small Decision Record

  • State: the question, the chosen approach, the reason, the consequence and the review trigger.
  • It need not be a long report.
  • “Use in-memory records for kickoff because the immediate question concerns reservation decisions; records disappear on restart; reconsider when a demonstration must survive restarting.”

“Database Later” Is Not a Decision

  • It records neither the current limitation nor the condition that will make the work necessary.
  • The team could postpone essential persistence indefinitely, or introduce it immediately without a reason.
  • A clear trigger lets the plan change when the evidence changes.

Do Not Settle Every Future Policy

  • Resolve the questions that determine the next slice’s behaviour.
  • Keep other questions visible, each with an owner and a decision point.
  • This supports progress while preventing a temporary assumption from quietly becoming a permanent rule.

4.2 Sketching the Screens and Walking Through Examples

A wireframe is a question made visible


What a Wireframe Is For

  • A lightweight wireframe helps users and developers discuss information, actions and outcomes before spending time on presentation.
  • It may be a table, a sketch or a simple sequence of views.
  • Its value comes from the questions it exposes, not from visual polish.

Questions a Journey Exposes

  • What does the officer enter?
  • Does “end date” include that day?
  • What appears when equipment is unavailable?
  • Does a failed request leave a record?
  • Can the officer distinguish a successful reservation from an availability observation?
  • The next slides are proposed interface sketches, not screenshots of implemented screens.

View A: Request a Reservation

ElementExample or behaviour
Borrower identifierB1
Equipment identifierCAM1
Start date2026-10-20
Duration in days2
Period explanationIncludes 20 and 21 October; ends before 22 October
ActionRequest reservation
Scope noteControlled demo; records last only for this session

View B: Request Outcome

OutcomeInformation shown
ConfirmedReservation R1, B1, CAM1, [2026-10-20, 2026-10-22)
Item unavailableRequest rejected; no new reservation stored
Limit or duration failureRequest rejected; no new reservation stored
Malformed inputThe proposed interface should explain the field to correct

View B: What Is Real and What Is Drawn

  • The coordinator currently returns confirmation or rejection strings and throws exceptions for certain invalid arguments.
  • A future interaction layer must translate malformed input into a useful response. Drawing it does not implement it.
  • The coordinator combines borrower-limit and duration failures in one message.
  • A sketch showing two precise explanations introduces new work the backlog should identify.

View C: Availability Enquiry (Proposed US-02)

ElementExample or behaviour
Equipment identifierCAM1 from the controlled set of sample items
Start date2026-10-22
End date, exclusive2026-10-23
ActionCheck availability
ResultAvailable, unavailable, or an identifiable invalid-period outcome
ExplanationAn enquiry creates no reservation and does not guarantee a later request will succeed

Do Not Choose a Toolkit Yet

  • A user can understand the sketch without agreeing to a particular toolkit.
  • That leaves the team free to use a thin console interaction while learning about the domain.
  • Choosing a web framework now would add configuration and presentation tasks without resolving the date-boundary question.

Walk Through Concrete Examples

  1. Begin with no reservations.
  2. Submit B1, CAM1, 20 October, two days. Expect one stored reservation ending exclusively on 22 October.
  3. Submit B2, CAM1, 21 October, one day. Expect rejection and the original record preserved.
  4. Submit B2, CAM1, 22 October, one day. Expect a second stored reservation.

Why the Last Step Matters

  • Otherwise “no overlap” is ambiguous.
  • If the officer expects collection on 22 October to conflict with the earlier booking, either the period convention or an operational turnaround rule needs discussion.
  • Do not silently add a cleaning interval to the current rule.
  • Record it as a candidate requirement with examples.

For an Availability Enquiry, Record Two Things

  • The displayed answer.
  • The unchanged state.
  • A program that says “available” after quietly creating a reservation has not fulfilled the story.
  • An observation of availability is not permission to reserve: a borrower may already have three active records.
  • The proposed query does not establish borrower eligibility.

Three Descriptions of One Behaviour

  • Wireframes, examples and code are three descriptions of the same intended behaviour.
  • Disagreement among them is useful evidence.
  • Resolve it explicitly, instead of selecting whichever description is easiest to implement.

4.3 Prioritisation and Vertical Slicing

What first, how thin?


Preserve the Backlog’s History (1 of 2)

OrderItemCurrent interpretation
1US-01: Record an eligible reservationCore behaviour exists; confirm interaction and delivery evidence
2US-02: Inspect availability without reservingNew user outcome to refine and forecast
3EN-01: Automate build and checksEnabling work supplied in this chapter; hosted evidence still required

Preserve the Backlog’s History (2 of 2)

OrderItemCurrent interpretation
4US-03: Inspect a borrower’s active recordsFiltering and interaction remain new work
5US-04: Cancel a confirmed reservationAuthority, lifecycle and count effects need clarification
6US-05: Reject unknown equipment identifiersDepends on a catalogue source and failure policy

Order Is Judgement, Not a Rigid Instruction

  • Order is not an instruction to finish every task of one item before examining another.
  • EN-01 supports reliable delivery of US-02, so preparing automation during kickoff is reasonable. Explain that dependency openly.
  • Do not quietly move every technical improvement ahead of user feedback.
  • “Low priority” for catalogue validation is defensible only within the controlled set of sample items.

Vertical Slice

  • A vertical slice passes through the necessary parts of the system to deliver one observable outcome.
  • It need not use a graphical interface, database or network.
  • For this project, a console request that reaches the coordinator and displays a verified decision is a thin journey through interaction, domain behaviour and storage.

Vertical vs. Horizontal

Horizontal splitVertical sliceAll screens firstThen all servicesThen all storageWork exists, but nothing is demonstrable until the end.InteractionDomain behaviourStorageUS-02A thin path through every layer: one observable outcome.

Why Horizontal Splitting Hurts

  • “All screens first, then all services, then all storage” can leave substantial work without a demonstrable outcome.
  • Those activities may be implementation tasks.
  • They do not become useful increments merely because they are separate tickets.

A First Slice for US-02

The officer supplies one sample item and a valid date interval, receives an availability answer based on current reservations, and can verify that the enquiry left every stored reservation unchanged.


What the Slice Defers and What It Cannot

  • Can defer: calendar styling, multiple-item search, live inventory integration, availability across several servers.
  • Cannot defer: overlap semantics and state preservation. They determine whether the promised outcome is true.
  • The slice also needs an identifiable response to an equal or reversed interval.

Acceptance Examples for the Slice

IDGiven and enquiryExpected outcome
AQ-01Empty coordinator; CAM1 [20 Oct, 22 Oct)Available; records unchanged
AQ-02CAM1 reserved [20 Oct, 22 Oct); enquire [21 Oct, 23 Oct)Unavailable; records unchanged
AQ-03Same reservation; enquire [22 Oct, 23 Oct)Available; records unchanged
AQ-04Same reservation; example MIC1 for [20 Oct, 22 Oct)Available; records unchanged
AQ-05Equal or reversed start and endIdentifiable invalid-input outcome; records unchanged

“Records Unchanged” Means Contents Too

  • All dates are in 2026.
  • “Unchanged” means preserving contents as well as count.
  • A query that replaces one reservation with another can leave the same size and still be incorrect.
  • The exact query API and invalid-input representation are design choices to settle before implementation. The existing reserve method is not a query implementation.

When Is a Feature Small Enough?

  • When the team can understand and verify it within the iteration, including integration.
  • Making it smaller by removing meaningful rejection behaviour can produce misleading success.
  • A reservation feature that accepts requests but ignores conflicts is not a safe reduction of this outcome.

Necessary Work vs. Attractive Additions

  • Necessary for US-02: interpreting the interval, inspecting relevant reservations, reporting the result, checking non-mutation.
  • Optional: a calendar widget.
  • A persistent store becomes necessary only when the selected outcome requires data to survive a restart.
  • This lets the team negotiate scope without treating quality as optional.

Epics Group Direction, Not Iteration Size

  • “Reservation decisions” can contain US-01 and US-02.
  • “Record lifecycle” can contain US-03 and US-04.
  • EN-01 supports delivery across them.
  • An epic does not require an architecture layer or a Java package with the same name.

4.4 What Can We Really Finish?

Release outlook and iteration planning


An Outlook Is Not a Commitment

  • A release outlook communicates likely steps toward the product goal, and should show uncertainty.
  • The book’s teaching stages: kickoff makes the baseline reproducible; the MVP stage develops a small reservation and loan flow; later stages add integration boundaries and respond to a changed requirement.
  • These are teaching stages, not evidence that every team can deliver identical scope on identical dates.

Now, Next, Later

  • Use a short outlook with “now”, “next” and “later” if precise forecasts would be invented.
  • Keep the module’s official milestone schedule unchanged.
  • A planning table cannot create a new assessment deadline or promise that a poorly understood story will fit a particular week.

Two Different Goals

Kickoff goal: Make the reservation baseline reproducible and reviewable, and prepare a bounded availability slice for the next iteration.

Candidate next iteration goal: Let the officer inspect availability for one sample item and interval without changing reservations.


What Chapter 4 Provides

  • Chapter 4 provides the first goal’s starter materials.
  • It plans the second goal.
  • It does not claim to have implemented US-02.

Counting Capacity

Four members report 6 + 6 + 5 + 5 = 22 person-hoursReservedcoordination,integration, admin2resolve thequery contract4implement theread-only query3thin interaction,invalid input3query andregression checks2review, notes,rehearsal4unallocated:room for uncertaintySelected work: 14 person-hoursPlanning budget: 18 person-hours

The Arithmetic

  • These figures are invented planning data, not official contact hours or measured productivity.
  • Four members report 6, 6, 5 and 5 person-hours: 22.
  • Reserve four for coordination, integration support and routine administration.
  • The remaining planning budget is 18 person-hours.

Forecast Work for the Next Iteration

Forecast workPerson-hours
Resolve the query contract and update examples2
Implement the read-only enquiry behaviour4
Add the thin interaction and invalid-input response3
Add and run query checks alongside regression checks3
Review, integrate, update notes and rehearse the demonstration2
Selected work14
Unallocated capacity within the 18-hour budget4

How to Read the Estimates

  • They are discussion inputs, independently expressed in person-hours.
  • They are not converted from Chapter 3’s story points.
  • Two people pairing for one hour consume two person-hours.
  • Review and correction must fit in the budget. Calling them “after development” does not make them free.

The Four Unallocated Hours

  • They provide room for uncertainty.
  • They are not a promise to pull in another feature.
  • If the query needs an unexpected policy decision, use the capacity to resolve that risk while protecting the goal.
  • If things finish early, improve evidence or select additional work deliberately.

18 Hours Does Not Establish Feasibility

  • Suppose the only person who can explain the coordinator is unavailable until the last day, while the reviewer is free only at the beginning.
  • The work cannot flow as the arithmetic suggests.
  • Ask when collaboration can occur and whether knowledge must be shared earlier.

Ownership Without Specialists

  • A task owner ensures that progress and questions remain visible.
  • It does not mean nobody else may help.
  • Agree an early pairing session on the query contract and a review window before the final demonstration.
  • A little coordination prevents the entire slice waiting at the end.

When the Evidence Changes

  • If four hours of implementation become nine, discuss the forecast and remaining scope.
  • Do not hide the overrun by postponing checks or declaring the interaction “almost done”.
  • In Scrum, scope can be clarified and renegotiated with the Product Owner while protecting the Sprint Goal.
  • A classroom adaptation should make its decisions visible.

Replanning Example

  • Implementation revised from 4 to 9 hours after a contract ambiguity.
  • Selected work becomes 19 hours: already above the 18-hour budget.
  • Do not preserve the status by removing checks.
  • Revisit the ambiguity and reduce an optional part of the interaction, or revise the forecast transparently.
  • If no coherent verified slice fits, report the outcome as at risk and renegotiate the selected work.

The Practical Replanning Question

What complete, still useful outcome can we verify with the capacity left?

  • Sometimes the answer is a smaller interface.
  • Sometimes unresolved behaviour means the item cannot responsibly be called complete.
  • Honest incompleteness is more useful than a green status that conceals unfinished work.

4.5 How the Team Works Together

Agreements and review


Agree How Work Moves

  • Keep Chapter 3’s board: Backlog, Ready, Building, Review, Done.
  • Combined WIP limit of two across Building and Review.
  • A blocked item still occupies a slot. If review is blocked, helping it finish is usually better than starting a third item.

What a Team Agreement Answers

  • Where do requirements live?
  • How are questions raised?
  • Who reviews a change?
  • What evidence do reviewers need?
  • How is unfinished work reported?
  • The board tool is interchangeable. A shared Markdown table can work if updates are visible and ownership is clear.

Start From What You Decided Earlier

  • The issue board and branch policy from Chapter 2 Part D.
  • The Definition of Done from Chapter 3.
  • Your process note naming which Scrum elements you use as defined and which you adapt.
  • Revise them where experience shows a problem, and record why.

Small, Short-Lived Branches

  • One branch should contain a coherent change another person can understand.
  • “Set up every tool and rewrite the coordinator” is hard to review: functional and workflow changes become entangled.
  • A branch that adds the checking script and its documentation has a clearer purpose.

The Review Loop

Small changeand local checksReview and CIBlockingfinding?Correct and rerunIntegrate andverify theshared branchNoYesA team policy: git does not enforce review by itself.

A Policy, Not a Guarantee

  • This describes the chapter’s team policy. A repository does not enforce review because the README requests it.
  • Where supported, configure the shared branch to require the agreed check and review.
  • If enforcement is unavailable, state that the team follows a manual policy and retain its review evidence.

Review the Claim, the Change and the Evidence

  • Start with the intended behaviour.
  • Trace the relevant code or documentation.
  • Ask whether the evidence supports the claim.
  • “Looks good” alone does not say which risk was examined.

What to Ask About a CI Change

  • Does a compilation failure stop the job?
  • Do the check programs actually execute?
  • Does a failed assertion reach the job result?
  • For US-02 later: does the query mutate state, or depend on a borrower’s eligibility?
  • A code-style comment should not distract from a behavioural defect.

Author and Reviewer Duties

  • The author resolves blocking findings and reruns affected checks.
  • The reviewer inspects the correction.
  • Retain the actual feedback and response. Do not invent a review exchange to fill a template.
  • Working alone permits a documented self-review for practice. It is not independent review and should be labelled so.

Extending the Definition of Done

  • Carry forward Chapter 3: checked examples, reproducible execution, actual review, integrated code, updated notes, repeatable demonstration.
  • Add: the agreed automated check has passed on the integrated revision and the team can locate its result.
  • Acceptance criteria stay specific to a story. A passing pipeline is one part of the DoD.
  • It does not establish that a wireframe was understood or a useful outcome delivered.

Forms in Appendix C

  • C.6: the iteration plan of Section 4.4.
  • C.7: a team agreement.
  • C.8: a pull-request description.
  • C.9: the evidence note of this chapter’s companion.
  • Each form comes with a worked example.

4.6 A Starter Build and CI You Can Run

One command that means something


Make One Command Meaningful

  • CI needs a check that can run without relying on one developer’s open IDE.
  • The starter uses Java 17, Bash and the supplied executable Java checks. No external Java dependencies.
  • Maven and JUnit remain the planned tools for the fuller testing stage. This version keeps the plain-Java checks so kickoff does not need a test framework first.
  • On Windows, run the script from Git Bash or WSL. PowerShell cannot interpret it.

From the Flat Directory to a Conventional Layout

  • Continuing from the flat Chapter 2 directory, the move is mechanical:
    • application classes go to src/main/java,
    • check classes go to src/test/java,
    • their contents do not change.
  • The classes stay in the unnamed package: a layout change only.
  • It is the layout Maven expects, so a later move changes the build file and the checks, not where the sources live.

The Repository Files (1 of 2)

Relative pathPurpose
src/main/java/ReservationRules.javaExisting borrower-count and duration rule
src/main/java/LoanPeriod.javaExisting immutable interval and overlap behaviour
src/main/java/Reservation.javaExisting immutable reservation data
src/main/java/ReservationCoordinator.javaExisting sequential, in-memory reservation operation
src/main/java/Chapter04Demo.javaFixed three-request demonstration

The Repository Files (2 of 2)

Relative pathPurpose
src/test/java/Chapter02Checks.javaExisting 34 regression checks
src/test/java/Chapter03AcceptanceExamples.javaExisting five acceptance example groups
scripts/check.shCompile and execute the agreed gate
.github/workflows/java-check.ymlHosted CI template
.gitignore, .gitattributes, README.mdExclusions, LF endings, setup and scope

The Gate Script

#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")/.."
mkdir -p build/classes
find build/classes -type f -name '*.class' -delete

java -version
"${CHAPTER_JAVAC:-javac}" --release 17 -encoding UTF-8 -d build/classes \
  src/main/java/*.java src/test/java/*.java
java -cp build/classes Chapter02Checks
java -cp build/classes Chapter03AcceptanceExamples
echo "BUILD AND CHECKS PASSED"

What the Script Does

  • Clears generated class files, then compiles the listed source directories.
  • Runs Chapter 2’s 34 checks and Chapter 3’s five acceptance example groups, then prints a completion message.
  • Compilation targets Java 17 through --release 17; generated classes go into a separate output directory.
  • The script must stop when compilation or either checking program fails. The final message is reached only after success.

Why the Gate Is Built This Way

  • It changes to the project root relative to its own location: independent of the caller’s directory.
  • set -euo pipefail stops on a failing command, rejects unset variables and propagates pipeline failures.
  • There is no || true, no catch-and-ignore handler, no success message issued before checking.
  • Adding another check class also requires adding its run command: compiling does not run a main method.

The Gate as a Picture

Compilejavac --release 17Chapter02Checks34 checksChapter03AcceptanceExamples5 groupsBUILD ANDCHECKS PASSEDany failure stops the script: nonzero exit, no success lineHosted workflow (GitHub Actions template):actions/checkoutactions/setup-javaTemurin 17bash scripts/check.shthe same gate

What a Passing Run Prints

All 34 chapter checks passed.
AC-01 passed
AC-02 passed
AC-03 passed
AC-04 passed
AC-05 passed
All 5 acceptance examples passed.
BUILD AND CHECKS PASSED
  • The five groups contain multiple assertions. Do not add 34 and 5 and call it 39 individual assertions.

What a Pass Establishes

  • The five acceptance examples cover: eligible reservation, overlap rejection, adjacency, borrower limit, duration boundaries.
  • They do not test US-02, because that feature is not implemented.
  • A pass establishes something bounded: the supplied reservation checks passed against this revision in the recorded environment.
  • It is not a claim of complete correctness.

Run the Scripted Journey

java -cp build/classes Chapter04Demo
Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Rejected: item unavailable.
Confirmed: R2 for B2 on CAM1 [2026-10-22, 2026-10-23).
Stored reservations: 2
  • Fixed synthetic examples. Not interactive. The second request neither consumes an identifier nor creates a record.

Line Endings Can Break the Script

  • Git on Windows may convert LF to CRLF on checkout.
  • A CRLF script fails under WSL or Linux with an error such as set: pipefail: invalid option name.
  • The repository includes a .gitattributes file containing *.sh text eol=lf.
  • Check that it is committed before a teammate tries a fresh clone.

Read a Check as Arrange, Act, Assert

// Arrange: the coordinator already holds B1's reservation of CAM1.
// Act: B2 asks for CAM1 on an overlapping day.
String conflict = coordinator.reserve("B2", "CAM1", day.plusDays(1), 1);
// Assert: the request is refused, and nothing new was stored.
require(conflict.equals("Rejected: item unavailable."), "AC-02 rejection");
require(coordinator.reservations().size() == 1, "AC-02 unchanged count");

The Three Steps

  • Arrange builds the situation.
  • Act performs the one operation under test.
  • Assert states what must be observed, including what must not have changed.
  • A check asserting only the message would pass for a request that was refused but still stored.
  • Chapter 7 names this structure Arrange-Act-Assert and builds on it.

The Hosted Workflow: Triggers

name: Java starter checks
on:
  push:
  pull_request:
  workflow_dispatch:
permissions:
  contents: read
  • Runs on pushes, pull requests and manual dispatch, with read-only access to the repository contents.

The Hosted Workflow: The Job

jobs:
  verify:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: '17'
      - name: Compile and check
        run: bash scripts/check.sh

Reading the Workflow

  • Stored under .github/workflows.
  • A hosted Ubuntu runner; Temurin Java 17; the same gate; a five-minute timeout.
  • A push tied to a pull request may produce both kinds of run. The simple template accepts that duplication.
  • A passing workflow does not configure merge requirements automatically.

GitHub Actions Is One Route

  • An institution’s alternative CI service is suitable if it checks the intended revision, runs the agreed command and preserves inspectable results.
  • A badge or YAML file alone is not a passing run.
  • Confirm that the workflow actually starts and that its compilation and checking steps execute.

Record the Run

  • The revision.
  • The environment.
  • The command.
  • The result, with a link or exported log.
  • For a pull request, identify the tested revision. After integration, retain the shared branch’s passing result too.
  • A branch check can become stale after another change is integrated.

Version Selectors Can Move

  • The example uses a Java major version and action major tags for readability.
  • Those selectors can resolve to updated releases, so record the resolved versions in evidence.
  • Stronger dependency pinning, wider environment coverage and delivery stages belong in Chapter 13.

Prove That a Failure Is Observable

  1. Begin with a passing run.
  2. On a disposable branch or copy, change one acceptance check to expect two records after the first successful reservation.
  3. That expectation is intentionally wrong: valid behaviour stores exactly one.
  4. Run the same gate and expect a nonzero result identifying AC-01.

The Deliberate Failure

// original
require(coordinator.reservations().size() == 1, "AC-01 one record");
// intentionally wrong, on a practice branch only
require(coordinator.reservations().size() == 2, "AC-01 one record");
All 34 chapter checks passed.
Exception in thread "main" java.lang.AssertionError: AC-01 one record
  • The author run returned exit status 1. BUILD AND CHECKS PASSED must be absent.

Restore and Retain Evidence

  • Restore the correct expectation and run again.
  • On a hosted practice branch, retain the failed run and the restored passing run.
  • Never merge the deliberate defect into the shared baseline.
  • A local copy proves local failure propagation but not hosted CI. Report the distinction.

Environment Failure vs. Assertion Failure

  • A failed download or missing JDK does not prove failure propagation. It shows an environment failure before the assertion was reached.
  • Read the log and identify the actual failed step.
  • Replacing a test command with echo passed makes a green workflow that checks nothing.

Author Verification Boundary

  • Executed locally by the author: compilation, the 34 checks, the five acceptance groups, the demonstration and the failing-check experiment.
  • The GitHub Actions workflow is supplied for learners to run.
  • No hosted execution or independent team review is claimed.
  • Completing the milestone’s hosted CI evidence requires an observed run in the team’s repository.

4.7 Getting Ready for Milestone 1

An evidence trail that agrees with itself


Assemble an Evidence Trail

  • Milestone 1 connects the project direction to an executable starting point.
  • The documents should agree with one another.
  • A vision promising live inventory, a backlog assuming sample identifiers and a demo using invented equipment without explanation leave a reviewer unable to interpret success.
  • The preparation map is a navigation aid, not an extra assessment rubric.

The Preparation Map (1 of 3)

EvidenceQuestion it should answer
Vision and scopeWhose outcome matters, and under which boundaries?
Epics, stories and ordered backlogWhat is supported, what is next, what is uncertain?
Wireframes and acceptance examplesWhat will the officer do, see and change?

The Preparation Map (2 of 3)

EvidenceQuestion it should answer
Capacity and iteration planWhy is the selected work plausible, including verification and review?
Team agreement and review recordHow will changes be inspected and integrated?
Repository and READMECan another person obtain the intended revision and run it?

The Preparation Map (3 of 3)

EvidenceQuestion it should answer
Passing CI resultDid the configured service execute the gate successfully?
Deliberate failure and restored passDoes a failed check produce failure, and is the baseline healthy again?
Decision and limitation notesWhich assumptions restrict the claims being made?

  • Story to acceptance examples.
  • Examples to the checking program.
  • Change to review.
  • Revision to CI result.
  • Links are useful when they let another person answer a question.
  • A large spreadsheet of repeated identifiers adds little if the linked check does not exercise the promised behaviour.

Rehearse From Another Person’s Starting Point

  • Ask a teammate to follow the README from a fresh checkout. Watch where they need undocumented help.
  • Does the command assume a shell they do not have?
  • Is the JDK a development kit or only a runtime?
  • Does the demo require records created in an earlier session?
  • Correct the instructions and repeat the affected step.

Transparent Gaps

  • Do not report an unrun command as verified because it looks familiar.
  • If the hosted service is unavailable, preserve the local evidence and name the missing hosted result as a gap.
  • A transparent gap gives the team a concrete next action.
  • The exit version: a bounded plan, inspectable sketches and a repeatable baseline with a delivery gate.

4.8 Challenging an Overambitious Plan with AI

Verdicts, not vibes


Ground Rules for the Activity (1 of 2)

For this activity
Your goalEvaluate a proposed iteration plan against scope, capacity, behaviour and delivery evidence
You start withThe Chapter 3 backlog, the capacity figures in 4.4 and the Chapter 4 starter
What is in boundsReview and replace the plan; do not implement any feature

Ground Rules for the Activity (2 of 2)

For this activity
How to checkYour capacity arithmetic; the code (is there a query method? does reserve change state?); the actual gate output
How to decideA verdict and reason for each distinct proposal, then a bounded replacement plan
No AI account?The ready-made example is enough
  • Chapter 15 collects the recurring AI workflows. This activity is self-contained.

The Ready-Made Example (1 of 3)

Author-written fictional AI output, constructed for this offline exercise. Not a record of an actual model conversation.

Four students have 22 person-hours, so plan 22 hours of features. Deliver US-02, cancellation, a mobile app, a database and email confirmations. Assign one student to each layer and integrate on the final evening.


The Ready-Made Example (2 of 3)

Convert the three-point availability story into three hours. Show “available” by calling reserve and deleting its record afterwards. Skip failure cases until next sprint.


The Ready-Made Example (3 of 3)

Mark EN-01 complete when the YAML file is committed; a green badge proves the whole story works.

  • Create a review table: a verdict and a reason for each distinct proposal.
  • Replace the plan with a bounded goal, a selected slice, a capacity calculation and explicit evidence.
  • Find at least one domain error, one estimation error, one integration risk and one unsupported completion claim.

Model Critique (1 of 3)

ProposalVerdictReason and correction
Allocate all 22 hours to featuresRejectCoordination, review and uncertainty consume capacity; use the explicit budget
Deliver query, cancellation, mobile, database and email togetherRejectThe selected goal is one query slice; the additions lack agreed requirements
One layer per member; integrate at the endReviseOwnership can help; final-day integration delays learning

Model Critique (2 of 3)

ProposalVerdictReason and correction
Convert three story points into three hoursRejectNo valid conversion was established; estimate tasks from real understanding
Call reserve, then delete the recordRejectA query must not change state; deletion is not even a supplied operation; eligibility differs
Defer failure casesRejectInvalid intervals and non-mutation are part of this outcome’s acceptance

Model Critique (3 of 3)

ProposalVerdictReason and correction
Mark EN-01 complete when YAML is committedRejectConfiguration must execute successfully and detect the intended failure
A green badge proves the whole storyRejectThe gate checks only executed conditions; current checks cover reservation, not US-02
  • A corrected response states the availability goal, AQ-01 to AQ-05, the 18-hour budget and 14-hour forecast, plus review and observed CI evidence.

Independent Verification

  • Trace reserve to its reservations.add call.
  • Observe the absence of a query method.
  • Check the arithmetic.
  • Inspect the actual check output.
  • Asking a second AI model whether the first plan is good does not replace those observations.
  • An AI-use record names: the prompt or example, claims examined, decisions, evidence, remaining uncertainty.

4.9 Planning Mistakes That Look Reasonable

Eight to avoid


Mistakes 1 to 3

MistakeWhy it failsBetter action
Count starter behaviour as newly deliveredHides the actual changeSeparate existing capability from new evidence or functionality
Slice only by technical layersNo usable end-to-end resultSelect one observable journey and its necessary tasks
Fill all nominal capacity with featuresReview, coordination and uncertainty go unfundedBudget those activities explicitly

Mistakes 4 to 6

MistakeWhy it failsBetter action
Use a wireframe as proof of implementationConfuses an agreed sketch with working behaviourLabel proposal and implementation separately
Treat an availability answer as a reservationOmits eligibility and later state changesKeep query and command outcomes distinct
Create CI configuration without running itNo evidence the gate executesInspect an actual run against the intended revision

Mistakes 7 and 8

MistakeWhy it failsBetter action
“Fix” a red job by ignoring its exit statusConceals the defectCorrect the cause; preserve failure propagation
Invent review or meeting evidenceMakes the project state misleadingReport actual observations and remaining gaps

4.10 Try It Yourself

Prepare for the kickoff


Project and Input

  • The activity runs on your team’s project.
  • Equipment lending: the supplied listings are your starting repository.
  • Another provided domain: apply each step to that domain’s starter codebase, using the supplied files as the model for layout, script and workflow.
  • You start with: the Chapter 3 backlog, the current Java baseline and the Chapter 4 repository listings.
  • Do not change domain without following the module’s arrangements.

Instructor Demonstration and Guided Practice

  • Demonstration: walk through the three reservation requests, then compare an ordinary passing run with the deliberately wrong acceptance check. Ask what each result establishes.
  • Guided practice: create a one-page scope statement and wireframe. Have another pair explain the journey back to you. Resolve one discrepancy between their interpretation and your intended behaviour.

Independent Team Work (1 of 2)

  1. Write the vision, boundaries, assumptions and two decision records with review triggers.
  2. Group the backlog into epics, preserve story identifiers, and distinguish existing capability from new work.
  3. Refine US-02 (or an equivalent thin slice) with normal, boundary and rejection examples, including state effects.
  4. Create an iteration goal and capacity plan using your actual availability, with coordination, review and uncertainty shown.

Independent Team Work (2 of 2)

  1. Agree ownership, review and board policies. Apply them to one small repository change.
  2. Create the starter files, run the gate and reproduce the scripted demonstration.
  3. Enable the hosted workflow. Retain a passing run, a deliberate assertion-failure run on a practice branch, and a restored passing run. Record the integrated baseline’s result.
  4. Assemble the evidence map; ask another team member to follow the README from a fresh checkout.

Expected Output

  • Vision and scope notes.
  • Ordered backlog and selected story.
  • Wireframes.
  • Capacity plan.
  • Team agreement and review record.
  • Repository instructions.
  • Verification evidence.
  • Unresolved questions with owners.

Completion Checks

  • Another person can explain the chosen outcome.
  • They can distinguish implemented and planned behaviour.
  • They can run the baseline and locate the actual CI results.
  • They can trace at least one acceptance example to an executed check.
  • A missing hosted run remains an explicit gap.
  • Use the module leader’s submission requirements for the assessed hand-in.

Model Document: Vision and Scope

  • Working vision: help an equipment officer demonstrate clear reservation decisions for a controlled set of student borrowers and equipment, and inspect accepted records without overlapping reservations for the same item in one coordinator.
  • In scope: one item per request; inclusive start, exclusive end; the student rules; deterministic synthetic examples; sequential calls in one process.
  • Outside: persistent storage, real catalogue validation, cancellation, returns, email, concurrency, a polished interface.

Model Document: Open Questions

  • Treatment of past dates.
  • Operational turnaround between loans.
  • Authority to cancel.
  • What “active” means after cancellation or completion.
  • The catalogue source and its failure behaviour.
  • Resolve only those that block the selected next outcome. Attach an owner and a review trigger to the others.

Model Decision Record D-01: Storage

  • Question: must kickoff preserve records across restarts?
  • Decision: keep one in-memory coordinator.
  • Reason: the demonstration evaluates reservation decisions in one session.
  • Consequence: a restart loses records and the identifier sequence restarts.
  • Review trigger: a selected outcome requires a session to survive restart.
  • Status: the existing teaching baseline.

Model Decision Record D-02: Query Interaction

  • Question: what input expresses the query interval?
  • Proposed decision: a start and an explicitly labelled exclusive end.
  • Reason: it makes the half-open interval visible in discussion.
  • Consequence: the officer needs clear date guidance; equal or reversed bounds must be reported.
  • Review trigger: a walkthrough shows persistent confusion or a preference for duration.
  • Status: proposed; obtain team agreement first.

Model Document: The US-02 Contract

  • Story: as an equipment officer, I want to inspect whether one sample item is available for a stated interval, so I can discuss a possible reservation without creating one.
  • Acceptance: AQ-01 to AQ-04 report availability for empty state, overlap, adjacency and a different item; AQ-05 reports an identifiable invalid-period outcome; every enquiry leaves all reservation contents unchanged.
  • Still to decide: the invalid-input representation. Do not present two as simultaneously implemented.

Review Trap: Availability Is Not Eligibility

  • If B1 already holds three active reservations, an unreserved item can be available even though B1 cannot reserve it.
  • Reusing reserve as the enquiry mixes those questions and changes state.
  • Necessary tasks: agree the contract; add a read-only operation; add the thin interaction; test results and non-mutation; review and integrate; update instructions; demonstrate.

Model Team Agreement

  • Keep stories and acceptance examples in shared project notes; preserve identifiers when refining wording.
  • Backlog, Ready, Building, Review, Done; Building plus Review limited to two; blocked items count.
  • Branches small enough for a reviewer to explain.
  • Authors run the gate before review and after corrections. Reviewers inspect behaviour, state effects and evidence.
  • Record the shared branch’s check result after integration. Mark missing evidence as a gap; never manufacture it.

The Evidence Note (Blank Template) (1 of 2)

FieldTeam entry
Repository and baseline revisionThe actual repository and commit
Implemented behaviourOnly the behaviour present at that revision
Local environment and commandJava version, shell, command
Local check resultObserved exit status and relevant output
Hosted passing runRun URL or retained log, tested revision, date

The Evidence Note (Blank Template) (2 of 2)

FieldTeam entry
Deliberate failurePractice revision, failed assertion, failed run
Restored passRestored revision and passing run
Independent reviewActual reviewer, finding, correction, follow-up
Unresolved questionsOwner, impact, next decision point
Known limitationsScope boundaries relevant to interpreting the demo
  • A template, not completed evidence.

Feedback Questions (Not Document Counts)

DimensionUseful feedback question
ScopeCan the team explain the conditions under which its claim is valid?
SliceDoes the result support one observable outcome, including necessary failures?
PlanningIs review and uncertainty accounted for, and can people collaborate when needed?
TraceabilityDoes the cited check actually exercise the criterion?
WorkflowIs there real evidence of review, corrections and the integrated result?
HonestyAre proposed features, local checks and hosted runs labelled accurately?

If You Want More

  • Review whether the query slice should use duration or an exclusive end date at its interaction boundary.
  • Compare the input errors each choice creates. Update the sketch and examples.
  • Do not implement a second interface solely for this extension.
  • A suggested solution: duration matches the current reservation input and suits “two days”; an exclusive end makes boundaries explicit but may be misread as an inclusive last day.
  • The choice should follow a user walkthrough.

4.11 Questions to Think with

Concept and diagnosis


Concept Questions

  1. How do a vision, an iteration goal and an implementation task differ? Use the equipment project for each.
  2. Why can a console-based journey be a vertical slice even when it has no database?
  3. Why is total available person-time insufficient to establish a feasible iteration plan?
  4. What does a passing starter check establish, and what does it leave unproven?

Diagnosis Questions

  1. A team reports the availability story done because the screen says “available”. Its implementation calls reserve, then removes the new record. Identify the violated requirement and the state evidence you would inspect.
  2. A CI job compiles successfully, runs java -cp build/classes Chapter03AcceptanceExamples || true, then prints “all checks passed”. Explain how this can report success after an assertion failure and how you would verify the correction.

Guidance: Questions 1 and 2

  1. The vision describes the officer’s improved ability to make clear reservation decisions. The iteration goal concerns inspecting availability without changing records. A read-only operation is one task supporting it. A list of technical tasks alone does not express the user outcome.
  2. A slice is complete relative to its promised outcome when input reaches the relevant behaviour and storage decision and produces an inspectable result. A console can support that. A database or graphical interface is needed only if the outcome requires it.

Guidance: Questions 3 and 4

  1. Summed hours omit dependencies, calendar overlap, skill availability and review timing. Four people available on different days may be unable to pair or review when needed. Check the sequence and collaboration windows.
  2. The particular checks executed successfully against the compiled revision in the recorded environment. Unproven: untested cases, unspecified rules, hosted execution if only run locally, independent review, concurrency safety, user acceptance. It does not certify that US-02 exists.

Guidance: Questions 5 and 6

  1. Calling reserve violates the read-only requirement before any cleanup: a success appends a record, and eligibility can reject the request even when the item is free. Inspect before-and-after record identifiers, borrower/item identifiers and period bounds, not only the count.
  2. || true turns the failing Java command into a successful shell list. Remove the suppression, rerun with the deliberately wrong AC-01 expectation, and verify nonzero exit and no final success marker. Restore, and verify a pass.

4.12 A Different Setting

The transfer challenge


Transfer Challenge: Clinic Appointments

A small clinic wants reception staff to inspect appointment-slot availability and make bookings. It has one supplied in-memory booking routine, a draft backlog and a four-person student team with uneven availability.

  • Create a bounded first-iteration goal, a wireframe and a capacity-aware plan.
  • Identify two assumptions to revisit before real use.
  • Give one adjacency example, and explain why equipment-lending date rules cannot simply be copied into a time-based domain.
  • Show what the first automated gate would establish and what still needs human review.
  • Use synthetic appointment data only.

Transfer Guidance (1 of 2)

  • Goal: let reception demonstrate an availability enquiry for one synthetic slot without creating or altering bookings, within one running coordinator.
  • Sketch: practitioner or resource identifier, date, start time, duration, a Check action, and a result that distinguishes availability from a confirmed booking. Show the agreed time zone.
  • Assumptions to revisit: one known synthetic resource and a single agreed scheduling zone; no real appointment authority, patient identity or clinical workflow.

Transfer Guidance (2 of 2)

  • Boundary example: one appointment occupies [09:00, 09:30) and another requests [09:30, 10:00). They do not overlap under that interval model. A preparation interval could still make the second invalid, so clarify it.
  • The equipment starter uses whole dates. Do not paste LocalDate.plusDays into a minute-based rule.
  • Gate evidence: compile, run checks for the agreed booking semantics, prove assertion failure propagates.
  • Human evidence: a walkthrough that reception understands the result and time labels; stakeholder discussion of authority and assumptions.

What to Take Away, and What Comes Next

What to carry forward


Summary: A Credible Iteration

  • A credible iteration connects a bounded outcome, a thin journey, a feasible plan and inspectable evidence.
  • Scope notes expose assumptions; wireframes reveal interaction questions; acceptance examples make behaviour testable.
  • Review and automation help the team preserve a shared baseline while learning what to build next.

Summary: What Changed in the Code

  • The Chapter 4 application behaviour remains the Chapter 2 to 3 reservation version.
  • Additions: repository organisation, a repeatable checking command, a scripted demonstration and a hosted-workflow template.
  • US-02 remains planned.

Next: Chapter 5

  • Responsibilities, Extension and Substitutability.
  • Use this protected starting point to examine reasons for change.
  • Make small, protected design improvements.
  • Comprehensive CI/CD, deployment and integration-testing choices remain for Chapter 13.

Sources and Further Reading

  1. Schwaber and Sutherland: The Scrum Guide, November 2020.
  2. Oracle: The javac command, Java SE 17.
  3. GitHub Docs: Building and testing Java with Maven (context for CI; this starter runs a Bash gate).
  4. GitHub Actions: setup-java and checkout.
  5. Martin: Agile Software Development, 2002. Rubin: Essential Scrum, 2012.
  • Full references and links are in the chapter text.

5 - Chapter 5: Responsibilities, Extension, and Substitutability

Object Oriented Development and Agile Practices (SE506)

Chapter 5: Responsibilities, Extension, and Substitutability

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 5.1 Why would this code change? Change pressure on the service
    • Compiler, your own reading and PMD, side by side. A new staff rule, stated before any design.
  • 5.2 One class, one reason to change? Responsibility and SRP
    • A reason to change is more useful than a method count.
  • 5.3 Making room for variation: OCP and anticipated change
    • Closed against which change? An honest intermediate design.
  • 5.4 What can a caller rely on? Behavioural contracts
    • Preconditions, postconditions, invariants.

Chapter Roadmap (2 of 3)

  • 5.5 When a subtype breaks its promises: LSP and unsafe inheritance
    • A subtype that compiles and still breaks a client.
  • 5.6 Small refactorings and the checks that guard them
    • Stage A (refactoring) and Stage B (feature) kept apart.
  • 5.7 When is an abstraction too early? Avoiding premature abstraction
    • Every boundary has a cost.
  • 5.8 Reviewing a “make this SOLID” suggestion with AI

Chapter Roadmap (3 of 3)

  • 5.9 Design habits that look principled
  • 5.10 Try it yourself: a protected design change
    • Eight steps; step 8 is on your team’s project.
  • 5.11 Questions to think with and 5.12 A different setting: the transfer challenge
    • A coursework feedback tool.
  • What to take away, and what comes next
    • Chapter 6: client-specific interfaces, dependency direction, manual assembly.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Identify independent reasons for a class to change using concrete requirements.
  2. Apply SRP to separate a responsibility and explain the cost of the new boundary.
  3. Justify an extension point for an observed variation without claiming existing code must never change.

Learning Outcomes (2 of 2)

  1. Describe a behavioural contract and diagnose a subtype that breaks a client’s expectations.
  2. Make a small protected refactoring, distinguish it from a feature change, and report the supporting evidence.

Where This Chapter Fits

  • The first SOLID chapter. It supports the formative smell analysis and does not change assessment arrangements.
  • Chapter 6: interface segregation, dependency direction and injection.
  • Chapter 7: testing practice. Chapter 11: systematic smells and legacy refactoring.
  • Readings: the SRP, OCP and LSP chapters of Martin, and “Bad Smells in Code” from Fowler.

Where You Start and the Two Stages

  • You start with: Chapter 4’s Java 17 reservation starter, its 34 regression checks and five acceptance example groups.
  • US-02 (availability enquiry) remains planned.
  • The staff rule is a small, explicitly introduced teaching variation, not an approved policy.
  • Stage A: extract formatting without changing existing behaviour.
  • Stage B: add the documented staff variation while preserving the old student entry point.

How to Use This Chapter

  • Core reading: Sections 5.1 to 5.7.
  • What you must do: steps 1 to 8 of Section 5.10, including the review in step 6 (with another person, or labelled a solo self-review).
    • Steps 1 to 7: the book’s project. Step 8: your team’s project.
  • The lab uses PMD 7.28.0, which needs a one-time download (described in 5.1).
  • If you want more: the AI activity, the extension in 5.10, the questions and the transfer challenge.

Opening Scenario: Two Requests, Two Proposals

  • The officer asks for a different confirmation layout.
  • The team must also demonstrate a second borrowing policy for staff.
  • One developer proposes a subclass of the reservation coordinator.
  • Another asks an AI assistant to “make the code SOLID”.
  • The assistant returns interfaces for almost every class and turns a rejection into an exception.

The Questions Nobody Has Answered

  • Which parts change for different reasons?
  • Which behaviour should vary?
  • What may existing callers continue to rely on?
  • How will the team distinguish an internal improvement from a new feature?
  • The code looks more elaborate. More classes, interfaces or principle names are not an improvement by themselves.

Three Principles as Ways to Reason

  • SRP: the Single Responsibility Principle.
  • OCP: the Open-Closed Principle.
  • LSP: the Liskov Substitution Principle.
  • Treat them as ways to reason about changes and contracts, not as slogans.

5.1 Why Would This Code Change?

Change pressure on the service


Begin With the Code That Exists

  • ReservationCoordinator validates arguments, counts a borrower’s reservations, asks ReservationRules, checks overlap, stores, and constructs a confirmation.
  • Chapter 2 moved string construction into a private method. Chapter 4 made the checks repeatable.
  • That is a useful baseline for asking whether a further boundary is justified.

Several Steps Are Not a Fault

  • Coordinating a reservation necessarily involves several collaborators and decisions.
  • The more useful question: do unrelated changes repeatedly force edits into the same place?
  • The table on the next slides lists five change pressures.

Change Pressures (1 of 2)

Change pressureMain knowledge involvedCurrent location
Change confirmation wordingPresentation of a completed reservationCoordinator’s private formatting method
Add a second count/duration policyBorrower eligibility rulesExisting rule plus new selection behaviour
Change interval overlap semanticsMeaning of a loan periodLoanPeriod

Change Pressures (2 of 2)

Change pressureMain knowledge involvedCurrent location
Preserve records across restartStorage lifetime and retrievalCoordinator’s in-memory list
Reject unknown equipment identifiersAuthority for equipment identityNot implemented
  • The first two are the chapter’s actual teaching changes.
  • Persistence and catalogue integration are future questions: a reason to document a limitation, not to build an extensibility framework now.

State the New Rule Before Choosing a Design

CategoryExisting active reservations allowed before a successful requestRequested duration
Student0, 1 or 21 through 7 days
Staff0, 1, 2, 3 or 41 through 14 days
  • A successful student request may create the third active reservation; a successful staff request the fifth.
  • Both reject negative counts and nonpositive durations.
  • Both still require a valid period and no overlap for the same item.

This Is a Feature Change

  • A staff request for fourteen days can now succeed where the existing four-argument operation would reject it.
  • Moving the existing formatter is a refactoring only if its observable results remain the same.
  • Combining the two in one unexplained edit makes failures harder to interpret.

The Category Is Trusted, Not Verified

  • The example receives the category from a trusted caller.
  • It does not authenticate staff status, persist a borrower profile or infer category from an identifier.
  • Passing STAFF is not proof that a person is staff.
  • A production boundary would need an authoritative category source. Adding an enum does not complete that task.

Define Success for Each Stage

  • Stage A succeeds when the formatting responsibility has moved and existing outputs, exceptions, state changes and signatures remain compatible for existing callers.
  • Stage B succeeds when the explicit category operation implements the new table while the old operation still means student borrowing.

Boundaries That Stay Fixed

  • All stored records still count as active.
  • Storage remains in memory; calls remain sequential; intervals remain half-open.
  • No cancellation, return processing, catalogue, email delivery or availability enquiry is added.
  • These boundaries stop a design exercise becoming an unreviewable expansion of the system.

The Two Stages

Baselinegate passesStage A:extract theformatterUnchangedchecksstill passStage B:add the staffpolicyOld and newchecks passrefactoringbehaviour unchangedfeature changestudent entry point kept

Three Kinds of Evidence, in This Order

  1. The compiler’s.
  2. Your own.
  3. The tool’s.
  • A static-analysis tool examines source without running it and reports patterns that often indicate problems.
  • Setting the three side by side is the point of the lab.

The Compiler: javac -Xlint:all

javac --release 17 -Xlint:all -d build/lint \
    src/main/java/*.java src/test/java/*.java
  • On the Chapter 4 starter it prints nothing.
  • That is informative: the compiler checks language-level hazards (unchecked conversions, fall-through), not whether wording and eligibility belong in one class.

Your Own Reading First

  • Do the manual smell review before you run any analyser, so the tool’s output has something to be compared with.
  • Read ReservationCoordinator once for each of four smells and write down each place with its line or method.
  • For each finding record: where, what you observed, whether it matches a change pressure, whether you would act.
  • Also name one pressure the compiler did not report and that you found only by reading.

The Four Smells (1 of 2)

SmellWhat to look for
More than one reason to changeA method that mixes eligibility, conflict search, record creation and message wording
Hidden constructionA class that creates its collaborators with new inside its own methods

The Four Smells (2 of 2)

SmellWhat to look for
Repeated or similar logicTwo loops over the stored list that differ only in the test, or rejection outcomes returned as plain strings a caller must compare
Primitive values standing for conceptsStrings or integers carrying meaning (an identifier, a number of days, a rejection reason) without a type of their own

Five Terms Before the PMD Output

  • Cyclomatic complexity: the number of independent paths through a method; each if, loop or case adds one.
  • Cognitive complexity: a variant that penalises nested conditions.
  • NPath: the number of possible execution paths through a method.
  • God class: does too much and knows too much. Data class: holds data and little behaviour.
  • In the Data-class line, WOC, NOPA, NOAM and WMC are PMD’s measures of how much of a class is behaviour. You do not need them to read the result.

The Analyser: PMD

  • The book uses PMD 7.28.0, which runs on Java 8 or above.
  • Download pmd-dist-7.28.0-bin.zip from the project’s GitHub release page.
  • Compare the SHA-256 digest with the one the release page shows (the companion records the digest that was checked).
  • Unzip it and set PMD_HOME to the pmd-bin-7.28.0 folder.
  • This is the only download the chapter asks for.

Choosing Rules Is Part of Using PMD

  • On the five-file starter, the quickstart rule set reports 5 findings, all the same one: classes without a package (chosen deliberately until Chapter 10).
  • The code-style category reports 34, for example parameters that “could be final”.
  • Neither helps a smell diagnosis.
  • The lab uses a small set of eight design rules from PMD’s design category, kept in config/pmd-smells.xml, with PMD’s default thresholds.

The Eight Lab Rules

  • Cyclomatic complexity from 10.
  • Cognitive complexity from 15.
  • NPath from 200.
  • God class.
  • Data class.
  • More than 10 methods.
  • 10 or more parameters.
  • 30 or more imports.

Running the Lab

export PMD_HOME=/path/to/pmd-bin-7.28.0
bash scripts/check.sh
bash scripts/pmd.sh
  • Run the compiler gate first, so PMD can read the compiled classes.
  • On the Chapter 4 starter it reports two findings (paths and wording shortened here):
Reservation.java:3:  DataClass: suspected Data Class (NOAM=4, WMC=9)
ReservationCoordinator.java:10:  CyclomaticComplexity: 'reserve(...)' has complexity 12
[INFO] Found 2 violations.

Finding 1: reserve Has Complexity 12 (Useful)

  • It validates identifiers, counts the borrower’s records, applies eligibility, searches for a conflict and creates the record.
  • That is your first smell seen from another side.
  • Act on the part that has a pressure: wording (Stage A) and eligibility (Stage B). Leave the sequence of checks, and say so.

Finding 2: Reservation Is a Data Class (Low-Value Here)

  • Reservation is deliberately an immutable record of an accepted booking. Holding data and little behaviour is its job.
  • It does carry a question: where does the behaviour that uses its fields live?
  • In this starter, in the coordinator’s confirmation wording. That is what Stage A moves.

What PMD Missed

  • It did not report the ReservationRules that the coordinator constructs for itself.
  • It cannot know that “borrower limit or duration” is one string for two different reasons.
  • The comparison the lab asks for places each finding in one of three groups:
    • useful,
    • low-value in this context,
    • a concern the tool missed.

A Finding Is Evidence, Not an Instruction

  • At this scale a tool may report items that do not matter, and it cannot know which changes your team expects.
  • Rerun PMD after each stage: the complexity finding stays at 12 after Stage A and Stage B.
  • Moving wording and policy out changed who decides, not how many branches the sequence of checks has.
  • Record only output you actually produced.

A Number Is Not a Verdict

  • Extract Method alone can lower the complexity number while leaving every responsibility where it was.
  • The companion’s experiment: extracting activeReservationsOf(borrowerId) and isUnavailable(itemId, requested) made the finding disappear. The gate still passed (44 checks, five acceptance groups).
  • The class still validates, counts, checks eligibility, searches and creates.
  • Say what the tool measures and what the change targeted.

5.2 One Class, One Reason to Change?

Responsibility and SRP


SRP Focuses on Reasons to Change

  • SRP focuses attention on changes arising from different responsibilities.
  • Martin connects responsibilities to the people or groups whose needs drive changes.
  • The people deciding borrowing limits may have different concerns from those deciding how an officer reads a confirmation.

Method Count Is Unreliable

  • A class with several methods can support one coherent responsibility.
  • A one-method class can mix policy, storage and presentation.
  • LoanPeriod has construction, accessors and overlap, yet describes one coherent interval abstraction.
  • An organisational chart is evidence about change, not a class-generation algorithm.

Twenty Tiny Helpers Are Not a Cure

  • Suppose the coordinator’s method is short because it delegates to twenty tiny helpers.
  • If they all reach into the same mutable list and repeat the same decision logic, the design may still be difficult to change.
  • Smaller methods are not the same as separated responsibilities.

Stage A: Extract a Collaborator

  • Move the confirmation construction into ConfirmationFormatter.
  • Its input is the immutable Reservation already accepted. Its output is a string.
  • It does not approve a request, add a record or send a message.
reservations.add(reservation);
return formatter.format(reservation);

The Formatter

public final class ConfirmationFormatter {
    public String format(Reservation reservation) {
        Objects.requireNonNull(reservation, "reservation");
        return "Confirmed: " + reservation.id()
                + " for " + reservation.borrowerId()
                + " on " + reservation.itemId()
                + " [" + reservation.period().start()
                + ", " + reservation.period().endExclusive() + ").";
    }
}

What the Extraction Preserves

  • Formatting is still performed after the reservation is stored.
  • There is no transaction handling or rollback if some future formatter fails.
  • If formatting later gains external dependencies, failure semantics need a separate design decision.
  • Passing one coherent reservation object avoids a long list of unrelated arguments.
  • The formatter must not receive the coordinator’s mutable list: that would give presentation access to storage decisions.

The Cost of the New Boundary

  • The original private method was already a useful seam.
  • A separate class gives the formatting change a named home and lets you examine it without running a reservation scenario.
  • The cost: another file, another object, another call to follow.
  • If formatting were a stable one-line expression with no independent change pressure, keeping the private method could be reasonable.

What Remains Together

  • The coordinator still creates collaborators directly and owns the list.
  • One extraction does not finish its design. Chapter 6 examines how collaborators are supplied.
  • The numeric rules were already outside the coordinator: do not claim to have newly separated them.
  • The actual change: eligibility and presentation now have named homes; the coordinator keeps sequencing.

A Rationale With a Reason, a Boundary and Evidence

Confirmation wording can change independently of eligibility and conflict handling. Move it to a formatter that accepts an accepted reservation. Keep the returned text and storage order unchanged, and run existing exact-output checks.

  • “Applied SRP” alone identifies none of the three.

Do Not Leak Decisions Into the Formatter

  • A formatter that checks the requested duration and returns confirmation or rejection recreates a policy decision in presentation.
  • One that allocates the identifier because it appears in the message confuses using an identifier with assigning it.
  • Ask: which object has the information and authority to make each decision?
  • The coordinator knows the stored records; the formatter knows how to represent an accepted one.

5.3 Making Room for Variation

OCP and anticipated change


Name the Axis of Extension

  • OCP encourages a design where an anticipated kind of extension can be introduced without repeatedly rewriting stable behaviour.
  • The relevant question: “closed against which change?”
  • There is no promise that every imaginable requirement can be added without touching existing code.

The Observed Variation

  • A count-and-duration decision for two borrower categories.
  • Not storage technology, notification transport, date arithmetic or authentication.
  • A small interface expresses exactly that decision.
public interface BorrowingPolicy {
    boolean mayRequest(int activeReservations, int requestedDays);
}

An Interface Needs a Contract

  • A class declares implements BorrowingPolicy and supplies its public method.
  • @Override lets the compiler check that the declaration matches.
  • The interface does not supply the meaning of the operation by itself. We must document its contract too.

The Two Policies

public final class ReservationRules implements BorrowingPolicy {
    @Override
    public boolean mayRequest(int activeReservations, int requestedDays) {
        return activeReservations >= 0 && activeReservations < 3
                && requestedDays >= 1 && requestedDays <= 7;
    }
}
  • StaffReservationRules is identical in shape with < 5 and <= 14: an illustrative teaching policy, not an approved rule.

One Algorithm, Two Policies

  • The coordinator obtains a policy and asks the same question of it.
  • Overlap detection and storage are not duplicated into separate student and staff coordinators.
  • BorrowerCategory is a simple enum: STUDENT, STAFF.

Preserve the Old Entry Point Explicitly

public String reserve(String borrowerId, String itemId,
                      LocalDate start, int requestedDays) {
    return reserve(borrowerId, itemId, start, requestedDays,
                   BorrowerCategory.STUDENT);
}
  • The old four-argument operation delegates with STUDENT: existing code compiles and keeps its student interpretation.
  • Choosing staff behaviour requires an explicit call.

Do Not Infer Status From an Identifier

  • Do not use borrowerId.startsWith("S").
  • That would hide a new identity rule in a convenient string convention.
  • The synthetic identifier S1 in the demo is just a label. The category argument selects the policy.

Counts Are Per Borrower Identifier

  • Every record for the same borrower identifier contributes to the active count, whichever overload created it.
  • There are no separate student and staff buckets, and the borrower’s category is not remembered.
  • If a caller supplies inconsistent categories, each request uses the supplied category against all that borrower’s records.
  • A limitation of the example boundary, not a model of authorised category changes.

The Remaining Selector

private BorrowingPolicy policyFor(BorrowerCategory category) {
    return switch (category) {
        case STUDENT -> studentPolicy;
        case STAFF -> staffPolicy;
    };
}
  • Adding another enum category still needs another arm here. The coordinator is not completely closed to new categories.
  • The improvement: the algorithm delegates the varying rule.

Figure 5.1: Collaborations After Stage B

ReservationCoordinator«interface»BorrowingPolicy+mayRequest(int, int) booleanReservationRulesStaffReservationRulesConfirmationFormatterasks eligibilityformatscreatescreates

Reading Figure 5.1

  • Solid arrows are associations: the coordinator holds a BorrowingPolicy reference and a ConfirmationFormatter.
  • Dashed lines with a hollow triangle are realisations: each rules class implements BorrowingPolicy.
  • Plain dashed arrows are dependencies: the coordinator creates the concrete policies itself.
  • The interface gives one type to ask, but does not remove the dependence on the concrete classes.
  • When Chapter 6 moves creation outward, those two dashed arrows should disappear.

Compare With a Smaller Alternative

  • A single BorrowingLimits class with validated maximumActive and maximumDays could also be reasonable.
  • The two policies differ only in constants. A compact conditional could even do in a short-lived demo.
  • The interface earns its place by making the decision contract explicit and allowing policy behaviour to be examined separately.
  • It adds names and selection work. Do not justify it by asserting two constants always require polymorphism.

Do Not Invent Requirements

  • Do not invent sponsor approval, network checks or a rule engine to make the abstraction look necessary.
  • Those changes need their own requirements.
  • The later changed-requirement exercise remains later work.
  • This chapter uses only the small second rule stated above.

5.4 What Can a Caller Rely on?

Behavioural contracts


A Signature Leaves Questions

  • Can the method reject input with an exception?
  • Does it mutate anything?
  • Is the result stable for the same input?
  • What exactly does true mean?
  • A signature describes parameter and result types and answers none of these.

The BorrowingPolicy Contract (1 of 2)

AspectPromise
Accepted input domainEvery pair of Java int values can be passed
Invalid numeric valuesNegative count or nonpositive duration returns false
Decisiontrue exactly when the inputs satisfy that implementation’s documented limits

The BorrowingPolicy Contract (2 of 2)

AspectPromise
Side effectsDoes not create reservations, change a counter or perform external I/O
RepeatabilitySame inputs and fixed policy configuration give the same result
Scope of approvalOnly count and duration; no assertion about availability or identity

Different Results, Same Contract

  • Student and staff have different documented limits, so they can return different booleans for (0, 14).
  • A client of the common interface must not assume every policy uses seven days. That belongs to the student policy’s own contract.
  • The interface is meaningful because all implementations answer the same kind of question under common input and side-effect rules.
  • “Do anything and return a boolean” would give no basis for client reasoning.

Preconditions, Postconditions, Invariants

  • A precondition states what must hold before an operation for its promised behaviour to apply.
  • A postcondition states what the operation guarantees afterwards.
  • An invariant is a property maintained by an object’s valid observable states.
  • They turn vague expectations into reviewable statements.
  • LoanPeriod: construction requires non-null dates, start strictly before exclusive end; success establishes that invariant.

A Decision: Negative Counts

  • Chapter 1 noted that returning false for a negative count treats an impossible input like an ordinary refusal. This contract settles it deliberately.
  • Return false: every existing client keeps working (they branch on the result), and the policy is defined for every pair of integers.
  • Throw: signals a caller bug loudly, but changes a result existing callers and checks rely on, and adds a new exception to every client’s contract.
  • Here, preserving behaviour decides. In new code you control, the exception can be better. Write the choice into the contract.

Postconditions and Order

  • A successful reservation: one new stored record and a confirmation identifying it.
  • An eligibility or overlap rejection: stored records unchanged.
  • Order matters: count and duration are checked before the requested period is built and before overlap. Reordering can change which failure a caller observes.

Exceptional Outcomes Are Part of the Contract

  • The four-argument operation rejects blank identifiers with IllegalArgumentException, null dates with NullPointerException, and an unrepresentable end date through Java’s date exception.
  • The new overload rejects a null category.
  • Refactoring is not an opportunity to normalise those outcomes silently. A more consistent error model is a separate API change.

Check Observable Behaviour, Not Only Return Values

  • An implementation returning the expected rejection string but first inserting a reservation: a string-only check misses it.
  • One returning false for a negative count on the first call and throwing on the second: one isolated assertion misses it.
  • Choose checks that match real promises: state after rejection, accepted and rejected boundaries, calls through the common type.

Evidence, Not Proof

  • Direct policy checks make boundaries easy to diagnose; coordinator-level checks verify the right policy is selected and applied.
  • Passing examples are not a proof for every integer or call sequence.
  • For tiny predicates, reading the complete expression complements boundary tests; the lack of fields and external calls supports the claim that they are stateless.

5.5 When a Subtype Breaks Its Promises

LSP and unsafe inheritance


Subtyping Promises Something to Clients

  • LSP asks whether clients of a supertype can keep relying on that type’s behavioural promises when given a subtype.
  • Liskov and Wing’s treatment covers input requirements, output guarantees, invariants and history.
  • Matching Java signatures is necessary for many substitutions. It does not establish those behavioural properties.

A Practical Review Asks

  • Does the subtype require more from the caller?
  • Does it guarantee less afterwards?
  • Does it violate a valid-state property?
  • Does it change state in ways the supertype contract excluded?
  • Different implementations need not give identical results: their differences must stay within what the common contract allows.

Staff Is Not a Subclass of Student

  • Both implement the broader BorrowingPolicy contract.
  • A client promised the student’s seven-day maximum should not receive an object that approves fourteen days while still claiming to fulfil that exact student contract.
  • The reservation classes do not need an inheritance hierarchy to illustrate LSP.

A Separate Teaching Example: IntHistory

  • Contract: accept any int, append it once, preserve earlier entries in order.
  • A client is entitled to do this and expect [5, -2]:
history.add(5);
history.add(-2);

The Invalid Subtype

static final class PositiveOnlyHistory extends IntHistory {
    @Override
    public void add(int value) {
        if (value <= 0)
            throw new IllegalArgumentException("Positive values only.");
        super.add(value);
    }
}
  • It compiles. The original client now fails on -2.

The Substitution Problem

client(IntHistory h)h.add(5);h.add(-2);expects [5, -2]IntHistory[5, -2]contract keptPositiveOnlyHistoryextends IntHistoryIllegalArgumentExceptionon add(-2)

What Went Wrong

  • The subtype strengthened the input requirement.
  • Its restriction may suit a different domain object, yet it breaks this supertype’s contract.
  • Calling the exception “validation” does not repair the substitution.
  • The key evidence: a valid supertype client and the changed observable result.

Other Faulty Variants

  • Accept -2 but ignore it: avoids the exception, weakens the append guarantee.
  • Erase earlier entries whenever a new value arrives: violates preservation of history.
  • Three different failures, one identical signature void add(int).

Repair 1: Make the Type Relationship Honest

static final class PositiveHistory {
    private final IntHistory entries = new IntHistory();

    public void addPositive(int value) {
        if (value <= 0)
            throw new IllegalArgumentException("Positive values only.");
        entries.add(value);
    }
    public List<Integer> values() { return entries.values(); }
}

Why the Repair Works

  • PositiveHistory openly requires a positive value and does not advertise itself as a substitute for the broader type.
  • It may delegate storage to an internal IntHistory.
  • Composition alone is not magic: if it still claimed an interface promising acceptance of every integer, the conflict would remain.
  • It works because the public contract and the advertised type relationship now agree.

Repair 2: Redefine the Abstraction

  • Redefine the common abstraction so clients are told which values an operation may reject.
  • That changes the client contract and may require client changes.
  • It is not a behaviour-preserving fix for callers who already rely on accepting every integer.
  • Decide whether the broad contract or the restrictive type is the intended abstraction before editing.

Exceptions and History Need Context

  • Do not memorise “throwing an exception violates LSP”. An exception is compatible when the supertype contract permits that outcome for that input and state.
  • Adding a method to a subtype is not automatically safe: a subtype-specific eraseAll can contradict a promise that previously stored entries remain present.
  • In a review: state the promise, demonstrate the call sequence, inspect the outcome. “This violates LSP” is the conclusion, not a substitute.

Run the Counterexample

javac --release 17 -d after/build/lsp after/examples/lsp/LspExample.java
java -cp after/build/lsp LspExample
Base contract client passed.
Expected counterexample: Positive values only.
Separate positive-history contract passed.
  • Exit status zero means the counterexample was successfully observed, not that the invalid subtype satisfies the contract.

5.6 Small Refactorings and the Checks That Guard Them

Structural and behavioural steps kept apart


Refactoring Keeps Observable Behaviour

  • Refactoring changes internal structure while retaining observable behaviour (Fowler).
  • Stage A follows that idea: introduce the formatter, move the existing expression, replace the call, run the existing checks.
  • Avoid renaming domain concepts, changing validation order and adding staff behaviour in the same step.

A Practical Sequence (1 of 2)

  1. Run the Chapter 4 gate and record the starting result.
  2. Add the concrete formatter with the existing confirmation expression.
  3. Replace the private formatting call and remove the now-unused private method.

A Practical Sequence (2 of 2)

  1. Compile and rerun the original regression and acceptance checks.
  2. Inspect the diff for unrelated changes and record what behaviour was preserved.
  3. Begin Stage B in a separate change, with the staff rule and its new checks stated explicitly.
  • Keep one coordinator version per source directory: several classes with the same name is a setup error, not evidence about the design.

Read the Tests in Relation to the Change

  • The exact confirmation assertions protect punctuation, identifiers and date formatting.
  • Overlap, adjacency and count checks protect the reservation algorithm.
  • Snapshot checks protect observation of stored state.
  • These stay unchanged through Stage A and the compatibility part of Stage B.

What Stage B Adds

  • Staff’s accepted fifth reservation and rejected sixth.
  • Accepted fourteen-day duration and rejected fifteen-day duration.
  • Cross-category: a staff booking still blocks an overlapping student request.
  • The original overload still rejects a student’s eight-day request.
  • AQ-01 to AQ-05 are not part of this result: no availability API exists.

Diagnose a Failing Check Before Editing It

  • If the old seven-day check fails after introducing staff rules, inspect policy selection first. The old entry point was promised student behaviour. Updating the test would hide a compatibility defect.
  • If an exact confirmation check fails because the formatter says “Reserved” instead of “Confirmed”, the extraction changed a client-visible result. Restore the text, or separate and approve a wording change.
  • A test is not sacred, but its replacement needs a reason grounded in the intended contract.

Report the Evidence and Its Limits

  • Distinguish the baseline run, Stage A’s unchanged checks and Stage B’s additional checks.
  • Include the command, the revision or version, the observed output and the limitations.
  • Do not claim independent review or hosted CI when only local checks ran.
  • A clean local result supports review; it does not replace it or establish production readiness.

Stage B Code: The Overload

public String reserve(String borrowerId, String itemId, LocalDate start,
                      int requestedDays, BorrowerCategory category) {
    Objects.requireNonNull(category, "category");
    // ... same validation and counting as before ...
    if (!policyFor(category).mayRequest(activeCount, requestedDays)) {
        return "Rejected: borrower limit or duration.";
    }
    // ... same overlap check, store, then:
    return formatter.format(reservation);
}

The Demonstration

System.out.println(coordinator.reserve("S1", "CAM1", day, 14,
        BorrowerCategory.STAFF));
System.out.println(coordinator.reserve("B1", "MIC1", day, 14));
System.out.println(coordinator.reserve("B1", "CAM1", day.plusDays(1), 1));
System.out.println(coordinator.reserve("B1", "CAM1", day.plusDays(14), 1));
  • A staff fourteen-day booking succeeds; the same duration through the old entry point is rejected; an overlap with the staff booking is rejected; a request from the staff booking’s end is accepted.

Do the New Checks Detect Faults? (1 of 4)

Fault introduced into a copyResult
Staff count limit < 5 to <= 5Caught: staff sixth rejected
Staff duration <= 14 to < 14Caught: staff upper accepted boundaries
Staff duration <= 14 to <= 15Caught: staff fifteen days rejected
Staff negative-count guard removedCaught: negative count returns false

Do the New Checks Detect Faults? (2 of 4)

Fault introduced into a copyResult
Student count limit < 3 to < 5Caught: fourth rejected
Student duration <= 7 to <= 14Caught: eight-day duration rejected
Selector arms swappedCaught: fourth rejected
Old overload delegates with STAFFCaught: fourth rejected

Do the New Checks Detect Faults? (3 of 4)

Fault introduced into a copyResult
Confirmation text changed to Reserved:Caught: exact first confirmation
Confirmation drops the end dateCaught: exact first confirmation
Storing the reservation skippedCaught: one success stored
Count restricted to staff requests onlyCaught: fourth rejected

Do the New Checks Detect Faults? (4 of 4)

Fault introduced into a copyResult
Overlap check ignores the itemCaught: different item and seven-day limit allowed
Explicit Objects.requireNonNull(category, ...) removedNot caught, and not a gap
  • Thirteen of fourteen faults caught.
  • The survivor: switch (category) on null still throws NullPointerException before any state changes. Only the order against identifier validation differs, which the contract does not specify.
  • A surviving fault can mean the fault is behaviourally equivalent, not that the checks are weak.

5.7 When Is an Abstraction Too Early?

Avoiding premature abstraction


A Boundary Costs Something

  • A new interface needs a name, a contract, implementations and a place where one is selected.
  • An abstraction that hides a real variation can reduce future edits.
  • One that merely mirrors a single class can add navigation without changing which decisions are coupled.

The Cost of Each Decision (1 of 2)

DecisionBenefitCost or remaining limitation
Extract a concrete formatterIndependent presentation changes get a homeAnother object and file; no configurable presentation yet
Use a small policy interfaceNames the count/duration contract shared by two rulesExtra types and selection; numeric parameterisation is also possible
Keep a selector in the coordinatorIntermediate stage is easy to inspectNew categories still require a coordinator edit

The Cost of Each Decision (2 of 2)

DecisionBenefitCost or remaining limitation
Retain direct constructionKeeps focus on SRP, OCP and contractsDependency assembly stays coupled until Chapter 6
Avoid coordinator inheritancePrevents duplicating or overriding the workflowVariation must be expressed through explicit collaborators
  • A reasonable review can conclude that a cost outweighs its benefit for a particular project. Principles make the judgement explainable; they do not remove it.

Warning Signs Without a Smell Catalogue

  • Repeated edits to the same policy conditional.
  • Message changes breaking reservation logic.
  • Subclasses that disable inherited operations.
  • Investigate the change history or construct a client counterexample before prescribing a repair.
  • A long method alone does not tell you which boundary is needed.

Do Not Create IReservation Because a Tool Said “SOLID”

  • No IReservation, ILoanPeriod, abstract coordinator factory or generic notification bus simply because an automated suggestion calls them SOLID.
  • Ask: what concrete variation does each support, and what client contract does it improve?
  • A missing answer is a reason to postpone that abstraction.
  • SRP, OCP, LSP, ISP and DIP are not interchangeable names for adding interfaces.

5.8 Reviewing a “Make This SOLID” Suggestion with AI

Verdicts with concrete reasons


Ground Rules for the Activity (1 of 2)

For this activity
Your goalJudge a “make this SOLID” proposal against concrete requirements, contracts and costs
You start withThe Chapter 4 source, the staff-rule table in 5.1 and the policy contract in 5.4
What is in boundsReview the proposal; do not apply it wholesale

Ground Rules for the Activity (2 of 2)

For this activity
How to checkCompilation (is ReservationRules final?), the exact-output checks, the rejection and state checks
How to decideA verdict with a concrete defect or cost for each proposal, then the smallest defensible two-stage change
No AI account?The ready-made example is enough

The Ready-Made Example (1 of 2)

Author-written fictional AI output, not an actual model transcript.

Create an interface for every class, including immutable reservation and period values. Add a coordinator factory and an abstract base coordinator. Replace all rejection strings with exceptions because exceptions are cleaner.


The Ready-Made Example (2 of 2)

Make StaffReservationRules extend ReservationRules and override the seven-day limit. Remove all category conditionals so no existing file ever changes. While extracting the formatter, shorten the confirmation to “Reserved”. The green build proves the design follows SOLID.

  • Identify one proposal that would not compile, one that changes existing behaviour, one whose benefit has not been demonstrated.

Model Review (1 of 3)

Proposed changeVerdictConcrete reason
An interface for every classNot justified as statedNo second role or client need for immutable values; adds navigation
Coordinator factory and abstract baseDeferTwo rules do not require a factory hierarchy
Replace rejection strings with exceptionsSeparate feature/API proposalExisting callers and acceptance examples observe the current results

Model Review (2 of 3)

Proposed changeVerdictConcrete reason
Staff rules extend student rulesRejectReservationRules is final; and staff approval is incompatible with the exact student predicate
Remove all conditionals so no file ever changesReject the promiseAn implementation must still be selected somewhere

Model Review (3 of 3)

Proposed changeVerdictConcrete reason
Shorten the confirmation to “Reserved”Reject during Stage AThe exact output changes: no longer the promised extraction
Green build proves SOLIDRejectChecks examine limited behaviour; responsibility claims need reasoning
  • Minimal corrected plan: baseline; extract the unchanged formatter; verify Stage A; state the staff rule; small policy contract and explicit category path; preserve the old default; run old and new checks; inspect remaining direct dependencies.

A Good AI Review Record

  • The candidate claim.
  • The accepted, rejected or deferred verdict.
  • The reason and the evidence: the final declaration, exact confirmation assertions, rejection checks, the selector’s source, the source of the category argument.
  • Do not reject the whole output merely because it contains many classes. Identify which boundary lacks a purpose or which behaviour changes.

5.9 Design Habits That Look Principled

Ten to avoid


Mistakes 1 to 3

MistakeCounterexample or consequenceBetter reasoning
SRP means one method per classA one-method service can still mix policy and presentationIdentify independent change pressures
OCP forbids editing existing codeA new category still needs an explicit selection decisionState the protected axis and the remaining edit boundary
Same signature means safe subtypePositiveOnlyHistory.add(-2) rejects valid base inputEvaluate the behavioural contract

Mistakes 4 to 6

MistakeCounterexample or consequenceBetter reasoning
Any different policy result violates LSPStudent and staff intentionally have different limitsCompare with the shared contract
Better wording is part of a refactoring“Reserved” breaks the preserved resultSeparate the presentation feature from extraction
A passed LSP demo validates the subtypeThe harness may have successfully detected a violationState what the demonstration observed

Mistakes 7 and 8

MistakeCounterexample or consequenceBetter reasoning
A category parameter verifies authorityA caller can supply STAFF without proving identityLabel the trusted-example boundary
More interfaces mean better designUnused variations add navigation and decisionsRelate every boundary to a concrete need

Mistakes 9 and 10

MistakeCounterexample or consequenceBetter reasoning
Switch on every analyser ruleDozens of low-value findings hide the one that mattersChoose a small rule set for a stated purpose and record why
Read an unchanged finding as a failed refactoringA tool measures one thing, such as branchingSay what the tool measures and what the change targeted

5.10 Try It Yourself

A protected design change


What You Start With, and the Demonstration

  • You start with: Chapter 4’s source and checks, the staff-rule table and the companion listings. Java 17. Keep the LSP micro-example separate from the application.
  • Steps 1 to 7: the book’s project. Step 8: your team’s project.
  • Demonstration: trace the existing operation and identify three independent change pressures. Run one valid base-type client against both IntHistory and the invalid subtype and explain the difference.
  • Guided practice: perform Stage A, reviewing the formatter’s input, output and lack of reservation authority.

Independent Work: Steps 1 to 3

  1. A responsibility table for the coordinator, rules, period and proposed formatter; a concern you deliberately leave unchanged. Run javac -Xlint:all, the manual smell review and PMD, and place each finding in one of three groups.
  2. Record a passing baseline, extract the formatter, rerun the original checks without altering their expected behaviour.
  3. Add the explicit staff variation while preserving the student overload. Document the policy contract and the source of the category argument.

Independent Work: Steps 4 to 6

  1. Add or use the boundary and cross-category checks. Explain what each group establishes.
  2. Run the separate LSP example. Identify the violated promise and compare the composition-based repair with changing the base contract.
  3. Review the complete diff with another person where available. Retain actual findings and corrections; label solo review honestly.

Independent Work: Steps 7 and 8

  1. Write a short rationale connecting each new class or interface to a change pressure and naming the remaining dependency-selection limitation. Say what PMD reported after Stage A and after Stage B, and why.
  2. On your team’s project: the same kind of responsibility table for one class in your starter codebase; run the compiler, manual and PMD checks on it; name one change pressure you can point to in its backlog or history.

Expected Output and Completion

  • Output: a smell and responsibility diagnosis with the tool findings compared; the step 8 table; before, refactored and extended versions; check results; a contract table; a subtype counterexample explanation; a concise design rationale.
  • Complete when: Stage A preserves behaviour; Stage B implements the staff boundaries and keeps student compatibility; rejections leave records unchanged; you can explain why the policy interface helps and why the selector still changes for another category.
  • The rationale must distinguish a rule invented for this book from an approved institutional rule.

If You Want More

  • In a scratch copy of Stage B, extract two private methods from reserve (one counts a borrower’s reservations, one tests whether an item is unavailable). Run the gate, rerun PMD and explain what changed and what did not.
  • Then implement the two numeric policies using a validated limits object in a separate experiment. Compare files changed, clarity and likely future variation.
  • Do not merge both alternative designs into the running application merely to demonstrate them.

Model Responsibility Diagnosis (1 of 2)

ElementPresent responsibilityDecision in this chapter
ReservationCoordinatorSequence validation, eligibility, conflict detection and storagePreserve sequencing; delegate formatting and policy evaluation
ReservationRulesStudent count/duration decisionPreserve predicate; implement the shared policy contract
LoanPeriodValid interval and overlap semanticsLeave unchanged

Model Responsibility Diagnosis (2 of 2)

ElementPresent responsibilityDecision in this chapter
ReservationImmutable accepted-record detailsLeave unchanged
Confirmation expressionRender an accepted record as textExtract a concrete formatter
Staff eligibilityNew count/duration variationAdd a separate policy implementation
  • The original coordinator already delegated numeric rules. Claiming this chapter first extracts them would misdescribe the starting point.

Model Manual Smell Review (1 of 2)

SmellWhere it appears in reserveAct now?
More than one reason to changeOne method validates, counts, applies eligibility, searches for a conflict, creates and stores the record, and produces wordingYes for wording (A) and eligibility (B); no for storage
Hidden constructionprivate final ReservationRules rules = new ReservationRules();Only as far as Stage B needs; full choice is Chapter 6

Model Manual Smell Review (2 of 2)

SmellWhere it appears in reserveAct now?
Repeated or similar logicTwo loops differ only in the test; rejections are two plain stringsNo: two short loops are clearer than a new abstraction
Primitives standing for conceptsIdentifiers and days are strings and an integer; rejection reasons are free textNo: record it as a limitation
  • -Xlint:all showed none of these. Reading alone did not settle whether the repeated loops justify an abstraction: a correct answer may reasonably leave them.

Model PMD Comparison

SourceFindingGroup
PMD CyclomaticComplexityreserve has complexity 12 (threshold 10)Useful
PMD DataClassReservation is a suspected data classLow-value here
Manual readingnew ReservationRules() inside the coordinatorMissed by the tool
Manual readingOne string for two different reasonsMissed by the tool
Manual readingTwo similar loopsNot reported at default thresholds; leave them

Rule Sets Compared on the Same Five Files

PMD rule setFindings on beforeWhat they were
Lab set (eight design rules)2The two above
rulesets/java/quickstart.xml5All NoPackage
category/java/codestyle.xml34MethodArgumentCouldBeFinal, LocalVariableCouldBeFinal, NoPackage, OnlyOneReturn and similar
category/java/bestpractices.xml4SystemPrintln in the demonstration class
  • This is why the lab uses a small set.

Suggested Evidence

EvidenceWhat to record
Starting pointActual version and passing original gate
Stage ARelevant diff; original checks with unchanged expectations
Stage BNew rule table; policy contract; compatibility and staff-boundary results
LSP exampleValid base call sequence; subtype outcome; precise violated promise
ReviewActual reviewer or labelled self-review; findings and corrections
  • Do not invent a peer review. A supplied model rationale is not evidence that someone inspected your work.

Limits Object vs. Separate Classes

Future changeSeparate policy classesConfigured numeric limits
Change staff days from 14Edit the staff predicateEdit the staff configuration
Add a third pair of simple limitsAdd another policySupply another configuration
A qualitatively different eligibility ruleA new implementation can express itMay need a broader design
  • No approach wins merely by having fewer files. Constructor validation and request-input behaviour are different contracts.

5.11 Questions to Think with

Concept and diagnosis


Concept Questions

  1. Why is counting methods an unreliable way to apply SRP? Give one example from the running project.
  2. Against which variation is the policy design relatively protected, and which selection code remains open to change?
  3. How can two implementations return different booleans and still satisfy the same policy interface contract?
  4. Why is extracting the formatter a refactoring while introducing staff limits is a feature change?

Diagnosis Questions

  1. A policy implements the right Java signature but throws for a negative active count and increments an internal request counter on every call. Compare it with this chapter’s policy contract. What evidence would expose each problem?
  2. A developer subclasses a history that promises to append every integer. The subclass silently ignores negative numbers rather than throwing. Explain why avoiding an exception does not make the subtype safe, and propose an honest repair.

Guidance: Questions 1 and 2

  1. LoanPeriod has construction, accessors and overlap supporting one interval abstraction. A single process method could contain borrowing rules, persistence and formatting. Count does not identify independent change pressures.
  2. The algorithm delegates count and duration decisions, so changing staff limits need not alter overlap or storage. The selector and directly constructed policy fields remain in the coordinator: adding a category means updating that selection.

Guidance: Questions 3 and 4

  1. The common contract defines the kind of question, valid input handling, determinism and side-effect limits. It allows implementation-specific documented limits. A client assuming seven days for every implementation has assumed more than the interface promises.
  2. Stage A changes where a fixed result is produced while preserving outputs, state effects and failure behaviour. Stage B introduces successful requests that were not available before.

Guidance: Questions 5 and 6

  1. Throwing changes an observable outcome and can break a client that branches on the boolean: pass (-1, 1) through a BorrowingPolicy reference and assert false. The counter conflicts with the no-counter-change contract; equal results alone do not prove no counter was incremented, so inspect the implementation.
  2. After add(5); add(-2); a result of [5] weakens the append postcondition though nothing was thrown. Compare the complete resulting sequence. Retain broad acceptance, or expose a separate narrower type whose contract restricts input.

5.12 A Different Setting

The transfer challenge


Transfer Challenge

A coursework feedback tool accepts a score and produces feedback text. A new requirement adds a second grading policy and a different feedback layout. Its current class validates input, selects a grade and formats the message.

  • Write the change pressures, one proposed responsibility boundary and a contract for the grading decision.
  • Give a small input/output table for two invented policies.
  • State what remains stable and where a policy is selected.
  • Construct a subtype counterexample and explain the repair.
  • Keep the rules illustrative, not university assessment policy.

Transfer Guidance: Policies

  • Pressures: the score input domain, the policy mapping a valid score to a grade, and presentation.
  • Illustrative GradePolicy.grade(int score): scores 0 to 100 return PASS or REVIEW; others raise IllegalArgumentException. Deterministic, no I/O.
ScorePolicy A: pass at 50Policy B: pass at 60
49REVIEWREVIEW
50PASSREVIEW
60PASSPASS
-1 or 101exceptionexception

Transfer Guidance: Counterexample and Repair

  • A client must not assume every policy passes 50.
  • The formatter receives the score and computed grade and does not recompute the grade.
  • Counterexample: a subtype that throws for a valid score of zero (“not submitted”), or returns null though a grade is promised.
  • Repair: keep zero valid and model submission status separately, or expose a narrower operation that does not claim to be a substitute.
  • Protect the existing message with unchanged examples; add boundary examples around both thresholds.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • Responsibility boundaries are most useful when they respond to distinct change pressures.
  • Extension points should protect a named variation.
  • Subtyping must preserve the promises clients of the broader type can rely on.
  • Small, separately verified steps make those design claims easier to assess.

Where the Project Stands

  • A concrete confirmation formatter.
  • Student and staff count-and-duration policies, with the original student entry point preserved.
  • The subtype example remains separate; the availability enquiry remains planned.

Next: Chapter 6

  • Client-specific interfaces.
  • Dependency direction.
  • Manual object assembly: moving the two “creates” arrows out of the coordinator.

Sources and Further Reading

  1. Martin: The Single Responsibility Principle and The Open Closed Principle, 2014.
  2. Oracle: Java Language Specification, Java SE 17, Chapter 9: Interfaces.
  3. Liskov and Wing: A Behavioral Notion of Subtyping, 1994.
  4. Fowler: Definition of Refactoring, 2004; Refactoring, 2nd ed., 2018.
  5. Martin: Agile Software Development, 2002.
  6. PMD: installation and Java design rules, 7.28.0.
  • Full references and links are in the chapter text.

6 - Chapter 6: Interfaces, Dependencies, and Composition

Object Oriented Development and Agile Practices (SE506)

Chapter 6: Interfaces, Dependencies, and Composition

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 6.1 Why dependencies make change hard
    • A real boundary: notification output, first built the coupled way.
  • 6.2 Designing an interface around what the client needs
    • Design the interface around what a particular client needs.
  • 6.3 Who decides what, and what happens when notification fails
    • Three decisions, and the partial-success case made explicit.
  • 6.4 Who owns the abstraction? Dependency inversion
    • Source dependency direction; DI can exist without DIP.

Chapter Roadmap (2 of 3)

  • 6.5 Handing collaborators in: injection and the composition root
    • Two views of one design: classes and objects.
  • 6.6 Inversion of control and containers: what do they add?
    • An educational container makes lifetime visible.
  • 6.7 What abstraction costs
  • 6.8 Reviewing an interface that hides very little, with AI

Chapter Roadmap (3 of 3)

  • 6.9 Dependency habits that mislead
  • 6.10 Try it yourself: assemble a replaceable output
    • Nine steps; step 9 is on your team’s project.
  • 6.11 Questions to think with, 6.12 A different setting: the transfer challenge
  • 6.13 Revising for Quiz 1: where to look
  • What to take away, and what comes next
    • Chapter 7: test design, controlled substitutes, red-green-refactor.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Design an interface around what a particular client needs.
  2. Explain ISP through a concrete dependency that is unnecessarily broad.
  3. Distinguish DIP, dependency injection and inversion of control.

Learning Outcomes (2 of 2)

  1. Construct a small object graph using constructor injection and an explicit composition root.
  2. Explain the benefits and costs of an interface, including the limits of what it guarantees.

Where This Chapter Fits and Where You Start

  • Supports LO1 and LO4. Quiz 1 revision points appear later and refer to Chapters 2 to 5, matching its Weeks 1 to 4 coverage. This chapter’s material is not silently added to that coverage.
  • You start with: Chapter 5’s Java 17 code: student and illustrative staff policies, extracted formatter, the original 34 checks, the five acceptance groups and the 44 Chapter 5 checks.
  • Formatting a string has never meant sending email. Notification output is a new, explicitly bounded teaching feature.

How to Use This Chapter

  • Core reading: Sections 6.1 to 6.6. Section 6.6 compares manual assembly with a small educational container.
  • What you must do: steps 1 to 9 of Section 6.10, including step 7 (the container walkthrough). Steps 1 to 8: the book’s project. Step 9: your team’s project.
  • If you want more: the AI activity, the extension in 6.10, the questions and the transfer challenge.
  • Readings: the DIP and ISP chapters of Martin, and Fowler’s article on dependency injection.

Opening Scenario: A Console Sender Inside the Coordinator

  • The coordinator now uses separate borrowing policies and a confirmation formatter.
  • A new requirement: pass each accepted reservation to a notification output.
  • One member creates a console sender inside the coordinator. It works.
  • But another member cannot inspect notifications without watching console output.
  • Replacing the output requires editing the reservation workflow.

An AI Proposes an Interface

  • The coordinator’s field becomes NotificationSender.
  • But its constructor still creates a particular sender and reads provider settings.
  • The diagram looks different. The code still chooses its own environment.
  • Naming an abstraction is not the same as depending on a useful contract or being supplied a collaborator from outside.

What the Chapter Introduces

  • The Interface Segregation Principle (ISP).
  • The Dependency Inversion Principle (DIP).
  • Constructor injection and a composition root to assemble the running application.
  • A small container walkthrough that makes object lifetime visible, with no framework installation.

6.1 Why Dependencies Make Change Hard

Begin with a real boundary


What a Dependency Is

  • A dependency exists when one part of the program needs knowledge or behaviour from another.
  • Dependencies are necessary: the coordinator needs dates, reservations and borrowing decisions.
  • The design problem: which knowledge should remain stable, and where should changing details live?

What Chapter 5 Left Behind

  • Two visible decisions inside the coordinator: construction of concrete policies and selection of a policy by category.
  • The formatter was also constructed there.
  • A deliberate intermediate version.
  • Now: move assembly out of the request workflow while introducing a notification boundary.

The New Requirement

After storing an accepted reservation, attempt one notification through the configured output. Rejected or invalid requests produce no notification. The configured output may record the accepted reservation in memory or print a notice to a supplied stream.

  • Local teaching outputs: not an email service, durable queue or proof of receipt.
  • A normal return means the output completed its own local operation, not remote delivery.

The Deliberately Coupled Version

private final NotificationSender notifications =
        new ConsoleNotificationSender(System.out, new ConfirmationFormatter());
  • The field has an interface type, but the coordinator still:
    • names the console implementation,
    • chooses the global output stream,
    • constructs its formatting dependency.
  • The interface alone has not separated configuration from use.

Feature Change, Then Structural Change

  • This intermediate version is newly introduced teaching code, not a description of Chapter 5.
  • Adding notification is a feature change.
  • Replacing direct construction with an injected collaborator is a subsequent structural change, with an explicit constructor migration.
  • The concrete pressure: one caller wants console output; another wants to inspect accepted records in memory. The same algorithm should serve both.

Decisions That Should Remain in the Coordinator

  • The coordinator still decides when a reservation has been accepted, and so when notification is eligible.
  • Moving that decision into the sender would duplicate policy and conflict handling.
  • The sender decides how its local output handles the accepted record, without granting or rejecting anything.

A Reviewable Dependency Diagnosis

  • “Remove all dependencies” is not a useful goal.
  • “Remove the coordinator’s knowledge of which notification provider to construct, while keeping its acceptance sequencing” is reviewable.
  • A good diagnosis lists both the unwanted choice and the decision that belongs in the client.
  • Output failure needs an application-level answer too: injection does not settle it.

6.2 Designing an Interface Around What the Client Needs

Design for the caller’s role


ISP

  • ISP directs attention to clients forced to depend on operations they do not need.
  • The needed role here is small: accept an already confirmed reservation for notification output.
public interface NotificationSender {
    void send(Reservation reservation);
}

A Broad Interface to Avoid

  • An all-purpose EquipmentPlatform interface with:
    • send,
    • registerBorrower,
    • exportInventory,
    • deleteAccount,
    • configureMailServer,
    • rotateCredentials.
  • The coordinator uses only the first. The rest expose unrelated responsibilities and encourage implementations that supply meaningless methods or throw unsupported-operation exceptions.

What a Narrow Role Gives

  • A console output and an in-memory recorder implement what they genuinely support.
  • The role does not need every capability of a future provider.
  • An administration tool may need a different contract, if that requirement actually exists.
  • Do not create an administration interface until an administration client exists.

Small Is Not the Same as One Method

  • The notification contract happens to have one method. ISP does not prescribe one-method interfaces.
  • A client that genuinely needs several cohesive operations may benefit from a role containing all of them.
  • Example: a document-writing client needing both writing and closing a session. Splitting them only to cut method count could allow a write without the required lifecycle management.

Two Views of One Object

  • received() is an inspection operation on the concrete recorder.
  • The coordinator does not need it, so it is not on NotificationSender.
  • A check can keep a reference to the recorder while supplying the same object through the narrow sender role.
  • A deliberate use of two views of one object.

Specify Outcomes and State Effects

  • The sender contract: a non-null accepted reservation; it must not modify that record or the coordinator; expected output failures use NotificationException.
  • The recorder appends the immutable record to its own list. The console prints a notice to its supplied stream.
  • Both have output effects, so this is not a pure policy operation. Chapter 5’s BorrowingPolicy remains side-effect-free under its contract.
  • Do not apply one collaborator’s restrictions blindly to another.

Why the Sender Receives a Reservation

  • This keeps the notification tied to the accepted record, not raw identifiers and dates assembled afresh.
  • It means the boundary deliberately knows that small domain type.
  • If an external transport later needs a different representation, an adapter can translate it. That need does not justify a transport-specific payload now.

Do Not Mirror a Provider SDK

  • A provider SDK might expose account management, diagnostics and bulk operations.
  • A client-specific interface should express the application’s needs, not the SDK.
  • Review question: can the contract be explained without referring to a vendor or transport setting?
    • “Send this accepted reservation to the configured output”: yes.
    • “Send this vendor request with the provider’s retry token”: no.

The Sender Code

@FunctionalInterface
public interface NotificationSender {
    void send(Reservation reservation);
}

public final class NotificationException extends RuntimeException {
    public NotificationException(String message) { super(message); }
}
  • A normal return is not a promise of email delivery.

The Recorder

public final class RecordingNotificationSender implements NotificationSender {
    private final List<Reservation> received = new ArrayList<>();

    @Override
    public void send(Reservation reservation) {
        received.add(Objects.requireNonNull(reservation, "reservation"));
    }

    public List<Reservation> received() { return List.copyOf(received); }
}

The Console Sender

public void send(Reservation reservation) {
    output.println("Notice: " + formatter.format(reservation));
    if (output.checkError()) {
        throw new NotificationException("Console output failed.");
    }
}
  • PrintStream normally records I/O errors rather than throwing, so the sender checks checkError() and translates the expected local error.
  • The error state is sticky: once reported, later notices through the same sender are reported as failed too.

6.3 Who Decides What, and What Happens When Notification Fails

Three decisions, and partial success


Separate Three Decisions

DecisionOwner in this versionReason
Whether the request is acceptableCoordinator and selected borrowing policyDepends on reservation rules and current records
How notification is represented locallySender implementation and formatterDepends on the chosen output
Which concrete objects are usedApplication composition rootDepends on the demonstration environment

Moving Assembly Does Not Remove It

  • The first decision should not depend on console configuration.
  • The last decision must exist somewhere: a running program needs concrete objects.
  • Moving assembly to the boundary makes it explicit rather than pretending it disappears.
  • “Policy” has levels: a numeric borrowing policy decides limits; the coordinator implements the broader application policy (store before notifying; handle expected failure).

The Partial-Success Case

  • The completed operation stores the reservation, formats the confirmation and calls the sender.
  • On an expected NotificationException it returns the confirmation followed by:
 Notification failed; reservation retained.
  • The leading space joins the suffix to the confirmation.
  • The record remains stored and counts as active. It is not deleted, not reported as rejected, and not retried.

The Failure Outcomes

Request accepted:checks passedStore therecordFormat theconfirmationnotifications.send(reservation)Returns normallythe confirmationNotificationExceptionconfirmation + suffixrecord retained, no retryAny other exceptionpropagates; the recordis already storedone attempt per accepted request

Why the Rule Matters

  • An author-chosen rule invented for this book. Another application might choose differently, but must make storage and communication consequences explicit.
  • A result that just says “failed” can encourage callers to repeat a reservation request that already succeeded.
  • Repeating it here normally gives an overlap rejection. It is not a notification retry.
  • There is no queue or worker: do not call a failed notification “pending”.

Invocation Is Not Delivery

  • The coordinator attempts the sender once per successful reservation call: a local control-flow fact, not an exactly-once delivery guarantee.
  • A future remote sender might deliver and then fail before confirming. Retrying could duplicate the delivery.
  • Even the console may partially write before reporting an error.

What the Coordinator Catches

  • It catches that specific exception.
  • Unexpected programming failures are not converted into apparent success.
  • Such failures may still occur after storage. The example does not claim transactional atomicity across formatting, storage and output.
  • A richer result model and reliable delivery design belong to later integration work.

Acceptance Examples Before Refactoring (1 of 2)

ExampleReservation resultNotification
Eligible available requestExact confirmation; one record storedOne accepted record supplied to the sender
Same-item overlapExisting rejection; records unchangedNo call
Invalid durationExisting rejection; records unchangedNo call

Acceptance Examples Before Refactoring (2 of 2)

ExampleReservation resultNotification
Invalid identifierExisting exception; records unchangedNo call
Expected output failure after acceptanceConfirmation plus failure suffix; record retainedOne attempt; no automatic retry
Repeat of the accepted requestOverlap rejectionNo second attempt
  • These constrain the feature whether assembly is manual or container-based.

6.4 Who Owns the Abstraction?

Dependency inversion


DIP: Source Dependency Direction

  • DIP separates higher-level policy from changing implementation details through abstractions.
  • The coordinator names NotificationSender, an application-owned role. Concrete outputs implement it.
  • The contract does not import a provider’s SDK type or inherit its configuration vocabulary.
  • At runtime the coordinator still calls a concrete object. DIP does not reverse that call: it changes what the source needs to know and which side defines the abstraction.

Figure 6.1: Source-Level Dependencies

ReservationApplicationReservationCoordinator«interface»NotificationSender+send(Reservation reservation) voidConsoleNotificationSenderRecordingNotificationSendercreatescreatesnotifiesEvery “creates” arrowstarts at the application.The policy map and theformatter are built there too.

Reading Figure 6.1

  • Solid arrows are associations: the coordinator holds a NotificationSender, a ConfirmationFormatter and a Map<BorrowerCategory, BorrowingPolicy>.
  • Dashed lines with a hollow triangle are realisations.
  • Dashed arrows labelled “creates” show which class’s source names which concrete class.
  • The recorder is an alternative implementation, not an additional mandatory destination: the application creates only the console sender.

Compare With Figure 5.1

  • There, two “creates” arrows ran from the coordinator to the concrete policy classes.
  • Here, every “creates” arrow starts at ReservationApplication.
  • The coordinator’s outgoing arrows end at roles, apart from the concrete ConfirmationFormatter, which stays concrete on purpose.
  • The change in arrow direction is visible evidence that its source no longer chooses its environment.

Source Knowledge, Not Time Order

  • The arrows show source-level knowledge, not time order.
  • The composition root knows the concrete console implementation because choosing it is the root’s job.
  • The files still use the unnamed Java package. Contract ownership is a design decision, not a build-enforced boundary. Chapter 10 organises larger application boundaries.

DI Can Exist Without DIP

  • The coordinator receives ConsoleNotificationSender through its constructor: that is dependency injection. Someone else supplies the collaborator.
  • It still depends on the concrete provider type: the boundary is not yet inverted.
  • Conversely, a field typed NotificationSender assigned new ConsoleNotificationSender(...) inside the coordinator uses an abstraction while keeping concrete construction knowledge. This is the intermediate defect.

Two Different Questions

  • What type of behaviour does the client require? (the role: DIP, ISP)
  • Who supplies the object? (injection)
  • The final design uses the small application contract and passes the implementation from the application root.
  • They support each other but answer different questions.

A Preview: An Outside Data Source Behind a Contract

/** Empty means absent or inactive, never a provider outage. */
@FunctionalInterface
public interface BorrowerDirectory {
    Optional<Borrower> find(String borrowerId);
}
  • Chapter 8 builds this equipment-directory contract. Not part of this version.
  • Empty means “no such active borrower”; an outage is a different outcome (an exception); the contract uses the application’s own types.

Not Every Concrete Dependency Is Harmful

  • The coordinator still constructs accepted Reservation and LoanPeriod values and uses an in-memory list. Creating a small domain value differs from selecting an environmental output provider.
  • The concrete ConfirmationFormatter is supplied in the constructor, but has no interface: a substitutable formatting role has not been required.
  • Using Java’s Map interface does not itself demonstrate DIP. The decision is that policy mappings are assembled outside the coordinator.

6.5 Handing Collaborators in

Injection and the composition root


The Completed Constructor

public ReservationCoordinator(
        Map<BorrowerCategory, BorrowingPolicy> policies,
        ConfirmationFormatter formatter,
        NotificationSender notifications) {
    this.policies = Map.copyOf(Objects.requireNonNull(policies, "policies"));
    for (BorrowerCategory category : BorrowerCategory.values()) {
        if (!this.policies.containsKey(category)) {
            throw new IllegalArgumentException("Missing policy: " + category);
        }
    }
    this.formatter = Objects.requireNonNull(formatter, "formatter");
    this.notifications = Objects.requireNonNull(notifications, "notifications");
}

What the Constructor Checks

  • Null values, and an entry for every current borrower category.
  • Map.copyOf produces an unmodifiable map and rejects null keys or values. A caller cannot change the coordinator’s mapping by editing the original map later.
  • The copy is shallow: it does not clone the policy objects. The supplied policies are stateless; a mutable or dishonest policy would not be made safe.
  • Missing configuration fails at construction, not halfway through a request.

The Request, Now With Notification

reservations.add(reservation);
String confirmation = formatter.format(reservation);
try {
    notifications.send(reservation);
} catch (NotificationException failure) {
    return confirmation + " Notification failed; reservation retained.";
}
return confirmation;
  • policies.get(category) replaces the former category switch.

Assemble Once at the Boundary

public static ReservationCoordinator create(NotificationSender sender) {
    return new ReservationCoordinator(
            Map.of(BorrowerCategory.STUDENT, new ReservationRules(),
                   BorrowerCategory.STAFF, new StaffReservationRules()),
            new ConfirmationFormatter(), sender);
}
  • main chooses a console sender with a supplied stream and starts the demonstration.
  • A check chooses a recorder instead.

A Check Chooses a Recorder

RecordingNotificationSender inbox = new RecordingNotificationSender();
ReservationCoordinator coordinator = ReservationApplication.create(inbox);
  • The check can later inspect inbox.received() because it retained the concrete reference.
  • The coordinator only sees the sender role.
  • Replaceability without a mocking library. Chapter 7 develops the vocabulary of test doubles.

Figure 6.2: The Objects Built at Run Time

Applicationentry pointStudent and staffpolicy mapConsole senderinstanceConfirmationformatterCoordinatorinstancecreatescreatespassed to theconstructor

Two Views, Not One

  • Figure 6.1 says which classes know about which. Figure 6.2 says which objects were wired to which.
  • The sender also receives its own formatter and output stream, left out for readability.
  • Neither shows the order of calls: the coordinator checks eligibility and conflicts, stores, formats, then calls the sender already supplied.
  • Do not read event order from either diagram.

The Composition Root

  • A composition root is the small application-entry area where concrete collaborators are assembled.
  • It may call helper factory methods. They do not become a service locator merely because they are static.
  • What matters: the coordinator is passed its dependencies and does not call back into a global registry to find them.

The Constructor Migration, Honestly Stated

  • The no-argument coordinator constructor is removed. Callers must supply dependencies, normally through ReservationApplication.create.
  • The completed source is not a drop-in constructor-compatible replacement for Chapter 5.
  • Existing checks and demos are migrated only at their construction expressions; their reservation assertions stay the same.
  • Claiming every old source line is unchanged would be inaccurate.

What Stays the Same

  • The four-argument reserve still means student borrowing; the explicit category overload still uses the trusted category supplied.
  • Student and staff limits, date boundaries, count behaviour and rejection messages remain as before on ordinary paths.
  • The new notification side effect and failure suffix are separate feature behaviour.

Constructor Injection vs. Later Setters

  • Required dependencies fit constructor injection: an object cannot be used before they are supplied.
  • With later setters, the program may contain an unusable coordinator, and every operation may need to ask whether configuration has happened.
  • Setters are not universally wrong: optional or intentionally replaceable runtime collaborators have a different lifecycle. This coordinator has no requirement to swap its sender while processing.

A Long Constructor

  • A very long constructor can expose a class with too many responsibilities, but parameter count alone does not prove it.
  • Do not hide ten unrelated services inside a generic context object to shorten the signature.
  • The dependencies would still exist, with less visibility.

6.6 Inversion of Control and Containers

What do they add?


Distinguish the Terms

  • Dependency injection: supplying collaborators from outside an object.
  • Inversion of control: another component controls some aspect of execution or assembly the object would otherwise control.
  • DI container: a tool that uses registration or configuration to assemble and often manage objects.
  • The manual coordinator already uses DI. A container automates repeated assembly; it cannot decide whether the contract or failure policy is right.

Terms and the Question Each Answers (1 of 2)

TermQuestion answered in this example
DIPDoes the coordinator depend on the application’s notification role rather than a provider detail?
DIWho supplies the sender, formatter and policy mapping?
IoCWhich component controls assembly or later calls into application code?

Terms and the Question Each Answers (2 of 2)

TermQuestion answered in this example
DI containerWhat mechanism resolves registrations and manages selected object lifetimes?
Service locatorDoes application code fetch collaborators from a registry rather than receive them explicitly?

The Educational Container

  • DemoContainer, in ContainerWalkthrough.java: isolated from the running application, single-threaded.
  • It registers a provider function for a type and lazily creates one instance of each registered type.
  • It caches those instances, reports a missing binding and detects a construction cycle.
  • ReservationCoordinator has no container import, field or lookup call.

The Container

static final class DemoContainer {
    private final Map<Class<?>, Supplier<?>> providers = new HashMap<>();
    private final Map<Class<?>, Object> instances = new HashMap<>();
    private final Set<Class<?>> constructing = new HashSet<>();

    public <T> void bind(Class<T> type, Supplier<? extends T> provider) { /* ... */ }
    public <T> T get(Class<T> type) {
        if (instances.containsKey(type)) return type.cast(instances.get(type));
        Supplier<?> provider = providers.get(type);
        if (provider == null)
            throw new IllegalArgumentException("Missing binding: " + type.getSimpleName());
        if (!constructing.add(type))
            throw new IllegalStateException("Dependency cycle: " + type.getSimpleName());
        /* run the provider, cache, return */
    }
}

The Registrations

container.bind(ConfirmationFormatter.class, ConfirmationFormatter::new);
container.bind(NotificationSender.class, RecordingNotificationSender::new);
container.bind(ReservationCoordinator.class, () -> new ReservationCoordinator(
        Map.of(BorrowerCategory.STUDENT, new ReservationRules(),
               BorrowerCategory.STAFF, new StaffReservationRules()),
        container.get(ConfirmationFormatter.class),
        container.get(NotificationSender.class)));
  • The provider calls the same ordinary constructor. Only configuration code uses the container.

Trace the First Resolution

  1. Registration stores a provider without running it.
  2. The first coordinator resolution runs its provider, which requests the formatter and sender.
  3. Those providers run; their instances are passed to the constructor.
  4. The coordinator is cached.
  5. A second resolution returns the same coordinator: the reservation stored through the first reference is visible through the second.

Lifetime Is a Design Decision

  • Sharing the coordinator means sharing its in-memory reservation list.
  • A container does not make that list thread-safe.
  • A fresh coordinator per request would discard earlier reservations unless storage were shared elsewhere.
  • Neither lifetime is automatically correct for a future server application.

Configuration Failures

Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Repeated resolution shares the same coordinator and one reservation.
Missing binding: NotificationSender
Dependency cycle: NotificationSender
  • A missing registration and a cycle fail with identifiable configuration errors, not business-rule rejections.
  • Zero exit status means the walkthrough observed the intended cases.

The Container’s Limits Are Deliberate

  • No reflection, annotation scanning, concurrent resolution, scopes beyond one cached instance per type, or resource cleanup.
  • Do not extend it into a home-grown production framework.
  • Its purpose: expose registration, resolution and lifetime in ordinary Java.
  • Real frameworks (such as Spring’s IoC container) supply much more machinery. The manual graph gives you a basis for reading such configuration.

Whichever Container You Use

  • Identify its registrations, supplied constructor arguments and instance lifetimes before adding annotations.
  • The library or framework name should not replace an explanation of the object graph.
  • A library container arrives with libraries of its own, which the plain javac gate does not manage. The lab uses the educational container, so everything needed is in the chapter and companion.

6.7 What Abstraction Costs

Give each boundary a job


Each Boundary Earns Its Place

  • The notification interface earns its place: the same client needs two outputs and a predictable failure boundary.
  • The borrowing-policy interface already expresses a genuine variation.
  • The formatter remains concrete. No interface on immutable Reservation or LoanPeriod.
  • The root may know concrete classes. Hiding every choice behind another factory makes the program hard to trace without protecting any additional change.

The Policy Map Has a Cost Too

  • It removes a category switch from the coordinator but adds a configuration invariant: every category must have a non-null policy.
  • Constructor validation makes that cost visible.
  • A design explanation should include it, rather than presenting fewer conditionals as an unconditional improvement.

Recognise a Disguised Dependency

  • An injected ApplicationContext with arbitrary lookups hides all the coordinator’s real needs. A method calling context.get("mail") is still locating its own collaborator.
  • An interface whose send needs server addresses, passwords and transport-specific objects moves provider setup into every caller.
  • The interface exists syntactically; the application is still coupled to implementation details.

Concrete Review Questions

  • Can I choose the recorder without editing reservation logic?
  • Can I see the required dependencies at construction?
  • Can a provider-specific change stay at the boundary?
  • Can I explain the state after expected output failure?
  • These reveal more than counting interface declarations.

What the Chapter Has Not Solved

  • An in-memory, sequential teaching application.
  • No authoritative borrower categories, persistent notification history, retry queue, remote delivery, cancellation or availability enquiry.
  • It does not implement the whole MVP reservation-and-loan flow.
  • The new structure makes selected changes and tests easier. It does not manufacture those features.

6.8 Reviewing an Interface That Hides Very Little, with AI

Does the refactoring separate anything?


Ground Rules for the Activity (1 of 2)

For this activity
Your goalJudge whether a generated refactoring really separates configuration and policy from the reservation workflow
You start withThe coupled coordinator from 6.1, the notification requirement and Figure 6.1
What is in boundsEvaluate the proposal and draw a corrected object graph; do not apply it wholesale

Ground Rules for the Activity (2 of 2)

For this activity
How to checkThe coordinator’s source (which concrete classes does it name?), the recorder-based checks, the failure case
How to decideA verdict with a concrete reason for each proposal, then a corrected constructor
No AI account?The ready-made example is enough

The Ready-Made Example (1 of 2)

Author-written fictional AI output.

Add NotificationSender and type the coordinator field with it. Inside the coordinator constructor, read the provider settings and create ConsoleNotificationSender. Add configure, exportHistory and deleteBorrower to the same interface so it can support future features.


The Ready-Made Example (2 of 2)

Catch every runtime exception and still return the ordinary confirmation. Pass a global container into the coordinator so future dependencies can be found when needed. The new interface means DIP and DI are both complete.

  • Find where configuration still occurs, what the client actually needs, and what a caller would learn about failure.
  • Explain receiving a sender versus receiving a registry.

Model Critique (1 of 3)

Claim or changeVerdictReason
Type the field as NotificationSenderUseful but insufficient aloneIt expresses a role, but concrete construction can remain
Read provider settings inside the coordinatorRejectEnvironmental configuration still changes the workflow’s source
Construct the console sender inside the coordinatorReject for the final designThe caller cannot select a recorder without editing the client

Model Critique (2 of 3)

Claim or changeVerdictReason
Add configure/export/delete to the sender roleReject without a client needThey couple this client to unrelated capabilities
Catch every runtime exception, return ordinary confirmationRejectIt hides programming defects; expected failure becomes indistinguishable from success

Model Critique (3 of 3)

Claim or changeVerdictReason
Inject a global container for future lookupsRejectReal required collaborators become hidden behind a registry
Declare DIP and DI complete because an interface existsRejectContract ownership, provider knowledge and actual assembly must be inspected
  • Corrected answer: three explicit constructor parameters, the application-owned sender contract, two implementations, external assembly; the stated notification timing and failure outcome; the constructor migration named; the still-limited in-memory model.

Independent Verification

  • Inspect concrete constructor calls.
  • Search the coordinator for provider and container references.
  • Run with the recorder.
  • Observe the failure case.
  • A second model agreeing with the first is not a substitute for those observations.

6.9 Dependency Habits That Mislead

Eight misleading beliefs


Mistakes 1 to 3

MistakeWhy it is misleadingBetter action
An interface-typed field proves DIPConcrete construction or provider-specific types may remainInspect the actual source dependencies
DI means a frameworkOrdinary constructors can receive dependenciesExplain assembly before adding tooling
Every class needs an interfaceMany stable values have no variationName the client need and change pressure

Mistakes 4 to 6

MistakeWhy it is misleadingBetter action
A sender must expose provider administrationThe coordinator does not need those operationsKeep the application role narrow
A container singleton is thread-safeLifetime and concurrent access are separateInspect mutable state and execution model
Notification failure means reservation failureStorage may already have succeededState the partial-success outcome explicitly

Mistakes 7 and 8

MistakeWhy it is misleadingBetter action
One call means exactly-once deliveryOutput may fail after partial or remote effectsDescribe only the guarantee implemented
Old checks passed, so all compatibility is preservedConstruction changed and notification adds effectsSeparate API migration from retained domain behaviour

6.10 Try It Yourself

Assemble a replaceable output


What You Start With, and the Demonstration

  • You start with: Chapter 5’s exit code, this chapter’s requirement and companion listings. Java 17, the plain-Java gate. The container walkthrough is separate from the application.
  • Demonstration: compare the Chapter 5 baseline, the coupled notification version and the injected version. Point to every concrete provider choice and explain which belongs at the entry point.
  • Guided practice: inject a recorder, accept one reservation, reject an overlapping request and inspect both stored reservations and recorded notifications; then use an output that throws the documented exception.

Independent Work: Steps 1 to 3

  1. Define the sender role and identify at least two operations it should not contain.
  2. Move output construction and category-to-policy mapping to the composition root. Require valid collaborators in the constructor.
  3. Migrate existing caller construction while preserving existing reservation assertions.

Independent Work: Steps 4 to 6

  1. Demonstrate both console and recording outputs with the same coordinator implementation.
  2. Verify no notification on rejection, one attempt after acceptance, and the chosen failure outcome without automatic retry.
  3. Draw separate source-dependency and object-assembly views, labelling what each arrow means. In the source view, check that every “creates” arrow starts at the application class.

Independent Work: Steps 7 to 9

  1. Run the educational container walkthrough and explain shared state, missing registration and cycle detection.
  2. Review the change and write a rationale for every new boundary. Retain real review findings.
  3. On your team’s project, find one environment or provider choice that a class constructs for itself (an output, storage, an external service, configuration). Sketch where it would be assembled instead, using both views.

Expected Output and Completion

  • Output: injected notification contract; two implementations; explicit assembly code; migrated and additional checks; the two diagrams; failure-policy explanation; a short rationale; the step 9 sketch.
  • Complete when: provider substitution requires no edit to the reservation algorithm; setup failures are distinguishable from reservation rejections; old rule expectations remain valid; expected notification failure preserves the record and reports its outcome accurately.
  • If you want more: compare the supplied map with a dedicated policy-resolver in a design sketch. Explain the client benefit before adding another interface.

Review Evidence Worth Inspecting (1 of 2)

Review concernEvidence worth inspecting
Role is client-specificCoordinator uses only the sender operation and the domain record
Provider choice is externalConcrete output construction is in assembly code, not the coordinator
Valid configurationNull and missing-policy failures occur before request processing
Mapping is stableEditing the caller’s map does not rewire an existing coordinator

Review Evidence Worth Inspecting (2 of 2)

Review concernEvidence worth inspecting
Existing behaviourMigrated setup with unchanged reservation assertions passes
Feature effectsAccepted record supplied once; rejection causes no send; expected failure retains state
LifetimeRepeated container resolution shares the same coordinator
Claims are boundedNo claim of email delivery, atomic messaging, thread safety or a hosted run

What the 28 New Checks Establish

  • The accepted result, one recorded notification, the exact accepted record, and no notification on conflict, duration rejection or invalid identifiers.
  • A controlled expected failure after storage: the suffix, record retention, no automatic retry.
  • Two further checks: a sender throwing IllegalStateException propagates, with the record already stored. Without them, widening the catch to RuntimeException would pass every check.
  • Constructor checks for null dependencies and missing category configuration; a mutable input map changed after construction does not rewire the coordinator.

Do the Checks Detect Faults? (1 of 4)

FaultResult
Notification sent before the record is storedCaught: notification failure retains accepted record
catch (NotificationException) widened to catch (RuntimeException)Caught: unexpected sender failure is not swallowed
Failure suffix text changedCaught: explicit partial-success result
One retry added after a failed sendCaught: one attempt and no automatic retry

Do the Checks Detect Faults? (2 of 4)

FaultResult
Sender called twice on successCaught: one notification per accepted request
Map.copyOf removedCaught: caller map mutation cannot rewire coordinator
Missing-category check removedCaught: missing staff policy
Null sender check removedCaught: null sender

Do the Checks Detect Faults? (3 of 4)

FaultResult
Null formatter check removedCaught: null formatter
Policy lookup always uses the student policyCaught: staff confirmation with new limit
Console checkError() test removedCaught: console reports expected output failure
Console prefix Notice: changedCaught: console implementation uses supplied stream

Do the Checks Detect Faults? (4 of 4)

FaultResult
Recorder returns its mutable listCaught: inbox snapshot unmodifiable
Factory ignores the supplied senderCaught: one notification per accepted request
  • All fourteen faults caught.
  • The checks do not prove thread safety, durable output or remote delivery.

6.11 Questions to Think with

Concept and diagnosis


Concept Questions

  1. Why does NotificationSender omit the recorder’s received() method?
  2. How can constructor injection be present while the intended DIP boundary is still missing?
  3. What does the composition root know that the coordinator should not know?
  4. Why can sharing a coordinator through a container change application behaviour even when its methods are unchanged?

Diagnosis Questions

  1. A coordinator catches a sender failure, deletes the accepted reservation, and returns “Rejected”. The sender may already have printed or delivered a notice. Diagnose the inconsistent outcome and compare it with this chapter’s explicit failure rule.
  2. A constructor copies a map of policies. A reviewer says this proves that policy behaviour can never change. Explain what the copy protects and what still depends on the policy objects’ contracts.

Guidance: Questions 1 and 2

  1. The coordinator submits an accepted record; it does not inspect output history. The recorder’s inspection API serves another client, such as check code. Adding it would force the console implementation to invent a history it does not need.
  2. A constructor taking ConsoleNotificationSender uses DI but depends on that concrete provider. A contract copied from a provider SDK keeps provider coupling despite its interface name. Define the application role and inject a conforming implementation.

Guidance: Questions 3 and 4

  1. The root knows which category maps to which concrete policy, which sender is selected, which stream it uses, and how formatters are constructed. The coordinator knows roles and when to invoke them.
  2. Two resolutions of one cached coordinator share the same reservations. Two fresh coordinators have separate empty lists and can each accept what looks like the first reservation. The method source is identical; the assembled graph changes behaviour.

Guidance: Questions 5 and 6

  1. The output may already have occurred, so deleting cannot undo it: a notice for a record that no longer exists. “Rejected” hides the earlier acceptance and may prompt a repeat. This chapter retains the record and reports the output failure. Another policy needs an explicit consistency design, not a catch block pretending to reverse external effects.
  2. The copy fixes associations between categories and policy references. It is shallow: an externally held policy could still change internally if its implementation permitted it. The stateless policy contract provides the further stability.

6.12 A Different Setting

The transfer challenge


Transfer Challenge

A document conversion service validates a request, converts a file and sends a completion notice. A desktop demonstration displays notices in a window; an automated run records them in memory.

  • Define a client-specific notification role.
  • Identify the assembly boundary and what stays in the conversion workflow.
  • Decide what callers observe if conversion succeeds but notification fails.
  • Draw a dependency diagram; give two acceptance examples separating conversion state from notification state.
  • Synthetic filenames only; no assumed retry queue.

Transfer Guidance

  • A CompletionNotifier role taking an immutable successful-conversion result. It does not validate, convert or administer accounts.
  • The workflow decides when conversion has succeeded. The application root selects the desktop or recording notifier.
  • Failure rule: keep the converted file; return a result separating conversion success from notification failure.
ExampleConversion stateNotification state
Valid input convertsOne output producedOne completion result submitted
Conversion failsNo success reportedNo notice submitted
Conversion succeeds, notifier failsOutput retainedFailure reported separately

6.13 Revising for Quiz 1

Where to look


What Quiz 1 Covers

  • Quiz 1 is held in Week 5, but its stated coverage is Weeks 1 to 4: OO foundations, Agile practices and user stories, Git workflows, and the first SOLID principles.
  • Revise Chapters 2 to 5. Do not treat all of Chapter 6 as newly included quiz content.
  • Follow the module leader’s confirmed guidance. These pointers add no assessment rules and predict no questions.

Revision Focus (1 of 2)

ChapterRevision focus
2Encapsulation, responsibilities, cohesion/coupling, composition and inheritance, UML class diagrams, Git history, branches and small reviewed changes
3Agile values in decisions, iterative/incremental delivery, Scrum/Kanban distinctions, stories, acceptance criteria and Definition of Done

Revision Focus (2 of 2)

ChapterRevision focus
4Vision and scope, vertical slicing, capacity-aware planning, review workflow and meaningful CI evidence
5SRP, OCP, behavioural contracts, LSP counterexamples and protected refactoring

What to Take Away, and What Comes Next

What to carry forward


Summary

  • A useful interface describes a client’s needed role.
  • DIP concerns the direction and ownership of dependencies; DI supplies collaborators from outside; a container is one possible assembly mechanism.
  • The running example now makes provider and policy choices explicit at its entry boundary and defines the observable result of expected notification failure.

Where the Project Stands

  • Local console and recording outputs.
  • Explicit construction and 28 new checks.
  • Existing reservation rules remain covered by their previous expectations, with caller setup migrated.

Next: Chapter 7

  • Test design.
  • Controlled substitutes (test doubles).
  • A complete red-green-refactor cycle.

Sources and Further Reading

  1. Martin: Design Principles and Design Patterns (2000); A Little Architecture (2016).
  2. Fowler: Inversion of Control Containers and the Dependency Injection Pattern (2004); Inversion of Control (2005).
  3. Oracle: Java SE 17 Map and PrintStream.
  4. Spring: Introduction to the Spring IoC Container and Beans.
  5. Martin: Agile Software Development, 2002.
  • Full references and links are in the chapter text.

7 - Chapter 7: Testing as a Design Practice

Object Oriented Development and Agile Practices (SE506)

Chapter 7: Testing as a Design Practice

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 7.1 What does a test actually show? Behaviour and test evidence
    • Executable availability examples; observe result and state together.
  • 7.2 What a test looks like: Arrange–Act–Assert
    • Assertions that can be wrong; fresh, small setup.
  • 7.3 Red, green, refactor: one cycle in detail
    • One recorded cycle, and why the first failure must be the intended one.
  • 7.4 Edges, clocks and a small lending flow
    • Boundary values, a controlled clock, a bounded lending flow.

Chapter Roadmap (2 of 3)

  • 7.5 Test doubles: dummies, stubs, fakes, spies and mocks
    • Name the role a substitute plays, then add Mockito for one interaction.
  • 7.6 What testing tells you about design
  • 7.7 Tests that look good but are weak or brittle
    • Coverage is not correctness; a mutation experiment; a mutant that survives.
  • 7.8 Preparing the Milestone 2 evidence

Chapter Roadmap (3 of 3)

  • 7.9 Keeping implementation and expected behaviour apart, with AI separate implementation from expected behaviour.
  • 7.10 Testing habits that mislead
  • 7.11 Try it yourself: a test-first change and Milestone 2 evidence
    • Steps 1 to 9; steps 8 and 9 on your team’s project.
  • 7.12 Questions to think with, and midterm practice
    • A five-task practice set for Chapters 2 to 7.
  • What to take away, and what comes next
    • Chapter 8: construction and compatibility patterns.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Translate acceptance examples into focused tests with meaningful expected outcomes.
  2. Demonstrate red-green-refactor and explain why the first failure is the intended one.
  3. Control data, time and collaborators to make tests repeatable.

Learning Outcomes (2 of 2)

  1. Choose a useful test double and distinguish state checks from interaction checks.
  2. Diagnose weak or brittle tests and explain the limits of passing evidence.
  3. Assemble MVP, testing, demonstration and retrospective evidence for Milestone 2.

Module Connection

  • Supports LO3; Milestone 2 preparation.
  • The midterm practice near the end draws on Chapters 2 to 7 (Weeks 1 to 6).
  • No new assessment weights, deadlines or marking rules.
  • Readings: the testing chapter and “A Programming Episode” in Martin, and Part I of Beck.

Milestone 2 Checklist: Use It From the Start (1 of 2)

Milestone 2 falls in this week. By your demonstration you need:

  1. A working MVP flow that someone else can run from written instructions.
  2. Acceptance examples mapped to named tests.
  3. One red-green-refactor sequence recorded from your own code.
  4. A short rationale for your object-oriented and SOLID choices.
  • Start with items 1 and 3.

Milestone 2 Checklist (2 of 2)

  1. A justified test double or controlled clock.
  2. A build result for the revision you demonstrate.
  3. Real review feedback and a short retrospective with a next action.
  4. A statement of what the MVP does not do.
  • Section 7.8 explains each item.
  • The checklist is preparation. The module leader’s approved Milestone 2 requirements remain authoritative.

Where You Start and the Tools

  • You start with: Chapter 6’s Java 17 application, explicit collaborator construction, student and staff policies, local notification outputs. The earlier executable checks remain available.
  • New tools: Maven, JUnit Jupiter and Mockito, with an explicitly versioned configuration in the companion.
  • Core reading 7.1 to 7.8. Section 7.5 introduces hand-written doubles before Mockito.
  • Lab: steps 1 to 6, 8 and 9. Steps 1 to 6 on the book’s project; **steps 8 and 9 on your team’s project.

Opening Scenario: A Green Build That Is Wrong

  • A developer adds the availability enquiry and reports the build is green.
  • At the demonstration the officer asks about a reserved camera and receives “available”.
  • The test had checked only that the method returned a boolean without throwing.

Three Plausible, Wrong Implementations

  • One passes because the test only checks “no exception”.
  • Another rejects every enquiry and still passes the overlap example.
  • A third accepts adjacent dates incorrectly because it treats the earlier reservation’s end as inclusive.
  • Each looks plausible when its tests ask incomplete questions.

What Testing Is For

  • Not to accumulate green indicators.
  • To create evidence about specific behaviour, reveal misunderstandings and support changes in design.
  • This chapter turns the availability examples of Chapters 3 and 4 into executable tests, demonstrates a complete red-green-refactor cycle, and uses controlled collaborators.

7.1 What Does a Test Actually Show?

Behaviour and test evidence


The Examples Are Now Implemented

  • Chapter 3 proposed US-02: an officer inspects availability without creating a reservation.
  • Chapter 4 refined five acceptance examples.
  • Now they become executable.

AQ Examples (1 of 2)

ExampleSetup and actionRequired observation
AQ-01Empty schedule; enquire about CAM1 for a valid intervalAvailable; records and notifications unchanged
AQ-02CAM1 reserved [20 Oct, 22 Oct); enquire [21 Oct, 23 Oct)Unavailable; no side effects
AQ-03Same booking; enquire [22 Oct, 23 Oct)Available because the end is exclusive

AQ Examples (2 of 2)

ExampleSetup and actionRequired observation
AQ-04Same booking; enquire about MIC1 for the same intervalAvailable for the different sample item
AQ-05Equal or reversed intervalIdentifiable invalid-input outcome; no side effects
  • All dates are in 2026.

The API Decision

  • The selected Java API is isAvailable(itemId, start, endExclusive), returning a boolean for a valid interval.
  • Blank or null item identifier: IllegalArgumentException.
  • Equal or reversed dates: IllegalArgumentException.
  • Null dates: NullPointerException through LoanPeriod.
  • These choices settle the earlier open API question for this teaching version.

What Availability Means

  • No conflicting reservation for the item and interval.
  • It does not mean a particular borrower is eligible.
  • The query accepts any positive interval, including more than seven days; student limits apply when a reservation is requested.
  • It does not know whether an item is physically on loan or present in a real catalogue.

Observe Result and State Together

  • The overlap example checks: a false result, unchanged reservations, unchanged recorded notifications.
  • An implementation that calls reserve and undoes it would also send a notice: the unchanged-notices check exposes it.
  • One that adds a temporary record directly and removes it again leaves nothing for a sequential test to see.
  • We reject that design by reading it, not by testing it.

Choose Observations by the Promise

  • For an immutable snapshot, compare the before and after lists: they expose replacement, deletion or reordering. Checking only the count misses changes to contents.
  • Inspecting a private list through reflection binds the test to representation when a public snapshot already exposes the behaviour.
  • A separate example confirms that an enquiry does not consume the next reservation identifier.

The Expected Answer Must Be Independent

  • It must come from an agreed example or rule.
  • Calculating it by calling the same method under test is circular.
  • Copying the implementation’s overlap expression into a test reproduces its mistake.
  • Concrete dates with an independently understood boundary are a better starting point.

Introduce a Runner Without Losing Earlier Evidence

  • JUnit provides annotations, assertions and reporting. Maven compiles and runs it through the configured Surefire plugin.
mvn --batch-mode verify
  • The existing scripts/check.sh now delegates to that command.
  • A LegacyRegressionTest invokes the four older main methods from four JUnit tests. A class merely placed in a test directory would not run its main.

Counts Are Not Interchangeable

  • The wrapper reports four JUnit cases.
  • Their internal output still reports: 34 Chapter 2 checks, five Chapter 3 example groups, 44 Chapter 5 checks, 28 Chapter 6 checks.
  • Do not merge those counts as if they were identical units.
  • The useful question: which behaviours execute, and what failures reach the build?

7.2 What a Test Looks Like

Arrange–Act–Assert


Arrange, Act, Assert

  • Arrange: establish the relevant state.
  • Act: perform the operation being examined.
  • Assert: inspect the outcome.
  • A reading aid, not a requirement of exactly one statement per phase.

The First Availability Test

boolean available = coordinator.isAvailable(
        "CAM1", start.plusDays(1), start.plusDays(3));

assertAll(
        () -> assertFalse(available, "An overlapping item must be unavailable"),
        () -> assertEquals(recordsBefore, coordinator.reservations()),
        () -> assertEquals(noticesBefore, inbox.received()));
  • Arranged: a two-day CAM1 reservation, with snapshots of records and notices.

A Name That Says What Broke

  • overlappingEnquiryIsUnavailableWithoutChangingRecordsOrNotifying
  • A precise name reduces the need to reverse-engineer why a setup contains particular values.
  • Avoid test1 or worksCorrectly: when the test fails, the name should tell another developer which promise was broken.

How Many Assertions?

  • Several assertions can belong together when they describe one outcome.
  • Splitting this test into three methods with identical setup may obscure the relationship between a query result and its non-mutation promise.
  • One giant method for every query, loan and notification scenario is the opposite problem: a failure is hard to locate.

Use Assertions That Can Be Wrong

  • assertTrue(true) cannot reject an implementation.
  • “No exception occurred” may be valuable for a specific promise, but it does not establish availability.
  • A meaningful assertion must distinguish the behaviour you want from a plausible defect.

Exceptional Outcomes

  • Use assertThrows for a specified exceptional outcome, and inspect state afterwards when preservation matters.
  • A catch block that prints an error and continues does not establish that the right exception occurred.
  • Wrapping a test in a broad catch can turn a failure into a false success.

Parameterised Tests

  • Small tables of policy boundaries: each row states category, count, days and expected decision.
  • The runner reports individual invocations, so a particular boundary failure is visible.
  • The table is authored from the rule, not generated by evaluating the policy itself.

The Policy Table (BorrowingPolicyTest)

CategoryCountDaysExpected
STUDENT01true
STUDENT27true
STUDENT31false
STUDENT08false
STAFF414true
STAFF51false
STAFF015false
  • Plus three more rows: a negative count and two non-positive durations. Ten rows in all.

Keep Setup Independent and Small

  • Each scenario constructs fresh mutable collaborators unless sharing is part of the behaviour.
  • A static coordinator reused across tests makes results depend on which test ran first: the same test may pass alone and fail in the suite.
  • Prefer the smallest setup that makes the scenario meaningful, using real simple domain objects.

7.3 Red, Green, Refactor

One cycle in detail


TDD in Short Cycles

  • Express an expected behaviour.
  • Observe its failure.
  • Implement enough to satisfy it.
  • Improve structure while preserving the tests.
  • A development sequence, not a label for tests added after a complete feature.

The Cycle and Its Decision Point

Write abehaviour testRun the testIntended assertionfails?Inspect setup, test orexisting behaviourImplement enoughto passConfirm greenRefactor and runregression testsYesNotests pass: next behaviourtests fail: repair the refactoring

The Starting Point for Red

  • The worked cycle begins with a compiling isAvailable that validates its arguments but always returns true.
  • This supplies an API for the first test without pretending to implement overlap detection.
mvn --batch-mode -Dtest=AvailabilityTddTest test

The Observed Red Result

  • One executed test, one failure, zero errors.
  • The assertion reports that an overlapping item was expected to be unavailable but the method returned true.
  • The unchanged-state assertions do not fail.
  • That makes the missing behaviour precise.

Not Every Failure Is Red

  • A missing library, a syntax error or an unresolved dependency is not this red result.
  • Those failures prevent or interrupt the experiment.
  • Fix the environment or compilation issue, then rerun until the intended assertion exposes the missing behaviour.

Green: Implement the Required Decision

  • Construct a LoanPeriod, inspect existing reservations, return false if the same item has an overlapping period, otherwise true.
  • It does not call reserve, allocate an identifier or invoke the sender.
  • The same test then passes: useful evidence for that example, not permission to stop thinking.

An Always-False Method Passes Too

  • An implementation that always returned false would also satisfy the first overlap test.
  • More examples are needed to distinguish overlap rejection from universal rejection:
    • empty schedule,
    • adjacency,
    • different item,
    • invalid input,
    • identifier preservation,
    • eligibility distinction.

Refactor: Share Conflict Detection

  • The green query duplicates conflict scanning already in reserve.
  • Extract a private hasConflict(itemId, requestedPeriod) used by both.
  • reserve keeps its validation and eligibility order and still stores exactly one record.
  • The query uses the same conflict meaning without acquiring reservation’s mutation or borrower-limit behaviour.

After the Extraction

  • Rerun the TDD test and the legacy bridge.
  • Observed: five JUnit cases (the new test and four legacy wrappers) pass.
  • The extraction is justified because both callers need the same meaning of a conflict, not because duplicated text is forbidden.
  • If one caller later needs stock or maintenance status, do not force that meaning into the helper.

Preserve a Traceable Sequence

  • The companion includes red, green and refactored coordinator snapshots with their commands and results.
  • In a team repository, separate commits or a recorded diff sequence give equivalent evidence.
  • Keep the actual failing assertion and correction, not just a screenshot saying tests passed.
  • A wrong implementation supplied after the fact shows test sensitivity. It does not reconstruct a test-first history that never happened.

7.4 Edges, Clocks and a Small Lending Flow

Edges that carry meaning, and time under control


Test the Edges That Carry Meaning

  • Borrowing: a student with two records may add a third; one with three may not. Seven days allowed, eight not. Staff: five records, fourteen days.
  • Overlap: an enquiry beginning at the exclusive end is available; equal start and end is invalid; a different item does not conflict because dates match.
  • Each example distinguishes a plausible error in the rule.

Boundary Selection Is Reasoning

  • It is not an exhaustive proof over every integer or date.
  • Combine examples with source inspection and, where justified, broader techniques later.
  • Do not introduce a coverage target as a substitute for an explanation of the risks.

Control Time Where Behaviour Depends on Today

  • Availability needs no clock in this version.
  • The new loan checkout does: checkout is allowed only on a date within the confirmed reservation’s half-open period.
  • LoanService receives a Java Clock and uses LocalDate.now(clock).
  • Tests use Clock.fixed, or a small controlled clock when the scenario must advance.

Instant and Zone

Instant2026-10-19 22:30 UTCZone UTClocal date: 19 OctoberZone Asia/Baghdad (UTC+3)local date: 20 OctoberLocalDate.now(clock)needs both

Why the Zone Matters

  • A fixed UTC instant does not define a local date without a zone.
  • One test uses 19 October 22:30 UTC with Asia/Baghdad and expects the local checkout date 20 October.
  • A deliberate example of zone-sensitive conversion, not reliance on the machine’s default zone.
  • Do not wait until midnight or call Thread.sleep: change the supplied clock.

A Bounded Lending Flow

  • The MVP increment adds a small LoanService linked to one coordinator in the same process.
  • It records checkout and return against a confirmed reservation.
  • A Loan is a Java record with validated identifiers, a checkout date and an optional return date. Null means one documented thing: the loan is active.
  • Records give concise value-oriented data, but validation still has to be written.

The Loan Record

public record Loan(String reservationId, String borrowerId, String itemId,
                   LocalDate borrowedOn, LocalDate returnedOn) {
    public Loan {
        if (reservationId == null || reservationId.isBlank()
                || borrowerId == null || borrowerId.isBlank()
                || itemId == null || itemId.isBlank()) {
            throw new IllegalArgumentException("Loan identifiers must not be blank.");
        }
        Objects.requireNonNull(borrowedOn, "borrowedOn");
        if (returnedOn != null && returnedOn.isBefore(borrowedOn)) {
            throw new IllegalArgumentException("Return cannot precede checkout.");
        }
    }
    public boolean active() { return returnedOn == null; }
}

LoanService.checkOut

LocalDate today = LocalDate.now(clock);
if (today.isBefore(reservation.period().start())
        || !today.isBefore(reservation.period().endExclusive())) {
    throw new IllegalStateException("Checkout must be within the reservation period.");
}
boolean itemAlreadyOut = loans.values().stream()
        .anyMatch(loan -> loan.active() && loan.itemId().equals(reservation.itemId()));
if (itemAlreadyOut) throw new IllegalStateException("Item is already on loan.");

LoanService.returnLoan

Loan current = loans.get(reservationId);
if (current == null || !current.active()) {
    throw new IllegalStateException("No active loan for this reservation.");
}
Loan returned = new Loan(current.reservationId(), current.borrowerId(),
        current.itemId(), current.borrowedOn(), LocalDate.now(clock));
loans.put(reservationId, returned);
  • The service replaces an active loan value with a returned value instead of mutating earlier snapshots.

The Lending Rules (1 of 2)

OperationRule and state effect
CheckoutReservation must exist; today’s date must be within its period
Repeat checkoutOne reservation can create at most one loan, including after return
Item already outA second active loan for the same item is rejected

The Lending Rules (2 of 2)

OperationRule and state effect
ReturnOnly an active loan can be returned; repeated return is rejected
Return dateCannot precede the recorded checkout date
ObservationLoan snapshots are unmodifiable and contain immutable loan values

Do Not Invent Lifecycle Rules Silently

  • Returning a loan does not delete or cancel its reservation.
  • The reservation-count rule still counts every stored reservation; availability stays based on those records.
  • An early return therefore does not free a reserved interval or restore a borrower’s allowance.
  • An explicit, tested inherited limitation. Changing it would disguise a feature change as testing work.

A Bounded In-Memory Flow

  • One coordinator and one loan service for the session.
  • Multiple independent loan services have independent loan maps.
  • No shared persistent or concurrent protection.
  • Not a complete lending operation.

7.5 Test Doubles

Dummies, stubs, fakes, spies and mocks


Test Doubles (1 of 2)

RolePurposeExample in this project
DummySupplies an argument the scenario should not useA sender passed to an immediately rejected request
StubProduces a controlled answer or failureA sender that throws the expected notification exception
FakeA working simplified substituteAn in-memory output standing in for external delivery

Test Doubles (2 of 2)

RolePurposeExample in this project
SpyRecords calls for later inspectionThe recorder, when a test examines received reservations
MockSupports verification of expected interactionsA Mockito sender checked for one call or no calls
  • The vocabulary follows Meszaros, summarised by Fowler. Roles overlap; the class name alone does not determine the role.

Keep the Subject Real

  • The coordinator is the subject under test in notification scenarios.
  • Mocking its reserve method would replace the behaviour we want to examine.
  • Keep the real coordinator, rules and small immutable values. Replace the output boundary that would otherwise complicate observation.
  • A hand-written recorder is often clearer than a framework: its list makes the effect visible.

Mockito for a Specific Interaction

NotificationSender sender = mock(NotificationSender.class);
ReservationCoordinator c = ReservationApplication.create(sender);
c.reserve("B1", "CAM1", day, 2);
ArgumentCaptor<Reservation> captured = ArgumentCaptor.forClass(Reservation.class);
verify(sender).send(captured.capture());
Reservation sent = captured.getValue();
assertAll(() -> assertEquals("R1", sent.id()),
        () -> assertEquals("B1", sent.borrowerId()),
        () -> assertEquals("CAM1", sent.itemId()));

Two More Mockito Tests

// no notification after a rejection
assertEquals("Rejected: borrower limit or duration.", c.reserve("B1", "CAM1", day, 8));
assertTrue(c.reservations().isEmpty());
verifyNoInteractions(sender);

// expected failure: record retained, exactly one attempt
doThrow(new NotificationException("Expected test failure"))
        .when(sender).send(any(Reservation.class));
/* ... reserve ... */
verify(sender, times(1)).send(any(Reservation.class));

Public Boundary, Not Private Helpers

  • These checks concern public boundary behaviour.
  • The exact number of invocations matters because the feature says one attempt per acceptance and no automatic retry.
  • Counting how often a private list is inspected would be a brittle assertion of a different kind.

Mockito Setup Notes

  • The supplied setup uses its interface-only proxy mock maker: mocks are Java dynamic proxies, and no class or static mocking is needed.
  • On newer JDKs, Mockito may still attach its ByteBuddy agent and the JVM prints “A Java agent has been loaded dynamically”. The tests still pass.
  • Passing -XX:+EnableDynamicAgentLoading to the test JVM removed the message. The JVM says dynamic loading will be disallowed by default in a future release: check Mockito’s documentation for your JDK.

A Preview: Doubles for an Outside Data Source

BorrowerDirectory knownStudent = id -> Optional.of(new Borrower(id, BorrowerCategory.STUDENT));
BorrowerDirectory nobody = id -> Optional.empty();
BorrowerDirectory offline = id -> { throw new DirectoryException("provider offline"); };
  • The contract has one method, so a lambda is enough. Chapter 8 builds the real types.
  • Compiled and run against Chapter 8’s types, they print STUDENT, false and failure: provider offline.

What Each Directory Double Answers

  • knownStudent: a stub with a fixed answer: tests what the service does with a borrower it can trust.
  • nobody: a stub saying “absent or inactive”: tests the ordinary rejection.
  • offline: a throwing stub: tests that an outage is reported as an outage, not as “not found”.
  • None needs a framework. None proves that a real provider behaves this way.

Match the Double to the Question

  • A recorder when you want to inspect accumulated notices.
  • A throwing stub when you need a predictable output failure.
  • An interaction-verifying mock when the contractual call itself is the observation.
  • A fixed clock supplies controlled time; it does not prove the real system clock is correct.
  • Neither recorder nor mock proves that email is delivered. Chapter 13 covers real adapters.

Do Not Verify Everything

  • A blanket “nothing else was called” assertion can make harmless implementation changes fail.
  • Verify only absence or ordering that is meaningful to the requirement.
  • “No notification after rejection” is meaningful.
  • “Exactly three calls to the snapshot getter” is not.

7.6 What Testing Tells You About Design

Difficult tests as information


Difficult Setup Can Reveal a Design Problem

  • Chapter 6’s constructor injection lets tests choose a recorder, failing sender and policy mapping without altering the coordinator.
  • LoanService receives a clock for the same reason.
  • A difficult test may expose hidden global configuration, mixed responsibilities or unclear results. It may also expose a poorly chosen test: is the scenario trying to inspect a private detail?

The Query’s Clear Contract

  • The availability query creates no records and has no notification side effects: easy to test result and state separately.
  • Implementing it by calling reserve would entangle eligibility, identifiers and notifications.
  • Test difficulty would then be useful feedback about the proposed design.

Use Real Simple Collaborators

  • LoanPeriod is cheap, deterministic and central to the rule. Mocking it to say “overlaps” would prevent the tests from exercising real period semantics.
  • An ordinary list or map should usually remain real: replacing it with a mock leads to assertions about add, get and ordering, not preserved records.
  • A substitute should remove an obstacle or expose a meaningful boundary, not erase the behaviour under examination.

Preserve Evidence During Migration

  • Moving to JUnit does not require rewriting every old assertion at once.
  • The bridge makes earlier runners visible to the new build; new tests are focused methods with readable names.
  • The bridge is transitional: four coarse results give less precise reporting. Later migration can improve diagnosis without changing expected behaviour.
  • Do not discard earlier checks because their syntax predates the framework.

7.7 Tests That Look Good but Are Weak or Brittle

Executing code is not asking a question


Coverage Is Not Correctness

  • Coverage records which code was exercised under a particular measurement.
  • It does not establish that the expected answer was correct or that the assertions would reject a defect.
  • A test can execute every line of a method and accept every result.

Compare Two Query Tests

  • One only calls isAvailable.
  • The other expects an overlapping interval to be false and preserves the recorded state: it distinguishes a meaningful defect.
  • A further adjacency example distinguishes a different defect the overlap example would miss.

A Targeted Mutation Experiment

  • In a separate copy, change the second comparison in LoanPeriod.overlaps:
other.start.isBefore(endExclusive)        // correct
!other.start.isAfter(endExclusive)        // deliberately wrong
mvn --batch-mode -Dtest=AvailabilityTest#adjacentPeriodAndDifferentItemAreAvailable test

The Result

  • One failure, zero errors, nonzero build exit. The assertion expects true and receives false.
  • A behavioural failure in the adjacency example, not a missing tool or compile issue.
  • Restore the original and rerun: the test passes.
  • It supports one claim: this test detects this fault. It is not a mutation score.
  • Never change the correct expected value to accommodate the mutant.

Sixteen Single Faults

  • Introduced one at a time into copies of the completed project; each run through mvn test.
  • Fifteen were rejected by at least one named test.
  • One survived.
  • A surviving fault is information to examine, not always a defect in the tests.

Which Test Caught Which Fault (1 of 4)

FaultCaught by
isAvailable always returns trueAvailabilityTddTest overlapping enquiry
isAvailable always returns falseAvailabilityTest empty-schedule, adjacency, eligibility examples
Conflict check ignores the item identifieradjacency/different-item example and the Chapter 2 bridge
Inclusive end boundary in LoanPeriodadjacency test and both older bridges

Which Test Caught Which Fault (2 of 4)

FaultCaught by
Checkout allowed on the exclusive end daycheckoutOutsideHalfOpenPeriodIsRejected
Checkout allowed before the startthe same test
One reservation checked out twiceaReservationCannotBeCheckedOutTwiceEvenAfterReturn
Item-already-on-loan check removedunreturnedItemBlocksTheNextAdjacentReservationUntilReturned

Which Test Caught Which Fault (3 of 4)

FaultCaught by
Return of an inactive loan allowedcheckoutAndReturnUseTheSuppliedClock
Real system clock used instead of the supplied onethree LoanServiceTest methods
Clock zone ignoredclockZoneDeterminesTheCheckoutDate
Return date recorded as the checkout datecheckoutAndReturnUseTheSuppliedClock and the backward-clock test

Which Test Caught Which Fault (4 of 4)

FaultCaught by
Loan no longer rejects a return before checkoutbackwardClockCannotRecordReturnBeforeCheckout
Sender called twice on successNotificationMockitoTest, Chapter06Checks, an availability test

A Real Gap Found

  • The rule table says loan snapshots are unmodifiable, but no test checked it.
  • Replacing List.copyOf(loans.values()) with a plain ArrayList passed the other tests.
  • One assertion was added (in checkoutAndReturnUseTheSuppliedClock), which now rejects that fault.

The Mutant That Survives

  • An enquiry that adds a temporary record and removes it again.
  • With identifiers derived from the list size, it leaves no trace: records, notices and next identifier end exactly as before.
  • In a sequential program, no test can tell it from the correct one. Only a concurrent observer could.
  • Response: reject the design by reading it; do not claim a test can catch it. The identifier example is still worth keeping.

Do Not Let Tests Reproduce the Same Misunderstanding

  • An AI-generated implementation and AI-generated tests may agree because both assume inclusive end dates. Agreement is not independent evidence.
  • Check expected values against the human-reviewed convention and concrete examples.
  • Avoid expected values derived from the result: an identifier equal to the identifier in the same returned object is no independent fact. For the first reservation, an explicit R1 is meaningful.

Avoid Unnecessary Implementation Coupling

  • A test asserting the query calls private hasConflict exactly once fails under a different correct structure.
  • The public result and state contract should survive refactoring.
  • Exact text assertions are fine when text is part of the existing contract, but wording changes then require deliberate expectation updates.

When a Test Fails, Classify the Failure

  • A requirement misunderstanding.
  • An implementation defect.
  • An outdated expectation.
  • An environment or configuration problem.
  • Test fragility.
  • A green build obtained by suppressing the assertion’s exit status answers none of these.

7.8 Preparing the Milestone 2 Evidence

Demonstrate an outcome with its boundaries


The Demonstration Journey

Available before reservation: true
Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Available after reservation: false
Loan active after checkout: true
Loan active after return: false
Accepted-reservation notices: 1
  • Fixed clock; one coordinator and loan service. Checkout and return send no new notices.

What the Demonstration Is and Is Not

  • A concrete example of a bounded MVP increment. Your assessed domain and scope may differ.
  • No remote delivery, database or hosted execution.
  • Another person should be able to reproduce it and explain what remains outside its claim.
  • A hosted CI run is separate evidence from the local Maven runs.

Milestone 2 Evidence (1 of 3)

EvidenceWhat it should establish
Working MVP demonstrationA coherent user outcome runs from documented setup
Acceptance mappingSelected examples connect to executed tests
TDD sequenceIntended failure, implementation and protected refactoring are visible

Milestone 2 Evidence (2 of 3)

EvidenceWhat it should establish
OO/SOLID rationaleCollaborators and responsibilities have concrete reasons
Double choiceControlled behaviour or interaction is justified
Build resultActual tests ran against the intended revision

Milestone 2 Evidence (3 of 3)

EvidenceWhat it should establish
Review and retrospectiveReal feedback, a change in understanding and a next action are recorded
LimitationsIn-memory storage, lifecycle boundaries and missing integrations are explicit
  • A test report is not the whole milestone.

Make the Retrospective Useful

  • Connect an observation to an action.
  • Example (record it only if it happened): availability tests silently assumed the machine’s current date, so all time dependencies become explicit.
  • Avoid pre-filled success narratives: “we collaborated well” explains nothing.
  • Distinguish a proposed improvement from one already tested.

7.9 Keeping Implementation and Expected Behaviour Apart, with AI

Do not let outputs validate each other


Use the Reviewed Examples as Authority

  • Ask an AI assistant for a candidate query implementation.
  • Separately ask for omitted boundary cases.
  • Evaluate both outputs rather than letting them validate each other.
  • Chapter 15 collects the recurring AI workflows. This activity is self-contained.

Ground Rules for the Activity (1 of 2)

For this activity
Your goalSeparate a proposed implementation from the evidence that would accept or reject it
You start withAQ-01 to AQ-05 as reviewed, and Chapter 6’s coordinator
What is in boundsEvaluate the candidate implementation and its tests; do not treat either as proof of the other

Ground Rules for the Activity (2 of 2)

For this activity
How to checkThe reviewed examples, the labelled wrong-boundary mutation, the build result
How to decideA verdict with independent evidence for each claim
No AI account?The ready-made example is enough

The Ready-Made Example

Author-written fictional AI output.

An enquiry can call reserve and remove the temporary record. Treat the end date as inclusive so adjacent bookings conflict. A test is sufficient if the method returns without throwing. Since the implementation and generated tests agree, coverage will prove correctness. For time-sensitive loan tests, use the current date and wait until tomorrow to test the next state.


Model Critique (1 of 2)

ClaimVerdictIndependent check or correction
Query by reserve-and-removeRejectreserve sends a notice and can be refused by limits; adding and removing directly leaves no trace sequential tests can see, so reject by reading
Inclusive end means adjacency conflictsRejectThe agreed interval is half-open; [20,22) and [22,23) do not overlap
No exception is sufficientRejectAn always-true or always-false method can return normally and be wrong

Model Critique (2 of 2)

ClaimVerdictIndependent check or correction
Implementation and tests mutually prove correctnessRejectBoth may share the same mistaken convention
Coverage proves correctnessRejectExecution does not validate expected values or sensitivity to defects
Use the real date and wait for tomorrowRejectSupply a fixed or controlled clock and an explicit zone
  • The wrong-boundary experiment is a concrete verification step: no need to ask another AI whether the mutant is wrong.

7.10 Testing Habits That Mislead

Eight to avoid


Mistakes 1 to 3

MistakeConsequenceBetter action
Count a dependency-download error as TDD redBehaviour was never examinedReach the intended assertion failure
Write expected values by calling the implementationReproduces the same mistakeUse independently reviewed examples
Mock the object under testReplaces the behaviour being investigatedKeep the subject real

Mistakes 4 to 6

MistakeConsequenceBetter action
Use real time for a deterministic date ruleResults change with date or zoneInject an explicit clock
Treat every recorder as the same double roleHides why it is usedDescribe its role in the particular test
Verify private call countsBreaks harmless refactoringObserve public behaviour or meaningful boundary interactions

Mistakes 7 and 8

MistakeConsequenceBetter action
Remove old checks during tool migrationLoses existing evidenceBridge or migrate them explicitly
Call the bounded demo production-readyConceals lifecycle and integration gapsState what the MVP actually demonstrates

7.11 Try It Yourself

A test-first change and Milestone 2 evidence


What You Start With, and the Demonstration

  • You start with: Chapter 6’s version, the reviewed query criteria, the new loan-flow rules and the companion’s Maven/JUnit setup.
  • Demonstration: run the labelled red stage, inspect the precise failure, apply the green implementation, extract conflict detection, rerun the new test and the legacy bridge.
  • Guided practice: convert one policy boundary table into parameterised examples and discuss which incorrect result each row distinguishes. Compare a hand-written recorder with the Mockito interaction test.

Independent Work: Steps 1 to 3

  1. Reproduce the supplied TDD cycle, or conduct a small new one. Record the actual sequence.
  2. Complete the remaining query examples, including side effects and adjacency.
  3. Demonstrate the loan flow with controlled dates and rejection of an invalid return.

Independent Work: Steps 4 to 6

  1. Choose a substitute for one collaborator and justify its role.
  2. Run the labelled wrong-boundary implementation in a separate copy, identify the failing assertion and restore the source.
  3. Run the full build and map selected acceptance examples to named tests.

Independent Work: Steps 7 to 9

  1. Rehearse the MVP demonstration from documented instructions, obtain actual review feedback and write a short retrospective with a next action.
  2. On your team’s project, take one backlog behaviour and develop it test-first. Record the actual red failure, the green change and the refactoring. Do not reconstruct a sequence afterwards.
  3. On your team’s project, work through the Milestone 2 checklist: rehearse your own demo, get review feedback, write a retrospective. Record which items are complete, which are gaps and who will close each one.

Expected Output and Completion

  • Output: runnable source; focused tests; TDD and mutation evidence; double rationale; actual build result; demonstration notes; review and retrospective notes; explicit limitations; the step 8 and 9 records.
  • Complete when: the failure is behavioural and understood; query results and state effects match the examples; time-sensitive tests do not depend on the actual day; the whole regression suite executes; the demo includes reservation, checkout and return.
  • If you want more: sketch how completed reservations should affect counts and early-return availability. Do not silently implement it.

What the Build Establishes

  • 35 JUnit invocations: one TDD test, eight additional availability invocations, ten policy rows, nine loan invocations, three Mockito tests, four legacy wrappers.
  • The expected output ends Tests run: 35, Failures: 0, Errors: 0, Skipped: 0 and BUILD SUCCESS.
  • An execution inventory, not a claim of exhaustive correctness or a quality score.

Versions Pinned for This Chapter

ToolTested selectionRole
JavaJava 17 language/API targetApplication and test execution
Maven3.9.11Build and dependency resolution
Compiler Plugin3.14.1Java compilation
Surefire Plugin3.5.4JUnit Platform execution
JUnit Jupiter5.13.4Assertions, discovery, parameterised cases
Mockito5.20.0Interface mock, stubbing, verification

Acceptance Mapping (1 of 2)

Requirement or riskExecutable evidence
AQ-01 empty schedule availableemptyScheduleIsAvailableWithoutAnySideEffect
AQ-02 overlap unavailableAvailabilityTddTest overlapping enquiry
AQ-03 adjacent periods allowedadjacentPeriodAndDifferentItemAreAvailable
AQ-04 different itemthe same test checks MIC1 separately
AQ-05 equal or reversed intervalparameterised invalidPeriodFailsWithoutChangingState

Acceptance Mapping (2 of 2)

Requirement or riskExecutable evidence
Enquiry does not allocate an IDqueryDoesNotAllocateAnIdentifier
Availability differs from eligibilitytwo tests: count limit and a thirty-day enquiry
Checkout window excludes its endtwo invocations of checkoutOutsideHalfOpenPeriodIsRejected
Local date depends on clock zoneclockZoneDeterminesTheCheckoutDate
Backward time cannot predate checkoutbackwardClockCannotRecordReturnBeforeCheckout

Suggested Evidence Packet (1 of 2)

DocumentUseful content
Scope noteSelected query and loan rules; synthetic inputs; lifecycle limitations
TDD recordActual red message, same test at green, refactoring diff, regression result
Acceptance mappingNamed tests tied to concrete examples
Double rationaleWhich collaborator is replaced, why, and what observation it enables

Suggested Evidence Packet (2 of 2)

DocumentUseful content
Mutation recordIsolated faulty comparison, selected failure and restored pass
Build resultExecuted revision, tool versions, actual report
DemonstrationReproducible steps from empty state through reserve, checkout, return
Review and retrospectiveActual observations, corrective action, a next-iteration experiment
  • A copied sample table is not evidence that a team ran the tests or conducted a review.

7.12 Questions to Think with, and Midterm Practice

Concept, diagnosis, transfer, and the practice set


Concept Questions

  1. Why is “the method did not throw” insufficient evidence for an availability query?
  2. What distinguishes the red stage of the worked cycle from a missing-dependency failure?
  3. When does the notification recorder act as a spy, and why can a framework mock serve a similar purpose?
  4. Why do both the instant and zone matter when testing a local-date checkout rule?

Diagnosis Questions

  1. A test calculates its expected availability by calling the same overlap helper used by production code. Both treat adjacency as overlap. Explain why the green result is weak and replace it with an independent example.
  2. A loan test passes alone but fails after another test, and both use a static shared loan service. Diagnose the likely cause and propose a setup strategy that still allows intentional multi-step scenarios.

Guidance: Questions 1 and 2

  1. A valid query must distinguish available from conflicting intervals and preserve state. Always-true returns normally but fails AQ-02; always-false fails AQ-01 and AQ-03.
  2. The worked red run discovered and executed the named test, reached the assertion and reported false expected but true actual. A dependency-resolution failure never establishes application behaviour.

Guidance: Questions 3 and 4

  1. When the test examines its received records, the recorder provides observed interaction history: a spy. A Mockito mock can record and verify too. The classification depends on the test’s purpose and observation.
  2. One instant gives different dates in different zones. The test must state both when the rule concerns a local date. The Baghdad example fixes the interpretation.

Guidance: Questions 5 and 6

  1. The test derives its expectation from the same faulty rule, so the two agree without independent validation. Book [2026-10-20, 2026-10-22), enquire [2026-10-22, 2026-10-23), explicitly expect true, and run the mutant to show this rejects the inclusive comparison.
  2. Earlier tests may have inserted or returned loans that alter later tests’ starting state. Use a fresh coordinator, sender and loan service per independent scenario. A multi-step lifecycle test may share its own service while advancing a controlled clock.

Transfer Challenge: Room Booking

A room-booking service permits availability enquiry and check-in only during the booked interval.

  • Design three acceptance examples, one clock-controlled test and one collaborator substitute.
  • Explain which expectations transfer from equipment lending and which rules need stakeholder clarification, such as cleaning time or late check-in.

Transfer Guidance

  • Examples: an empty schedule is available; an overlapping booking makes an enquiry unavailable without adding a booking; an enquiry starting at the previous exclusive end is available if no turnaround rule applies.
  • Check-in: fixed clock at the booking start: accept; at the exclusive end: reject if that is the agreed policy. A recorder observes one check-in notice.
  • Clarify cleaning intervals, early arrival, late check-in, no-show expiry, who authorises entry.
  • Represent the time zone and choose an appropriate time type. Do not copy LocalDate merely because the earlier project used whole days.

Midterm Practice Set: What It Covers

  • The midterm covers Chapters 2 to 7 (Weeks 1 to 6).
  • According to the module descriptor, it asks you to:
    • model a scenario with UML class diagrams,
    • identify SOLID violations and smells in supplied code,
    • refactor short snippets by introducing interfaces and dependency injection,
    • write unit tests with test doubles for a small component.
  • Do not add Chapter 8’s patterns. Whether Mockito is allowed in the exam is the module leader’s decision; hand-written doubles work anywhere.

Task 1: UML

Draw a class diagram. Include multiplicities, one composition, one interface realisation and one dependency, and write down one rule your diagram cannot express.

  • A library lends laptops. Each laptop has an asset tag and a model.
  • A loan links one borrower to one laptop and records a start date and a due date.
  • A borrower and a laptop can each appear in many loans over time.
  • A loan can be renewed any number of times; a renewal records a new due date and has no meaning without its loan.
  • When a loan is overdue, a notice is delivered through a channel: email today, other channels later.

Task 1: One Model

Borrower-String nameLaptop-String assetTag-String modelLoan-LocalDate startedOn-LocalDate dueOn+notify(NoticeChannelchannel) voidRenewal-LocalDate newDueOn«interface»NoticeChannel+deliver(Stringmessage) voidEmailChannel10..*borrows10..*is lent in10..*hasnoticesimplements

Task 1: Reading the Model

  • A borrower and a laptop each link to any number of loans over time: 0..* at the loan side.
  • A renewal cannot exist without its loan: composition.
  • The channel is an interface because other channels are expected; Loan depends on the interface, not on EmailChannel.
  • A rule such as “a laptop is on at most one active loan” cannot be expressed by multiplicity: write it as a note or invariant.
  • Penalise inheritance for “has”, a solid line for realisation, a composition diamond between Borrower and Loan.

Task 2: Diagnose This Class

public class OverdueReminder {
    public boolean remind(String borrowerEmail, LocalDate dueDate, String itemName) {
        if (LocalDate.now().isAfter(dueDate)) {
            MailClient client = new MailClient("smtp.campus.example", 25);
            client.send(borrowerEmail, "Overdue: " + itemName,
                        "Please return " + itemName + ".");
            System.out.println("Reminder sent to " + borrowerEmail);
            return true;
        }
        return false;
    }
}
  • MailClient opens a real network connection: assume you cannot call it from a test.

Task 2: A Strong Diagnosis

  • Three jobs mixed: deciding overdue, building and sending the message, reporting to the console (single responsibility).
  • Reads the system clock directly: the result depends on the date the test runs.
  • Constructs MailClient with a hard-coded host and port: a concrete provider it cannot swap (dependency inversion), and untestable without a mail server.
  • The boolean does not distinguish “not overdue” from “sent”.
  • Connect each defect to a consequence: “the overdue path cannot run in a test; here it throws IllegalStateException from MailClient”.

Task 3: Refactor It

public interface ReminderSender {
    void send(String to, String subject, String body);
}

public final class OverdueReminder {
    private final Clock clock;
    private final ReminderSender sender;

    public OverdueReminder(Clock clock, ReminderSender sender) { /* null checks */ }

    public boolean remind(String borrowerEmail, LocalDate dueDate, String itemName) {
        if (!LocalDate.now(clock).isAfter(dueDate)) return false;
        sender.send(borrowerEmail, "Overdue: " + itemName, "Please return " + itemName + ".");
        return true;
    }
}

Task 3: What Was Preserved

  • A MailReminderSender adapts the supplied MailClient and keeps the console message, so behaviour is preserved.
  • The composition root would call new OverdueReminder(Clock.systemDefaultZone(), new MailReminderSender(new MailClient("smtp.campus.example", 25))).
  • The constructor is a deliberate API change; the observable behaviour of remind is preserved.
  • No credit for adding an interface to MailClient (you do not own it) or for a factory nobody needs.

Task 4: Tests With a Double

@Test void noReminderOnTheDueDateItself() {
    assertFalse(reminderOn("2026-10-20T23:00:00Z").remind("[email protected]", DUE, "CAM1"));
    assertTrue(sender.calls.isEmpty());
}
@Test void exactlyOneReminderTheDayAfterTheDueDate() {
    assertTrue(reminderOn("2026-10-21T00:00:00Z").remind("[email protected]", DUE, "CAM1"));
    assertEquals(List.of("[email protected]|Overdue: CAM1|Please return CAM1."), sender.calls);
}
  • A hand-written spy records calls; the clock is fixed; expected values come from the scenario.
  • Also test an earlier date. State which role your double plays.

Task 5: Trace a Rejected Reservation

  • Trace, in order: argument validation, borrower count, policy decision, conflict checking.
  • A rejection occurs before storage and notification.
  • Choose observations that show it: the rejection outcome, unchanged records, no sender interaction.
  • Identify the role of the injected sender.
  • A good answer connects design, behaviour and evidence rather than listing acronyms.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • Meaningful tests distinguish intended behaviour from plausible mistakes.
  • The worked cycle makes one missing rule fail visibly, implements it and protects the following structural change.
  • Controlled collaborators and clocks make outcomes reproducible; carefully chosen assertions keep tests useful during refactoring.

Where the Project Stands

  • Reservation availability enquiry.
  • A bounded checkout and return flow.
  • Lifecycle and integration limits remain explicit.

Next: Chapter 8

  • Construction and compatibility patterns.
  • They build on these tests, rather than replacing design judgement with pattern names.

Sources and Further Reading

  1. JUnit Team: JUnit 5 User Guide, 5.13.4.
  2. Apache Maven: Surefire with the JUnit Platform; Maven 3.9.11; Compiler Plugin.
  3. Fowler: Test Driven Development; Test Double (2006).
  4. Oracle: Java SE 17 Clock; Record Classes.
  5. Mockito Team: API documentation; Mockito 5.20.0.
  6. Martin: Agile Software Development (2002). Beck: Test-Driven Development: By Example (2002).
  • Full references and links are in the chapter text.

8 - Chapter 8: Creating and Connecting Objects

Object Oriented Development and Agile Practices (SE506)

Chapter 8: Creating and Connecting Objects

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 8.1 When does creating an object become a design problem?
    • Creation, compatibility and added behaviour are different problems.
  • 8.2 Factory Method: letting subclasses choose what to create
    • An overridable creation hook inside an inherited workflow.
  • 8.3 Abstract Factory: products that must belong together
    • Related product roles that must vary together.
  • 8.4 Builder: configuring a value you can trust
    • Configure, validate, publish an immutable value.

Chapter Roadmap (2 of 3)

  • 8.5 Adapter: translating an outside system into your own terms
    • The one pattern wired into the project: a directory adapter.
  • 8.6 Decorator: adding behaviour around an operation
    • Optional behaviour around an operation; order and failure matter.
  • 8.7 When the simpler option is the better one
    • A decision another developer can challenge.
  • 8.8 Classifying an AI’s pattern suggestions before accepting them classify before accepting.

Chapter Roadmap (3 of 3)

  • 8.9 Pattern habits that mislead
  • 8.10 Try it yourself: the directory boundary and pattern exercises
    • Nine steps; step 9 is on your team’s project.
  • 8.11 Questions to think with, and a transfer challenge
    • A library application with numeric category codes.
  • What to take away, and what comes next
    • Chapter 9: changing behaviour with behavioural patterns.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Identify a concrete construction or interface-compatibility problem.
  2. Distinguish Factory Method, a simple factory and Abstract Factory by their responsibilities.
  3. Build a validated immutable request through a staged construction interface.

Learning Outcomes (2 of 2)

  1. Adapt provider records, absence and failures into an application-owned contract.
  2. Compose decorators and predict the effects of order and exception handling.
  3. Justify adopting or declining a pattern using tests and change costs.

Module Connection and Prerequisites

  • Supports LO2. The midterm remains on Chapters 2 to 7: these patterns are not added to that scope.
  • Prerequisites: Chapters 5 to 7: contracts, interfaces, constructor injection, composition, meaningful tests. Java 17 and the Chapter 7 Maven/JUnit setup.
  • Readings: the Factory and Adapter chapters of Martin, and the creational and structural patterns in Gamma et al.
  • All provider records are synthetic. No real directory, network service or personal data.

How to Use This Chapter

  • Core reading: 8.1, 8.2, 8.5 and 8.7. Factory Method and Adapter are the two suggested live demonstrations.
  • Working-knowledge reading: 8.3 (Abstract Factory), 8.4 (Builder), 8.6 (Decorator). Read each, run its example and, for the decorator, predict both audit histories before you run the tests. Working knowledge is not optional, but asks for understanding, not independent implementation.
  • Minimum lab: steps 1 to 3, 5 and 9. This is midterm week: do the minimum lab first.
  • If you want more: steps 4 and 6 to 8.

Opening Problem

  • The office wants borrower categories to come from a university directory, not supplied by the calling code.
  • The proposed directory returns records containing S, T, Y and N.
  • It returns null when a person is missing and throws its own exception when lookup fails.

Two Proposals, Neither Argued

  • Proposal 1: put these details inside ReservationCoordinator. It spreads external conventions into reservation behaviour.
  • Proposal 2: a factory, builder, adapter and decorator around every class. It introduces structures before identifying what must vary.
  • The useful question: which responsibility needs a boundary, and what is the smallest design that makes that boundary clear?
  • The answer adds one justified Adapter to the running project.

8.1 When Does Creating an Object Become a Design Problem?

Three problems that all get called “factory”


Start With the Ordinary Constructor

  • Creating a LoanPeriod with two required dates is clear: the constructor validates a small invariant.
  • Replacing it with a hierarchy of factories would not improve the rule.
  • Chapter 6’s ReservationApplication.create(sender) is a useful assembly function: it connects known policies, a formatter and a supplied sender.

When Construction Becomes Troublesome

  • The decision about which object, which compatible group, or which configuration is repeated across places that should not own it.
  • A caller needs only an export operation yet inspects an output-format flag each time.
  • Another picks a heading renderer and a body renderer independently and combines incompatible formats.
  • A third passes six positional arguments whose meanings are hard to review.

Different Problems, One Misleading Name

  • Giving all three solutions the name “factory” conceals which responsibility changed.
  • A pattern name should shorten a precise explanation after the problem is understood. It should not replace that explanation.

Creation, Compatibility, Added Behaviour

PatternConcerned with
Factory Method, Abstract Factory, BuilderConstruction decisions
AdapterConnecting an existing interface to the one a client needs
DecoratorAttaching an optional responsibility while keeping the component interface
  • These categories organise discussion. Actual code must still be examined for intent and consequences.

The Directory Request Is a Translation Problem

  • The awkwardness is not that constructing a directory object needs many arguments.
  • It is that its data and failure meanings differ from the application’s.
  • A builder would not answer what T means or whether an outage is a missing borrower. Those are translation decisions.
  • Similarly, an audit wrapper around a sender adds behaviour without choosing the sender’s class. Calling it a factory distracts from call order and errors.

State the Change Pressure Before the Structure

A useful design note names:

  1. the current simple solution,
  2. the change it handles poorly,
  3. the selected boundary,
  4. the cost of adding it.
  • Here: passing a trusted category worked for local examples; provider codes now need central translation; an application-owned directory interface isolates it; additional types and mapping tests are the cost.

Do Not Predict Every Future Directory

  • There is already one incompatible interface.
  • Inventing five hypothetical provider families is not evidence that an Abstract Factory is needed today.
  • Keep construction visible at the composition point: hiding it in a global registry can make dependencies harder to discover.
  • The goal is understandable ownership, not a ban on constructors.

8.2 Factory Method

Letting subclasses choose what to create


A Simple Factory Is Often Enough

  • The isolated export example: validate an input, obtain an exporter, execute it.
  • A simple factory can choose a plain-text or HTML exporter from a flag. That is often enough when one assembly location owns the choice.
  • The Factory Method scenario assumes subclasses must specialise product creation while keeping the workflow inherited from a common creator.
  • The pressure is an extension point in the workflow, not merely two product classes.

The Structure

public abstract static class ExportJob {
    protected abstract Exporter createExporter();

    public final String run(String value) {
        Objects.requireNonNull(value, "value");
        return createExporter().export(value);
    }
}
  • run owns the workflow and is final. The protected hook is the deliberate variation point.

The Two Creators

public static final class PlainJob extends ExportJob {
    @Override protected Exporter createExporter() { return value -> "TEXT: " + value; }
}
public static final class HtmlJob extends ExportJob {
    @Override protected Exporter createExporter() { return value -> "<p>" + escape(value) + "</p>"; }
}
  • The products are lambdas in this compact example. They still implement the product interface.

Figure 8.1: Factory Method

«abstract»ExportJob+run(value) String#createExporter() ExporterPlainJobHtmlJob«interface»Exporter+export(value) Stringcreates and usesextends

Reading the Notation

  • # marks a protected member: subclasses can override createExporter, outside callers cannot call it.
  • <<abstract>> marks a class that cannot be instantiated on its own.
  • A solid line with a hollow triangle is inheritance.
  • The dashed arrow is a dependency: ExportJob creates and uses exporters without keeping one in a field.
  • It is Factory Method because subclasses vary creation inside an inherited workflow.

Compare With a Simple Factory Honestly

public static Exporter simpleExporter(boolean html) {
    return html ? value -> "<p>" + escape(value) + "</p>"
                : value -> "TEXT: " + value;
}
  • An ordinary selection expression. A simple factory, not Factory Method.
  • A static method named create is not sufficient evidence of the pattern.

Same Output, Different Mechanism

new PatternExamples.PlainJob().run("R1");
PatternExamples.simpleExporter(false).export("R1");
  • Both can produce TEXT: R1. Identical output does not make their extension mechanisms identical.
  • One central selection function changes when an option is added; the other introduces a concrete creator subclass.
  • Code structure and change requirements settle the classification.

Factory Method: Test, Trade-Off and Non-Use

  • Tests exercise both concrete jobs and an instrumented creator whose hook returns a distinctive exporter: the inherited workflow uses the chosen product; null input is rejected before export.
  • The hook-count assertion documents this tiny example. It is not advice to verify private call counts throughout the application.
  • Factory Method gives an explicit extension point but creates inheritance coupling: readers follow the inherited workflow and its override.

When to Decline It

  • A constructor accepting an Exporter can be simpler when callers already know the product and need no creation hook.
  • So the running project keeps ReservationApplication.create(sender) as a simple assembly function.
  • We do not convert it into a creator hierarchy solely to display the pattern.
  • The separate example demonstrates the mechanism; the design decision explains when to decline it.

8.3 Abstract Factory

Products that must belong together


The Problem

  • A receipt has two product roles: a heading renderer and a body renderer.
  • Plain text needs plain versions of both; an HTML fragment needs HTML versions of both.
  • Independent format flags can accidentally select an HTML heading and a plain body.
  • A simple attempt is one renderer that switches format for every part: for two tiny operations that may remain the best implementation.

The Structure

  • ReceiptFamily exposes heading() and body().
  • PlainFamily supplies plain implementations of both roles; HtmlFamily supplies HTML ones.
  • The receipt function obtains both products from the supplied family and combines them. No format switch.
  • The essential distinction is the related set of product roles. Abstract Factory is not just a factory declared with Java’s abstract keyword.

The Family

«interface»ReceiptFamily+heading() Heading+body() Body«interface»Heading+render(text) String«interface»Body+render(text) StringPlainFamilyHtmlFamilycreatescreatesreceipt(family, title, detail) uses only the interfaces: it holds no format switch.

Family Consistency Is Encouraged, Not Proven

  • Our types encourage consistency by accepting one factory for a receipt.
  • They do not prevent someone writing a defective factory that returns a mixed pair.
  • Tests and review still establish that the concrete family fulfils its promise.

Test the Relationship

  • Render a complete receipt with each family: Loan and R1 as separate text lines in one; an HTML heading and paragraph in the other.
  • An angle-bracket input checks the HTML text-node escaping (&, <, >). These are fragments, not a website or a general sanitiser.
  • A test that merely asserts both product references are non-null would miss a mixed-format family.
  • The expected complete result observes the relationship that motivated the design.

Trade-Off: Which Axis Becomes Easy?

  • Adding another family (say Markdown) can leave the client workflow unchanged: one new implementation supplying both roles.
  • Adding a new product role (a footer) changes the factory contract: every family must supply it.
  • One axis of extension becomes convenient by making another more coordinated.

Do Not Infer a Family From Proximity

  • A clock and a borrower directory constructed near each other do not form a compatible product family.
  • One sender with two implementations does not justify two product roles invented for the diagram.
  • In the lending version, the small assembly function remains sufficient.
  • If the real receipt stays this small, one formatter per output style is a reasonable conclusion.

8.4 Builder

Configuring a value you can trust


The Problem: Configuration Hard to Read

  • A report request: a required borrower identifier, an optional date interval, a returned-loan option, an ordered list of column labels.
  • A positional constructor works, but calls with nulls, a boolean and a list become hard to review as options grow.
  • A sequence of setters on the final request lets clients observe an incomplete request and change it after submission.

The Structure

var request = ReportRequest.forBorrower("B1")
        .during(start, start.plusDays(7))
        .includeReturned(true)
        .column("item")
        .column("date")
        .build();
  • Named operations configure a mutable builder; build() produces an immutable request.

Configure, Validate, Publish

Builder (mutable, one construction sequence)forBorrower("B1").during(start, start.plusDays(7)).includeReturned(true).column("item").column("date")build()validate,copy listsReportRequestimmutableborrowerIdfrom / toExclusiveincludeReturnedcolumns = List.copyOf(...)

What Kind of Builder This Is

  • A fluent builder with one product representation.
  • The classical Builder discussion also separates construction steps from representations and, when useful, a director controlling the steps. This example does not claim that.
  • Chaining methods alone is insufficient: what matters is staged construction followed by a validated, independent product.

Defaults and Invariants

  • The required identifier must be nonblank.
  • Omitting dates means no date restriction; supplying dates requires a positive half-open interval.
  • Returned loans are excluded by default.
  • Column labels form an ordered list, empty by default. The example stores labels but implements no report generation.

Validation Before Publication

  • build() checks the interval; the constructor copies the builder’s column list with List.copyOf.
  • Further builder changes cannot change a previously built request.
  • The builder is mutable and used for one sequence at a time. An immutable product does not make its builder thread-safe.
  • Settings remain until changed if the builder is reused.

The Constructor’s Checks

if ((builder.from == null) != (builder.toExclusive == null)) {
    throw new IllegalArgumentException("Both dates or neither are required");
}
if (builder.from != null && !builder.from.isBefore(builder.toExclusive)) {
    throw new IllegalArgumentException("Positive interval required");
}
columns = List.copyOf(builder.columns);
  • No partially valid request is ever returned.

Builder: Test, Trade-Off and Non-Use

  • Tests check defaults, explicit options, invalid dates, and the independence of two products built before and after another column is added. Attempting to modify a product’s columns must fail.
  • Checking only that chained calls compile would not detect an alias to mutable builder state.
  • Costs: another type, more methods, a distinction between temporary state and a valid value.
  • A small constructor with two meaningful required parameters is often clearer: LoanPeriod(start, endExclusive) remains a constructor call.

8.5 Adapter

Translating an outside system into your own terms


The Problem

  • The provider exposes lookup(key) and returns Person(key, kind, status).
  • The application wants find(borrowerId) returning a domain Borrower when an eligible record exists.
  • The mismatch includes data meaning and failure behaviour, not only method names.
  • Provider codes inside the coordinator would make reservation logic depend on their spelling; repeating translation in callers risks inconsistent defaults.

An Object Adapter

  • It implements the application interface while holding the incompatible provider collaborator.
  • The provider interface here is a local model of an external contract, not an HTTP client. A map-backed example makes every case reproducible.
  • A real client later needs provider-specific integration tests.

Agree the Translation Contract First (1 of 2)

Provider outcomeApplication outcome
Same identifier, kind S, status YPresent student borrower
Same identifier, kind T, status YPresent staff borrower
Null rowEmpty result: borrower absent
Recognised kind, same identifier, status NEmpty result: borrower inactive

Agree the Translation Contract First (2 of 2)

Provider outcomeApplication outcome
Unknown or null kind or status; mismatched identifierDirectoryException: invalid provider data
Provider’s declared FailureDirectoryException retaining the cause
Unexpected programming exceptionPropagates; never converted into absence
  • Codes are exact and case-sensitive. T means staff only because this teaching contract says so. No trimming, normalising or defaulting.

Error Precedence Is Part of the Contract

  • The adapter validates the returned identifier and category before interpreting inactivity.
  • So an inactive row with an unknown kind is still malformed: Person("B1", "X", "N") is an error, not an empty result.
  • Covered by a test; another policy would need an explicit decision.

The Application-Owned Contract

  • BorrowerDirectory belongs to the application’s vocabulary; LegacyDirectoryAdapter implements it and delegates to LegacyDirectoryClient.
  • Borrower is an immutable identifier/category value.
  • Optional<Borrower> expresses a legitimate empty lookup; an exception expresses inability to obtain a trustworthy result.
  • Optional represents presence or absence. It does not define which failures should become absence.

The Adapter’s find (1 of 2)

LegacyDirectoryClient.Person row;
try {
    row = client.lookup(borrowerId);
} catch (LegacyDirectoryClient.Failure failure) {
    throw new DirectoryException("Borrower directory unavailable", failure);
}
if (row == null) return Optional.empty();
if (!borrowerId.equals(row.key())) throw new DirectoryException("Mismatched borrower id");

The Adapter’s find (2 of 2)

BorrowerCategory category;
if ("S".equals(row.kind())) category = BorrowerCategory.STUDENT;
else if ("T".equals(row.kind())) category = BorrowerCategory.STAFF;
else throw new DirectoryException("Unsupported borrower category");
if ("N".equals(row.status())) return Optional.empty();
if (!"Y".equals(row.status())) throw new DirectoryException("Unsupported borrower status");
return Optional.of(new Borrower(row.key(), category));

The New Entry Point

  • DirectoryReservationService validates the local identifier, item and date, looks up the borrower, and calls the existing coordinator only after obtaining a valid domain borrower.
  • The caller supplies no category to this entry point.
  • The coordinator still owns borrowing limits, interval conflicts, storage and notification.

The Service’s reserve

Objects.requireNonNull(start, "start");
var found = directory.find(borrowerId);
if (found.isEmpty()) return "Rejected: borrower not found or inactive.";
Borrower borrower = found.get();
if (!borrowerId.equals(borrower.id())) {
    throw new DirectoryException("Directory returned a different borrower");
}
return coordinator.reserve(borrower.id(), itemId, start, days, borrower.category());

Reading a Sequence Diagram

  • A class diagram shows what exists; a sequence diagram shows what happens, in time order from top to bottom.
  • Each box at the top is a participant; the vertical line below it is its lifeline.
  • A solid arrow is a call; a dashed arrow is a return.
  • A frame labelled alt, split by else, shows alternative paths: exactly one happens in a given run.
  • A note spans the participants it concerns.

Figure 8.2: An Active Borrower

ReservationserviceDirectoryadapterProviderclientCoordinatorfind(borrowerId)lookup(borrowerId)Person(B2, T, Y)Borrower(B2, STAFF)reserve(..., STAFF)existing reservation outcome

Figure 8.2: Missing, Inactive, or Failed

ReservationserviceDirectoryadapterProviderclientCoordinatorfind(borrowerId)lookup(borrowerId)altnull, or status NOptional.empty()no reservation, no notificationelse declared FailureFailureDirectoryException (cause kept)no reservation, no notification

What the Sequence Highlights

  • The point before which reservation state must remain untouched.
  • Malformed provider data follows the same no-mutation boundary as a provider failure, though its exception describes a different cause.
  • Walk through reserve("B2", "CAM1", 2026-10-20, 10): lookup("B2") returns Person("B2", "T", "Y"); T maps to STAFF; the unchanged coordinator applies the staff policy, stores R1, formats and makes its one notification attempt.

The Demonstration Output

Confirmed: R1 for B2 on CAM1 [2026-10-20, 2026-10-30).
Rejected: borrower limit or duration.
Rejected: borrower not found or inactive.
Reservations: 1
Notices: 1
  • Staff B2 is allowed ten days; student B1’s ten-day request is rejected; the missing identifier creates no record; only the accepted request creates a notice.

A Data-Backed Collaborator

  • The directory is the book’s data-backed collaborator: the component that stands outside the application and answers from its own data.
  • Chapter 1 promised it; Chapter 6 previewed its contract; Chapter 7 showed three doubles; this chapter builds the adapter; Chapter 10 places it in the application structure; Chapter 13 adds timeouts and an explicit fallback-or-failure policy.
  • Here the provider is a synthetic in-process example: no transport, timeout or storage to fail.

The Same Questions for a Model

  • Any component returning data or predictions is in the same position, including one that calls a trained model.
  • What does an empty answer mean? What counts as a failure? Which outputs will you refuse to trust? How will a test substitute the component?
  • For an ML model-serving domain: your adapter sits between your service and the model runtime. Map its label codes to your own types; treat a missing prediction and a timeout as different outcomes; reject values outside the documented range.

Rejection Is Not Failure

  • A missing or inactive borrower produces the explicit rejection string.
  • A directory outage throws DirectoryException.
  • Returning “borrower not found” for both would make an operational failure look like a valid eligibility decision.
  • Defaulting to staff would be worse: a longer loan without a trusted category.

Order of Checks

  • The service delegates duration policy to the coordinator, so lookup happens before deciding whether ten requested days are allowed.
  • Local blank identifiers and a null start date fail before lookup.
  • A deliberate, documented order, not an accident of catch blocks.

An Entry Point, Not Authentication

  • Knowing an identifier does not establish who the caller is or whether they may reserve for that person.
  • The earlier coordinator APIs remain for trusted internal use, so callers can bypass the directory.
  • A real exposed application needs a controlled authenticated entry path and reviewed authorisation rules.
  • There is no caching, HTTP client, retry, persistence or cancellation in this increment.

Test the Boundary and Its Cost

  • Synthetic rows: both category mappings, absence, inactivity, unknown codes, null fields, identity mismatch, expected failure translation.
  • A service test: a staff record permits ten days; a student record rejects ten days using the same existing policies.
  • Failure tests inspect state and notices, then perform a valid request to show the failure did not consume R1.
  • Unexpected exceptions are not caught broadly.

The Cost of the Adapter

  • A mapping layer and a contract that must evolve with provider changes.
  • If two components already share the same interface and meaning, direct injection is sufficient.
  • If they disagree on essential capabilities, an adapter cannot supply them.
  • This implementation does not cache, retry, authenticate, persist or contact a real provider.

Acceptance Mapping (1 of 2)

PromiseExecutable evidence
Known active codes map correctlymapsKnownActiveCategories (two rows)
Missing and inactive rows yield absenceabsenceAndInactiveAreEmpty
Unknown or differently cased codes are not defaultedunknownCodesAreErrors (four rows)
Null fields, mismatch, malformed inactive rows are errorsmalformedRowsAreErrors

Acceptance Mapping (2 of 2)

PromiseExecutable evidence
Invalid identifiers never reach the providerinvalidIdentifierDoesNotReachProvider (null, empty, blank)
Expected outage translated with its causetranslatesExpectedFailureAndKeepsCause
Unexpected failure remains visibleunexpectedProgrammingFailureIsNotDisguisedAsAbsence
Directory category selects the duration policycategoryFromDirectoryControlsReservationPolicy
Absence and outage change nothing and keep R1absenceAndOutageDoNotStoreNotifyOrConsumeId

8.6 Decorator

Adding behaviour around an operation


The Problem

  • An isolated notification exercise needs optional audit observations and a single retry after a declared delivery failure.
  • Subclasses for every combination multiply quickly. Flags on one large sender combine responsibilities and hide order.
  • A Decorator implements the same interface as its wrapped delegate and adds behaviour around delegation.
  • Java’s FilterInputStream illustrates the wrapping mechanism: an input stream containing another input stream.

The Two Decorators

// AuditedSender.send
events.add("attempt");
try { delegate.send(message); }
catch (DeliveryFailure failure) { events.add("failed"); throw failure; }
events.add("sent");

// RetryOnceSender.send
try { delegate.send(message); }
catch (DeliveryFailure failure) { delegate.send(message); }

Order Changes the Observations

AuditedSenderRetryOnceSenderdelegate:fails once,then succeedsevents:attempt → sentone logical call · 2 delegate callsRetryOnceSenderAuditedSenderdelegate:fails once,then succeedsevents:attempt → failed → attempt → sentevery attempt audited · 2 delegate calls

Reading the Order

  • Audit outside retry: audit observes one logical call whose internal retry succeeds.
  • Retry outside audit: each attempt passes through audit separately.
  • Neither event sequence is universally correct. Decide whether the audit requirement concerns operations or individual attempts.

Failure Is Part of the Contract

  • If both attempts fail, the second failure propagates; no third attempt.
  • An unexpected IllegalArgumentException is not retried.
  • Catching every exception would confuse invalid input or programming defects with the declared retryable condition.

What the Stand-In Does Not Promise

  • The audit list is an in-memory observation example, not durable audit storage.
  • If a real audit sink can fail, decide whether to propagate that failure, preserve the original delivery failure, or report it separately.
  • A real sender may deliver and then lose the acknowledgement: a retry can duplicate the effect. This toy has no idempotency key.
  • “Sent” means only that the delegate returned normally.
  • A same-signature wrapper is not automatically behaviourally substitutable under every error policy.

Decorator: Test, Trade-Off and Non-Use

  • Tests assert both event sequences, the two-attempt bound, and propagation of expected and unexpected failures.
  • The decorators stay isolated. Chapter 6’s notification contract says one attempt, no automatic retry: installing this retry there would change that contract. A pattern name does not authorise it.
  • Use one straightforward sender when optional combinations are unnecessary. Decorators add another path to trace and can obscure which layer handles an error.

8.7 When the Simpler Option Is the Better One

A decision aid, not a rule


Compare by the Decision Isolated (1 of 2)

Observed pressureCandidateSimpler alternative
Subclasses must create a workflow’s productFactory MethodInject a ready product into the workflow
Related product roles must vary togetherAbstract FactoryOne formatter or one assembly function
Many named options configure an immutable valueBuilderA clear constructor or named creation method

Compare by the Decision Isolated (2 of 2)

Observed pressureCandidateSimpler alternative
Provider interface and meanings differAdapterDirect injection if the contracts already match
Optional responsibilities compose around one interfaceDecoratorOne explicit implementation with fixed behaviour
  • Explain who owns the variation, how failures travel, and how the structure changes future edits.

What the Running Project Gained

  • Chapter 8 adds the directory-backed reservation service, domain borrower and provider adapter.
  • The Chapter 7 coordinator, policies, availability query, loan service and notification implementations remain unchanged. The original test suite stays in the build.
  • New claims: a staff example can request ten days through a category-free entry point; a student cannot; missing or inactive borrowers are rejected before storage; provider failures are identifiable and leave state untouched.

The Inherited Limits Remain

  • Returned loans do not remove reservations, free their intervals or restore counted allowances.
  • The project still runs in memory within one process.
  • Pattern work does not resolve these separate requirements.
  • The build executes 63 JUnit invocations: 35 inherited, 17 directory-boundary cases and 11 pattern cases.

Record a Decision Another Developer Can Challenge

Provider codes and null/error conventions differ from the application. An object adapter translates them behind BorrowerDirectory. This keeps provider details out of reservation policy, at the cost of mapping tests and explicit provider-contract maintenance.

  • Then name what was declined.

What Was Declined

  • The assembly function needs no creator hierarchy.
  • No related provider product family is required.
  • The core value objects do not need builders.
  • Retry would change the notification promise.
  • Declining those structures is part of the design outcome.
  • If the only explanation is “this chapter teaches the pattern”, keep it in the teaching examples, not the running system.

8.8 Classifying an AI’s Pattern Suggestions Before Accepting Them

Names do not make patterns


Ground Rules for the Activity (1 of 2)

For this activity
Your goalClassify each suggested structure by what it actually does, and decide which are justified
You start withThe isolated pattern examples, the directory contract in 8.5 and the Chapter 6 notification contract
What is in boundsClassify, correct and decline; wire no pattern into the application without a requirement

Ground Rules for the Activity (2 of 2)

For this activity
How to checkThe source (is there a subclass hook?), the mapping table, tests for unknown codes, outage, immutability and decorator order
How to decideA verdict with a structural or behavioural reason for each claim
No AI account?The ready-made example is enough

The Ready-Made Example (1 of 2)

Author-written fictional AI output.

Rename the static create function to FactoryMethod and it becomes Factory Method. Add an Abstract Factory even though there is only one product role. The adapter should catch every exception and return a staff borrower to keep the system available.


The Ready-Made Example (2 of 2)

Wrap all senders in retry decorators; order does not matter. A builder means the finished object is immutable even if it retains the builder’s mutable list.

  • Identify the unsupported claims. For each, name the missing requirement or structural evidence and a concrete test.

Solution (1 of 2)

Fictional claimDiagnosis and correction
Renaming static create makes Factory MethodNames do not create a subclass hook: compare simpleExporter with ExportJob.createExporter
One product role requires Abstract FactoryNo related set has been established: keep simple assembly
Catch every exception and return staffHides failure and grants an unsupported category

Solution (2 of 2)

Fictional claimDiagnosis and correction
Wrap all senders with retryChanges the one-attempt contract: keep retry isolated
Decorator order does not matterChanges which layer observes each attempt: the two exact audit-history tests
A builder guarantees immutable productsMutable aliases can remain after build: snapshot and unmodifiable-list tests
  • Pattern classification needs source inspection as well as output tests.

Three Wrong Candidates to Diagnose

// 1. an unfamiliar code silently acquires staff privileges
category = "S".equals(row.kind()) ? STUDENT : STAFF;

// 2. turns provider or programming failures into absence
catch (RuntimeException failure) { return Optional.empty(); }

// 3. the product shares the builder's mutable list
columns = builder.columns;
  • Each was applied to a copy and rejected by named tests: unknownCodesAreErrors, translatesExpectedFailureAndKeepsCause, builtValueIsIndependentOfReusableBuilder.

8.9 Pattern Habits That Mislead

Eight to avoid


Mistakes 1 to 3

MistakeConsequenceBetter question
Classify by names such as create or FactoryMisidentifies the extension mechanismWhich operation varies, and how?
Put provider codes inside borrowing policiesCouples business rules to provider representationWhere should translation live?
Treat absence and outage identicallyHides failure as an eligibility decisionWas a valid lookup completed?

Mistakes 4 to 6

MistakeConsequenceBetter question
Assume a family interface prevents all mixingOverstates guaranteesDo concrete factories return compatible products?
Keep the builder’s mutable listPreviously built values can changeDoes construction copy mutable state?
Assume decorator order is irrelevantChanges audit meaning and failure visibilityWhich calls pass through each layer?

Mistakes 7 and 8

MistakeConsequenceBetter question
Install retries without revisiting the contractDuplicate attempts or effectsWhat outcome does the existing promise allow?
Require all patterns in the semester projectComplexity without a problemWhich real change justifies this structure?

8.10 Try It Yourself

The directory boundary and pattern exercises


Input and Demonstrations

  • You start with: the Chapter 7 version, the synthetic provider contract and mapping table, and the Chapter 8 code file. Steps 1 to 8: the book’s project; **step 9: your team’s project.
  • Demonstrations: first compare the simple factory and the overridden creation hook; then trace an active borrower and a failed lookup through the adapter boundary.
  • Guided practice: add an independently chosen malformed provider case and explain why it produces failure, not absence. Run the decorator tests and predict both audit histories first.

Independent Work: Steps 1 to 3

  1. Integrate the borrower-directory boundary using the selected mapping contract.
  2. Show staff and student duration outcomes without accepting a caller-supplied category at the new entry point.
  3. Prove that missing, malformed and failed lookups create no reservations or notices.

Independent Work: Steps 4 to 6 (Optional)

  1. Compare two designs for a second scenario: injected exporter versus Factory Method, or constructor versus Builder.
  2. Write a problem-solution-consequence decision, including a justified non-use case.
  3. Inspect the fictional AI answer and retain independent verification of your corrections.

Independent Work: Steps 7 to 9

  1. Builder exercise: in the isolated patterns package, add an optional maximum-row setting. Absent by default; rejects zero and negative; does not change an already built request. Write the tests first.
  2. Decorator exercise: add a third decorator that counts attempts. Predict the count for one failure then success when it sits outside the retry and inside it; check with tests.
  3. On your team’s project, find a place where your code uses an external or mismatched interface directly. Decide whether an adapter is justified using problem-solution-consequence, and say what you would decline to build.

Builder Exercise: A Solution

public Builder maximumRows(int value) {
    if (value <= 0) throw new IllegalArgumentException("Positive row limit required");
    maximumRows = OptionalInt.of(value);
    return this;
}
  • OptionalInt maximumRows = OptionalInt.empty(); in the builder; copied at construction.
  • OptionalInt is immutable, so copying the reference keeps a built request independent.
  • Absence is explicit. A sentinel such as 0 for “no limit” is weaker: it makes “zero rows” unrepresentable.

Decorator Exercise: The Prediction

public static final class CountingSender implements Sender {
    public void send(String message) {
        attempts.incrementAndGet();
        delegate.send(message);
    }
}
  • One failure then success. A counter outside the retry sees one logical call: 1. A counter inside sees both delegate attempts: 2.
  • The same order effect as the audit histories, measured by a number. With these three tests, PatternExamplesTest runs 14 tests.

What You Should End Up With, and When You Are Done

  • What you should end up with: runnable source and tests; a mapping table; a reproducible demonstration; a short design decision; the AI claims record; the two exercise solutions with tests; the step 9 note.
  • Accept when: known codes map correctly; unknown codes never grant a category; provider outage is distinct from absence; legacy behaviour remains tested; the second comparison identifies costs, not counts of classes.
  • If you want more: a changed provider code for staff, or a second provider. Do not add automatic fallback between providers without defining conflict, outage and identity semantics.

An Example Decision Note

  • Problem: the directory’s lookup, raw codes, null absence and provider exception differ from the application’s needs.
  • Solution: an application-owned BorrowerDirectory and an object adapter; a service looks up a borrower before invoking the coordinator with the mapped category.
  • Consequences: translation in one tested boundary; several small types and mapping tests; local examples cannot establish real-provider compatibility.
  • Revisit when: a real SDK contract exists, an authenticated entry point is designed, or a requirement adds a related family of integration products.

8.11 Questions to Think with, and a Transfer Challenge

Concept, diagnosis, transfer


Concept Questions

  1. What distinguishes the worked Factory Method from simpleExporter?
  2. Why are two unrelated construction methods insufficient justification for Abstract Factory?
  3. Which property makes the built report request independent of subsequent builder changes?
  4. How do Adapter and Decorator differ even though both may contain another object?

Diagnosis Questions

  1. A directory adapter catches RuntimeException and returns an empty result. Describe at least two different failures this hides and the resulting problem for the application.
  2. A team changes Audit(Retry(sender)) to Retry(Audit(sender)) because both satisfy Sender. Predict the audit histories for one failure followed by success and explain the requirement that selects between them.

Guidance: Questions 1 and 2

  1. ExportJob owns an inherited workflow and subclasses override a creation hook. simpleExporter selects and returns a product in an ordinary function. A method named create does not alone establish the former.
  2. A genuine family needs several product roles that must vary together for a compatibility reason (heading and body share an output format). Constructing an unrelated clock and directory together does not.

Guidance: Questions 3 and 4

  1. The request copies mutable builder state, validates before publication and exposes no mutation operation. Returning a final reference to the builder’s mutable list would not suffice.
  2. Adapter translates one contract into the contract its client expects. Decorator keeps the component interface and adds optional responsibilities around delegation. Holding another object is a mechanism, not a classification.

Guidance: Questions 5 and 6

  1. It conceals a legitimate provider outage and a programming defect (say an unexpected illegal state). Both look like a completed lookup that found no borrower: a misleading rejection, and operators lose the cause.
  2. Audit outside retry: attempt, sent. Retry outside audit: attempt, failed, attempt, sent. Choose the former when the record concerns logical operations, the latter when every delivery attempt must be observed.

Transfer Challenge: A Library Application

A library application receives patron records from an external system using numeric category codes and a separate inactive flag.

  • Design the application-owned lookup contract, an adapter mapping and three failure cases.
  • Decide whether constructing its receipt output needs a simple factory or a family factory.
  • Explain what additional requirement would change your answer.

Transfer Guidance

  • Obtain an explicit provider mapping first. Hypothetically: category 10 student, 20 staff, an inactive flag excludes an otherwise valid patron. Do not transplant the equipment example’s string codes.
  • An application-owned PatronDirectory.find(id); immutable patron values; eligibility policy stays outside the translation layer.
  • Failures: unknown category, returned identifier differs, declared outage: all prevent the borrowing operation before state changes; the outage keeps its cause.
  • One renderer: direct injection or simple factory. Several matching plain/HTML components: Abstract Factory becomes a candidate.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • Factory Method varies product creation through a subclass hook.
  • Abstract Factory groups related product roles.
  • Builder stages construction.
  • Adapter translates incompatible contracts.
  • Decorator composes optional responsibilities whose order still matters.
  • Creation and connection patterns are useful when they isolate a specific decision.

Where the Project Stands

  • The running project now has a tested directory-backed entry point.
  • The pattern examples stay isolated in the patterns package.
  • Chapter 9 moves to changing behaviour, examining further patterns through concrete variation pressures and the same demand for evidence.

Sources and Further Reading

  1. Gamma, Helm, Johnson, Vlissides: Design Patterns: Elements of Reusable Object-Oriented Software.
  2. Gamma et al.: the Abstract Factory and Adapter publisher excerpts.
  3. Oracle: Java SE 17 Optional and FilterInputStream.
  4. JUnit Team: JUnit 5.13.4 User Guide.
  5. Martin: Agile Software Development (2002).
  • Full references and links are in the chapter text.

9 - Chapter 9: Designing Changing Behaviour

Object Oriented Development and Agile Practices (SE506)

Chapter 9: Designing Changing Behaviour

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 9.1 What changes, and why? Variation in behaviour
    • Four questions for one domain: which rule, which action, who reacts, what request.
  • 9.2 Strategy: swapping one rule for another
    • Already present in the borrowing policies.
  • 9.3 State: behaviour that depends on where you are in a lifecycle
    • A transition table first, then two implementations.
  • 9.4 Observer: telling interested parties without knowing who they are
    • Order, subscription changes and failure, defined before dispatching.

Chapter Roadmap (2 of 3)

  • 9.5 Command: a request you can carry before running it
    • A request as an object: when is it captured, and when do its effects happen?
  • 9.6 Testing behaviour that patterns organise
    • Let the requirement supply the expected result.
  • 9.7 Choosing a pattern, and knowing its limits
  • 9.8 Strategy or State? Judging AI proposals Strategy or State?

Chapter Roadmap (3 of 3)

  • 9.9 Behaviour-pattern habits that mislead
  • 9.10 Try it yourself: lifecycle, publisher and command
    • Six steps; step 6 is on your team’s project.
  • 9.11 Questions to think with, and a transfer challenge
    • A document workflow: draft, approved, published.
  • 9.12 The critical book review
    • The critical book review is due in Week 8.
  • What to take away, and what comes next
    • Chapter 10: application structure and dependency direction.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Distinguish an interchangeable policy from lifecycle-dependent behaviour.
  2. Explain and test Strategy selection using the existing borrowing policies.
  3. Define a transition table and implement a small State-based lifecycle.

Learning Outcomes (2 of 2)

  1. Specify Observer ordering, subscription changes and failure behaviour.
  2. Represent a request with Command and explain execution-time side effects.
  3. Justify adopting or declining a pattern using a concrete change and evidence.

Module Connection and How to Use This Chapter

  • Supports LO2 and LO4. Entry: Chapters 7 and 8.
  • Core reading: 9.1 to 9.3, 9.6 and 9.7 (Strategy, State, testing, selection).
  • Working-knowledge reading: 9.4 and 9.5 (Observer, Command): understand the structure, ordering and failure rules and cost; see each work in the examples; you do not implement either on your own.
  • Minimum lab: steps 1 to 4 and 6. Steps 1 to 4: the book’s project. **Step 6: your team’s project.
  • If you want more: the AI activity (step 5), review questions and transfer challenge.

Starting Version

  • Chapter 8’s Java 17 application, directory adapter and complete Maven suite.
  • The existing borrowing policies already demonstrate Strategy.
  • A notification publisher is an optional running collaborator.
  • The proposed lifecycle is a separate exercise: it does not replace the running project’s reservation or loan rules.
  • The critical book review (Week 8) is governed by the module leader’s instructions; evidence must come from the selected book, not an AI summary.

Opening Problem: Three Requests

  1. Borrowing limits may vary by borrower category.
  2. A proposed workflow should allow cancellation before checkout but reject it after the item is on loan.
  3. A successful reservation should inform several local observers, even when one expected notification attempt fails.

Different Reasons to Vary

  • Category selects a rule.
  • Lifecycle history determines which action is legal.
  • Subscribers determine who reacts to an accepted reservation.
  • One large conditional method could combine them. One generic “behaviour pattern” would conceal their differences.
  • A fourth question: a request as an object a caller can execute later. When is it captured, and when do its side effects happen?

9.1 What Changes, and Why?

Variation in behaviour


Four Different Questions

QuestionPrimary variationCandidate
Which count/duration rule applies?Chosen algorithm or policyStrategy
Which action is legal now?Current lifecycle position and transitionsState
Who reacts to this occurrence?Registered interested recipientsObserver
What action should this request object execute?Captured receiver and argumentsCommand

Policy vs. Lifecycle

  • BorrowingPolicy answers whether a count and duration are permitted. Changing the policy does not change what a reservation is or whether a camera is already reserved.
  • Lifecycle asks something else: the same cancellation may be legal for a confirmed reservation and illegal after checkout. A completed transition changes which later actions are possible.
  • Selecting another duration algorithm cannot capture that history.

Observer and Command Concern Others

  • Observer: interested recipients. A desk display and a local audit observer respond to the same confirmed reservation without becoming borrowing rules.
  • Command: a request as an action object. It captures the receiver and arguments, then invokes the receiver when executed.
  • The requirements determine which structure is useful. Before drawing classes, write one example where the behaviour varies and one outcome that must remain unchanged.

The Same Application Need Not Use All Four

  • A two-branch conditional may remain clearer than an abstraction if the decision is small and stable.
  • A direct method call is often sufficient if the request never needs to be stored, passed or handled independently.
  • “All conditionals violate good design” is false. The question is whether decisions are duplicated, mixed with unrelated responsibilities or hard to extend safely.
  • Pattern-based code still contains conditions: it relocates them.

Keep Structural and Behavioural Changes Distinct

  • The State exercise compares two implementations of the same proposed table: a structural comparison in an isolated example.
  • Integrating it into the lending application would introduce new lifecycle behaviour and decisions about counts, availability and loans.
  • The optional publisher adds observers behind the existing notification boundary: its ordering and failure policy are new, explicit behaviour.
  • Not everything is “only refactoring”.

9.2 Strategy

Swapping one rule for another


Strategy Is Already Present

  • Strategy supplies interchangeable implementations of a behaviour through a shared contract.
  • ReservationRules: a student count below three, one to seven days.
  • StaffReservationRules: below five, up to fourteen days (illustrative).
  • The coordinator depends on BorrowingPolicy, not on the calculations. Its category-to-policy map is supplied through construction.

What Is and Is Not Dynamic

  • A strategy need not be replaced while an operation runs.
  • The policy map is fixed for a coordinator instance; each request selects an entry by category.
  • Configuration-time selection plus per-request lookup is enough to show the role.
  • Chapter 8’s directory supplies the category at the entry point: it does not choose arbitrary policy code or rewrite duration limits.

Compare With the Conditional Starting Point

  • The teaching test includes a small conditional: if student, student limits; otherwise (for the two supported enum values) staff limits.
  • Understandable for two rules in one place.
  • The pressure for Strategy appears when algorithms acquire distinct reasons to change or repeated callers need consistent selection.
  • The conditional is a teaching example only: no duplicate policy engine in the application.

Independent Expected Results

  • The comparison checks both the conditional and the policy objects against independently written boundary results.
  • Testing only that the two implementations agree is weaker: both could contain the same mistaken maximum.
  • Eight rows: a student at two records requesting seven days; a student already at three; staff at four requesting fourteen; negative counts; zero and excessive durations.

The Common Contract

  • Every policy accepts the same two integers and returns a deterministic boolean without changing application state.
  • Negative counts and non-positive durations return false.
  • A true answer means the rule permits a request. It says nothing about identity, stock or interval conflicts.
  • A strategy that performs directory lookup, sends a message or stores a reservation no longer behaves like this contract, even with a matching signature.

A Temporary Staff Strategy

  • A test supplies a staff strategy permitting at most one counted reservation and two days. The coordinator is unchanged.
  • Three days: rejected. Two days: accepted (R1). Another request for that borrower: rejected. A student’s seven-day request still succeeds under its own strategy.
  • An isolated configuration test, not a change to the book’s default staff policy.

Trade-Off and Simpler Alternatives

  • Strategy separates variation from the use case and makes the chosen behaviour easy to supply in a test.
  • It adds a selection responsibility: someone must choose a compatible policy, and the constructor must reject an incomplete map.
  • If the only difference is two numeric limits, one parameterised LimitPolicy(maxCount, maxDays) may be simpler. If rules later differ substantially, separate strategies clarify.
  • Do not add a setPolicy method to look dynamic: it raises questions about requests in progress and reproducibility.

9.3 State

Behaviour that depends on where you are in a lifecycle


The Proposed Rules

  • Begins at REQUESTED; may be confirmed or cancelled.
  • A confirmed reservation may be checked out or cancelled.
  • An on-loan reservation may be returned.
  • RETURNED and CANCELLED are terminal for the four supported actions.
  • Teaching rules for an isolated model. The running system confirms accepted reservations immediately, tracks loans separately, and has no cancellation operation.

The Transition Table

Current statusConfirmCheck outReturnCancel
REQUESTEDCONFIRMEDRejectRejectCANCELLED
CONFIRMEDRejectON_LOANRejectCANCELLED
ON_LOANRejectRejectRETURNEDReject
RETURNEDRejectRejectRejectReject
CANCELLEDRejectRejectRejectReject

What a Rejection Means

  • Every rejection throws IllegalStateException and leaves the status unchanged.
  • Repeating cancellation is a rejection, not an idempotent success.
  • That choice belongs in the table before it appears in Java.
  • Twenty parameter rows cover five statuses and four actions: five permitted transitions and fifteen rejected combinations.

Reading a State Diagram

  • A state diagram shows the positions an object can be in and the events that move it: nothing about implementation.
  • Each rounded box is a state; an arrow is a transition labelled with its event.
  • The filled circle is the start point (REQUESTED).
  • A state with no outgoing arrow (RETURNED) allows none of the four actions.
  • A guard is a condition in square brackets that must hold before a transition fires. This model has none.

Figure 9.1: The Proposed Lifecycle

REQUESTEDCONFIRMEDON_LOANRETURNEDCANCELLEDconfirmcheck outreturncancelcancelTerminal (dark): none of the four actions is allowed.

The Absent Edges Are Specification Too

  • The diagram shows permitted paths only.
  • An action with no arrow from a state is not allowed there.
  • The table remains the complete specification: it states the absent edges, and those are essential for testing.
  • “Terminal” means no supported outgoing action. It does not mean deleting a persisted record.

The Conditional Implementation

public void cancel() {
    switch (status) {
        case REQUESTED, CONFIRMED -> status = Status.CANCELLED;
        default -> throw rejected("cancel", status);
    }
}
  • ConditionalReservation switches on a status enum inside four operations.
  • The repeated selection is easy to inspect at this size. As operations grow, one state’s complete behaviour is spread across several methods.

The State Implementation

  • StateReservation holds a current state object and delegates each operation to it.
  • Each state provides its allowed successors; default implementations reject unsupported operations.
  • The context assigns the returned successor only after the state operation completes.
public void cancel() {
    phase = phase.cancel();
}

Why Status Survives a Rejection

  • If phase.cancel() throws, the assignment never occurs: the local reason the status remains unchanged.
  • It is not a general transaction mechanism for arbitrary side effects inside state operations.
  • Unsupported actions throw before assignment.

Figure 9.2: Two Designs, One Contract

«interface»Lifecycle+status() Status+confirm() void+checkOut() void+returnItem() void+cancel() voidConditionalReservation-Status statusStateReservation-Phase phase«enumeration»PhaseREQUESTEDCONFIRMEDON_LOANRETURNEDCANCELLEDdelegates to

Reading Figure 9.2

  • Both implement the same Lifecycle interface: a caller cannot tell them apart.
  • ConditionalReservation keeps a status value and switches on it.
  • StateReservation holds a Phase and delegates: the enum constants are the state objects.
  • In the code Phase is private; the diagram shows it because it explains the design.
  • The realisation arrows say both keep the same promise.

State Objects as an Enum (1 of 2)

private enum Phase {
    REQUESTED(Status.REQUESTED) {
        @Override Phase confirm() { return CONFIRMED; }
        @Override Phase cancel()  { return CANCELLED; }
    },
    CONFIRMED(Status.CONFIRMED) {
        @Override Phase checkOut() { return ON_LOAN; }
        @Override Phase cancel()   { return CANCELLED; }
    },
  • Each constant overrides only the transitions the table permits.

State Objects as an Enum (2 of 2)

    ON_LOAN(Status.ON_LOAN) { @Override Phase returnItem() { return RETURNED; } },
    RETURNED(Status.RETURNED),
    CANCELLED(Status.CANCELLED);

    Phase confirm()    { throw rejected("confirm", status); }
    Phase checkOut()   { throw rejected("check out", status); }
    Phase returnItem() { throw rejected("return", status); }
    Phase cancel()     { throw rejected("cancel", status); }
}
  • Everything not overridden is rejected by default, so the terminal states need no code.

Shared State Objects, Separate Progress

  • The enum constants contain no reservation-specific mutable data.
  • Two contexts can refer to the same REQUESTED constant without sharing progress: each context owns its own current-state reference.
  • A test advances one context while leaving another unchanged.
  • An enum used only as a tag in a central switch would be the conditional design instead.

No Public Setter

  • No caller can assign a new phase directly.
  • A public setter would let a caller skip confirmation or resurrect a returned reservation without passing the rules.
  • Read-only status is sufficient for observation.
  • That the State example has no public setter is established by reading the API, not by a runtime test.

Test, Trade-Off and Integration Boundary

  • Twenty parameter rows, each run against both implementations, with an independently specified outcome; rejected actions preserve state.
  • Comprehensive for the small table, not for a real reservation process: time windows, permissions, conflicts, cancellation effects and loan storage are outside it.
  • State groups behaviour by lifecycle position but adds indirection. For this small fixed table, keeping the conditional is defensible.
  • Before integrating: do cancelled reservations stop counting? Do intervals become available? These are requirements, not consequences of State.

9.4 Observer

Telling interested parties without knowing who they are


Problem and Simple Attempt

  • After acceptance, several local components may react.
  • Hard-coding each observer in the coordinator couples reservation decisions to the changing recipient list.
  • A direct call remains simpler for a single fixed collaborator: the existing sender interface already supports that.

The Optional NotificationPublisher

  • It implements NotificationSender, so the coordinator uses it through its existing boundary.
  • Observers register a callback receiving the immutable confirmed reservation.
  • Registration and dispatch are the publisher’s responsibility, not new methods on the coordinator.
  • Java’s PropertyChangeSupport is a listener-management example. This publisher has its own documented contract: it does not inherit another library’s ordering, concurrency or failure guarantees.

Define the Observable Behaviour First

  • Synchronous, sequential, in memory.
  • Observers are called in registration order, using a snapshot taken at the start of publication.
  • Each observer in that snapshot is attempted once (unless an unexpected exception stops dispatch).
  • No retries.

The Publisher’s Contract (1 of 2)

SituationSelected outcome
No observersNormal return; no delivery claim
Same observer object registered twiceReject duplicate registration
Subscription closedRemove that registration; repeated close is harmless
Subscribe or unsubscribe during a callbackChange subsequent publications, not the current snapshot

The Publisher’s Contract (2 of 2)

SituationSelected outcome
Expected NotificationExceptionRetain failure and continue to later observers
Unexpected exceptionPropagate immediately; later observers are not attempted
Recursive publication through the same publisherReject with IllegalStateException
  • Observer identity uses the same object reference, not custom equality. The publisher does not deduplicate recipients by business identity.

The Publisher’s Core

for (Entry entry : List.copyOf(entries)) {          // snapshot
    try {
        entry.observer.onConfirmed(reservation);
    } catch (NotificationException failure) {        // expected failure
        if (aggregate == null)
            aggregate = new NotificationException("One or more observers failed");
        aggregate.addSuppressed(failure);
    }
}
if (aggregate != null) throw aggregate;
  • A finally block resets the recursion guard.

Subscription Handles Own One Registration

  • A handle owns one registration. Closing an old handle again must not remove a later registration of the same observer.
  • The implementation keeps a distinct entry object per registration to make that ownership precise.
  • An implementation removing by observer identity on every close could incorrectly remove a newer registration.

Publication Occurs After Storage

  • The coordinator stores the accepted reservation before calling its sender.
  • If an observer fails with the expected exception, the publisher continues, collects the failures, and throws one aggregate NotificationException.
  • The coordinator returns its existing suffix: notification failed; reservation retained.

Figure 9.3: One Failing Observer

CoordinatorPublisherDeskobserverDisplayobserverAuditobserverReservation already storedsend(reservation)onConfirmed(reservation)Normal returnonConfirmed(reservation)NotificationExceptiononConfirmed(reservation)Normal returnAggregate NotificationExceptionConfirmation + failure suffix

Reading Figure 9.3

  • Partial success: the desk and audit observers ran; the display observer failed.
  • It does not roll back earlier observer effects, and the suffix does not mean every observer failed.
  • The aggregate keeps individual exceptions as suppressed exceptions.
  • The coordinator returns only its established message: no detailed observer diagnostics.

Unexpected Failures

  • An unexpected programming exception stops dispatch and escapes the coordinator, which catches only NotificationException.
  • The reservation still exists because storage happened earlier.
  • A caller must not infer “nothing happened” from the fact that the operation threw.
  • The recursion guard is not a concurrency lock or a rollback system. Callbacks must not mutate the coordinator or start new reservations.

The Snapshot Rule in Action

first, second, first, third
  • During the first publication, the first observer removes the second and adds a third.
  • The snapshot already includes the second, so it still runs. The next publication sees the updated registrations.
  • A declared contract, not an accident of ArrayList iteration. Deterministic sequence tests, not concurrent-race tests.

Trade-Off and Non-Use Case

  • Observer reduces direct knowledge of recipients but makes execution less obvious: work happens inside registered callbacks.
  • Ordering dependencies can become hidden. If one observer must finish a business step before another begins, explicit orchestration may express it better.
  • Keep a single sender for a fixed output.
  • No durable delivery, broker, asynchronous execution or exactly-once guarantee. Not an automatic next step.

9.5 Command

A request you can carry before running it


Problem

  • DirectoryReservationService.reserve immediately performs lookup and reservation.
  • A user interface action or a small invoker may need an object containing the request, to execute when selected.
  • Command<R> has one method, R execute().
  • ReserveEquipmentCommand captures the service, borrower id, item id, start date and days.

The Command Record

public record ReserveEquipmentCommand(DirectoryReservationService receiver,
        String borrowerId, String itemId, LocalDate start, int days) implements Command<String> {
    public ReserveEquipmentCommand {
        Objects.requireNonNull(receiver, "receiver");
        if (borrowerId == null || borrowerId.isBlank() || itemId == null || itemId.isBlank()) {
            throw new IllegalArgumentException("Identifiers must not be blank.");
        }
        Objects.requireNonNull(start, "start");
    }
    @Override public String execute() {
        return receiver.reserve(borrowerId, itemId, start, days);
    }
}

What Construction Does Not Do

  • Construction validates required references and identifiers.
  • It performs no lookup, allocates no reservation, notifies no one.
  • The receiver stays responsible for the use case: the command does not reproduce borrowing policy or store its own reservation list.
  • A lambda or method reference can be enough if no named request data is needed. Runnable is a familiar no-result action, but using it is not a Command architecture.

Three Moments of a Command

1 Constructnew ReserveEquipmentCommand(…)captures the receiverand the arguments;no lookup, no reservation,no notice2 Executecommand.execute()the service evaluates theCURRENT state: lookup,eligibility and conflict3 Execute againcommand.execute()re-enters the service: thestored record now conflicts;a different result; onerecord and one notice remainstate may changesame command

Time of Execution Changes the Outcome

  • Between construction and execution, another request may reserve the item.
  • The command must evaluate current receiver state when executed. Capturing an earlier “available” result and using it as permission bypasses the actual conflict check.
  • The record’s fields are immutable, but it holds a reference to a mutable service. It is not a snapshot of application state.
  • A test constructs the command, creates a competing reservation, and verifies rejection when it later executes.

Repeated Execution

  • First execution creates a reservation; the second sees a conflict: a different result, without another notice.
  • That does not mean commands are idempotent. There is no request identifier, deduplication record or replay protocol.
  • “Command makes retries safe” is false.

Failure, Undo and Alternatives

  • A directory failure propagates unchanged: the command does not convert it into success, retry it or compensate.
  • Undo needs its own semantics. Deleting a record would not undo an observer’s side effect; the project has no cancellation contract.
  • Naming a class Command supplies no undo, transactions, queueing or persistence.
  • Use a direct call when there is no reason to represent the action separately.

9.6 Testing Behaviour That Patterns Organise

The requirement supplies the expected result


What Each Pattern’s Tests Need

  • A test that checks an object implements BorrowingPolicy cannot establish correct limits.
  • A test that constructs a state object cannot establish a valid transition.
  • A test that counts subscribers cannot establish what happens after one throws.
  • Strategy: independent boundary examples. State: the transition table, including rejected edges. Observer: ordered event histories and state after failures. Command: distinguish construction from execution.

Compare Without Circular Proof

  • The lifecycle tests send the same action to the conditional and State implementations, but both are checked against a fixed table.
  • Equality between the two alone is insufficient: if both allowed cancellation on loan, they would agree while violating the requirement.
  • The table covers this exercise’s complete state/action space. It does not prove arbitrary sequences with time, external state or permissions. Keep the claim proportional to the model.

Hide State Identity

  • Tests observe status() and operations, not the private enum field.
  • They survive replacing enum-based states with separate state classes, as long as the public contract is unchanged.

Test Partial Effects Explicitly

  • Unexpected exception: earlier callbacks ran, later ones did not, the coordinator retained its record. More informative than checking only the thrown exception.
  • Expected failure: later observers still run, and the aggregate keeps both failures in order.
  • Subscription changes: removal during a callback affects the next publication; the removed observer still appears in the current snapshot.

Evidence Narrows Uncertainty

  • The command replay test documents one concrete repeated execution. It guarantees nothing about future commands or other receiver states.
  • Passing evidence should narrow uncertainty about the selected promise, not expand into unsupported general assurance.
  • The full build executes 107 JUnit invocations: 63 inherited and 44 new.

9.7 Choosing a Pattern, and Knowing Its Limits

Compare alternatives by their costs


Reasonable First Options

SituationFirst optionPattern when justified
Two small fixed limit rulesOne conditional or parameterised policyStrategy for independent implementations
A small fixed transition tableEnum and explicit operationsState when grouping state-specific behaviour clarifies
One fixed notification outputDirect injected senderObserver for a changing set of local recipients
One immediate reservation requestDirect service callCommand when the request must be represented separately

No Row Says More Classes Is Progress

  • A useful explanation identifies where a future edit will occur, which contract stays stable and which new obligations arise.
  • Observer centralises registration but needs decisions about order and failure.
  • State centralises each state’s actions but needs consistency between model and tests.

Be Precise About This Version

  • The project keeps Chapter 8’s directory boundary and all previous application sources.
  • NotificationPublisher is an additional sender option; no earlier demo is silently rewired.
  • The State model stays in the separate behaviour package and never updates real reservation or loan storage.
  • Inherited limits remain: in-memory storage, trusted internal APIs, no enforced identity, and reservation records that still count and block intervals after loan return.

A Design Decision Must Be Challengeable

Several local observers need to react; a publisher owns registration and dispatch; one fixed sender remains simpler where recipients do not vary; failures can leave partial effects.

  • “Observer decouples everything” cannot be assessed.
  • “State eliminates invalid operations” is too broad unless the API and rules enforce the model.

9.8 Strategy or State?

Judging AI proposals


Ground Rules for the Activity (1 of 2)

For this activity
Your goalDecide whether each proposal fits a policy-selection problem or a lifecycle problem, and what each allows a caller to do
You start withThe lifecycle table in 9.3, the existing strategies, and the failure and replay behaviour in 9.4 and 9.5
What is in boundsCompare the proposals and qualify the reliability claim; adopt none without the table

Ground Rules for the Activity (2 of 2)

For this activity
How to checkThe transition table, the Strategy and State tests, the publisher and command tests
How to decideA verdict with evidence for each claim
No AI account?The ready-made example is enough

Two Fictional Proposals

Proposal A: “Implement RequestedPolicy, ConfirmedPolicy and ReturnedPolicy as strategies. Expose setPolicy so callers can choose any policy before each operation. Calling a return method should simply select the returned policy.”

Proposal B: “Store the current phase inside a reservation context. Delegate confirm, checkout, return and cancel to phase-specific behaviour. Allow only transitions from the reviewed table and preserve the current phase after a rejected action.”


Verdicts

  • Proposal A exposes policy selection with no transition authority: a caller can pick the returned policy though no checkout occurred. Calling the classes strategies does not solve legal histories.
  • Proposal B puts the phase in the context and restricts changes to reviewed actions: the stronger match, provided the actual implementation rejects invalid transitions and preserves state. More appropriate wording is not evidence.
  • For student/staff duration rules the pressure differs: they are selected policies, not stages.

A Third Claim

Observer guarantees that all recipients receive the event once, and Command makes retries safe.

  • Observer does not guarantee every callback completes: the expected-failure policy continues but still reports partial success; an unexpected failure stops later callbacks.
  • Command does not make replay safe: repeated execution re-enters the receiver; the conflict check here is not a general deduplication mechanism.
  • Undo and compensation need separate agreed semantics.

Evidence to Retain

  • For each AI claim: the requirement, the claimed structure, the confirming or rejecting test, the remaining limitation.
  • Example entry: “Model claims cancellation is legal from any state; transition table forbids ON_LOAN/CANCEL; test requires an exception and unchanged ON_LOAN; proposal rejected.”
  • Do not use generated implementation and generated expectations as mutual proof.

9.9 Behaviour-Pattern Habits That Mislead

Eight to avoid


Mistakes 1 to 3

MistakeConsequenceBetter action
Treat category policies as lifecycle positionsConfuses selection with permitted transitionsIdentify what changes the behaviour
Expose an unrestricted state setterAllows invalid jumpsChange state only through reviewed operations
Store context data in shared state objectsOne reservation may affect anotherKeep progress in each context

Mistakes 4 to 6

MistakeConsequenceBetter action
Catch all observer errors as expected failuresHides programming defectsSpecify and catch the declared failure type
Assume a thrown notification means no reservation existsMisreads partial effectsCheck the storage-before-publication sequence
Remove observers from a live iterationMakes dispatch fragileDefine and test snapshot semantics

Mistakes 7 and 8

MistakeConsequenceBetter action
Treat a captured command as earlier permissionBypasses current receiver stateValidate the use case at execution
Assume a pattern supplies retries or undoInvents unimplemented guaranteesDefine those semantics separately

9.10 Try It Yourself

Lifecycle, publisher and command


What You Start With, and the Demonstration

  • You start with: the Chapter 8 version, the lifecycle table, the conditional and State examples, the publisher contract. Steps 1 to 5: the book’s project. **Step 6: your team’s project.
  • Demonstration: locate the existing strategies; trace permitted and rejected lifecycle actions before showing the two implementations. Run one expected observer failure and show the retained reservation and the later callback.
  • Guided practice: choose a table row and explain its result independently of the code. Predict the effect of unsubscribing during dispatch. Compare a direct service call with constructing and executing a command.

Independent Work: Steps 1 to 3

  1. Replace the repeated conditional in the isolated lifecycle with State delegation, preserving the table.
  2. Show permitted and rejected actions, including unchanged status on failure and independent contexts.
  3. Demonstrate the publisher’s order and expected-failure continuation. Construct a ReserveEquipmentCommand without executing it, show construction performs no reservation, then execute it.

Independent Work: Steps 4 to 6

  1. Choose a second scenario and justify adopting or declining Strategy, Observer or Command against a simpler implementation.
  2. Evaluate the fictional AI proposals and retain independent evidence.
  3. On your team’s project, find one conditional that selects behaviour by type or tracks a lifecycle. Write a test that protects its current behaviour, then decide whether Strategy, State or neither is justified. Record what you would decline to build and why.

Expected Output and Acceptance

  • What you should end up with: before and after source; a reviewed transition table; runnable tests; a reproducible demo; a concise problem-solution-consequence note; the step 6 decision.
  • Accept when: no unrestricted phase setter; correct terminal behaviour; outcomes match independently stated expectations; observer failure does not imply rollback; command construction performs no reservation; the old regression suite still runs.
  • If you want more: propose real cancellation. Specify effects on counts, availability, active loans, identifiers and notifications before changing code.

A Suggested State Refactoring Sequence

  1. Begin with the complete table and ConditionalReservation; run the table tests.
  2. Introduce private State objects implementing the four operations: reject by default, override only permitted transitions.
  3. The context delegates and accepts a returned successor only on success.
  4. Run the same table against both; add the independent-context check.
  5. Inspect the public API for a setter.
  • A reasoned conclusion may still favour the conditional for this small model.

A Second Decision: Adopt Observer Selectively

  • Problem: several local components react after acceptance, and their registrations can change.
  • Solution: an optional publisher implementing the existing sender interface; ordered snapshots; the declared failure rule; the demo supplies it explicitly.
  • Consequences: the coordinator stays independent of observer identities; partial effects; observers need lifetime management; unexpected failures escape after storage.
  • Simpler alternative: one injected sender; or explicit orchestration when several steps form one business operation.

Decline a Pattern With a Concrete Reason

  • A button handler can call the service if it never needs to carry the request separately: Command adds a type with no demonstrated benefit.
  • One count/duration formula parameterised by two limits may be enough: separate Strategy classes pay off when rules deserve independent implementations.
  • Name which change each alternative makes easier and which obligation it adds.

9.11 Questions to Think with, and a Transfer Challenge

Concept, diagnosis, transfer


Concept Questions

  1. Why is category-based borrowing policy a Strategy example while the proposed reservation lifecycle is a State example?
  2. Why can stateless enum-based State objects be shared while reservation progress must remain separate?
  3. What does snapshot iteration mean for an observer removed during publication?
  4. What does a Command capture, and which decisions still belong to execution time?

Diagnosis Questions

  1. An unexpected observer exception occurs after two callbacks succeed. The caller assumes no reservation was stored and immediately repeats the request. Explain why that assumption is unsupported and identify the observations needed before choosing a recovery action.
  2. An AI-generated State implementation has setStatus(RETURNED) and a happy-path test. Explain the missing guarantee and design tests that reject an invalid jump or action sequence.

Guidance: Questions 1 and 2

  1. Category selects which count/duration policy applies: it does not advance a lifecycle. State makes the meaning or legality of an operation depend on the context’s current position. Similar delegation structures can serve different purposes.
  2. The constants hold only behaviour and a fixed status. Each context owns its current reference. Mutable reservation data in a shared constant would couple contexts. The independence test advances one context and observes the other still at REQUESTED.

Guidance: Questions 3 and 4

  1. An observer present when publication begins still receives that publication even if a prior callback closes its subscription; it is absent from the next snapshot.
  2. The receiver and request arguments, not a snapshot of receiver state or an advance permission. Lookup, eligibility and conflicts are evaluated when execute calls the service.

Guidance: Questions 5 and 6

  1. The coordinator stores before publishing, so the exception is compatible with a stored reservation and partial external effects. Inspect the reservation snapshot and the known event history before recovery; do not blindly repeat.
  2. A setter bypasses the transition table. Test: checkout before confirmation, cancellation while on loan, repeated return: each must reject and preserve status. Add valid sequences and independent contexts so the code cannot pass by rejecting everything.

Transfer Challenge: A Document Workflow

A document workflow has draft, approved and published stages. Review policy differs by department, several local views react after publication, and a user interface can hold a publish request before executing it.

  • Identify the four variation pressures; choose only the justified patterns.
  • Specify one invalid transition, one observer failure and one stale-command outcome.
  • State which requirements need clarification before implementation.

Transfer Guidance

  • A department review algorithm: Strategy candidate. Draft, approved, published: State candidate. Local views reacting: Observer. A UI-held publish request: Command, if it needs separate representation.
  • Do not require all four.
  • Invalid transition: publishing a draft before approval; it must stay a draft.
  • Observer failure: a preview fails after publication was stored: do remaining observers run, and what does the caller see?
  • Stale command: a publish captured while approved, executed after an edit returned it to draft: the receiver must check current state and reject.

9.12 The Critical Book Review

Method in Appendix E


What the Version Says

  • Your individual critical review of a book from the approved list is due in Week 8.
  • Appendix E develops the method: a reading record, questions for each part, prompts for checking sources.
  • The current assignment brief is always the authority on length, sources, format and deadline.
  • Read the selected book directly, not a summary. Keep apart what the author claims, what you infer and what an AI tool suggested.

For a Pattern Discussion

  • Does the book’s example establish the extension benefit it claims?
  • Which simpler alternative did the author consider?
  • What cost does the author acknowledge?
  • A criticism resting on a source and a concrete case is stronger than a catalogue of definitions.
  • Reading this textbook does not satisfy the assignment, and an AI summary of an unread book is not evidence about it.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • Strategy selects a behaviour.
  • State governs behaviour through transitions.
  • Observer connects interested recipients to an occurrence.
  • Command represents an action for later invocation.
  • Each creates obligations as well as opportunities, especially around state, ordering and partial effects.

Where the Project Stands

  • An optional, tested local notification publisher.
  • A request-action example.
  • The proposed lifecycle remains clearly separated.
  • Chapter 10 examines how these responsibilities fit into application structure and dependency direction.

Sources and Further Reading

  1. Gamma, Helm, Johnson, Vlissides: Design Patterns: the Strategy, State, Observer and Command entries.
  2. Oracle: Java SE 17 PropertyChangeSupport, Throwable and Runnable.
  3. JUnit Team: JUnit 5.13.4 User Guide.
  4. Martin: Agile Software Development (2002): the Strategy, Command, Observer and State chapters.
  • Full references and links are in the chapter text.

10 - Chapter 10: From Classes to Application Structure

Object Oriented Development and Agile Practices (SE506)

Chapter 10: From Classes to Application Structure

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 10.1 Why designing good classes is not enough
    • Responsibilities exist at more than one scale.
  • 10.2 Layers: giving each boundary a reason
    • Eight packages, each with a reason.
  • 10.3 What each layer does: the four layers in practice
    • Where an outside collaborator sits.
  • 10.4 Which way do dependencies point, and how do we check?
    • Source dependencies, runtime calls and a compiled dependency check.
  • 10.5 Cutting packages well: cohesion, coupling and stability
    • CCP, CRP, REP, ADP, SDP, SAP and a worked placement comparison.

Chapter Roadmap (2 of 3)

  • 10.6 Entities and value objects: identity or value?
    • Identity, immutability and Java equality are three different things.
  • 10.7 Repositories and services: a storage contract
    • The smallest useful storage contract.
  • 10.8 Recording a small architecture decision
    • ADR-010.
  • 10.9 Peer review and preparing Milestone 3

Chapter Roadmap (3 of 3)

  • 10.10 Checking an AI’s dependency review verify a dependency review.
  • 10.11 Structure habits that mislead
  • 10.12 Try it yourself: restructure and check the dependencies
    • The book’s project B1 to B6; your team’s project T1 to T5.
  • 10.13 Questions to think with, and a transfer challenge
    • A clinic appointment application.
  • What to take away, and what comes next
    • Chapter 11: refactoring and legacy code.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Assign existing responsibilities to presentation, application, domain and infrastructure code.
  2. Distinguish runtime calls from source-code dependency direction.
  3. Explain entity identity, immutability and value equality without conflating them.
  4. Locate a focused repository contract and its implementation.

Learning Outcomes (2 of 2)

  1. Restructure a use case while checking its observable behaviour and compatibility limits.
  2. Record an architecture decision and prepare evidence for peer review and Milestone 3.
  3. Compare two package designs using cohesion, coupling and stability, and read dependency counts without treating them as a measure of quality.

Module Connection and Starting Point

  • Supports LO2 and LO6; Milestone 3 preparation. Readings: Martin’s principles of package design and selected chapters of Evans’s domain-driven design.
  • Starting point: Chapter 9’s tested Java 17 project, including the stronger assertions added when it was reviewed.
  • The completed version preserves its use-case results and earlier demonstrations.
  • Package names, imports, launcher names and direct coordinator construction change: not a claim of source or binary compatibility.

Milestone 3 Checklist (1 of 2)

Milestone 3 falls in this week: design patterns and layered architecture, with a demonstration and a retrospective. On your team’s project:

  1. A design pattern from Chapters 8 and 9 used where it removes a real problem, and one place you declined to use one.
  2. A compact package diagram saying which packages may depend on which.
  3. One restructuring change, with moves kept separate from logic changes.
  4. Passing checks, including a dependency check or a documented review of imports.

Milestone 3 Checklist (2 of 2)

  1. A short architecture decision record with alternatives, consequences and a revisit trigger.
  2. Real peer review of that change, with the feedback addressed.
  3. A demonstration and a retrospective with a next action.
  4. A statement of what the architecture does not do.
  • Start with items 2 and 5, which need no code.
  • The approved Milestone 3 instructions decide what is submitted. This list is preparation, not a rubric.

How to Use This Chapter

  • Core reading: 10.1 to 10.5 (except the Ports and Adapters comparison), 10.7, 10.8, 10.9.
  • Working knowledge: 10.6 (entities and value objects): understand identity versus value meaning, and why Java equality is a separate decision. You do not implement either.
  • Minimum lab: B1 to B3 (the book’s project: reading, tracing, diagnosis; nothing to rebuild) and T1 to T5 (your team’s project; your Milestone 3 evidence).
  • Optional: Ports and Adapters, the AI activity, B4 to B6 (break the rules on purpose; reproduce the migration), review questions, transfer.

Opening Problem: Understandable Classes, No Visible Structure

  • The application now has borrowing policies, a directory adapter, notification collaborators, loan operations and several demonstrations.
  • Individual classes have understandable responsibilities, but the production code still sits largely in Java’s unnamed package.
  • Nothing tells a new developer whether a borrowing policy may depend on a directory client, or where a storage implementation should be constructed.

A Deliberately Modest Change

  • Make those boundaries visible while preserving the demonstrated reservation behaviour.
  • Decide which classes express domain rules, which coordinate, which communicate with external mechanisms, which assemble the application.
  • Moving files alone will not answer those questions.
  • The chapter adds a small reservation storage contract, moves concrete storage outside the coordinator, and documents dependency rules that can be checked against compiled code. No database, web framework or distributed system.

10.1 Why Designing Good Classes Is Not Enough

Responsibilities at more than one scale


Class Correctness Is Not an Application Boundary

  • A well-designed LoanPeriod can reject an invalid interval while the surrounding application has poor dependency organisation.
  • A correct directory adapter can be called directly from several user interfaces, each repeating eligibility logic differently.
  • Chapters 5 to 9 separated several responsibilities. Now we make the distinctions visible across packages.

Layering

  • Presentation, domain logic and data access are a familiar separation, with an application service boundary where operations need coordination.
  • Layering narrows the responsibilities a developer must consider together.
  • The chapter’s exact packages are a worked design decision, not a universal folder template.

The Program Already Has an Architecture

  • Someone chooses its dependencies, owns its state and decides when effects occur. Those decisions exist even without a server or database.
  • The question is whether they are explicit enough to review and change.
  • The coordinator owns an ArrayList: easy to follow, but it ties use-case orchestration to one storage mechanism.

The Cost of Extracting a Boundary

  • Extracting a narrow storage contract demonstrates dependency direction and storage ownership without inventing persistence requirements.
  • It creates another interface and requires a contract for ordering, snapshots and duplicate identifiers.
  • If a program will remain a tiny script with one private list, it may not be worth it. Here it supports the structural learning goal and a concrete, independently testable boundary.

What Must Remain Unchanged

  • Accepted and rejected reservation strings.
  • Policy limits and half-open overlap rules.
  • Directory failure distinctions, loan behaviour.
  • Notification timing: storage still precedes notification; an expected notification failure still retains the reservation.
  • The optional publisher keeps its declared partial-failure semantics.

What Does Change

  • Callers import named packages.
  • Direct ReservationCoordinator construction requires an additional repository argument.
  • Command-line launchers use fully qualified names.
  • These must be documented even when every user-facing transcript stays identical.
  • Behaviour is protected within the supplied sequential, in-memory version, not for an unseen application that reflects on class names or depends on the old constructor.

10.2 Layers

Giving each boundary a reason


The Package Root

  • The root is edu.se506.lending.
  • Domain expresses the equipment vocabulary and rules.
  • Application coordinates operations.
  • Infrastructure implements technical boundaries.
  • Presentation adapts an interaction to the application.
  • Bootstrap constructs the chosen object graph.

Packages and Their Types (1 of 2)

PackageResponsibilityRepresentative types
domainValues, records and borrowing decisionsLoanPeriod, Reservation, BorrowingPolicy
applicationUse-case coordination and result formattingReservationCoordinator, DirectoryReservationService, LoanService
application.portContracts needed by application operationsReservationRepository, BorrowerDirectory, NotificationSender
infrastructure.directoryProvider-specific translationLegacyDirectoryAdapter, LegacyDirectoryClient

Packages and Their Types (2 of 2)

PackageResponsibilityRepresentative types
infrastructure.notificationSelected local notification mechanismsConsoleNotificationSender, NotificationPublisher
infrastructure.persistenceConcrete reservation storageInMemoryReservationRepository
presentationThin console interactionReservationConsole
bootstrapAssembly and executable demonstrationsReservationApplication, Chapter10Demo
  • The persistence package names a storage responsibility. The implementation is in memory and loses data when the process ends.

Packages Communicate; Rules Enforce

  • A package is a namespace. Public Java types can still be imported from anywhere.
  • A folder named domain does not stop a developer constructing a console sender inside it.
  • So the chapter states permitted production dependencies and adds a test that examines compiled class dependencies.
  • Tests may cross boundaries to assemble examples; startup wiring has a deliberate exception because it must choose concrete implementations.

The Dependency Matrix

Source packagePermitted local targets
domaindomain
application.portdomain, application.port
applicationdomain, application.port, application
infrastructuredomain, application (including ports), infrastructure
presentationdomain, application, presentation
bootstrapevery application package, and explicitly demonstrated isolated examples

Isolated Examples Stay Isolated

  • Ordinary production code must not refer to the isolated patterns, behaviour or examples packages.
  • The value-equality demonstration lives in examples.structure.
  • A bootstrap demonstration may show an isolated example explicitly, as Chapter 9 did.

Avoid Mechanical Layer Multiplication

  • A class does not need an interface merely because it crosses a package boundary. DirectoryReservationService stays a concrete application service.
  • Not every domain object needs a repository and a service. LoanPeriod is a value-shaped object: storing it through its own repository would obscure its role as part of a reservation.
  • The loan service’s internal map stays where it was: this chapter extracts one justified boundary, not a generic storage framework.

10.3 What Each Layer Does

The four layers in practice


Presentation Translates an Interaction

  • ReservationConsole receives a directory-backed service and a PrintStream.
  • Its reserve method invokes the service and prints the existing result.
  • It neither selects a borrower category nor duplicates the seven-day student rule.
  • A test supplies a byte buffer and checks exact output; a directory exception propagates without printing a success line.

A Thin Adapter

  • Not an interactive menu, input parser or complete interface.
  • The bootstrap demo supplies synthetic directory rows and chooses the sender.
  • Presentation does not construct the adapter or storage, so output behaviour can be examined without hiding construction decisions.

Application Coordinates the Use Case

  • DirectoryReservationService performs lookup and delegates a valid borrower to the coordinator.
  • The coordinator applies policy, checks conflicts, creates the record, stores it, invokes its sender.
  • LoanService coordinates checkout and return using the supplied clock.
  • These arrange several responsibilities to complete a use case. The label does not require a remote service, a DI framework or one service per entity.

Why ConfirmationFormatter Stays Here

  • Existing returned strings are part of the observable contract, so it remains in application.
  • It is also used by the console notification adapter.
  • A future structured result could separate domain outcomes from wording, but doing it now would mix an API redesign with this restructuring.

Domain Expresses Meaning and Invariants

  • Borrower categories, borrowing policies, reservations, loans and loan periods belong to the domain vocabulary.
  • They do not know about console output, provider codes or repository implementations.
  • Domain code may use standard-library types (LocalDate, collections). Independence means avoiding orchestration and external mechanisms the rules do not need.
  • No full Domain-Driven Design set: first explain the actual identity, invariants and ownership.

Infrastructure Implements Selected Mechanisms

  • The directory adapter translates the provider contract into BorrowerDirectory; the in-memory repository implements storage; notification adapters write output or dispatch callbacks.
  • Their code depends on application-owned contracts and the domain types they handle.
  • Infrastructure does not own eligibility: the seven-day limit in the directory adapter would tie policy to the provider representation.
  • The repository should not send a notification when a record is added: orchestration decides when.

Bootstrap Chooses and Connects

  • Its concrete imports are intentional.
  • Keeping construction there makes selection visible without a framework or global service registry.

Where an Outside Collaborator Sits

  • The equipment directory, the book’s data-backed collaborator, shows where any outside collaborator belongs (a data source, or something that scores or predicts).
  • Contract BorrowerDirectory and its failure DirectoryException: application.port. Callers must tell an outage from an absent borrower.
  • Implementation LegacyDirectoryAdapter with LegacyDirectoryClient: infrastructure.directory.
  • Construction: bootstrap passes the adapter to DirectoryReservationService.

The Same Three Positions for a Model

  • A model-backed collaborator would occupy the same three positions: a narrow port with bounded outcomes and a defined failure; an adapter in infrastructure; construction in bootstrap.
  • The application would not import the adapter.
  • A test could substitute the port exactly as the directory tests do.

10.4 Which Way Do Dependencies Point, and How Do We Check?

Arrows are source references


Figure 10.1: Permitted Source Dependencies

Bootstrapchoose implementationsPresentationApplicationand portsInfrastructureadaptersDomainA missing arrow means the reference is not permitted:Domain refers to nothing else.

Reading Figure 10.1

  • Each box is a group of packages; an arrow means classes in the first group may refer to classes in the second.
  • The application has no arrow to infrastructure. ReservationCoordinator imports ReservationRepository, an application-owned contract; InMemoryReservationRepository imports and implements it; bootstrap constructs the implementation.
  • The figure shows source references only, not the order of calls.

The Port Package’s Narrower Rule

  • Within application, the port package may depend on domain types and other ports, not concrete application services.
  • This keeps a storage contract from requiring the coordinator it exists to support.

Runtime Calls Travel Toward an Adapter

  • At runtime the coordinator calls an object that happens to be an in-memory repository.
  • That does not require its source to import the concrete class: interface dispatch connects the call to the implementation selected during assembly.
  • Source dependency points toward the port; the runtime call goes through it.

Figure 10.2: One Accepted Reservation at Run Time

ReservationConsoleDirectoryServiceReservationCoordinatorIn-memoryRepositoryNotificationsender impl.reserve(request)resolve the borrower through the directory portreserve with mapped categoryfindAll() for policy and conflict checkssnapshotadd(accepted reservation)send(confirmed reservation)normal return, or NotificationExceptionon failure: keep the reservation, append the suffixexisting result stringresult string

Reading Figure 10.2

  • Each box at the top is an object; time runs down the page; solid arrows are calls, dashed arrows returns.
  • The coordinator may take more than one snapshot during a request. The diagram groups those reads: it does not claim one storage call or a transaction.
  • The application still assumes one sequential session.

Check What the Code Actually References

  • DependencyRulesTest invokes the JDK’s jdeps on compiled production classes and rejects local dependency edges that contradict the policy.
  • It also checks that dependencies were actually discovered (no vacuous pass on missing output).
  • A second test builds the package-to-package graph and searches for a cycle.
  • It catches a fully qualified reference even with no import.

What the Dependency Test Cannot See

  • Reflective class names in strings, runtime configuration relationships.
  • A semantically misplaced rule that uses only permitted types.
  • So source review and behaviour tests remain necessary.
  • It needs a full Java 17 JDK with the jdk.jdeps module. Package rules are project decisions, not universal rules the analyser infers.

Optional: Ports and Adapters

  • The directory, sender and repository interfaces can be discussed as ports, with infrastructure implementations as adapters.
  • The console drives the application; storage and notifications are collaborators the application calls.
  • Consistent with Cockburn’s discussion of isolating application behaviour from runtime mechanisms.
  • It does not require six adapters, a hexagonal deployment or a package per method. Use it only when it clarifies which side owns the contract.

10.5 Cutting Packages Well

Cohesion, coupling and stability


Package Cohesion: What Changes Together

  • A cohesive package holds types that change for the same reason and are used together.
  • CCP, Common Closure Principle: things that change together belong together.
  • CRP, Common Reuse Principle: things used together belong together, so a client is not forced to depend on classes it never uses.
  • REP, Reuse/Release Equivalence Principle: the unit you reuse is the unit you release. This project has no separately released package, so REP is context, not a check.
  • Test the cuts with concrete changes, not slogans: for each change, list the packages whose production code must change.

Five Change Requests (1 of 2)

Change requestPackages that changeWhat it shows
Staff may reserve for 21 daysdomain (StaffReservationRules)The policy change stays in one package
The provider renames its status code Yinfrastructure.directoryProvider vocabulary stays outside the application
The confirmation wording changesapplication (ConfirmationFormatter)Its users, including the console sender, see it without being edited

Five Change Requests (2 of 2)

Change requestPackages that changeWhat it shows
Reservations are stored in a fileinfrastructure.persistence (a new class) and bootstrapThe coordinator is not edited: it names only the port
A third borrower category is addeddomain, infrastructure.directory and bootstrapOne business idea touches three packages
  • Most changes touch one or two packages. The last row is a legitimate cost to record, not a flaw to hide. Always add a change you expect to be awkward.

Package Coupling

  • Coupling between packages is the number of references that cross a package boundary.
  • Habit 1: depend on a small contract where the other side may vary. The coordinator refers to ReservationRepository, not the in-memory class.
  • Habit 2: keep the package graph free of cycles. If application referred to infrastructure.notification while it referred back, neither could be compiled, tested or replaced without the other (the Acyclic Dependencies Principle, ADP).

Layers Rule Out Some Cycles, Not All

  • The layer matrix rules out cycles between layers, because each pair allows references in one direction only.
  • It does not rule them out inside one layer: the matrix lets infrastructure.directory and infrastructure.notification refer to each other, so a cycle between them would pass the direction test.
  • That is why DependencyRulesTest has the separate cycle check.

The Port Package and Reuse

  • application.port holds five small contracts and their failure types, so a class that needs one is not forced to depend on a large service.
  • If a port package grew to hold many unrelated contracts, splitting it would be worth considering: a client using only the sender should not be affected when the directory contract changes.

Package Stability

  • A package is stable when it is costly to change: many depend on it, it depends on little.
  • A package is unstable when little depends on it and it depends on many.
  • Both kinds are needed. The Stable Dependencies Principle (SDP): dependencies should point towards stability, so code that changes often depends on code that changes rarely.

Counting Stability

  • Afferent coupling Ca: classes outside P that depend on classes inside P (incoming).
  • Efferent coupling Ce: classes inside P that depend on classes outside P (outgoing).
  • Instability I = Ce / (Ca + Ce), from 0 (maximally stable) to 1 (maximally unstable).
  • This book counts classes, treats a nested class as part of its outer class, counts only references between this project’s packages, and ignores the Java standard library.

Package Metrics (1 of 2)

PackageClassesCaCeI
domain81600.00
application.port51430.18
infrastructure.notification3730.30
application6940.31

Package Metrics (2 of 2)

PackageClassesCaCeI
infrastructure.directory2210.33
infrastructure.persistence1110.50
presentation1110.50
bootstrap7071.00
  • From tools/PackageMetrics.java, run as java tools/PackageMetrics.java target/classes.

Instability as a Picture

packageI = 0I = 1Ca / Cedomain0.0016 / 0application.port0.1814 / 3infrastructure.notification0.307 / 3application0.319 / 4infrastructure.directory0.332 / 1infrastructure.persistence0.501 / 1presentation0.501 / 1bootstrap1.000 / 7The orange pair is the one arrow that points to a slightly less stable package.

Reading the Numbers

  • domain has I = 0: sixteen classes depend on it and it depends on nothing else. Keep it small and free of mechanisms: a change can ripple everywhere.
  • bootstrap has I = 1: nothing depends on it, so it can change freely, which suits the place where concrete choices are made.
  • Most arrows run from a higher I to a lower one: application (0.31) to application.port (0.18) to domain (0.00).

The One Arrow That Does Not

  • infrastructure.notification (0.30) depends on application (0.31), because ConsoleNotificationSender uses the concrete ConfirmationFormatter.
  • The difference is one hundredth, from a handful of classes: no defect.
  • It is the arrow to examine first in a review: the one concrete dependency running from a mechanism to an application class.

An Isolated Package Has No Defined Instability

  • patterns and examples.structure have no references to or from the lending packages: Ca + Ce = 0, and the formula divides by zero. The tool prints undefined.
  • Do not report it as 0 (maximally stable) or as 1. It is simply isolated, as intended.
  • behaviour is referenced only by the Chapter 9 demo: Ca = 1, I = 0.

Numbers Are Questions, Not Grades

  • With one to eight classes per package, adding a single class moves a ratio by several hundredths.
  • A package can have a good number and a muddled responsibility; a poor number can be the right design (bootstrap).
  • Compare numbers only from the same tool and counting rules.

Package Abstractness: The Stable Abstractions Principle

  • SAP: a package that many others depend on cannot change freely, so it should be abstract enough to be extended without being edited.
  • Martin measures abstractness A as the share of a package’s classes that are abstract classes or interfaces.
  • This book does not compute A: it needs a second tool, and with one to eight classes per package the ratio moves with every class added. SAP is used as a reading question.

Reading SAP on the Version

  • application.port: contracts and their failure types, so mostly abstract, and stable (I = 0.18). SAP satisfied.
  • bootstrap: concrete and unstable (I = 1.00). SAP satisfied: nothing depends on it.
  • domain deserves the question: sixteen classes depend on it, yet it is mostly concrete records and rules. It stays safe because it changes only with the borrowing vocabulary, and BorrowingPolicy gives the varying part an abstract seam.
  • The warning sign would be a package that is stable, concrete and changes often.

Worked Comparison: Where Does NotificationException Live?

  • Option A (the version’s choice): in application.port, with the NotificationSender contract.
  • Option B: in infrastructure.notification, beside the senders that throw it (“implementations should own their failures”).
  • Option B was tried on a copy.

Option B Is Rejected by Two Checks

  • The direction check failed: application.ReservationCoordinator -> infrastructure.notification.NotificationException: the coordinator must name the exception to catch it, so application would depend on infrastructure.
  • The cycle check also failed: infrastructure.notification already depends on application.
  • The instability figures would not have shown it (application 0.31; notification 0.27).
  • The rule: a type that callers must catch or inspect belongs with the contract they depend on, not with one implementation.

10.6 Entities and Value Objects

Identity or value?


Identity Is a Domain Decision

  • A reservation has an identifier such as R1.
  • Whether two records with that identifier and different data mean an update or an invalid duplicate depends on the storage contract. This version is append-only and rejects duplicate identifiers.
  • Reservation is immutable and does not override equals or hashCode: two objects with the same ID are not equal through inherited object equality.
  • The repository uses the explicit identifier as its key.

Entity and Value Object

  • An entity is distinguished by its identity rather than its attributes. A value object is one where you care only about attributes and logic, treated as immutable (Evans).
  • Calling a class an entity does not silently implement equality, persistence or a lifecycle.
  • Making it immutable does not remove its domain identity.

Value Meaning vs. Java Value Equality

  • A LoanPeriod is characterised by its start and exclusive end, not by a generated identifier.
  • But the running class keeps inherited object equality: two periods with equal dates are distinct by equals. The chapter preserves that rather than change equality during a package move.
  • examples.structure.PeriodValue is a record demonstrating component-based equality and hashing: two equal-date records are one logical value in a set.
  • Adopting it in the real model is a separate, reviewed change.

Immutability Supports Snapshots

  • An unmodifiable list prevents adding or removing through that list. It does not generally make the elements immutable.
  • The repository’s snapshots are safe because Reservation and its period are also immutable.
  • A previously returned snapshot stays unchanged when another reservation is stored.
  • If a later reservation becomes mutable, a shallow copy no longer establishes a historical snapshot.

10.7 Repositories and Services

A storage contract


The Port

public interface ReservationRepository {
    List<Reservation> findAll();
    void add(Reservation reservation);
}
  • Insertion-ordered, stable, unmodifiable snapshots.
  • add rejects null or duplicate IDs without replacing existing records.
  • The in-memory adapter uses a LinkedHashMap keyed by reservation ID and returns List.copyOf snapshots.

Where the Port Lives

  • The interface is in application.port because the application use case consumes it.
  • Domain-owned repository contracts are another possible design when the domain’s needs justify that ownership.
  • Location should follow actual responsibilities, not a rule that all repository interfaces belong in one layer.
  • A deliberately smaller example than the classic repository: no data mapper, no query language.

What the Extraction Does Not Change

  • The coordinator still calculates counts, checks conflicts and allocates size-based identifiers. None of that moves into storage.
  • The factory supplies a fresh repository per coordinator: the earlier session ownership and R1 starting point stay.
  • The new constructor is an injection point, not a promise to support preloaded or shared stores.
  • No delete, generic save, transaction manager or repository for every class.

A Port Does Not Supply Transactions

  • A test supplies a repository whose add throws before appending: the exception propagates and notification is not reached.
  • That establishes the order for this specific failure, not that a future database cannot fail after a write succeeded.
  • Snapshots and duplicate-ID checks do not make count-check, conflict-check and append atomic across concurrent requests.
  • A database would require reviewing those assumptions.

Three Different Responsibilities

  • An application service coordinates operations.
  • A repository manages access to stored domain records.
  • Domain policies express rules.
  • The boundaries are useful because they expose what each responsibility does and does not promise.

10.8 Recording a Small Architecture Decision

ADR-010


The Worked Decision

  • ADR-010: named packages, application-owned ports, one in-memory reservation adapter, explicit bootstrap wiring.
  • Status: implemented teaching example; peer review pending. The wording separates author verification from a review that has not occurred.
  • Problem: construction and storage ownership were insufficiently visible as the example grew.
  • Compares: keeping the unnamed package; narrow boundaries; a larger framework or generic repository.

ADR-010: Alternatives and Consequences

  • Alternatives: keep the unnamed package and private list (less code, weaker visibility); narrow packages and one storage port (selected); a generic CRUD repository, ORM, database or DI framework (declined: not required).
  • Consequences: new imports; fully qualified launcher names; one more coordinator constructor argument; a full-JDK dependency for the architecture test; more files to maintain.
  • Revisit triggers: persistent storage; multiple writers; a cancellation lifecycle; a new identifier scheme; value equality for the period; structured results.

Your Decision Will Differ

  • ADR-010 is the worked model for the book’s project. On your team’s project: a different boundary, different alternatives, different costs.
  • Do not copy ADR-010’s decision unless it is also yours.
  • An alternative of “do nothing” and a revisit trigger naming an event you could observe make a record more useful than one that only justifies what was already done.

A Reviewable Migration

  • Separate package moves from the repository extraction and from newly added checks.
  • Older tests move into a regression package; direct constructor calls receive the new repository; expected behaviours are retained.
  • The version is not evidence of Git commits or an opened pull request. Record your actual sequence and review comments.

10.9 Peer Review and Preparing Milestone 3

One scenario across boundaries


Review One Scenario Across Boundaries

  • Trace a valid staff request from presentation through directory lookup, policy evaluation, storage and notification.
  • Then a missing borrower and an expected notification failure.
  • Check the layer diagram matches both source dependencies and the observed order of effects.
  • Ask: who creates each concrete adapter? Does application code import it? Does domain depend on application services?

Two Kinds of Evidence

  • The dependency test examines compiled relationships. The regression suite examines selected behaviour.
  • A semantically poor design can pass both. A reasoned human review remains part of the evidence.
  • Verify that presentation has not copied a policy rule to make the demo pass.
  • Inspect the repository’s snapshot and duplicate-ID contract.

Preparing Evidence (1 of 3)

ItemWhat to produceWhat it should establish
1Pattern noteA pattern used where it removes a real problem, with the reason; one place you declined
2Compact package diagramActual responsibilities and declared dependency direction, matching your code
3Restructuring diff or PRWhat moved and what logic changed, kept apart

Preparing Evidence (2 of 3)

ItemWhat to produceWhat it should establish
4Regression result and dependency reportExisting examples still pass; references follow the chosen rules; any cycle is reported
5ADRProblem, alternatives (including doing nothing), decision, consequences, revisit triggers
6Actual review recordA reviewer examined the change and the feedback was addressed

Preparing Evidence (3 of 3)

ItemWhat to produceWhat it should establish
7Demonstration and retrospectiveWhat worked, what did not, one next action
8Limits statementWhat the structure does not provide: persistence, concurrency safety, a cancellation lifecycle
  • Local author testing is not hosted CI evidence. A sample PR description is not a completed peer review.

A Better Reviewer Question

  • Not: “Does it have four layers?”
  • But: “Where would this concrete change belong, and which existing tests protect its consequences?”
  • The answer should name files, contracts and trade-offs, not only package names.

10.10 Checking an AI’s Dependency Review

A claim needs a trace


The Activity

  • Objective: examine a proposed dependency review without treating its wording as evidence.
  • Supply the package map, the relevant Java files and the declared dependency policy.
  • Limit the task to identifying references and responsibility concerns: do not ask the assistant to invent a database or rewrite requirements.

The Ready-Made Example

The coordinator calls an in-memory repository at runtime, so it must import infrastructure and violates dependency inversion. Move the repository interface into infrastructure. Every entity needs a repository and a service. Making the reservation list unmodifiable makes every future element immutable. The bootstrap imports adapters, so remove it and let services construct their dependencies. Since all tests pass, the pull request has been peer reviewed.


Findings (1 of 2)

Fictional claimFinding
A runtime call to the in-memory store proves an application import of infrastructureIncorrect: interface dispatch and source references are different relationships
Put the repository interface in infrastructureNot justified: the consuming application would depend on an infrastructure-owned contract
Every entity needs a repository and serviceNo such requirement follows from identity; LoanPeriod has neither

Findings (2 of 2)

Fictional claimFinding
An unmodifiable list makes every element immutableIncorrect for mutable elements
Bootstrap must not import adaptersIncorrect for the selected composition role
Passing tests establishes peer reviewIncorrect evidence substitution: record real reviewer comments
  • A dependency review should name the referencing type and the referenced type. “Infrastructure coupling” without that trace is only a claim to investigate.

10.11 Structure Habits That Mislead

Eleven to avoid


Mistakes 1 to 4

MistakeConsequenceBetter action
Treat package names as enforced architecturePublic types can still form forbidden dependenciesState and check a dependency policy
Confuse runtime calls with source importsMisdiagnoses interface-based dispatchTrace both relationships separately
Move provider codes into the domainCouples rules to an external representationKeep translation in the adapter
Duplicate eligibility in presentationInconsistent entry pointsDelegate the use case

Mistakes 5 to 8

MistakeConsequenceBetter action
Call immutable objects automatically value-equalMisreads equals and collectionsInspect and test equality semantics
Add generic CRUD to every classInvents unsupported lifecycle operationsAdd only needed storage capabilities
Construct adapters inside application servicesHides concrete dependenciesAssemble them explicitly at bootstrap
Claim passing tests prove peer reviewSubstitutes one type of evidence for anotherRecord actual review separately

Mistakes 9 to 11

MistakeConsequenceBetter action
Grade a package by its instability numberA coarse number from few classes; a muddled package can score wellUse it to pick what to examine, then reason about responsibility
Report an isolated package as I = 0 or I = 1The formula is undefined when Ca + Ce = 0Report it as undefined and explain why
Accept a cycle because the layers look rightTwo packages in one layer can still depend on each otherCheck the package graph for cycles

10.12 Try It Yourself

Restructure and check the dependencies


What You Start With, and the Demonstration

  • You start with: the migrated version, the package responsibility table, the metrics table and your team’s repository.
  • Demonstration: trace the coordinator’s storage access, then the port and adapter after extraction. Compare the source diagram with the runtime sequence. Run tools/PackageMetrics.java and DependencyRulesTest, and one demo under its new fully qualified launcher name.
  • Guided practice: classify a domain policy, directory adapter, application service and bootstrap class. Explain why the same concrete import is right in bootstrap and forbidden in application code.

B1: Classify

For StaffReservationRules, LegacyDirectoryAdapter, DirectoryReservationService, InMemoryReservationRepository and ReservationApplication: name the package, what it may refer to, and one thing it must not.

ClassPackageMust not refer to
StaffReservationRulesdomainAny application, infrastructure or presentation class
LegacyDirectoryAdapterinfrastructure.directorypresentation, bootstrap, an isolated example
DirectoryReservationServiceapplicationAny infrastructure class
InMemoryReservationRepositoryinfrastructure.persistencepresentation, bootstrap
ReservationApplicationbootstrapNothing forbidden, but no business rule

B2: Check the References

  • Run the build and the compiled dependency check.
  • A permitted reference: LegacyDirectoryAdapter -> application.port.BorrowerDirectory. Infrastructure may refer to the application’s ports, and the adapter implements the contract.
  • The reference from infrastructure.notification to application: ConsoleNotificationSender -> application.ConfirmationFormatter. The console sender builds its notice text with the shared formatter, preserving existing wording.

B3: Diagnose the Package Design

  • (a) Write two change requests of your own and list the packages each would change. (Model: “the console prints the borrower’s name”: presentation only, or application too, which exposes the string-result limit. “Add an email notifier”: a new class in infrastructure.notification and one construction change in bootstrap.)
  • (b) Identify the one arrow to a less stable package; would you act?
  • (c) Should ConfirmationFormatter stay in application? Name the test or number that would change your mind.

B3 (c): The Model Answer

  • Stay in application. The coordinator needs it to build the returned text: moving it into infrastructure.notification would make application depend on infrastructure, the same failure as moving NotificationException.
  • Moving it into domain would put wording in the domain.
  • What would change the answer: a second notification format needing its own formatter, or the dependency report showing more concrete application classes referenced from infrastructure.

T1 and T2

  • T1. Diagram. A compact package diagram of your team’s codebase in the style of Figure 10.1: boxes for groups of packages, arrows for permitted references. Mark one reference you would forbid and say why. No packages yet? Propose the groups.
  • T2. Decision. One architecture decision your team actually faces (where a boundary or port belongs; whether to extract a storage contract; where construction happens). An ADR in ADR-010’s form: context, decision, alternatives including doing nothing, consequences, revisit triggers.

T3, T4 and T5

  • T3. Restructure. One small change on a branch. Package moves separate from logic changes; existing checks passing; say which behaviour you protected.
  • T4. Check the direction. A compiled dependency check (DependencyRulesTest or jdeps) if your build can; otherwise a manual import review, recorded as manual. Look for cycles as well as forbidden directions.
  • T5. Review. A teammate reviews the change and the ADR. Record the feedback and what you changed. Then work through the Milestone 3 checklist: which items are complete, which are gaps, who will close each.

What a Good Team Answer Shows

  • T1: every arrow justified in one sentence; at least one forbidden reference marked; the dependency check confirms it does not occur. A weak diagram copies Figure 10.1 without checking.
  • T2: the context names a problem someone could observe (“the checkout class creates the payment client and cannot be tested”); alternatives include doing nothing; the trigger is an event.
  • T4: says whether the check was compiled or manual, lists forbidden references found or states none, says whether cycles were searched.
  • T5: records a real reviewer’s comments and the response.

If You Want More: The Book’s Project

  • B4. Break the rules on purpose. In a scratch copy, construct InMemoryReservationRepository inside ReservationCoordinator. Run the dependency test, record the violation, restore the original.
  • B5. Reproduce the migration from the Chapter 9 project: move classes into the declared packages, extract the repository contract, supply it through construction, update imports and launchers, preserve results.
  • B6. The Section 10.10 AI activity.

What You Should End Up With, and When You Are Done

  • What you should end up with: answers to B1 to B3; for your team’s project the T1 diagram, T2 decision record, T3 diff or PR description, T4 dependency result, T5 review record with the checklist status.
  • Accept when: your diagram matches your code; your decision states a real alternative and an observable revisit trigger; moves are apart from logic changes; dependency evidence covers direction and cycles and says compiled or manual; the review is real.
  • Do not claim review or hosted CI that has not occurred.

Fixing the Flaws: Four Wrong Alternatives (1 of 2)

// WRONG: the application now names a concrete infrastructure type
private final ReservationRepository repository = new InMemoryReservationRepository();

// WRONG: callers could change the store without add's checks
public List<Reservation> findAll() { return backingList; }
  • The first: the dependency check rejects the compiled edge. The second: the snapshot test checks non-mutation and historical independence.

Fixing the Flaws: Four Wrong Alternatives (2 of 2)

// WRONG: a second object with the same ID replaces the first
records.put(reservation.id(), reservation);

// WRONG: the console now owns a policy the domain already decides
if (days > 7) { output.println("Rejected: borrower limit or duration."); return; }
  • The third: duplicate IDs must reject without mutation. The fourth: a ten-day staff request exposes it (staff policy allows it): the final console test sends exactly that.

Do the Tests Detect Faults? (1 of 3)

FaultBefore the three added testsWith the final tests
Duplicate id replaces the original; modifiable snapshot copy; live snapshot view; HashMap instead of insertion orderAll four caughtUnchanged
Explicit null check removed in the repositorySurvived: still throws NullPointerException, only the message differsSurvives (equivalent behaviour)
Stores after notification; reservations() modifiable copy; identifier skips a number; conflict check ignores stored recordsAll four caughtUnchanged

Do the Tests Detect Faults? (2 of 3)

FaultBefore the three added testsWith the final tests
Null repository acceptedSurvived: no test constructed a coordinator without oneCaught by coordinatorRequiresARepository
One repository shared by every sessionCaughtUnchanged
Console prints without a newline; swallows directory failuresBoth caughtUnchanged
Console duplicates the seven-day ruleSurvived: the console test used only a studentCaught by the ten-day staff test

Do the Tests Detect Faults? (3 of 3)

FaultBefore the three added testsWith the final tests
PeriodValue validation removed; LoanPeriod gains value equalityBoth caughtUnchanged
Seven dependency-direction violationsAll caught by the direction testUnchanged
directory and notification packages refer to each otherSurvived: each reference is individually permittedCaught by the cycle test
A class name held in a stringSurvived: jdeps reads compiled referencesSurvives: a documented limit
  • Of the 25 faults, 20 caught before the three tests were added and 23 by the final tests.

The Test Inventory

Test classInvocationsPurpose
RepositoryBoundaryTest7Snapshots, duplicate/null rejection, storage before notification, failed add, independent sessions, required repository
ValueSemanticsTest2Record equality; preserved running period equality
ReservationConsoleTest3Output contract, a staff request above the student limit, unhidden directory failure
DependencyRulesTest2Compiled references against the matrix; no package cycle
Migrated earlier suite107Existing policies, directory, loan, notification, pattern, lifecycle
Total121Complete version gate

10.13 Questions to Think with, and a Transfer Challenge

Concept, diagnosis, transfer


Concept Questions

  1. Why can the coordinator call an infrastructure object without depending on its concrete class?
  2. What distinguishes an application service from a domain policy and a repository?
  3. Why does an immutable reservation still have entity identity, and why does that not automatically define Java equality?
  4. What can the jdeps check establish, and what does it leave for review?

Diagnosis Questions (1 of 2)

  1. A console class checks the seven-day limit before calling the service. A new API endpoint does not. Explain the responsibility problem and a test that exposes inconsistent behaviour.
  2. A developer adds a database repository and announces concurrent safety because add rejects duplicate IDs. Identify the missing consistency question across eligibility, conflicts and insertion.

Diagnosis Questions (2 of 2)

  1. In the metrics table bootstrap has I = 1.00 and domain has I = 0.00. Explain why neither is a defect and what a change to each would disturb.
  2. A new class in infrastructure.directory refers to infrastructure.notification, and a class there already refers back to the directory package. Which check catches this, and which check does not?

Guidance: Questions 1 to 4

  1. The coordinator refers to ReservationRepository; bootstrap supplies an implementation; at runtime dispatch reaches the concrete method. Static and runtime diagrams answer different questions.
  2. A service coordinates a use case; a policy expresses a rule; a repository provides access to stored records through a stated contract.
  3. R1 identifies the reservation independent of its data; immutable fields do not remove that. Java equality is a separate implementation choice; this class keeps object equality.
  4. jdeps reports compiled class relationships; the tests compare edges with the matrix and search for cycles. They do not prove responsibilities are well chosen, see reflection or strings, or establish transactionality.

Guidance: Questions 5 to 8

  1. The console and API disagree if only one copies the rule. Put it in the domain policy; delegate both to the same operation. Send the same student request through both; also test an allowed staff duration.
  2. Two requests can both observe a free interval and append different IDs. Unique IDs do not make eligibility, conflict check and insertion atomic.
  3. bootstrap I = 1 can change freely, but must not accumulate business rules. domain I = 0: a change ripples into every package, so keep it small.
  4. The cycle test catches it. The direction test does not: each reference is individually permitted.

Transfer Challenge: A Clinic

A clinic appointment application has eligibility rules, an external patient directory, a console view and an in-memory appointment list.

  • Propose responsibilities and dependency directions without inventing deployment services.
  • Choose one storage port, say which layer owns its contract, and distinguish an appointment’s identity from its scheduled-time value.
  • Name one behaviour to protect during restructuring.
  • Name one change request you would use to test whether your package cuts are cohesive.

Transfer Guidance

  • Domain: appointment times, patient identity, eligibility. Application: the booking operation, with a patient-directory port and a focused appointment-store port. Infrastructure: the provider adapter and in-memory store. Presentation delegates; bootstrap selects.
  • An appointment has an explicit identity; its interval is a value. Do not assume either gives Java equals: specify interval validation, equality and duplicate-ID behaviour.
  • Preserve: a conflicting interval is rejected without storing another appointment or notifying anyone. Also preserve provider failure versus missing patient.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • Application structure makes responsibilities and dependency decisions visible beyond individual classes.
  • The version adds named packages, a narrow storage port, explicit assembly and a thin presentation adapter while preserving tested outcomes.
  • Package design adds four questions: what changes together and is released together, what clients depend on, whether dependencies point towards stability, and whether a stable package is abstract enough to extend.

Deliberate Limitations

  • In-memory, sequential state.
  • String results.
  • Unchanged lifecycle rules.
  • Chapter 11 examines refactoring and legacy code, building on the tests and boundaries established here.

Sources and Further Reading

  1. Fowler: Presentation Domain Data Layering; Stafford: Service Layer; Hieatt and Mee: Repository.
  2. Oracle: the Java 17 jdeps command; Java SE 17 List.
  3. Cockburn: Hexagonal Architecture.
  4. Evans: Domain-Driven Design Reference (2015), pp. 10 to 17.
  5. Martin: Agile Software Development (2002): principles of package design.
  • Full references and links are in the chapter text.

11 - Chapter 11: Refactoring and Legacy Code

Object Oriented Development and Agile Practices (SE506)

Chapter 11: Refactoring and Legacy Code

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 11.1 Working with unfamiliar code
    • Observe, seam, pin, reshape, then change behaviour.
  • 11.2 Smells: prompts to investigate, not verdicts
    • A smell is only worth acting on when it blocks a change.
  • 11.3 Pinning what the code does: characterisation tests
    • Record what the code does, even when it looks wrong.
  • 11.4 Seams: getting hidden dependencies out of the way
    • Clock, locale and standard output.

Chapter Roadmap (2 of 3)

  • 11.5 Reshaping the report: Extract, Move and Rename
    • Four stages of one overdue report.
  • 11.6 Small steps, and how to step back
    • One step, one check, one commit.
  • 11.7 Technical debt: say it with evidence
  • 11.8 Reviewing an AI refactoring patch review a refactoring patch.

Chapter Roadmap (3 of 3)

  • 11.9 Refactoring habits that mislead
  • 11.10 Try it yourself: pin, seam and refactor the report
    • The book’s project B1 to B7; your team’s project T1 to T4.
  • 11.11 Questions to think with, transfer challenge and Quiz 2 revision
    • An ML-serving analysis script.
    • Quiz 2 revision pointers (Chapters 6 to 10).
  • What to take away, and what comes next
    • Chapter 12: measuring progress and flow.

Learning Outcomes (1 of 2)

By the end of this chapter, you should be able to:

  1. Diagnose a smell from a concrete difficulty of change, instead of treating it as a verdict.
  2. Distinguish a refactoring from a behaviour change, and state what must stay the same.
  3. Write characterisation tests that record current behaviour, including behaviour that looks wrong.

Learning Outcomes (2 of 2)

  1. Create a seam that removes a hidden dependency, so that code can be tested.
  2. Apply small refactorings (Extract Method, Extract Class, Move Method, Rename) with a check after each step and a way back.
  3. Record uncertainties, suspected defects and technical debt separately, with evidence.

Where This Chapter Fits

  • Supports LO3 and LO4. Readings: the refactoring chapter of Martin; Feathers on legacy code is the recommended companion. The Week 10 lab is refactoring untested legacy code with regression tests.
  • Quiz 2 is held in Week 10 and covers dependency injection, TDD and test doubles, design patterns and layered architecture (Weeks 5 to 9). The legacy lab is separate from that scope.
  • You start with: Chapter 10’s project and package structure. This chapter adds one isolated package, legacy, standing for the old records system’s overdue report. Production code must not refer to it, and the dependency test now checks that.

How to Use This Chapter

  • Core reading: Sections 11.1 to 11.6.
  • Working knowledge: 11.7 (technical debt and evidence): describe debt with evidence and separate defects from preferences. You do not manage a debt register for a real project.
  • Minimum lab: B1 to B3 (the book’s project: extend the supplied tests, take the first refactoring steps) and T1 to T4 (your team’s project: characterise one untested method and refactor it in small commits).
  • If you want more: B4 to B7, the review questions, the transfer challenge.

Opening Scenario: An Old Overdue Report

  • It lists every unreturned loan past its due date and adds a fine.
  • A laptop two days late shows a fine of 0.00 yet still counts as overdue.
  • A colleague on another machine sees amounts printed as 0,50 where everyone else sees 0.50.
  • The office asks for a small change: staff should get seven days of grace instead of five.

You Open the Code

  • One method of about forty lines.
  • The staff rule in one branch, the student rule in another, and a third branch for anyone else.
  • The grace number, five, appears twice in the staff branch; changing only one is a bug. The same literal 5 also stands for the cap, so a search-and-replace would change the wrong thing too.
  • It reads today’s date from the system clock: its output changes every day.
  • Nobody wrote tests, and the author has left.

A Cautionary Tale

  • A colleague once tidied a report like this one. The new version was shorter. It also listed fewer loans.
  • Nobody could say whether a defect had been repaired or a promise broken: nobody had recorded what the old report promised.
  • So what do you change first? Not the staff grace period.
  • Before changing a rule, know what the code does today, so you can tell afterwards that you changed only what you meant to.

The Method of This Chapter

  1. Observe.
  2. Pin what you observe.
  3. Reshape the structure in small checked steps.
  4. Keep every correction of behaviour separate from every improvement of structure.
  • Changing code you do not yet understand, without tests that protect it.

11.1 Working with Unfamiliar Code

Legacy means no tests


What Makes Code “Legacy”

  • Feathers: legacy code is code without tests. Age does not matter.
  • Code written last month becomes legacy when you must change it, cannot tell what a change will break, and nothing will tell you.
  • The report is old, but the habits that produced it (global state, hidden dependencies, one long method) are not.

The Dilemma

  • Refactoring changes internal structure while external behaviour stays the same.
  • To know behaviour stayed the same you need tests.
  • But the code is hard to test because of its structure, so you want to refactor first.
  • The way out is a short sequence.

The Way Out: Five Steps

  1. Observe: run the code and read it with a specific question in mind. You need not understand all of it.
  2. Make the smallest edit that lets you test it: a seam. Keep it mechanical, so the compiler and a quick read can check it.
  3. Pin: characterisation tests that record what the code does now.
  4. Reshape: refactor in small steps, tests after each.
  5. Only then change behaviour, in a separate step with its own tests.

Figure 11.1: The Order of Work

Observerun and readSeamsmallestmechanical editPincharacterisationtestsReshapesmall refactorings,test after eachChange behavioura separate step,new testsRoll back the stepa test failsredo itDebug backwards, not forwards:undo the last step and redo it.

The Code as Found (1 of 3)

public static String build(List<LoanRow> rows) {
    String out = "";
    double total = 0;
    int count = 0;
    LocalDate today = LocalDate.now();
    out += "OVERDUE REPORT " + today + "\n";
    for (int i = 0; i < rows.size(); i++) {
        LoanRow r = rows.get(i);
        if (r.returned) {
            continue;
        }
        long late = ChronoUnit.DAYS.between(r.due, today);
  • Read it once and write down what you notice before reading on.

The Code as Found (2 of 3)

        if (late > 0) {
            double fine = 0;
            if (r.kind.equals("S")) {
                if (late > 2) {
                    fine = (late - 2) * 0.50;
                    if (fine > 10) {
                        fine = 10;
                    }
                }
            } else if (r.kind.equals("T")) {
                if (late > 5) {
                    fine = (late - 5) * 0.25;
                    if (fine > 5) { fine = 5; }
                }
            } else {
                fine = 1.00 * late;
            }

The Code as Found (3 of 3)

            out += r.item + " " + r.borrower + " " + late
                    + " day(s) late, fine " + String.format("%.2f", fine) + "\n";
            total += fine;
            count++;
        }
    }
    out += count + " overdue, total fines " + String.format("%.2f", total) + "\n";
    return out;
}
  • LoanRow is the old system’s export format: item, borrower, kind ("S" student, "T" staff), due date, returned flag, all public mutable fields. Other programs read it, so it stays as it is.

Separate What You Know From What You Guess

ObservationHow you knowStatus
Returned loans are skippedRead: continue on returnedKnown
Fines start after a grace period and are cappedRead: two nested comparisons per kindKnown
Neither "S" nor "T" pays 1.00 a day, no grace, no capRead: the final elseKnown what, unknown whether intended

Separate What You Know From What You Guess (2)

ObservationHow you knowStatus
The output depends on the date the program runsRead: LocalDate.now()Known
Amounts print with a comma in some localesString.format without a locale; %f localises the separatorLikely; confirm by running
A loan two days late is listed with a fine of 0.00 and countedThe count is incremented inside late > 0Likely; confirm by running
  • “Unknown whether intended” is an honest status. It is not your job yet to decide it is wrong.

11.2 Smells

Prompts to investigate, not verdicts


What a Smell Is

  • A surface indication that usually corresponds to a deeper problem: not always a problem.
  • Its use here: a prompt to investigate.
  • Only worth acting on when you can name a concrete difficulty of change that it causes.

Smells in the Report (1 of 3)

SmellThe difficulty it causes
Duplicated branches (student and staff blocks have the same shape)A change to the rule shape must be made in every branch; the grace number appears twice in each, so editing one of the two gives a wrong result
Long methodTo change the fine you must read the loop, the formatting and the totals too

Smells in the Report (2 of 3)

SmellThe difficulty it causes
Hidden dependency on the system clockThe output differs every day: no test can compare it with a fixed answer
Hidden dependency on the default localeThe same data prints differently on two machines
Magic numbers (2, 0.50, 10, 5, 0.25)A reader cannot tell a grace period from a cap

Smells in the Report (3 of 3)

SmellThe difficulty it causes
A string kind compared with equalsAn unexpected kind falls into the final branch without anyone deciding that
String += in a loopNot a difficulty of change here; a possible slowness for very long reports
  • The last row is a deliberate counter-example.

Not Every Smell Needs Repair

  • A smell in code nobody changes costs very little. Its repair value depends on how often the code changes and how much the difficulty costs.
  • Decide where to spend effort by asking which changes are coming.
  • The office has just asked for a grace-period change: the duplicated branches and magic numbers are worth fixing now.
  • The String += loop is not.

11.3 Pinning What the Code Does

Characterisation tests


Two Different Promises

  • A regression test protects behaviour that everyone agrees is correct.
  • A characterisation test records what the code does today, whatever that is, so you notice if a change alters it.
  • They look the same in the test framework.
  • A passing characterisation test means “unchanged”. It does not mean “correct”.

How to Write One (1 of 2)

  1. Isolate the code so it can run in a test (often the hardest part).
  2. Write a test with a placeholder expectation, a value you know to be wrong.
  3. Run it and read the actual result from the failure message.

How to Write One (2 of 2)

  1. Record that result as the expectation, after you have looked at it.
  2. Rename the test to say what you now understand the code does.
  3. Keep exploring with further inputs, especially boundaries and odd cases.
  • Step 4 holds the discipline: do not paste the result blindly. Look at each one and classify it.

Classify Every Result

ClassMeaningWhat you do
ExpectedMatches the rule you were toldKeep the test
SurprisingYou did not expect it, but it may be intendedKeep the test; ask the owner
Suspected defectIt looks wrongKeep the test, mark it clearly, record the question; do not fix it here
UnknownYou cannot tellKeep the test and record the question

What the Report Does: Fines by Days Late

0.005.0010.0020.0030.00051015202530days latestudentstaffother kindscap at 22 dayscap at 25 daysno grace, no cap(suspected defect)

Observed Fines (1 of 2)

KindDays lateFineComment
S1, 20.00Listed and counted with no fine (grace)
S30.50First fine
S104.00
S22, 23, 10010.00The cap is reached at 22 days and holds
T1 to 50.00Listed and counted with no fine (grace)
T60.25First fine
T25, 26, 1005.00The cap is reached at 25 days and holds

Observed Fines (2 of 2)

KindDays lateFineComment
X (unknown)11.00Suspected defect: no grace
X (unknown)100100.00Suspected defect: no cap
s (lower case)33.00Suspected defect: a typo in the data costs six times the student fine
blank22.00Same branch
  • Run on the supplied code with today fixed at 20 October 2026.

Four More Observations

  • A loan due today or later is not listed.
  • A returned loan is ignored.
  • A null kind throws NullPointerException (and only for a loan that is actually overdue).
  • Under a German locale the amounts print with a comma.
  • One of each class: the grace listing and the comma are surprising; the unknown kinds are suspected defects; the null-kind failure is unknown.

One Test, in the Style

// SUSPECTED DEFECT, pinned on purpose: any other kind pays 1.00 a day
// with no grace and no cap.
@ParameterizedTest
@CsvSource({"X,1,1.00", "X,100,100.00", "s,3,3.00"})
void otherKindsPayTheFullRateWithNoGraceOrCap(String kind, int daysLate, String fine) {
    assertEquals(oneLoan(daysLate, fine), OverdueReport.build(List.of(open(kind, daysLate)), TODAY));
}
  • The comment is part of the test. In six months someone sees it fail during an unrelated change: the comment tells them whether to fix their change or ask a question.

A Second Net: A Differential Check

  • Keep a copy of the original code as an oracle, generate thousands of inputs with a fixed seed, compare old and new output.
  • The companion’s test runs 3,000 generated lists.
  • Stronger than the examples for the kinds of input it generates. It says nothing about inputs outside them.
  • Neither net proves equivalence: they show a chosen set of behaviours is unchanged. Add more where you are about to change more.

11.4 Seams

Getting hidden dependencies out of the way


What a Seam Is

  • A seam is a place where you can change what the program does without editing the code at that place.
  • To use it you need an enabling point: somewhere you can choose the other behaviour.
  • The report has three hidden dependencies, each needing a different answer.

Three Hidden Dependencies

Hidden dependencyWhere it isHow to isolate it
The system clockLocalDate.now() inside buildA parameter seam: add build(rows, today); the old method calls it
The default localeString.format("%.2f", ...)Pin the locale in the test and restore it afterwards
Standard outputprint writes to System.outCapture System.out in one test and restore it afterwards

The Clock Seam

public static String build(List<LoanRow> rows) {
    return build(rows, LocalDate.now());
}

public static String build(List<LoanRow> rows, LocalDate today) {
    String out = "";
    double total = 0;
    int count = 0;
    out += "OVERDUE REPORT " + today + "\n";
    // ... the rest of the method is unchanged

Why This Edit Is Safe Before Any Test

  • It is mechanical: one expression moves from the body to a call, one line is deleted.
  • It is checked by the compiler: if anything else changed, the code would not compile.
  • It is visible to a reviewer: a few lines, readable in a minute.
  • Existing callers see no difference. This is Chapter 7’s injected Clock at the smallest scale: a parameter suffices for a static method.
  • A bigger seam (injected Clock, non-static class) is better design but an untested edit: the very risk the chapter is about.

Check the Enabling Edit by Running It

  • The companion’s probe contains the original report with only its class name changed (OriginalUnseamed) and a program comparing the original with the seamed report through their public one-argument entry points.
  • It compares five rows (a late student, a late staff member, a row due today, an unknown kind, a returned loan) and an empty list, and checks one literal result.
  • It refuses to run if the date changes while it executes.
  • Expected: Original and seamed reports agree on 5 rows and on an empty list.

The Probe Is Bounded

  • It examines a few rows, not every input, and cannot tell you the report is correct.
  • What it adds to the compiler and your eyes: a direct comparison of the code as found with the code after the only untested edit.
  • Keep it small, run it once, then put the real protection in the characterisation tests.

Who Chooses the Zone?

  • The report now takes a date, so the caller decides which calendar date “today” is.
  • A Clock carries a zone; LocalDate.now(clock) converts its instant using it.
  • At 22:30 UTC on 21 October it is already 22 October in Baghdad. A loan due on 21 October is not late in UTC and one day late in Baghdad.
  • The seam did not remove the question of which zone is right. It moved it to the one place that has to answer it.

The Locale and the Output Stream

  • Left alone, because changing them would be a bigger edit than the tests justify. The tests deal with them instead:
    • a setup sets the default locale to Locale.ROOT before each test and restores it;
    • one test sets Locale.GERMANY and records the comma;
    • one test replaces System.out and restores it in a finally block.
  • Both are global, process-wide state. Such tests cannot safely run in parallel, and one that forgets to restore breaks an unrelated test far away.

Other Seams Worth Knowing

  • Subclass and override: make a class non-final; a test subclass replaces one method.
  • Extract interface: put a dependency behind an interface (Chapter 6’s move).
  • Both change more code than a parameter does: choose the smallest seam that gets the code under test.
  • A better design (a PrintStream parameter on print, a locale argument) is worth adding when you have a reason to change those lines.

11.5 Reshaping the Report

Extract, Move and Rename


Refactorings Have Names

  • So a team can say what it did and a reviewer can check it.
  • Fowler’s catalogue: Extract Function, Move Function; the first edition called them Extract Method and Move Method. Used interchangeably.
  • Each stage below was a separate step; the characterisation tests and the differential test passed after each.

Stage 2: Extract Method

double fine = fineFor(r.kind, late);
out += r.item + " " + r.borrower + " " + late + " day(s) late, fine " + money(fine) + "\n";
...
private static double fineFor(String kind, long late) { /* the three branches, moved as they were */ }
private static String money(double amount) { return String.format("%.2f", amount); }
  • The behaviour is the same because the moved code is the same.
  • The loop reads in one glance, and the formatting rule (and its locale dependency) lives in exactly one place.

Stage 3: A Missing Concept

  • The three branches differ only in three numbers: free days, a daily rate, a cap.
  • That is a missing concept, and it can be a small class.
record FinePolicy(int graceDays, double ratePerDay, double cap) {
    double fineFor(long daysLate) {
        if (daysLate <= graceDays) {
            return 0;
        }
        return Math.min((daysLate - graceDays) * ratePerDay, cap);
    }
}

Stage 3: Three Named Policies

private static final FinePolicy STUDENT = new FinePolicy(2, 0.50, 10.00);
private static final FinePolicy STAFF = new FinePolicy(5, 0.25, 5.00);
/** Suspected defect, preserved on purpose: any other kind pays 1.00 a day
 *  with no grace and no cap. */
private static final FinePolicy ANY_OTHER_KIND = new FinePolicy(0, 1.00, Double.MAX_VALUE);
  • fineFor moved into the new class (Move Method).
  • The odd third branch did not disappear: it now has a name, a comment and a policy of its own.

Is the New Code Equal to the Old?

Old codeFinePolicy.fineFor(daysLate) with the same numbers
if (late > 2) not taken: fine stays 0daysLate <= graceDays returns 0
fine = (late - 2) * 0.50; if (fine > 10) fine = 10;Math.min((daysLate - graceDays) * ratePerDay, cap)
Staff: the same with 5, 0.25 and 5The same with those values
Other kinds: fine = 1.00 * lateGrace 0, rate 1.00, cap Double.MAX_VALUE
  • Check it algebraically, then let the tests confirm the algebra for the inputs they cover.

Figure 11.2: After Stage 3

OverdueReport+build(List<LoanRow>) String+build(List<LoanRow>, LocalDate) String+print(List<LoanRow>) void-policyFor(String) FinePolicy-money(double) String«record»FinePolicyint graceDaysdouble ratePerDaydouble cap~fineFor(long) doubleLoanRow+String item+String borrower+String kind+LocalDate due+boolean returnedholds threereadsSTUDENT, STAFF,ANY_OTHER_KIND

What the Refactoring Did Not Do

  • The odd third branch is not hidden: hiding a suspected defect inside tidy code is the opposite of what refactoring is for.
  • LoanRow is unchanged; the signatures of build and print are unchanged, because other programs call them.
  • The solid arrow means OverdueReport holds three FinePolicy values; the dashed arrow means it only uses LoanRow.

Stage 4: Rename and Tidy

  • r becomes row, late becomes daysLate; a for-each loop; StringBuilder instead of String +=; count and total become overdueCount and totalFines.
  • None of this changes the output.
long daysLate = ChronoUnit.DAYS.between(row.due, today);
if (daysLate > 0) {
    double fine = policyFor(row.kind).fineFor(daysLate);
    report.append(row.item).append(' ').append(row.borrower).append(' ')
            .append(daysLate).append(" day(s) late, fine ").append(money(fine)).append('\n');
    totalFines += fine;
    overdueCount++;
}

The Request That Started the Chapter

  • The staff grace period is now a one-number change: new FinePolicy(7, 0.25, 5.00).
  • It is a functional change: it waits for its own commit and test change.
  • Run the suite after it: five of the 28 report invocations fail: the rows T,6 (0.25 becomes 0.00), T,25 (5.00 becomes 4.50), T,26 (5.00 becomes 4.75), the mixed-list test and the differential test.
  • Those failures are the expected evidence of the intended change. Update the expectations in the same commit as the rule.

11.6 Small Steps, and How to Step Back

One step, one check, one commit


The Commit Sequence

CommitKindWhat the reviewer checks
1. Add a date parameter to build (seam)Mechanical edit before testsThe old method delegates and nothing else moved
2. Add characterisation tests and the differential testTests onlyEvery expectation was run and reviewed; surprises commented
3. Extract fineFor and moneyRefactoringThe moved code is identical; all tests pass
4. Extract FinePolicyRefactoringThe algebra; all tests pass; the odd policy is visible
5. Rename and tidyRefactoringNo behavioural edits hide among the renames

Do Not Mix

  • Mixing a refactoring and a behaviour change in one commit is the most common way to lose control.
  • A failing test no longer says which of the two caused it.
  • Chapter 5 made the same point with its two-stage change.
  • The sixth commit in the companion (rejecting unknown kinds) is a different kind and reviewed as one: its message says what behaviour changed and why, and its test diff shows old expectations deliberately replaced.

Decide What a Difference Means

A check reportsa differenceWas this behaviourchange agreed?Inspect or undothe latest editSeparate thefunctional changeRecord the rule andaffected examplesRerun the unchangedexpectationsNoYes

Reading Figure 11.3

  • A check reports a difference: ask one question before touching anything: was this behaviour change agreed?
  • An agreement needs a specific rule: “clean up the report” is not one.
  • Until the records office agrees a rule for unknown kinds or for loans inside their grace period, a difference at those boundaries is an unintended change.

Roll Back, Do Not Debug Forward

  • After a step, a failing test gives you a precise failing change and a green state one step behind.
  • Undo the step (git restore for uncommitted work, git revert for a commit) and redo it more carefully.
  • Debugging forward from a half-changed structure is slower and riskier.
  • The failing test is evidence about what the step got wrong: keep its name and message in your notes.

Separate the Functional Correction

  • Suppose the records office confirms only "S" and "T" exist and anything else is a data error.
  • That is a functional change: its own branch and commit. policyFor throws IllegalArgumentException("Unknown borrower kind: ..."); ANY_OTHER_KIND disappears.
  • The tests that pinned the suspected defect are replaced deliberately in the same commit, and the message says so.
  • The differential test is restricted to the kinds that did not change.
  • The rule itself is illustrative: the records office decides it, not the developer.

11.7 Technical Debt

Say it with evidence


The Metaphor

  • “Technical debt”: attributed to Ward Cunningham, the accumulated cruft that makes a system harder to change.
  • The extra effort of every later change is the interest; cleaning up is paying down the principal.
  • It helps in conversations with people who do not read code. It misleads when it excuses neglect.
  • Fowler notes the cost cannot be measured objectively, only estimated.

Attach Evidence

  • “This code is messy” is an opinion.
  • “A change to the staff grace period needs two edits in one branch and breaks silently if one is missed, and there is no test” is a statement a colleague can check.
  • After the refactoring the same change is one number, protected by 27 characterisation tests.

A Register Entry

FieldEntry for the report
ItemOverdue report: fine rules duplicated, hidden clock and locale
EvidenceGrace number appears twice per branch; output varies by date and locale; no tests
Cost (interest)Each fine change needs careful edits in several places and a manual check
DecisionPaid down: seam, tests and refactoring in five commits
RemainingThree suspected defects, one unknown

Four Kinds of Statement

  • A known defect: confirmed wrong.
  • A suspected defect: looks wrong, unconfirmed.
  • Accepted behaviour: confirmed as intended, however odd.
  • A question: unknown.
  • Only the owner of the requirement can move an item between them. Mixing them is how a refactoring becomes an unreviewed behaviour change, and how a developer’s taste becomes a defect report.

The Pinned Behaviours and Questions for the Owner (1 of 2)

Pinned behaviourClassQuestion
A loan within grace is listed with fine 0.00 and countedSurprising (SD-11-2)Should it appear at all?
Student fines start day 3, 0.50 a day, stop at 10.00ExpectedNone
Staff fines start day 6, 0.25 a day, stop at 5.00ExpectedNone
Any other kind pays 1.00 a day, no grace, no capSuspected defect (SD-11-1)Do other kinds exist? Reject them?

The Pinned Behaviours and Questions for the Owner (2 of 2)

Pinned behaviourClassQuestion
A loan due today, due later, or returned is not listedExpectedNone
A null kind throws NullPointerException, only for an overdue unreturned loanUnknown (SD-11-3)Can the export contain a null kind?
Amounts use the default locale: 0,50 under Locale.GERMANYSurprising (SD-11-4)Should the separator follow the machine or the office?

11.8 Reviewing an AI Refactoring Patch

“Equivalent” is a claim to trace


Ground Rules for the Activity (1 of 2)

For this activity
Your goalDecide whether a proposed refactoring patch preserves behaviour, using evidence rather than the patch’s own claims
You start withThe refactored OverdueReport from 11.5, the characterisation suite and the differential test
What is in boundsEvaluate and, if worthwhile, repair the patch; do not change the tests to make it pass

Ground Rules for the Activity (2 of 2)

For this activity
How to checkTrace the boundary case by hand; apply each claim to a scratch copy; run the suite; read each failing test
How to decideA verdict on each claim, with the failing tests as evidence
No AI account?The ready-made example is enough

The Ready-Made Example

Author-written fictional AI output.

I tidied build. The overdue test is now !row.due.isAfter(today), which reads better and is equivalent to daysLate > 0. I also choose the fine policy before the check, so the loop has one path. Unknown kinds fall back to the student policy, since students are the only borrowers who should ever be fined. All formatting is unchanged, so behaviour is preserved.


The Patch

long daysLate = ChronoUnit.DAYS.between(row.due, today);
FinePolicy policy = row.kind.equals("T") ? STAFF : STUDENT;
if (!row.due.isAfter(today)) {
    double fine = policy.fineFor(daysLate);
    // ... append the line, add to the total and the count, as before
}
  • Trace the first claim before running anything. On the due date daysLate is 0, so the original skips the row. What does !row.due.isAfter(today) do for a row due today?

Verdicts (1 of 2)

ClaimFinding
!row.due.isAfter(today) is equivalent to daysLate > 0False on the due date: daysLate is 0 so the original skips the row, but isAfter is false so the patch includes it. 5 invocations fail
Choose the policy before the checkA second behaviour change: a row not yet due now has its kind inspected, so a null kind throws where it was ignored. 1 invocation fails: the only test that notices

Verdicts (2 of 2)

ClaimFinding
Unknown kinds fall back to the student policyA functional change presented as a tidy-up: X, s and blank paid 1.00 a day; now they follow student rules. 5 invocations fail
Formatting is unchanged, so behaviour is preservedTrue premise, false conclusion: formatting is not the only behaviour. 9 of the 38 report and bridge invocations fail
  • Reject it as a refactoring. The first two can be repaired; the third needs a decision from the records office.

Tracing and Running

  • The equality case can be settled by tracing the due date before any test runs.
  • The null-kind change is not visible from the patch text unless you ask what each line now does for a row that is not overdue.
  • Applying the claims one at a time is what lets each failing test point at one claim.
  • Do not use a generated test to approve a generated change.

11.9 Refactoring Habits That Mislead

Eleven to avoid


Mistakes 1 to 4

MistakeConsequenceBetter action
Fix a suspected defect during a refactoringOne commit cannot be reviewed as “no behaviour change”Pin it, record the question, fix it later in its own commit
Paste actual output into tests without reading itThe suite blesses bugs invisiblyClassify each result and comment the odd ones
Test only the happy path of a legacy methodOdd branches change unnoticedProbe boundaries, unknown inputs, nulls, hidden dependencies
Make a large edit to create a seamThe seam edit is itself untestedThe smallest mechanical edit, checked by eye

Mistakes 5 to 8

MistakeConsequenceBetter action
Leave a test changing the locale or System.out unrestoredUnrelated tests fail far awayRestore global state in finally or @AfterEach
Refactor several things per commitFailures cannot be attributedOne refactoring per commit, tests between
Read “all tests pass” as “behaviour identical”Untested behaviour was never protectedState what the tests cover and what they do not
Refactor everything that smellsTime spent where nothing will changePick smells that block a change you must make

Mistakes 9 to 11

MistakeConsequenceBetter action
Change the shape of a public type other programs useHidden callers breakKeep signatures and formats others depend on
Trust the seam edit because it compiledThe one untested edit could still have changed behaviourRun the original beside the seamed version on a few rows, then pin
Accept “equivalent” for a boundary rewriteisAfter and a day count differ exactly on the due dateTrace the equality case by hand, then run the tests

11.10 Try It Yourself

Pin, seam and refactor the report


Input and Demonstrations

  • You start with: the Chapter 10 version plus the legacy package (LoanRow, the stage 1 report, the characterisation suite, the differential test, the probe) and your team’s repository.
  • Demonstration: run the legacy report on one day and then another (or two dates through the seam) to show why the clock must be isolated. Run the probe. Pin one surprising result live, with its comment.
  • Guided practice: classify five pinned results from 11.3 and say what you would ask the records office about each.

B1 and B2

  • B1. Observe and characterise. Run the probe, then the supplied suite against stage 1. Add three characterisation tests of your own (many loans for one borrower; two loans for the same item; a due date far in the past). For each: record the actual result, classify it, comment if surprising.
  • B2. Refactor in small steps. From stage 1, apply Extract Method and Extract Class (stages 2 and 3), one commit each, running the suite after each. If a test fails, roll back and redo.

B1: Three Example Answers

InputObservedClass
Two rows for the same item, both overdueTwo lines, in input order, fines addedExpected
A student exactly 22 days late, then 2310.00 for both; the cap is reached at 22Expected (a boundary worth pinning)
A row with a blank item nameA line that starts with a spaceSurprising: can blank names occur?

B3 and Your Team’s Project Steps

  • B3. Report your findings. What changed structurally, what you checked after each step, what did not change on purpose (and why), and which questions remain. Add one suspected-defect register entry.
  • T1. Choose one method in your starter codebase with a smell and no tests, that you have a reason to change. Reading note: observation, how you know, status.
  • T2. Pin. The smallest enabling edit; run original beside edited; at least six characterisation tests including a boundary and an odd case; classify each.
  • T3. Refactor. At least three small refactorings, each its own commit, tests before each.
  • T4. Rationale and a teammate’s review.

If You Want More: The Book’s Project

  • B4. Break the boundary on purpose: change daysLate > 0 to daysLate >= 0. Predict which tests fail before you run them, read the messages, restore.
  • B5. A separate functional change: unknown kinds are rejected. Replace pinned tests deliberately; restrict the differential test; write the commit message to say what changed and why.
  • B6. The AI activity.
  • B7. Connect the report to the live loans through the bootstrap bridge.

B7: The Bridge to Live Loans

  • LoanSnapshotRows joins the loan and reservation snapshots into report rows. A loan carries no due date, so it comes from its reservation: the last permitted day is the day before the exclusive end. [20 Oct, 22 Oct) is due back on 21 October; the report counts it late from 22 October.
  • OverdueReportService reads one date from a Clock and one snapshot per call. A source failure propagates: it is never reported as “0 overdue”.
  • A loan with no matching reservation, one that disagrees with its reservation, and a borrower with no kind are each an error, not a row.
  • It lives in bootstrap, the only place allowed to know both the lending layers and legacy.

What You Should End Up With, and When You Are Done

  • What you should end up with: B1 to B3 with the commits or stage files; for your team’s project, observation notes, characterisation tests, at least three refactoring commits, the rationale and the review.
  • Accept when: every characterisation test was run before its expectation was recorded; surprising and suspect results are commented; no refactoring commit changes behaviour; tests pass after every commit; the seam edit is mechanical and was checked against the original; questions are recorded, not silently fixed; signatures others depend on are unchanged.
  • A passing suite is evidence for the behaviours it covers, not a proof of equivalence.

Do the Tests Detect Faults? (1 of 4)

AreaFaults triedResult
Student rulesGrace 2 to 3; rate 0.50 to 0.60; cap 10 to 9All three caught
Staff rulesGrace 5 to 4; rate 0.25 to 0.30; cap 5 to 6All three caught
Other kindsOne grace day; a cap of 10; lower-case s accepted as a studentAll three caught

Do the Tests Detect Faults? (2 of 4)

AreaFaults triedResult
Policy arithmeticMath.max instead of Math.min; grace test < instead of <=max caught. The < change survives: an equivalent mutant, since at exactly the grace day the fine is (grace - grace) * rate = 0 either way
Listing rulesDue today counted as overdue; returned loans not skipped; count off by two; total not accumulatedAll four caught
Zero-fine patchesZero-fine loans skipped; fine computed first and zero-fine loans skippedBoth caught

Do the Tests Detect Faults? (3 of 4)

AreaFaults triedResult
The AI claims!isAfter; policy looked up first; unknown kinds as students; all three togetherAll four caught
Hidden dependenciesAmounts always formatted with a point; header uses the system date; print writes to standard error; short entry point uses yesterdayAll four caught
Date arithmeticDates subtracted the wrong way round; days late from Period.getDays()Both caught

Do the Tests Detect Faults? (4 of 4)

AreaFaults triedResult
BridgeThe due day is the exclusive end; returned flag inverted; mismatched loan not detected; null kind passed throughAll four caught
ServiceA source failure becomes an empty report; null snapshot as empty; system date instead of the clock; clock zone ignored; snapshot cached between reportsAll five caught
  • 36 faults; 35 caught; 1 survives (the equivalent mutant). First round: 22 of 24 caught; the test aNullKindOnALoanThatIsReturnedOrNotYetDueIsIgnored was added to close one gap.
  • Period.getDays() is a realistic assistant suggestion: it returns the days field only, so 100 days late would not count as 100.

11.11 Questions to Think with, Transfer Challenge and Quiz 2 Revision

Concept, diagnosis, transfer, Quiz 2


Concept Questions

  1. Why is a characterisation test not the same as a regression test, and what does a passing characterisation test tell you?
  2. What is a seam, and why is the date parameter a better first edit than rewriting the method?
  3. Why should a suspected defect be pinned rather than fixed during a refactoring?
  4. What can a differential test show that a table of examples cannot, and what can it not show?

Diagnosis Questions (1 of 2)

  1. A teammate refactors the report and says “all tests pass, so nothing changed”. Name two kinds of behaviour the suite does not cover, and say what you would do about each.
  2. A developer extracts FinePolicy and, in the same commit, changes the staff cap from 5.00 to 6.00 “because it looked low”. Explain what is wrong with the commit and what the test run would and would not show.

Diagnosis Questions (2 of 2)

  1. The report runs at 22:30 UTC on a server whose clock zone is Baghdad. A loan was due on 21 October. Which date does the report use, what does it print for that loan, and which one object chooses the zone?

Guidance: Questions 1 to 4

  1. A regression test protects agreed-correct behaviour; a characterisation test records whatever the code does today. A pass means unchanged, not right.
  2. A seam lets you change behaviour without editing there; the second parameter of build is the enabling point. It is mechanical, compiler-checked and small enough to review, so it can precede any test.
  3. A refactoring is defined by unchanged behaviour. Combining a fix with it makes a failing test ambiguous and review impossible. Pinning keeps the suspect behaviour visible and protected until the owner decides.
  4. It compares thousands of generated inputs with the original, finding differences nobody wrote an example for. It cannot test inputs the generator does not produce, and is only as good as the oracle.

Guidance: Questions 5 to 7

  1. Not covered: other time zones or midnight (the short entry point reads the clock); null items or borrowers (a null name prints as null); very large lists. Add a test for the case you are about to touch, or record it as a known gap.
  2. The commit cannot be reviewed as “no behaviour change”. The staff rows and the differential test fail and look like a refactoring error, or the developer edits the expectations and hides the change. Revert the cap, refactor, then change the cap in its own commit.
  3. At 22:30 UTC it is 01:30 on 22 October in Baghdad. A Baghdad clock uses 22 October: one day late, fine 0.00, still listed. A UTC clock uses 21 October: not listed. The Clock supplied to OverdueReportService chooses the zone.

Transfer Challenge: An ML-Serving Script

An analysis script in an ML model-serving tool formats prediction scores for a daily summary. It reads the current time, prints numbers in the default locale, and has three near-identical branches for different model types. No tests.

  • Describe how you would find its seams.
  • Which six or seven inputs would you pin first?
  • Which refactorings would you apply?
  • Name one behaviour you would record as a question instead of fixing.

Transfer Guidance

  • Seams: a date parameter on the entry point; pin the locale in the example data; capture standard output once.
  • Pin first: a score at each boundary of a model type’s rule; one at the maximum; an unknown model type; an empty list; a null; the locale case.
  • Refactor: Extract Method for the formatting; Extract Class for the three near-identical branches (a small value object holding what differs); Rename.
  • Question, not fix: whatever an unknown model type does today.

Quiz 2 Revision Pointers (1 of 3)

  • Quiz 2 is held in Week 10 and covers dependency injection, TDD and test doubles, design patterns and layered architecture (Weeks 5 to 9).
  • Revise Chapters 6 to 10. Do not treat this chapter’s legacy lab as quiz content.
ChapterRevision focus
6Interfaces and ISP, dependency direction, constructor injection, composition root, what a container automates
7Red-green-refactor, test anatomy, stubs, fakes, spies and mocks, controlled time, weak tests

Quiz 2 Revision Pointers (2 of 3)

ChapterRevision focus
8Factory Method, Abstract Factory, Builder, Adapter, Decorator, and choosing the simpler alternative
9Strategy, State, Observer and Command, partial failure, and choosing a pattern by the variation it handles
10Layers and dependency direction, ports and adapters, repositories, entities and value objects, package cohesion, coupling and stability

Quiz 2 Revision Pointers (3 of 3)

  • Use the module leader’s confirmed quiz guidance if it provides more detail.
  • These pointers add no assessment rules and predict no particular questions.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • Changing code you do not understand is a sequence, not a leap: observe, make the smallest edit that lets you test it, pin behaviour, reshape in small steps with a check and a way back, and keep behaviour corrections apart from structure improvements.
  • Smells say where to look, debt claims need evidence, and a green suite is evidence for the behaviours it covers.

Where the Report Stands

  • A seam checked against the original.
  • A pinned suite.
  • A small policy class.
  • Four recorded oddities waiting for the records office.
  • Chapter 12 turns to measuring progress and flow, and introduces the visiting-researcher change request the rest of the project must absorb.

Sources and Further Reading

  1. Fowler: Definition of Refactoring; the Refactoring catalogue; Technical Debt; Code Smell; Legacy Seam.
  2. Feathers: Characterization Testing; Testing Effectively With Legacy Code; Working Effectively with Legacy Code (2004).
  3. Oracle: Java SE 17 Formatter, Clock, ChronoUnit and Period.
  4. JUnit: User Guide 5.13.4.
  5. Martin: Agile Software Development (2002).
  • Full references and links are in the chapter text.

12 - Chapter 12: Measuring Progress, Flow and Technical Health

Object Oriented Development and Agile Practices (SE506)

Chapter 12: Measuring Progress, Flow and Technical Health

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 2)

  • 12.1 What decision needs a measure?
    • A measurement contract, and a synthetic issue history.
  • 12.2 Estimates revisited: what they can and cannot tell us
  • 12.3 Velocity and its limits
    • Velocity versus throughput; conditional forecasts.
  • 12.4 Burn-down and burn-up: reading the lines
    • Reconciling scope additions and removals.
  • 12.5 How long do things take? Lead time, cycle time, throughput, WIP
    • And real repository evidence.

Chapter Roadmap (2 of 2)

  • 12.6 Technical health, and how metrics get misused
  • 12.7 A changed requirement, and what it touches
    • CR-12: visiting-researcher reservations.
  • 12.8 Checking an AI’s explanation, not its confidence check the explanation, not its confidence.
  • 12.9 Measurement habits that mislead
  • 12.10 Try it yourself: measure, interpret and prepare the change
  • 12.11 Questions to think with, and a transfer challenge
  • What to take away, and what comes next
    • Chapter 13: implement CR-12 and deliver it.

Learning Outcomes (1 of 2)

After this chapter, you should be able to:

  1. Define a measurement boundary and calculate lead time, cycle time, throughput, work in progress and work-item age.
  2. Distinguish velocity from item throughput and explain why neither ranks individual contribution.
  3. Reconcile burn-down and burn-up observations with additions, removals and completed work.

Learning Outcomes (2 of 2)

  1. Use a small body of evidence to frame a conditional forecast and identify missing information.
  2. Evaluate a technical-health or debt claim without confusing activity with quality or causation.
  3. Prepare acceptance examples, an impact analysis and an ordered response to a changed requirement.

Where This Chapter Fits and Where You Start

  • Supports LO5 and LO6. Required reading: the descriptor’s Martin (2002) planning review; recommended: selected chapters of Accelerate.
  • The required practical activity includes repository-metrics analysis.
  • The requirement change is introduced in this chapter, implemented and verified in Chapter 13 (Milestone 4), and evaluated during handover in Chapter 14.
  • The module leader releases your team’s assessed variant. The book’s worked change does not replace it.
  • Version: Chapter 11’s verified Java project. This chapter adds an isolated metrics exercise and planning records. It does not implement the feature.

How to Use This Chapter

  • Core: 12.1 to 12.7, with the measurement contract and the worked change specification.
  • Minimum lab: reproduce the synthetic calculations; inspect actual repository evidence; challenge the AI summary; preserve the pre-change version with a change-impact note.
  • Working knowledge: the supplied Java calculator and the repository extraction method.
  • Optional: richer visualisation, larger cohorts, probabilistic forecasting (after obtaining suitable data).

Opening Problem: The Line Went Up

  • The lending team has completed several changes, yet the remaining-work line has risen.
  • One item spent two days waiting for a directory decision. Another was removed from scope.
  • A team member says more commits prove the team is faster. An AI summary says the blocked item proves the architecture is poor.
  • The history is synthetic teaching data, not recovered history and not evidence about a real student team.

And a New Request

  • Visiting researchers should be able to reserve selected equipment after sponsor approval, with a shorter loan limit.
  • Can the team finish it next iteration? Which existing rules will it affect? What evidence would justify starting immediately rather than resolving an uncertainty first?
  • A metric cannot make these decisions. It can make part of the situation visible, if its unit, boundary and limitations are clear.

12.1 What Decision Needs a Measure?

A contract before a number


Begin With a Question That Could Change an Action

  • “Collect more metrics” is a weak objective.
  • “Should we finish the two open items before pulling the sponsor-approval story?” points to a decision.
  • We need: which items remain, how long they have been open, whether they are blocked, how comparable they are with past work. A commit count cannot answer all that.

State the Contract for Each Measure

  • The question, population, unit, time window, intended response.
  • Keep a note of the source and extraction date.
  • If a number cannot influence a decision or test an assumption, consider whether collecting it is useful.
  • For a small student project, a clear table often teaches more than a dashboard of unexplained indicators.
  • Compare definitions before comparing numbers: other teams may choose different boundaries.

The Measurement Contract (1 of 3)

FieldDefinition for the synthetic history
UnitOne uniquely identified backlog item; IDs A to H
AcceptedItem admitted to the tracked scope
StartedFirst transition into implementation
DoneAccepted against the team’s Definition of Done, including required checks and review
RemovedExplicitly taken out of scope without completion

The Measurement Contract (2 of 3)

FieldDefinition for the synthetic history
Reporting interval[2026-10-01T00:00:00Z, 2026-10-11T00:00:00Z)
DurationElapsed 24-hour days; waiting and weekends remain included
SnapshotState immediately after events at the stated timestamp
Completion cohortItems whose Done event falls within the reporting interval

The Measurement Contract (3 of 3)

FieldDefinition for the synthetic history
ExclusionsNo reopened, split, merged or re-estimated items in this small setup
  • The half-open interval includes completions at the starting instant and excludes the ending instant: adjacent windows cannot count the same completion twice.
  • A snapshot is different: a completion exactly at its timestamp has already left work in progress.
  • These are reporting choices, not new reservation-period rules.

The Complete Issue History (1 of 2)

IDIllustrative workPointsAcceptedStartedDoneRemoved
AReservation boundary examples3010103-
BDirectory mapping clarification5010206-
CConfirmation wording2010304-
DCommand replay protection examples3010508-

The Complete Issue History (2 of 2)

IDIllustrative workPointsAcceptedStartedDoneRemoved
EReport failure investigation50107--
FOptional export format201--08
GSnapshot consistency checks3040610-
HReview-notes improvement20608--
  • Every date is October 2026 at 00:00 UTC. A dash means the event has not occurred, not a duration of zero.

Reading the History as a Timeline

0102030405060708091011A3 ptsB5 ptsC2 ptsD3 ptsE5 ptsopenF2 pts×removedG3 ptsH2 ptsopenaccepted, waitingstarted to doneB blocked, 03 to 05

Context and Caution

  • B is recorded as blocked from 03 to 05 October while a mapping question is answered: supplied as synthetic context, not inferred from inactivity in Git.
  • G and H are scope additions; F is a removal.
  • The labels describe an imagined iteration; they do not assert that earlier files were created on these dates.

Data and Interpretation Are Different

  • A sound observation: “B spent four elapsed days between Started and Done.”
  • A possible explanation: “waiting for the mapping decision contributed to B’s delay.”
  • A stronger claim such as “the directory adapter caused all delays” needs evidence this table does not contain.

12.2 Estimates Revisited

What they can and cannot tell us


What an Estimate Is

  • A planning judgement made with incomplete knowledge.
  • Useful when it exposes uncertainty or supports a choice between smaller and larger slices.
  • Misleading when treated as a measured physical property of the work.
  • The points are a local relative scale: not hours, not an exchange rate between teams.

Points Are Not Days

  • B’s five points and C’s two express the team’s relative judgement before delivery.
  • B taking four days does not establish that one point equals 0.8 days: the duration includes waiting, and another item has different risks.
  • Do not rewrite old estimates after seeing durations to make history look consistent. If a team genuinely revises one, preserve the earlier value and record the event.

Split by Useful Behaviour

  • For the approval change, start with a concrete example: a recognised visitor requests CAM1 for two days with matching approval.
  • Compare that slice with the failure case where the approval register is unavailable: shared code, a distinct delivery risk.
  • Splitting by useful behaviour makes uncertainty easier to discuss than “write classes, write tests, write documentation”.

When Estimates Differ

  • Ask what each person assumed. One may expect an existing approval file; another a new sponsor-facing website.
  • The solution is to clarify scope, not average incompatible assumptions. The worked change uses a small local approval register and excludes an approval-management interface.
  • Capacity matters: a week with a midterm or an absent teammate is not interchangeable with an earlier week. Record the constraint without turning people into point-producing units.
  • A time-bounded investigation should have an output: a sample approval record and a checked parsing/failure contract.

12.3 Velocity and Its Limits

Two different units


Velocity and Throughput

  • Velocity (for a team using points): the sum of estimates of items completed within a defined iteration. A, B, C, D, G: 3 + 5 + 2 + 3 + 3 = 16 points.
  • Throughput counts completed items: five items in this ten-day interval.
  • Sixteen points and five items describe the same completions with different units. Calling 16 “throughput” would confuse the comparison.

What Does Not Count

  • E and H contribute no completed points yet. Calling E “80% done” does not add four points.
  • F contributes nothing: removing an optional item can be a sensible scope decision without becoming delivery.
  • Discussing progress on E is fine, but is not a reason to redefine Done.

Velocity Has Limits

  • It can help one team inspect its own planning history when the estimation scale, Definition of Done and work mix are consistent enough.
  • It cannot fairly rank teams that estimate differently.
  • It cannot reveal an individual’s contribution: reviewing a colleague’s difficult change may raise team completion with no story or commit under the reviewer’s name.
  • A burn chart is not a substitute for inspecting actual results.

Make Forecasts Conditional

  • Two remaining items totalling seven points: 7 / 16 = 0.4375 iterations.
  • That arithmetic does not establish a delivery date. It assumes comparable work, capacity, dependencies, scope and completion behaviour.
  • One observed iteration says little about variation; unfinished work may be exactly the work with unresolved risk.

A Defensible Planning Statement

We have completed five items in ten elapsed days in this synthetic example. E and H remain open. We will inspect their blockers and size the approval slice before committing to a date.

  • If later comparable history supports two to three items per five-day window, two remaining items might fit one window under unchanged conditions: a scenario, not a calibrated probability or guarantee.
  • Preserve forecasts as dated predictions and compare them with outcomes. Do not fit a confident percentile to a tiny classroom sample.

12.4 Burn-Down and Burn-Up

Reading the lines


Definitions

  • A burn-down shows remaining work.
  • A burn-up shows completed work, normally alongside the current scope total.
  • With points fixed and removed items never Done: remaining = current scope − completed.
  • Initial scope 20 points. Adding G and H: 25. Removing F: 23. At the end 16 complete, 7 remain.

The Daily Snapshots (1 of 2)

Snapshot dayScope pointsCompleted pointsRemaining pointsWIP items
01200201
02200202
03203172
04235181
05235182
062510152

The Daily Snapshots (2 of 2)

Snapshot dayScope pointsCompleted pointsRemaining pointsWIP items
072510153
082313103
092313103
10231672
11231672

The Burn-Up

05101520250102030405060708091011G addedH addedF removedscope 23done 167 leftOctober 2026 snapshot day

Read the Events, Not Only the Line

  • Day 04: the remaining line rises from 17 to 18 despite C’s two-point completion: three points arrived as G.
  • Day 08: it falls by five, but only three came from D’s completion; two came from removing F.
  • Without the scope events, both changes are easy to misread.
  • The scope history must stay visible if we want to explain the line.

Ideal Lines Are Not Evidence

  • An ideal diagonal burn-down is a reference for a fixed initial scope. It is not evidence that real work should finish in equal daily increments.
  • Review, integration and acceptance produce uneven completions.
  • Do not move Done timestamps to make a curve attractive.
  • Ask whether the team can explain the change and whether the remaining work still serves the goal.

12.5 How Long Do Things Take?

Lead time, cycle time, throughput, WIP


Definitions for This History

  • Lead time: Accepted to Done.
  • Cycle time: Started to Done.
  • The difference is the recorded wait before starting.
  • Neither measures hands-on effort.
  • The calculations use the completion cohort, not every row in the backlog.

The Finished Items

Finished itemLead daysCycle daysWait before start
A220
B541
C312
D734
G642
Mean4.62.81.8

The Arithmetic

  • Lead: 23 / 5 = 4.6 elapsed days. Cycle: 14 / 5 = 2.8.
  • Median cycle: sort 1, 2, 3, 4, 4 and take the middle: three days.
  • Wait: 9 / 5 = 1.8, consistent with 4.6 − 2.8.
  • B’s two blocked days stay inside its four-day cycle. Subtracting them answers a different question (mean 2.4) and still would not establish active coding time.

Open Items Have Ages

  • At the final snapshot E and H are started but unfinished: WIP is two.
  • Their ages are four and three days. They have no completed cycle time yet.
  • Excluding them from the mean is correct for a completed-item statistic, but forgetting them in the interpretation creates a misleadingly favourable account.
  • Report the completed cohort alongside open-item age.

Throughput by Window

  • [01, 06): two items, A and C.
  • [06, 11): three, B, D and G.
  • B belongs only to the second window, because it completes exactly at that window’s starting instant.
  • These are five-day windows, not claims about official Sprints.
  • Whole interval: five items in ten days, or 0.5 per elapsed day.

Equal Throughput, Different Delay

  • Two five-day periods, each finishing two items.
  • In one, both started one day before completion; in the other, both started seven days before and were carried in.
  • Throughput is two in both; completed cycle times are one and seven days.
  • Counting completions does not reveal how long recipients waited.

Use Actual Repository Events Carefully

  • The lab supplements the example data with real issue or PR history, using Repository Metrics Method RM-12 v1.0, exact timestamps and the supplied Java 17 calculator.
  • In the PR route: Started = PR opened; Done = merged; unit = a PR. Call the result PR open-to-merge elapsed time.
  • It is not issue cycle time, developer effort, deployment lead time or time since coding began.

Choose the Population Before the Result

  • For throughput, enumerate every qualifying merge in the chosen repository, target branch and interval, including all result pages.
  • A convenient sample of five PRs supports a sample-duration description, not the repository’s throughput.
  • Include PRs already open at the start; record open PRs at the final snapshot if reporting WIP.
  • State the inclusion rule for bots and dependency updates.

Keep Provenance

  • Keep URLs or IDs, original timestamps and the extraction date.
  • Local Git can cross-check commit identity, but author and committer dates are not substitutes for PR-open or server merge events; squashing and rebasing change the relationship.
  • If only Git history is available, report what it actually measures and obtain real PR/issue timestamps.
  • Synthetic rehearsal alone does not complete this part of the lab.

The Calculator

mkdir -p out
javac --release 17 -d out FlowMetrics.java FlowMetricsChecks.java
java -cp out FlowMetricsChecks
java -cp out FlowMetrics issues.tsv 2026-10-01T00:00:00Z 2026-10-11T00:00:00Z --scope
Completions in [from, until): 5
Completed points: 16
Mean lead days: 4.60
Mean cycle days: 2.80
Median cycle days: 3.00
WIP at until (after events at that instant): 2
Age E: 4.00 days
Age H: 3.00 days

RM-12 Route A: PR Open-to-Merge Duration

  1. Choose a source and rule: repository URL, target branch, fixed UTC interval, extraction timestamp, inclusion rule. Select the first five qualifying merged PRs by merge time and then PR number.
  2. Keep provenance for each PR: URL or ID, opened timestamp, merged timestamp, branch. Do not guess midnight if the UI shows only relative dates.
  3. Map the data: accepted and started both to the PR opened instant; done to the merged instant; leave points and removed blank.
  4. Calculate, then manually verify the longest duration.
  5. Interpret and review: coding may begin before a PR opens; draft and review waiting stay in.

RM-12 Route B: Throughput and Open Work

  • Enumerate all qualifying merged PRs in [from, until); check pagination and filters; record counts and exclusions.
  • Do not filter by opened date: that loses carried-in work.
  • For WIP at until, also include every qualifying PR opened by then that had neither merged nor closed. A PR merged after until was still open at until.
  • A PR closed without merging maps to removed, not done.
  • Only a complete population supports full-window claims.

12.6 Technical Health, and How Metrics Get Misused

Evidence, not activity


Combine a Risk, Evidence and a Response

  • “There are 159 passing tests in the Chapter 11 version” records a build result.
  • It does not say every rule is correct, every important boundary has a test, or more tests would produce a better design.
  • Chapter 11 supports one narrow claim: the selected tests detect a deliberately changed equality boundary.
  • A high line-coverage percentage can identify unexecuted areas but cannot tell whether assertions distinguish correct from incorrect outcomes.

Debt Needs an Expected Future Cost

  • The report’s duplicated selection rule had a plausible maintenance cost; the refactoring removed the duplication.
  • We have not measured hours saved or production defects avoided.
  • The sequential snapshot join may matter if concurrency enters scope, but a feature deliberately excluded from a teaching application is not automatically debt.
  • Use the debt idea to examine why a design decision was made, not to label all untidy code alike.

Claims and Cautious Responses (1 of 2)

ClaimEvidence to seekCautious response
Approval changes will touch many unrelated classesTrace actual category, directory, wiring and reservation referencesReview the boundary before estimating
Reports are unreliableReproduce a specific wrong or unavailable reportDistinguish SD-11 policy uncertainty from an implementation failure

Claims and Cautious Responses (2 of 2)

ClaimEvidence to seekCautious response
Builds are slowing feedbackTimed comparable builds, environment, wait causesTry one targeted improvement and observe later runs
A package is difficult to changeDependency evidence and affected responsibilitiesRefer to Chapter 10; do not use instability as a quality score

Goodhart’s Law

  • Pressure to maximise a proxy can weaken its connection to the goal.
  • A target for commit counts can encourage unnecessary splitting; a target for velocity can encourage inflated estimates.
  • Neither improves the borrower’s experience by itself.
  • Research on developer productivity warns against reducing it to a single activity measure.

Use Measures for Learning

  • Team learning and system improvement, not individual rankings.
  • A review, a careful deletion or help given to another person may be valuable without increasing the chosen count.
  • Pair numbers with a short account of what happened.
  • Investigate suspicious numbers without assuming misconduct, and do not present a tiny correlation as a causal explanation.

12.7 A Changed Requirement, and What It Touches

CR-12: visiting-researcher reservations


From Metrics to a Changed Requirement

  • The first six sections taught how to read evidence about a team’s work.
  • Now the same habits go to a new request: say what it must do, what it touches and what evidence will show it works.
  • It is the visiting-researcher change from the opening problem: Chapter 13 implements it and Chapter 14 reviews it.

The Book Exercise Contract

A recognised visiting researcher may request CAM1 or CAM2 for one to three calendar days when a trusted sponsor-approval record matches that borrower, item and exact loan period and is still valid at request time. Missing, rejected, mismatched, expired or unavailable approval must not create a reservation or send a confirmation. Student and staff behaviour must remain unchanged.


What CR-12 Is and Is Not

  • A precise book exercise, chosen for teaching. Not an approved institutional policy. Teams apply their own released requirement variant.
  • Approval comes from a local read-only data register, not a sponsor website. In Chapter 13 it becomes a genuine file-reading boundary, tested with temporary files as well as doubles.
  • Training a model, predicting trust and calling a live approval service are outside the exercise.
  • It gives the project another concrete contract and failure boundary.

Trace the Change Through the Design

  • BorrowerCategory has STUDENT and STAFF; the directory knows S and T; the coordinator requires a policy for every enum value.
  • Adding a category affects more than one enum: directory mapping, composition, exhaustive test setups and the visitor path must agree.
  • BorrowingPolicy.mayRequest(count, days) answers only count and duration: no identity, item, approval or clock.
  • Passing approval data through hidden global state would defeat that contract.

An Inherited Convention to Preserve

  • The coordinator counts every retained reservation for a borrower, despite the variable name activeCount. Returning a loan does not remove its reservation.
  • CR-12 preserves this convention. The companion records it as a separate question, SD-12.
  • Do not silently replace it with an active-loan count while adding visitors.

Planned Impact (1 of 2)

AreaPlanned impactEvidence needed in Chapter 13
Borrower and directoryAdd a visitor category and explicit external mappingKnown visitor, unknown code and existing category cases
Request orchestrationValidate a matching approval before a visitor reservationSuccess; denied, expired, mismatched and unavailable outcomes
Data boundaryParse the read-only approval registerReal temporary-file parsing and failure checks

Planned Impact (2 of 2)

AreaPlanned impactEvidence needed in Chapter 13
Policies and wiringAdd the three-day limit and all required collaboratorsBoundary tests and composition that includes every category
Existing reservation, loan and report behaviourRetain old results and SD-11 statusExisting regression suite plus targeted old-path examples
Delivery configurationOne visitor-enablement flagBoth settings, failure behaviour and removal plan

Rejection vs. Operational Failure

  • A missing approval is an expected rejection.
  • An unreadable or malformed register is an operational failure.
  • Both prevent a new visitor reservation, but the response should not falsely say a sponsor rejected something when no valid decision could be read.
  • Keep these outcomes visible in the application contract.

The Behaviour and Data Decisions (1 of 3)

IDBook contract
CR12-1A recognised VISITOR category; legacy directory code V maps to it; existing S/T, inactive and unknown-code behaviour stays; unknown codes never default to STUDENT
CR12-2Visitors may request only CAM1 or CAM2, for one, two or three calendar days; existing half-open period; zero, negative or four or more days rejected
CR12-3Reuse the student count limit: fewer than three retained reservations (the existing convention)

The Behaviour and Data Decisions (2 of 3)

IDBook contract
CR12-4One matching trusted approval for the exact tuple (borrowerId, itemId, start, endExclusive), nonblank sponsor, status APPROVED
CR12-5One request-time instant from an injected Clock; valid when validFrom <= now < validUntil; equality at expiry is invalid
CR12-6Missing match, DENIED, or out-of-window: expected rejection; no reservation, no confirmation
CR12-7Unreadable register, malformed row, duplicate key, unknown status, invalid interval: operational failure, distinguishable from sponsor rejection; no fallback approval

The Behaviour and Data Decisions (3 of 3)

IDBook contract
CR12-8Once approval and existing checks succeed, create the reservation with existing confirmation behaviour; an expected notification failure leaves the reservation in place
CR12-9One flag visitingResearchersEnabled, default false; disabled visitor requests are rejected without reading the register; student and staff requests never need approval
CR12-10Approval checked at reservation time only; later expiry or disabling does not cancel an existing reservation
  • The category-taking entry points are trusted teaching APIs, not authentication.

The Approval Register

borrowerId  itemId  start  endExclusive  sponsorId  status  validFrom  validUntil
V1  CAM1  2026-10-20  2026-10-22  S1  APPROVED  2026-10-18T00:00:00Z  2026-10-20T00:00:00Z
  • A local UTF-8 TSV with eight columns, tab-separated. Status exactly APPROVED or DENIED.
  • Require start < endExclusive and validFrom < validUntil. A malformed register is rejected as a whole; do not silently skip bad rows. Request keys unique per read.
  • At 2026-10-19T12:00:00Z this approves V1’s CAM1 request; at 2026-10-20T00:00:00Z it has expired.

The Acceptance Matrix (1 of 3)

ExampleInput differenceExpected observation
AC1CAM1, two days, matching approval valid at request timeOne reservation; existing confirmation; one notice
AC2CAM2, three days, its own matching recordAllowed; upper boundary
AC3Four daysReject; no state or notice change
AC4Zero or negative daysReject; no state or notice change
AC5MIC1 with matching approvalReject: outside the visitor allowlist
AC6Missing approval or DENIEDExpected rejection; no change

The Acceptance Matrix (2 of 3)

ExampleInput differenceExpected observation
AC7Approval for V2, CAM2 or a different end dateDoes not authorise this tuple
AC8now == validFromValid if all else holds
AC9now == validUntil, or before validFromRejected
AC10Unreadable file, malformed row or duplicate keyOperational failure; no change
AC11Three retained reservations existReject the count limit even if loans were returned
AC12Visitor feature disabledExplicit disabled result; approval source not called

The Acceptance Matrix (3 of 3)

ExampleInput differenceExpected observation
AC13Existing student or staff request, either flag settingExisting behaviour; approval source not called
AC14Approved item overlaps an existing reservationExisting availability rejection
AC15Valid visitor, expected notification failureReservation retained
AC16Reservation created, then approval expires or flag turns offCheckout and return stay available
AC17Unknown directory code or inactive visitorExisting semantics; never default to student
AC18Report generated after a visitor loan existsExisting mapping and SD-11 retained

Plan the Smallest Checked Response

  • Preserve the passing Chapter 11 version first.
  • Record the requirement, accepted examples, assumptions and affected files.
  • Order: a thin valid visitor path; the genuine register boundary and failure paths; old-path regression; delivery and demo evidence.
  • Each item gets an observable exit condition. Resolve the approval-record format early. Do not copy the synthetic history’s points onto these tasks.

The Ordered Backlog (1 of 2)

IDOrdered workExit evidence
C12-1Preserve baseline and confirm CR12/SD12 examplesPassing baseline; retained revision; reviewed contract
C12-2Add recognised visitor path, policies and explicit wiringValid visitor with a controlled approval source; old categories unchanged
C12-3Implement and test the actual approval-file adapterTemporary-file success and malformed, missing, duplicate failures; no mock replacing the reader under test

The Ordered Backlog (2 of 2)

IDOrdered workExit evidence
C12-4Complete matching, time and failure protectionAC boundary and operational-failure evidence
C12-5One flag; integrate CLI and demoBoth settings; old path; existing reservations usable after disable
C12-6Full regression, real boundary checks, delivery workflowObserved gate; repeatable demo; retrospective
  • C12-2 to C12-6 are planned, not implemented. A design note is not a delivered capability, and a green pre-change build is not evidence that the new rule works.

Keep the Version From Before the Change

Baseline: Chapter 11 running project
Source revision or complete-copy location:
Uncommitted/untracked source accounted for:
Java and Maven versions:
Command and actual result:
SD-11: due-today inclusion preserved; policy unresolved
SD-12: retained-reservation count preserved; policy unresolved
CR-12: specified; implementation pending
Reviewer or explicitly labelled solo review:
  • A commit ID suffices only if the intended files are committed; otherwise keep a complete copy such as pre-change-ch12.

12.8 Checking an AI’s Explanation, Not Its Confidence

Not its confidence


The Task

  • You start with: the eight-row history, the blocked interval, the measurement contract and the chart table.
  • Your task: verify every arithmetic or causal claim independently.
  • What is in bounds: explain the data and identify questions; do not change timestamps, estimates or requirements to fit the explanation.
  • Mark each claim supported, contradicted or not established.

The Ready-Made Example

Author-written fictional AI output.

The team delivered six items and 18 points because F left the backlog. Mean cycle time is two days after subtracting B’s blocked days. The rise on 4 October proves productivity declined, while the fall on 8 October proves five points were delivered. The legacy architecture caused the waiting. At 16 points per iteration, the remaining seven points will definitely finish in 0.44 iterations. Rank team members by commits to remove the bottleneck.


Verdicts (1 of 2)

Candidate claimJudgement
Six items and 18 points deliveredContradicted: F is removed, not Done. Five items and 16 points
Mean cycle is two after subtracting blockingContradicted: the defined mean is 2.8; subtracting would give 2.4, a different measure
The day 04 rise proves productivity declinedNot established: G adds three while C completes two; the line rises by one
The day 08 fall proves five points deliveredContradicted: D completes three; F removes two

Verdicts (2 of 2)

Candidate claimJudgement
Legacy architecture caused the waitingNot established: one mapping clarification is recorded
Remaining work will definitely finish in 0.44 iterationsUnsupported forecast: the division is roughly right; comparability, capacity and uncertainty are missing
Rank members by commitsReject: commits do not measure individual value or identify the cause of delay
  • A good next step: inspect E’s unresolved problem and settle the approval data contract. Another AI opinion is not verification.

12.9 Measurement Habits That Mislead

Seven to avoid


Mistakes 1 to 4

MistakeConsequenceCorrection
Treat removed work as DoneInflates delivery and hides scope decisionsRetain a separate removal event
Divide by all accepted items when averaging completed durationTreats unfinished cases as if their duration were knownUse the completed cohort; report open ages separately
Drop waiting from cycle time without saying soChanges the question; delay disappearsKeep elapsed time; define any extra measure separately
Infer work-item completion from commitsCounts workflow traces as delivered valueMatch the unit and event to the evidence

Mistakes 5 to 7

MistakeConsequenceCorrection
Use an incomplete PR sample as repository throughputUnderstates or selectively counts completionsEnumerate the full interval or label a sample
Convert one velocity observation into a deadlineHides variation, new scope and uncertaintyState conditional assumptions; revisit with new evidence
Add visitors by defaulting unknown categories to studentsBypasses the explicit new rulePreserve rejection of unknown categories; test mapping

12.10 Try It Yourself

Measure, interpret and prepare the change


What You Start With, and the Demonstration

  • Supplied: synthetic issue history and blocked-event note; Java calculator and checks; RM-12 v1.0; CR-12 contract and examples; backlog and impact templates; the Chapter 11 version.
  • Demonstration: calculate B’s lead and cycle time; explain day 08’s five-point drop; trace the count/duration policy boundary in the current code.
  • Guided practice: pairs reconcile the final scope and trace the interval-edge completion.

Steps 1 and 2: Calculate and Interpret

  1. The book’s project: calculate. Reproduce the duration table, completed count and points, final WIP and ages. Run the Java exercise and explain one boundary check.
  2. The book’s project: interpret. Explain the scope additions and removal. Write a cautious forecast paragraph and reject the unsupported AI claims.

Steps 3 to 5

  1. Your team’s project: required repository lab. Apply RM-12 to real PR or issue timestamps, or an instructor-supplied real export. Produce an auditable duration result and, if the population is complete, throughput. State missing evidence honestly. The synthetic table is rehearsal.
  2. The book’s project: preserve and plan. Retain the pre-change version; review CR-12’s matrix; order a small backlog for Chapter 13. Do not implement the feature.
  3. Your team’s project: transfer. Apply the impact method to your released variant. Record one dependency or policy question and how its answer would change the plan.

What You Should End Up With, and When You Are Done

  • What you should end up with: calculations; source and contract; cautious interpretation; reviewed AI claims; version reference; updated backlog; change-impact note. Keep real repository evidence separate from the synthetic sample.
  • Complete when: the arithmetic reconciles; event boundaries and population are explicit; open items and removals stay visible; repository results have traceable real sources; no individual ranking; new behaviour has acceptance examples; old behaviour and unresolved defects have a preservation plan.
  • Optional: compare two complete intervals with the same inclusion rule, or add reopened-item support with a clearly revised data model.

12.11 Questions to Think with, and a Transfer Challenge

Concept, diagnosis, transfer


Concept Questions

  1. How do lead time and cycle time differ under this chapter’s contract? Why must the boundaries travel with the reported number?
  2. How can a burn-down fall without an equivalent amount of work being delivered?
  3. Why can equal throughput coexist with different cycle times and different customer experiences?
  4. Why does a passing regression suite support preserving old behaviour without proving the new approval requirement?

Diagnosis Questions

  1. A dashboard calculates average cycle time only for items both started and finished this week, then claims it represents every item finished this week. What is wrong with the cohort, and which carried-in work could disappear?
  2. A developer adds VISITOR to the enum and passes sponsor approval through a static Boolean read by BorrowingPolicy. Identify the contract, test isolation and request-matching risks; propose a clearer boundary without implementing it.

Guidance: Questions 1 to 4

  1. Lead (Accepted to Done) includes the pre-start queue; cycle (Started to Done) does not. B: lead five, cycle four. Another team’s lead time may start at a customer request: the number cannot travel without its event definition.
  2. Removing F reduces remaining scope by two while delivering nothing; re-estimation could also move a line. Keep a scope event record; use a burn-up.
  3. A carried-in seven-day item and a newly completed one-day item count equally. Recipients experience different waiting.
  4. The old tests describe existing contracts; they contain no sponsor-approval requirement. Keep them; add new acceptance, unit and integration checks.

Guidance: Questions 5 and 6

  1. Filtering on both start and finish within the week excludes long-running carried-in work that finished this week. Define the cohort by the finish event, compute durations from actual start timestamps, and report unfinished-item age separately.
  2. Approval belongs to a specific borrower, item and period at a specific time. A static flag loses that identity and leaks across requests and tests, and violates the count/duration contract. Obtain a typed approval decision through an application boundary; validate match and time with explicit inputs before creating the reservation.

Transfer Challenge: ML Model Serving

An ML model-serving team: only approved model versions may be served to external callers. Its dashboard counts successful builds and reports “all requested releases are complete.”

  • Define a useful unit and Done event.
  • Identify the missing approval and serving evidence.
  • Plan a narrow change with an unavailable-registry case.
  • Explain why model accuracy, build success and release approval are different observations.
  • Synthetic model identifiers only.

Transfer Guidance

  • Unit: one requested release of a named model version to a stated serving environment. Done: the approved version is demonstrably served at that boundary with required checks, not a successful build.
  • Keep build, approval and activation timestamps separate. An unavailable approval registry must not be treated as permission to serve.
  • Plan a valid activation path; mismatched, expired or denied approval; registry failure. Test the genuine registry boundary separately from orchestration doubles.
  • Accuracy concerns predictive behaviour; build success construction checks; approval a governance decision. None substitutes for another.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • A measurement is useful when its definition supports a concrete decision.
  • The synthetic history shows why scope, elapsed time, unfinished work and completion counts must be read together.
  • Repository evidence adds traceability but does not reveal every cause of delay or every valuable contribution.

Where the Project Stands

  • The application remains at the pre-change version.
  • CR-12 and the ordered backlog give a precise basis for Chapter 13: implementation, genuine boundary tests and delivery evidence.

Sources and Further Reading

  1. Kanban Guides: The Kanban Guide, May 2025.
  2. Schwaber and Sutherland: The Scrum Guide, November 2020.
  3. Fowler: Technical Debt Quadrant (2009).
  4. Forsgren et al.: The SPACE of Developer Productivity, ACM Queue 19(1), 2021.
  5. Git Project: git-log documentation. Oracle: Java SE 17 Duration.
  6. Martin: Agile Software Development (2002). Forsgren, Humble, Kim: Accelerate (2018).
  • Full references and links are in the chapter text.

13 - Chapter 13: Integration, Delivery and Feedback

Object Oriented Development and Agile Practices (SE506)

Chapter 13: Integration, Delivery and Feedback

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 2)

  • 13.1 What can go wrong at a boundary? Risks and test scope
    • Start from the risk, then pick the check.
  • 13.2 Where our code meets the outside: integration and contract tests
    • A real file, a controlled local service, an explicit failure policy.
  • 13.3 A few end-to-end journeys
    • The real boundary of a command-line program is the process.
  • 13.4 Pipeline stages and quality gates
    • A pipeline orders the checks by cost.
  • 13.5 Delivery is not deployment

Chapter Roadmap (2 of 2)

  • 13.6 Feature flags, configuration and rollback
  • 13.7 Did the change work? Verifying the changed behaviour
    • The order of the visitor decision and the 18 acceptance examples.
  • 13.8 Preparing the Milestone 4 evidence
  • 13.9 An integration test that mocks its boundary: judging an AI proposal inspect an integration test that replaces its boundary.
  • 13.10 Delivery habits that mislead
  • 13.11 Try it yourself: run the pipeline and deliver a flagged change
  • 13.12 Questions to think with, and a transfer challenge
  • What to take away, and what comes next
    • Chapter 14: review the design, hand over, decide the flag’s removal.

Learning Outcomes (1 of 2)

After this chapter, you should be able to:

  1. Choose unit, integration and end-to-end checks for different risks, and say what each cannot show.
  2. Explain the scope of a contract test and write one that several implementations must pass.
  3. Implement a changed requirement with regression protection and a clear separation between expected rejections and operational failures.

Learning Outcomes (2 of 2)

  1. Configure a modest delivery pipeline, run it and read a failure in it.
  2. Distinguish continuous delivery from continuous deployment, and label a simulated deployment as one.
  3. Evaluate a feature flag and a rollback plan, including what rollback does not undo.

Where This Chapter Fits and Where You Start

  • Supports LO3 and LO6 and Milestone 4: the change absorbed, with integration tests and CI/CD.
  • Required reading: Martin (2002), Testing (review). Recommended: selected chapters of Humble and Farley, Continuous Delivery.
  • The practical activity is an automated build, test and integration pipeline.
  • This chapter creates no new deadlines, weights or marking rules; the approved Milestone 4 instructions decide what you submit.
  • Entry version: the Chapter 11 project, Chapter 12’s change specification CR-12, its acceptance matrix (AC1 to AC18) and its ordered backlog.
  • This chapter implements the change. The companion lists only the files that are new or changed.

How to Use This Chapter

  • Core: 13.1 to 13.5 and 13.7 to 13.8.
  • Working knowledge: 13.6, feature flags, configuration and rollback. Learn to run one flag safely; you do not build a flag platform.
  • What you must do: steps B1 to B4 (the book’s project: running, tracing and diagnosis, nothing to rebuild) and T1 to T5 (your team’s project: your Milestone 4 evidence).
  • If you want more: B5 to B7 (deliver and roll back a simulated release, the AI activity, break a boundary on purpose), the review questions, the transfer challenge.

Opening Problem: Demonstration Morning

The visiting-researcher rule passes every unit test and works on the developer’s laptop. On the morning of the Milestone 4 demonstration, three things go wrong:

  1. The sponsor register exported by the office has a stray column, so a visitor request ends in an exception.
  2. The borrower directory takes eleven seconds to answer, and the officer’s screen freezes.
  3. The new flag is on for the demonstration but off in the build: the build is green and the demonstration fails.

What Went Wrong

  • None of these is a mistake in the rule. Each lives at the boundary between our code and something it does not control: a file, a network service, a setting.
  • A unit test that builds its own objects by hand cannot see a boundary, because the boundary is exactly what the test has replaced.
  • This chapter checks the places where our code meets something else, orders those checks in a pipeline, and releases a change in a way that can be undone.
  • The worked change is CR-12: visiting researchers, with sponsor approval, behind one flag. It is a teaching contract, not an institutional policy.

13.1 What Can Go Wrong at a Boundary?

Risks and test scope


Which Kind of Test Should I Write?

  • That is the wrong first question.
  • Ask: what could go wrong in this change, where it would go wrong, and which check would notice.
  • Then choose the cheapest check that can notice it.
  • The next two slides do that for CR-12.

Risks and the Cheapest Check (1 of 2)

What could go wrongWhereCheapest check that can notice
Validity window off by one at its edgesApproval recordUnit test with a controlled instant
A visitor served without a matching approvalVisitor gate and serviceAcceptance examples with a controlled approval source
Stray column or a bad date in the registerFile adapterIntegration test with a real temporary file

Risks and the Cheapest Check (2 of 2)

What could go wrongWhereCheapest check that can notice
Directory slow, down or answering nonsenseHTTP clientIntegration test against a local stub service
Flag read in the demonstration but not in the buildWhole programEnd-to-end journey in a separate process
Old behaviour changed by accidentExisting codeThe existing regression suite
  • Each check has a limit. Next: what each one cannot show.

What Each Check Cannot Show

CheckWhat it cannot show
Unit test with a controlled instantThat the register file is read correctly
Acceptance examples with a controlled sourceThat the real register has the shape the source expects
Integration test with a real temporary fileThat the office’s real export looks like the test file
Integration test against a local stubThat the real provider behaves like the stub
End-to-end journey in a separate processBehaviour at scale, or with real users
Existing regression suiteAnything the old suite never covered

Three Names Used Throughout

  • A unit test runs a small part of the system in memory, with everything it collaborates with controlled.
  • An integration test runs our adapter against the real mechanism it adapts: a real file, a real HTTP client talking to a service under our control.
  • An end-to-end journey starts the whole program the way a user would, in a separate process.

Figure 13.1: The Two Boundaries Added by CR-12

Officer:command-linedemoDirectoryReservationServiceLegacyDirectoryAdapterHttpLegacyDirectoryClientDirectory service(stub in tests)VisitorGateflag, allowlist, windowFileApprovalSourceapprovals.tsvReservationCoordinatorHTTP, bounded waitreads on every call

Reading the Figure

  • Solid boxes are our code; the two outer boxes (the directory service and the register file) are what we do not control.
  • Unit tests stop at the adapters.
  • Integration tests cross the labelled arrows with the real mechanism.
  • An end-to-end journey runs the whole picture in a separate process.

Narrow Checks First

  • Fowler distinguishes narrow integration tests (our service’s code that talks to another service, with that service replaced by a double) from broad ones (live versions of everything).
  • Narrow checks are small, fast and resilient, so most boundary evidence in this chapter is narrow.
  • A few broad checks remain worth having, because only they show the pieces are joined correctly.
  • Rule of thumb: many narrow checks, a few broad ones, and a reason for each.

A Boundary Is Where We Stop Controlling the Answer

  • Our own classes answer the same way every time. A file may be missing today. A call may not return. A setting may differ between the machine that built the program and the one that runs it.
  • Each boundary needs two kinds of evidence: what happens when the other side behaves and what happens when it does not.
  • The most important design decision in the change is how a failure at a boundary is reported.

13.2 Where Our Code Meets the Outside

Integration and contract tests


The File Boundary: Use a Real File

  • CR-12 keeps sponsor approvals in a tab-separated register with eight columns.
  • FileApprovalSource reads it, rejects it as a whole if any row is invalid or any request key repeats, and returns the record for one exact request.
  • Its tests write real files into a temporary directory and read them back through the real adapter.
  • Nothing replaces the file system or the parser, because those are the boundary under test.

One Bad Row Rejects the Whole Register

@Test void oneBadRowRejectsTheWholeRegisterEvenWhenTheRequestMatchesAGoodRow() throws IOException {
    failureOf(register(HEADER, GOOD,
            "V9\tCAM2\tnot-a-date\t2026-10-22\tS1\tAPPROVED\t2026-10-18T00:00:00Z\t2026-10-20T00:00:00Z"));
}
  • The suite also tests: a missing file, a wrong header, a wrong column count, an unknown status, a bad date or instant, a blank sponsor, an invalid interval, a duplicate key, text that is not UTF-8, and a file that changes between two calls.

Expected Result vs Operational Failure

  • A missing record is an expected result: the source returns empty.
  • An unreadable or untrustworthy file is a failure: the source throws.
  • They must never be confused:
    • the first means “no sponsor approved this”;
    • the second means “we could not find out”.

The Network Boundary: A Controlled Local Service

  • The directory used to be an in-memory example. It is now reachable over HTTP.
  • HttpLegacyDirectoryClient asks GET /borrowers/<key> and understands one line: the provider’s kind and status, separated by a tab.
  • To test it we need a service we control: the JDK includes a simple HTTP server API for embedded servers, enough for a stub that answers known keys, returns an error, never answers, or sends nonsense.
  • A real service on the network would make tests slow and dependent on someone else’s availability.

The Stub Is a Stand-In, Not the Provider

  • DirectoryStub is a JDK HttpServer on the loopback address with an ephemeral port.
  • It answers keys B1, T1, V1, V2, VOFF, ODD and GARBAGE with prepared bodies, BROKEN with status 500, SLOW by not answering until the test releases it, and SLOWBODY by sending headers and one byte, then holding the rest.
  • Closing the stub releases any waiting request, so a test never leaves a thread hanging.
  • Every test that uses it shows how our client behaves when a service behaves in these ways. It does not show that the real service does.

An Explicit Policy for the Failure Case

  • A client that can wait for ever, retry silently or answer “not found” when it cannot reach the service has made an unreviewed policy decision.
  • The decision is written down instead, as a table of what the client sees and what it does.
  • Each row has a test.

The Directory Failure Policy (1 of 2)

What the client seesWhat the client does
200 with one line, kind and statusReturns the row
404Returns no row: the borrower is absent
500 or any other statusFails: the directory could not answer
No complete answer within the bounded wait, even if the headers arrivedFails with a timeout

The Directory Failure Policy (2 of 2)

What the client seesWhat the client does
Connection refusedFails
A body that is not exactly two fieldsFails: a guess is worse than a failure
An unknown kind codeFails at the adapter; never defaulted to student or staff

Policy: one attempt, a bounded wait, no retry and no fallback answer.


What the Policy Means

  • A failure becomes a DirectoryException in the adapter and stops the request: no reservation is made and nobody is notified.
  • An outage must never look like a missing borrower, because that would tell a real borrower that they do not exist.
  • No borrower is given a category the provider did not state.
  • A retry would repeat a slow call while a person waits; a cached answer would grant a category the provider is not currently stating. Both could be justified later by measured outages; neither is assumed now.

Which Test Pins Which Row

Policy rowTest
Ordinary requestanOrdinaryStudentRequestWorksThroughTheRealHttpClient
404aMissingRowIsAnOrdinaryRejection
500aServerErrorIsAnOutageNotANotFoundAndIsNotRetried
Slow provideraSlowProviderTimesOutAfterTheBoundedWaitAndFailsTheRequest
Stalled bodyaStalledBodyIsAFailureWithinTheOverallWait

Which Test Pins Which Row (continued)

Policy rowTest
Connection refusedaRefusedConnectionIsAnOutage
Malformed bodyaMalformedAnswerIsAnOutageNotAGuess
Unknown kind codeanUnknownCategoryCodeIsNeverDefaultedToStaffOrStudent
  • The wait is a constructor argument; the demonstration passes its own lending.directory.timeout.millis.

A Timeout That Covers Less Than You Think

  • The JDK HTTP client lets a request carry a timeout. The documentation says that if the response is not received within it, the call throws HttpTimeoutException, and that without a timeout the call can wait for ever.
  • It does not say whether a response whose headers have arrived and whose body has not is covered.
  • A client relying on that timeout alone, tested with a stub that waits before sending anything, passes. That is the only case the test imagines.

The Experiment

  • A server sends the headers and the first byte at once, then holds the rest for 1,600 milliseconds; the client’s timeout is 300.
  • The lookup succeeds after about 1,700 milliseconds.
  • The policy said “a bounded wait” and the client did not keep it: the timeout bounded the wait for the headers, not the whole answer.

The Fix: A Deadline on the Whole Lookup

  • The client sends the request asynchronously and waits for the result for at most the configured time.
  • If the time runs out it asks the client to drop the exchange and fails the lookup.
  • Asking is all it can do: cancelling the future attempts to cancel the exchange, which is not guaranteed to be prompt or to take effect at all. The caller does not depend on it, because it has already stopped waiting.
  • The header timeout stays as well.

What the New Test Shows

  • New stub scenario SLOWBODY. The test asserts the outcome, a failure and not a result, with a generous outer deadline so a defect shows as a failed test, not a hang.
  • Against a client relying on the request timeout alone it fails with execution timed out after 5000 ms.
  • Against this chapter’s client the lookup fails after about 330 milliseconds: Directory did not answer within 300 ms.
  • Removing the overall deadline is caught by the new test. Removing the header timeout alone cannot be detected by any test: the fault experiment records it as an equivalent change.

The Lesson Is Wider Than HTTP

A test of a failure path covers the failure you imagined, so try a different one.

  • A second reader, or a fault experiment that applies a different fault, can find what you could not.
  • Before the stalled-body test existed, no fault in the experiment concerned the body of the response, so the experiment could not have found the problem.

Contract Tests: Two Meanings, One Limit

  • Interface contract. DirectoryClientContract is written once and run against every implementation of LegacyDirectoryClient:
    • a known key returns its row;
    • an unknown key returns nothing and is not a failure;
    • a provider that cannot answer fails and never returns an empty answer.
  • It runs against the in-memory example and against the real HTTP client talking to the stub. Two implementations pass the same test, so a caller can swap one for the other.

Provider Contract Tests and the Limit

  • A provider contract test (Fowler) is a separate suite, run periodically against the real external service, checking that your test doubles still return what the real service returns.
  • It checks structure rather than exact data and follows the provider’s rate of change, so it is not part of every pipeline run.
  • This book has no real provider, so no such suite exists. The stub’s fidelity to the real directory is an assumption, written in the decision record and tested only when someone has a real service to test against.
  • That is a limit to record, not hide.

13.3 A Few End-To-End Journeys

The real boundary is the process


Find the Real Boundary of a Command-Line Program

  • A web application has a browser and a server. This program has none of them.
  • Its boundary is the process: how it is started, which settings it reads, what it prints, how it exits, and the network and files it touches.
  • A journey therefore starts Chapter13Demo as a separate Java process, with a directory service on the loopback address and a real approval file, and checks what the user sees.

Settings and What Journeys Check

  • Settings are system properties: the directory address, the approval file, the flag lending.visitors (default false), a fixed time for the test, and the directory timeout.
  • Journeys check what no in-process test can:
    • that the flag is actually read;
    • that the pieces are wired together;
    • that the classpath is complete.

Journey 1: Flag Off

Visitors enabled: false
Student B1: Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Visitor V1: Rejected: visitor reservations are not enabled.
Visitor V2: Rejected: visitor reservations are not enabled.
Unknown U9: Rejected: borrower not found or inactive.
Outage BROKEN: Unavailable: Borrower directory unavailable
Reservations: 1
Notices: 1
  • The first line prints the effective setting: a reason to read it before every demonstration.

Journey 2: Flag On

Visitors enabled: true
Student B1: Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Visitor V1: Confirmed: R2 for V1 on CAM2 [2026-10-20, 2026-10-22).
Visitor V2: Rejected: no valid sponsor approval.
Unknown U9: Rejected: borrower not found or inactive.
Outage BROKEN: Unavailable: Borrower directory unavailable
Reservations: 2
Notices: 2
  • An approved visitor is served and an unapproved one refused.

Journey 3 and Keeping Journeys Few

  • Journey 3 corrupts the register: only the visitor requests fail, reported as an unavailable register and not as a sponsor’s rejection, while the student is still served.
  • Journeys are slow (they start a process) and brittle (they check output text). Keep the number small.
  • Check the lines that carry the meaning; fix the clock and the ports so a run is repeatable; let narrower tests carry the detail.
  • A journey that duplicates every unit test is cost without evidence.

13.4 Pipeline Stages and Quality Gates

Order the checks by cost


A Pipeline Orders the Checks by Cost

  • A deployment pipeline splits an automated build into stages. Early stages give fast feedback; later stages add slower, broader checks.
  • If a stage fails, later stages should not run: there is no point starting a process when the code does not compile.

The Five Stages (1 of 2)

StageWhat runsWhy here
1 Compilemvn compileNothing else can run without it
2 Unit tests and architecture checksEverything not tagged integration or e2eFast, controlled, no outside resources
3 Integration testsTests tagged integrationReal file system, real HTTP client, local stub

The Five Stages (2 of 2)

StageWhat runsWhy here
4 End-to-end journeysTests tagged e2eNeeds compiled classes and a free port
5 Packagemvn package, tests skipped because stages 2 to 4 passedProduces the release candidate

What a Failure Means at Each Stage

StageIf it fails
1 CompileThe code does not build
2 Unit and architectureA rule or a boundary in our own code is wrong
3 IntegrationAn adapter does not meet its boundary
4 End-to-endThe pieces are not joined correctly, or a setting is not read
5 PackageThe package cannot be built

Figure 13.2: The Pipeline of This Chapter

1 Compile2 Unit tests andarchitecturechecks3 Integrationtests4 End-to-endjourneys5 Package:releasecandidateStop: later stagesdo not runfailsfailsfailsEach arrow means: only if the previous stage passed.

Selecting Stages With Tags

  • Stages are selected with JUnit tags: @Tag("integration") and @Tag("e2e") on the test classes, and Surefire’s groups and excludedGroups parameters, given on the command line as -Dgroups and -DexcludedGroups.
  • Surefire’s page documents the parameters in the POM, not the command-line form: the pipeline run in the companion is the evidence for that form.
mvn test -DexcludedGroups=integration,e2e     # stage 2
mvn test -Dgroups=integration                 # stage 3
mvn test -Dgroups=e2e                         # stage 4
mvn test -Dtest=VisitorAcceptanceTest         # one class

The Script and the Workflow

  • scripts/pipeline.sh runs the five stages in order and stops at the first failure, printing a banner at the start and end of each stage.
  • The same chain is a GitHub Actions workflow, where needs makes jobs run one after another.
  • The workflow file has not been run, and no hosted run is claimed.

A Passing Run

=== STAGE: 1 compile
=== PASSED: 1 compile (4 s)
=== STAGE: 2 unit tests and architecture checks
[INFO] Tests run: 203, Failures: 0, Errors: 0, Skipped: 0
=== STAGE: 3 integration tests ...
[INFO] Tests run: 26, Failures: 0, Errors: 0, Skipped: 0
=== STAGE: 4 end-to-end journeys (separate process)
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
PIPELINE PASSED
  • Read the counts, not the seconds.

Where the Test Counts Come From

StageInvocationsContents
2 Unit and architecture203The 159 inherited invocations, the 41 visitor tests and the 3 example contract invocations
3 Integration26FileApprovalSourceIntegrationTest (13), DirectoryOutageIntegrationTest (10), HttpDirectoryClientContractTest (3)
4 End-to-end3Chapter13JourneyE2ETest
  • Total 232 invocations: 159 inherited and 73 new (41 + 3 + 26 + 3).

Quality Gates Decide What Blocks a Release

  • A gate is a rule that stops the pipeline. Here: every selected test passes, the architecture checks of Chapter 10 pass, and a stage that selects no tests fails instead of passing silently.
  • The last rule matters: a mistyped tag would otherwise give a green stage that checked nothing.
  • mvn test -Dgroups=nosuchtag ends with No tests were executed! and BUILD FAILURE, because the POM sets failIfNoTests to true.
  • A pipeline is evidence about the checks it contains. It does not establish that the program is secure, that every rule is right, or that real users will be served.

Read a Failure: The Deliberate Fault

  • The register parser’s header check was removed, so a file with a wrong header is accepted.
  • The comparison lines.isEmpty() || !HEADER.equals(lines.get(0)) became lines.isEmpty().
=== PASSED: 2 unit tests and architecture checks (11 s)
=== STAGE: 3 integration tests ...
[ERROR] ...FileApprovalSourceIntegrationTest.aWrongOrMissingHeaderIsAFailure <<< FAILURE!
Expected ...ApprovalSourceException to be thrown, but nothing was thrown.
[ERROR] Tests run: 26, Failures: 1, Errors: 0, Skipped: 0
  • Exit status 1 after stage 3. Stages 4 and 5 never started.

Three Habits for Reading a Failing Pipeline

  1. Find the first failing stage and ignore everything after it.
  2. Read the name of the first failing test before the stack trace: the name says what the test promised.
  3. Ask whether the earlier stages should have caught it. Here they could not: no unit test reads the real file, and that is correct for their purpose. That is the reason stage 3 exists.
  • The other 25 integration tests passed, so the fault is local to the header.

13.5 Delivery Is Not Deployment

A release candidate is not a release


Delivery vs Deployment

  • Continuous delivery: software is built so that it can be released to production at any time.
  • Continuous deployment goes further: every change that passes the pipeline is put into production automatically.
  • You can do delivery without deployment, and you must do delivery to do deployment.
  • Our pipeline ends with a release candidate, a jar that has passed every stage. That is delivery. Nothing here deploys it to production.

The Simulation

  • The course needs an understanding of delivery, not a public deployment, so the companion supplies a simulation: scripts/simulate-release.sh.
    1. Builds the jar.
    2. Copies it into a local build/staging folder as the current release.
    3. Runs the end-to-end journeys against that copied jar as a smoke test.
  • A second delivery moves the old release to previous; rollback makes it current again and smoke-tests it.
RELEASE_LABEL=v1 bash scripts/simulate-release.sh
RELEASE_LABEL=v2 bash scripts/simulate-release.sh
bash scripts/simulate-release.sh rollback

Two Limits of the Simulation

  • The script packages with the tests skipped and then runs only the end-to-end journeys, so it assumes the pipeline already passed for the same candidate and does not enforce the other gates.
  • Rollback assumes the same version-derived jar name in both releases, so the two releases are two labels for one build, not a tested rollback between different versions.
  • This is a simulation: no server, no network deployment and no production.
  • A real deployment would add environment-specific settings, secrets, a health check and monitoring, which are outside the module.

13.6 Feature Flags, Configuration and Rollback

One flag, two paths


One Flag, Two Paths

  • A feature flag lets you ship code without turning the behaviour on.
  • CR-12 has exactly one: visitingResearchersEnabled, default off.
  • Off: a visitor request is rejected explicitly and the approval register is not read.
  • On: the new path.
  • Student and staff requests behave the same under both settings and never depend on the flag.
  • It is a release toggle: it hides unfinished or unproven behaviour, and release toggles should be short-lived.

Test Both Paths, But Few

  • Both paths are tested: in the acceptance examples, the end-to-end journeys and the service tests.
  • Testing both settings is cheap with one flag and becomes expensive with many, because each toggle state may go live.
  • Test the configuration you expect in production and the fallback with the new behaviour off.
  • An “off” that means legacy behaviour and an “on” that means the new behaviour keeps that simple.

A Removal Plan Is Part of the Flag

  • A flag nobody removes becomes a permanent branch.
  • The decision record sets the conditions:
    1. the released variant is accepted;
    2. both settings have passed in the pipeline for the release candidate;
    3. a decision to keep visitors enabled is recorded.
  • Then the flag, the disabled path and its tests are deleted together, in one commit.
  • Review the flag at the handover in Chapter 14. Flags are inventory, and inventory has a carrying cost.

Configuration Is Not a Flag

  • The directory address, the timeout and the register path are configuration: they differ between machines and are not decisions about releasing behaviour.
  • A flag chooses between behaviours; configuration chooses between places and values. Mixing them gives a program nobody can reason about.
  • Caution: the demonstration reads the flag with Boolean.parseBoolean, which treats any text other than “true” as false, so a mistyped value silently means off.
  • That is the safe direction, and a reason to print the effective setting, which the program does on its first line.

Rollback, and What It Does Not Undo

  • To stop new visitor reservations, turn the flag off.
  • To go back to an earlier build, redeliver the previous release.
  • Neither removes a reservation that already exists, undoes a notice already sent, or restores a decision a person already made on the strength of it.
  • AC16 pins one consequence: a visitor reservation made before the flag was switched off can still be checked out and returned.
  • A rollback plan is only a plan when it says what stays behind.

13.7 Did the Change Work?

Verifying the changed behaviour


Figure 13.3: The Order of the Visitor Decision

Visitorrequest(VISITOR)Flag on?Item CAM1or CAM2?Fewer thanone day?Read clock onceand registeronceFAILURE:nothing reservedor sentRejected:not enabledRejected:item not allowedRejected: no validsponsor approvalCoordinator:count and duration rulesRejected: borrowerlimit or durationAvailability,reservation, noticeyesyesnononoyesviolatedmetno matchvalid at therequest instantunreadable ormalformed

Reading the Decision Order

  • Rectangles are results; the one marked FAILURE is an exception that nothing on the request path catches.
  • Every rejection is an ordinary return value.
  • The gate runs first; the coordinator’s count and duration rules run last.
  • Inside the gate: flag, item, duration, one clock read, one register read, then an exact-request re-check and the validity window.

The Order Is Observable

  • A four-day visitor request with an unreadable register does not get the duration rejection: it fails with the register’s error, because the gate reads the register before the coordinator applies the duration rule.
  • A zero-day request is the opposite: the gate leaves it alone, the register is not read, and the coordinator rejects it.
  • precedence_theRegisterIsReadBeforeTheCountAndDurationRulesApply pins both ends. It is not one of the eighteen acceptance examples.
  • A different precedence (duration before register) is a change to the contract, the implementation and the tests together, not to the picture.

Why the Gate Sits in Front of the Coordinator

  • BorrowingPolicy.mayRequest(count, days) answers a count and duration question and nothing else. Approval needs the borrower, item, period and time.
  • Passing those through a flag or a static would give the policy hidden state and make it untestable for identity and time.
  • So the policy stays small (VisitorReservationRules), and the request-specific decision lives in VisitorGate in the application layer, called by DirectoryReservationService before the coordinator.
  • Student and staff requests never reach the gate.

The Exact-Request Re-Check

  • The gate re-checks that the record returned matches the exact request, though the source was already asked for it.
  • It looks redundant. It is there because the source is a boundary: a source that returns the wrong record (a bug, or a register changed underneath) must not approve.
  • aSourceThatReturnsADifferentRequestDoesNotApprove and the fault experiment test it.

One Acceptance Example in Code

@Test void ac9_theRequestInstantAtValidUntilOrBeforeValidFromIsRejected() {
    var atExpiry = new World(true, UNTIL).approve("V1", "CAM1", START, 2);
    assertEquals(VisitorGate.NOT_APPROVED, atExpiry.service.reserve("V1", "CAM1", START, 2));
    var early = new World(true, FROM.minusSeconds(1)).approve("V1", "CAM1", START, 2);
    assertEquals(VisitorGate.NOT_APPROVED, early.service.reserve("V1", "CAM1", START, 2));
    assertNothingChanged(atExpiry);
    assertNothingChanged(early);
}
  • Every example builds its own setup, so one failure cannot hide another.

Acceptance Examples by Group

GroupExamplesWhat they establish
Success pathAC1One matching, valid approval gives one reservation in the existing confirmation format and one notice
Duration and itemAC2 to AC5Upper boundary of three days, no zero or negative days, the two-item allowlist
Approval matchingAC6 to AC9Missing, denied, mismatched and expired approvals; equality at both ends of the window
Failure and flagAC10, AC12Unreadable register is a failure, not a rejection; a disabled flag never reads the register
Existing behaviourAC11, AC13 to AC18Count rule, students and staff under both settings, availability, notification failure, lifecycle, directory semantics, the report

Evidence for AC1 to AC6

ExampleTest in VisitorAcceptanceTest
AC1ac1_aVisitorWithAMatchingApprovalReservesCam1ForTwoDays
AC2ac2_cam2ForThreeDaysNeedsItsOwnRecordAndIsAllowed
AC3ac3_fourDaysIsRejectedEvenWithAMatchingRecord
AC4ac4_zeroOrNegativeDaysIsRejectedWithoutReadingTheRegister
AC5ac5_anItemOutsideTheAllowlistIsRejectedEvenWithAMatchingRecord
AC6ac6_aMissingOrDeniedApprovalIsAnExpectedRejection

Evidence for AC7 to AC12

ExampleTest in VisitorAcceptanceTest
AC7ac7_anApprovalForAnotherBorrowerItemOrPeriodDoesNotAuthorise
AC8ac8_theRequestInstantEqualToValidFromIsValid
AC9ac9_theRequestInstantAtValidUntilOrBeforeValidFromIsRejected
AC10ac10_anUnreadableApprovalRegisterIsAnOperationalFailureNotARejection
AC11ac11_threeRetainedReservationsBlockAFourthEvenAfterLoansAreReturned
AC12ac12_aDisabledFlagRejectsExplicitlyWithoutReadingTheRegister

Evidence for AC13 to AC18

ExampleTest in VisitorAcceptanceTest
AC13ac13_studentAndStaffBehaviourIsUnchangedUnderEitherSettingAndNeverReadsTheRegister
AC14ac14_anOverlapWithAnExistingReservationIsRejectedByTheExistingRule
AC15ac15_anExpectedNotificationFailureLeavesTheVisitorReservationInPlace
AC16ac16_aVisitorReservationStaysUsableAfterTheApprovalExpiresAndTheFlagIsOff
AC17ac17_anInactiveOrUnknownBorrowerIsRejectedAsBefore
AC18ac18_aVisitorLoanInTheOverdueReportKeepsTheExistingKindRuleSd11_1

What the Regression Suite Shows

  • Adding VISITOR to the category enum has a deliberate consequence: the coordinator requires a policy for every category, so every test that builds a coordinator with a complete policy map needed one more entry.
  • That was four constructions in three test files. No assertion changed. The inherited Chapter 11 tests passed unchanged afterwards.
  • A regression suite that passes shows that the old behaviours it covers are intact. It says nothing about behaviour the old suite never covered, and nothing about whether the new rule is right.

Unresolved Items Keep Their Names

  • AC18: a visitor who returns a loan late appears in the overdue report, which knows only student and staff kinds, so it applies its “any other kind” rule: one unit a day, no grace, no cap.
  • That is the suspected defect SD-11-1 from Chapter 11. It now has a real consequence. The change does not fix it: only the records office can say what a visitor’s late fine should be.
  • The count rule SD-12 also stays as it is.
  • Naming these items, and showing in a test what each does today, is more useful than silently resolving them.

Do the Tests Detect Faults?

  • Thirty faults were run one at a time in a copy of the project. Twenty-nine were caught. One survived, and it is an equivalent change (removing the header timeout while the overall deadline stays).
  • Examples caught: duration check removed; flag ignored; allowlist also admits MIC1; expiry instant valid; start instant invalid; DENIED also approves; unknown status becomes DENIED; duplicate keys allowed; register cached; 404 treated as outage.
  • Some faults were first noticed by an end-to-end journey: that is a reason to keep a few journeys.
  • These counts come from one fault set on one small change, chosen by the author. They do not show the suite catches every fault.

13.8 Preparing the Milestone 4 Evidence

Preparation, not a rubric


What You Should Be Able to Show (1 of 2)

By your demonstration you should show, on your team’s project:

  1. Your released requirement change, implemented from the smallest vertical slice outwards, behind one flag, with acceptance examples.
  2. Regression evidence from your existing suite, with any necessary changes to test setups disclosed.
  3. Integration evidence for at least one genuine boundary, including its failure cases.
  4. An observed pipeline run, and a failing run that you have read.

What You Should Be Able to Show (2 of 2)

  1. A delivery step, simulated or real, with a smoke test and a rollback.
  2. Flag evidence: both settings tested and a removal plan.
  3. A demonstration and a retrospective with a next action.
  4. A statement of what your evidence does not show.
  • The approved Milestone 4 instructions decide what is submitted; this list is preparation, not a rubric.

The Evidence Table

ItemWhat to produceWhat it should establish
1Change record and acceptance examplesRequirement and each example traceable to a test
2Regression resultOld behaviour intact; changed test setups listed
3Boundary testsReal file, service or store used; both outcomes checked
4Pipeline outputWhich stage ran which checks; one real failure read correctly
5Delivery recordCandidate smoke-tested; a rollback tried
6Flag evidenceBoth paths tested; a named condition for removal
7RetrospectiveWhat worked, what did not, one next action
8Limits statementE.g. hosted CI not run, real provider not tested, concurrency not addressed

Do Not Claim What Did Not Happen

  • Do not claim a hosted pipeline run, a public deployment or a peer review that did not happen.
  • A local pipeline run is evidence of a local run.
  • What this chapter’s own evidence does not show: behaviour against a real directory or approval workflow, concurrency (two writers of the register, two simultaneous requests), authentication, behaviour under load, a hosted run, and that SD-11-1 and SD-12 are acceptable.

13.9 An Integration Test That Mocks Its Boundary

Judging an AI proposal


The Activity

For this activity
Your goalDecide what a proposed integration test actually verifies, and repair it so it verifies what its name claims
You start withThe approval adapter, the visitor gate and the integration tests of 13.2
What is in boundsJudge and repair the proposed test; do not weaken the adapter or its other tests
How to checkIntroduce one fault into the parser in a scratch copy and see which tests notice
How to decideA verdict, a repaired test, and an explanation of what each verifies

The Ready-Made Example

Author-written fictional AI output.

I added an integration test for the approval register. It builds a visitor approval, makes the register return it, and checks that the gate accepts the visitor and that the register was consulted.

@Tag("integration")
class ProposedRegisterIntegrationTest {
    @Test void anApprovedVisitorIsAcceptedFromTheRegister() {
        ApprovalSource register = mock(ApprovalSource.class);
        ApprovalRecord record = new ApprovalRecord("V1", "CAM1", start, end, "S1", APPROVED, from, until);
        when(register.find("V1", "CAM1", start, end)).thenReturn(Optional.of(record));
        VisitorGate gate = new VisitorGate(register, clock, true);
        assertEquals(Optional.empty(), gate.rejection("V1", "CAM1", start, 2));
        verify(register).find("V1", "CAM1", start, end);
    }
}

Questions to Answer

  • Run it. It passes. Now introduce a fault into FileApprovalSource in a scratch copy: read the two instant columns the wrong way round, or skip a broken row instead of rejecting the file. Run it again.
  • Which part of the system does this test touch?
  • Is there a file anywhere in it?
  • What would it still say after you delete the parser?
  • What does @Tag("integration") promise that the body does not deliver?

The Answers

QuestionAnswer
Which part does it touch?VisitorGate and ApprovalRecord. Nothing else
Is there a file in it?No. mock(ApprovalSource.class) replaces the register; the parser and file system are not in the test
After the parser is deleted?The same: it passes
What does the tag promise?A test of a real boundary. It puts the test in stage 3, adding time and a false sense of cover
What does verify(...) establish?That the gate asked the mock for the exact request: a fact about the gate, not the register

Run Both Tests Against Two Faults

RunProposed testRepaired test
No faultPassesPasses
Fault A: validFrom and validUntil columns read the wrong way roundPassesFails: ApprovalSourceException: Row 2 is not a valid approval record.
Fault B: a broken row skipped instead of rejecting the filePassesFails: expected ApprovalSourceException, nothing was thrown

The Repair, and What the Original Is Still Good For

  • Repair: write a real register into a temporary directory, read it with the real FileApprovalSource, pass that source to the real gate. Add the failure case: a broken row appended to the file makes the gate throw the register’s exception, not reject politely.
  • The repaired test is deliberately small. A header fault would pass it: FileApprovalSourceIntegrationTest covers the header.
  • The original is a fair unit test of the gate’s decision logic with a controlled source. It belongs in the unit stage under an honest name.
  • Accept it as a unit test; reject it as an integration test. Do not use a generated test to approve generated code.

13.10 Delivery Habits That Mislead

Eleven to avoid


Mistakes 1 to 4

MistakeConsequenceBetter action
Mock the boundary and call the test an integration testPasses whatever the real adapter doesUse the real file or a controlled local service, and fault-test it
Test only the successful responseAn outage or bad data is found in the demonstrationTest missing, slow, malformed and refused answers
Report an unreachable service as “not found”A real borrower is told they do not existFail the request; say the service could not answer
Add a timeout without deciding what happens nextWaits less long and still behaves unpredictablyWrite the policy: one attempt, no retry, no fallback

Mistakes 5 to 8

MistakeConsequenceBetter action
Default an unknown category to the nearest known oneA visitor becomes a student without anyone deciding itTreat an unknown code as a failure
Let an end-to-end journey check everythingSlow, brittle, duplicates other testsCheck the lines that carry the journey’s meaning
Use real clocks, ports or paths in testsRuns differ by day, machine or luckInject the clock, take a free port, use a temporary directory
Assume a timeout covers the whole callA trickling response holds the caller as long as the server likesBound the whole operation; test the stall you did not think of

Mistakes 9 to 11

MistakeConsequenceBetter action
A stage that can select no testsA mistyped tag gives a green stage that checked nothingFail a stage that runs nothing
Treat a green pipeline as proofSecurity and unseen behaviour are not examinedState what the stages check and what they do not
Switch a flag on by default, or never remove itThe old path rots and the code keeps bothDefault off, test both paths, name a removal condition

Diagnostic Alternatives: Flawed Code

// WRONG: a failure of the provider is reported as an absent borrower.
try { return fetch(key); }
catch (IOException | InterruptedException failure) { return null; }

// WRONG: an unknown kind is given the nearest known category.
default -> BorrowerCategory.STUDENT;

// WRONG: no timeout; the request waits for ever.
HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
  • Caught by aRefusedConnectionIsAnOutage, anUnknownCategoryCodeIsNeverDefaultedToStaffOrStudent and aSlowProviderTimesOutAfterTheBoundedWaitAndFailsTheRequest.

Diagnostic Alternatives: Flag, Mock and Stage

// WRONG: a flag with no owner, on by default, global and mutable.
public static boolean visitingResearchersEnabled = true;
mvn test -Dgroups=integraton   # misspelt: no test has this tag
  • The global flag cannot be tested for both settings in one process without cleaning up and has no removal plan. This chapter’s flag is a constructor argument, default off.
  • Without failIfNoTests, a stage that runs zero tests passes. With it: No tests were executed!.

13.11 Try It Yourself

Run the pipeline and deliver a flagged change


What You Start With, and the Demonstration

  • You start with: the Chapter 11 version with the Chapter 13 files from the companion, Chapter 12’s CR-12 specification, and your team’s repository.
  • Steps B1 to B7 use the book’s project; steps T1 to T5 use your team’s project.
  • Instructor demonstration: run scripts/pipeline.sh and read each stage. Break the register parser on purpose and run it again to show which stage notices. Run the flag-off and flag-on journeys.
  • Guided practice: classify four risks of your choice as unit, integration or end-to-end, and say what each cannot show. Predict the outcome for a response the directory policy table does not list.

The Book’s Project: B1 and B2

  • B1. Run the pipeline. Run scripts/pipeline.sh or the five Maven commands in it. Record each stage, the number of tests it ran and its time. Say what a failure in each stage would mean.
  • B2. Trace three acceptance examples. Choose AC8 or AC9 (window boundary), AC10 (operational failure) and AC12 (the flag). For each, name the test, the production class that decides the outcome and the observable result, and say which stage runs it.

B2: A Good Answer

ExampleDeciding production codeObservable resultStage
AC9 (expiry boundary)ApprovalRecord.validAt, called from VisitorGate.rejectionRejected: no valid sponsor approval. at validUntil; no reservation2
AC10 (operational failure)FileApprovalSource.readAll throws; nothing catches it on the request pathApprovalSourceException; no reservation or notice2 and 4
AC12 (the flag)VisitorGate.rejection, the first checkRejected: visitor reservations are not enabled.; register not read2 and 4

The Book’s Project: B3 and B4

  • B3. Make a test bite. In a scratch copy, introduce one fault into FileApprovalSource or HttpLegacyDirectoryClient. Predict which stage fails first, run the pipeline, compare. Restore the original.
    • Example: an unknown status becomes DENIED. Prediction: stage 2 passes (no unit test reads the real file); stage 3 fails at anUnknownStatusIsAFailureNotADenial.
  • B4. Complete the evidence table. For the whole change, in the form of 13.1: the risk, the check that covers it, the test’s name, and what it cannot show. Mark at least one risk nothing here covers: the real directory’s behaviour, two concurrent writers of the register, a sponsor revoked after a reservation, the effect of an unusual time zone on the request instant.

Your Team’s Project: T1 to T3

  • T1. Implement your released change. Start with the thinnest vertical slice that behaves correctly, behind one flag that defaults to off, and record the acceptance examples. Keep expected rejections apart from operational failures.
  • T2. Test one genuine boundary. A file, a service or a store in your project, without replacing it: a real temporary file, a local stub service or a throw-away database. Cover at least one failure case.
  • T3. Protect the old behaviour and both flag settings. Run your existing suite, disclose any test setup you had to change, and add tests for the flag on and off.

Your Team’s Project: T4 and T5

  • T4. Build and read a pipeline. At least three stages (for example compile and unit, integration, and a smoke or end-to-end stage). Capture one observed run and one failing run, and read the failing one: first failing stage, first failing test, and why earlier stages could not have caught it.
  • T5. Prepare the Milestone 4 evidence. Record a flag removal plan and a rollback step, run your demonstration, write the retrospective with one next action, and complete the eight items of 13.8, marking each complete or a gap.

If You Want More: B5 to B7

  • B5. Deliver and roll back. Run simulate-release.sh twice with different RELEASE_LABEL values, then rollback. Record which release is current after each step: after the second delivery v2 is current and v1 previous; after rollback v1 is current and nothing is previous. The rollback changes a folder, not any data.
  • B6. AI activity. Complete 13.9.
  • B7. Break a boundary on purpose. Make the directory timeout twenty seconds in a scratch copy and run the slow-provider test. It fails after 20.03 seconds with the call must give up after its timeout, but took 20015 ms; the class takes 20.58 seconds instead of 1.01. A test of a timeout should use a short, injected timeout; a pipeline that waits for the real timeout of every failure case is itself a problem.

What You Should End Up With, and When You Are Done

  • What you should end up with: B1 to B4; for your team’s project, the implemented change with its acceptance examples, the boundary tests, the pipeline output (one passing and one failing), the delivery and rollback record, the flag evidence, and the Milestone 4 checklist. A pull request is a good vehicle where your course workflow permits it.
  • Acceptance: change behind a flag that defaults to off; rejections and failures distinguishable; one boundary tested without being replaced, with its failure cases; old behaviour protected and changed test setups disclosed; a stage that selects no tests fails; delivery labelled a simulation unless real; limits statement says what was not run.

13.12 Questions to Think with, and a Transfer Challenge

Concept, diagnosis, transfer


Concept Questions

  1. Why can a unit test not show that a file adapter parses the real register? What does it show instead?
  2. What does the directory contract test say, and what can it never show about the real provider?
  3. Why is “not found” the wrong answer when the directory times out? Name two other defaults the policy refuses.
  4. What is the difference between continuous delivery and continuous deployment, and which does simulate-release.sh imitate?

Diagnosis Questions

  1. A teammate replaces the file reader with a mock “so that the test is repeatable” and keeps the name FileApprovalSourceIntegrationTest. Which risk is no longer covered? How would you keep repeatability without replacing the reader?
  2. The pipeline is green and the demonstration fails because the flag was off. Name two checks, one in the pipeline and one in the process of delivering, that would have noticed.
  3. A client sets a request timeout of two seconds and the officer’s screen still freezes for forty. The server sends its headers at once, then the body a few bytes at a time. What does the timeout cover, what does it not, and what would you change and test?

Guidance: Questions 1 to 4

  1. A unit test controls everything the unit collaborates with, so it cannot show the real parser reads the real file: the file system, encoding, line endings and parser are exactly what it replaced. It shows that the logic that consumes a parsed record is right.
  2. A known key returns its row; an unknown key returns nothing and is not a failure; a provider that cannot answer fails. Two implementations pass. It can never show how the real provider behaves: the stub’s answers are the author’s assumption.
  3. After a timeout we do not know the borrower is absent. Two other refused defaults: an unknown kind code treated as student or staff, and a cached or earlier answer when the provider is down.
  4. Delivery keeps software releasable at any time; deployment releases every passing change automatically. The script imitates delivery only.

Guidance: Questions 5 to 7

  1. The parse of the real register is no longer covered: header, columns, dates, encoding, duplicate keys. Keep repeatability with a temporary directory: write the file inside the test, read it with the real adapter, delete it afterwards. Repeatable and real are not opposites.
  2. Pipeline: an end-to-end journey with the flag on, starting the program with the demonstration’s setting. Delivery: a smoke test of the packaged jar with the target’s settings and a printed first line stating the effective setting. Both check configuration, not logic.
  3. The timeout covers the wait for the response to be received; it does not cover a body that trickles in. Bound the whole operation with an overall deadline, and add a stub scenario that sends headers and a byte, then stalls.

Transfer Challenge: ML Model Serving

An ML model-serving team: only approved model versions may be served to external callers. The approval list is a file produced by another team; the model registry is queried over the network.

  • Which risks are unit, integration or end-to-end checks?
  • How is a registry timeout handled, and what must it not default to?
  • What does the one flag do, and what does the rollback not undo?
  • Name one thing your evidence would not show about the model’s accuracy.
  • Synthetic model identifiers only: no model needs to be trained.

Transfer Guidance (1 of 2)

RiskCheck
Approval rule: only approved versions served externallyUnit tests with a controlled approval source: approved, not approved, expired, a different version, the validity-window boundary
Approval list file has a stray column or duplicateIntegration test with a real temporary file and each failure case; a whole-file rule or skip rule, chosen and written down
Registry times out or returns nonsenseIntegration test against a local stub with a bounded wait: the request fails and the version is not served; an unknown version is never defaulted to the latest approved one

Transfer Guidance (2 of 2)

RiskCheck
Flag on in the demonstration and off in the buildOne end-to-end journey that starts the serving process with each setting
Old behaviour changedThe existing suite
  • The flag enables the rule for external callers. Rollback turns it off, but does not recall a response already served or revoke an approval a person already acted on.
  • The evidence would not show the model’s accuracy, fairness or drift: nothing here evaluates the model, only the gate in front of it.

What to Take Away, and What Comes Next

What to carry forward


Summary

  • A change is only as safe as the checks at the places where our code meets something else.
  • Test the real file and a controlled service without replacing them; write down what happens when they fail.
  • Order the checks in a pipeline from cheap to broad, release behind a flag that defaults to off, and say what a rollback cannot undo.
  • A green pipeline is evidence about the checks it contains and nothing more.

Where the Project Stands

  • The visiting-researcher change is implemented and verified, with two unresolved items still named: SD-11-1, which a visitor’s late fine now exposes, and SD-12, the count rule.
  • Chapter 14 reviews how the design accommodated the change, prepares the handover, and decides the flag’s removal.

Sources and Further Reading

  1. Fowler: Integration Test; Contract Test; Deployment Pipeline; Continuous Delivery.
  2. Hodgson: Feature Toggles (aka Feature Flags).
  3. GitHub: Workflow syntax for GitHub Actions. Maven: Using JUnit 5 Platform in Surefire.
  4. Oracle: HttpRequest.Builder, HttpClient, com.sun.net.httpserver (Java SE 17). JUnit: User Guide 5.13.4.
  5. Martin: Agile Software Development (2002). Humble and Farley: Continuous Delivery (2010).
  • Full references and links are in the chapter text.

14 - Chapter 14: Responding to Change and Handing Over

Object Oriented Development and Agile Practices (SE506)

Chapter 14: Responding to Change and Handing Over

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 3)

  • 14.1 Did the design absorb the change? Reviewing the outcome
    • Four questions, each with its own evidence.
  • 14.2 Fix, defer or accept: scope control in the last week
    • Fix, defer or accept, and freeze.
  • 14.3 Writing it down: documents, decision records and release notes
    • One document per question; the flag decision.
  • 14.4 Keeping the evidence true: rehearsal and traceability
    • A rehearsed flag removal; requirements traced to tests.

Chapter Roadmap (2 of 3)

  • 14.5 Setup and a demonstration that anyone can reproduce
    • Follow your own instructions in a fresh copy.
  • 14.6 Retrospective and individual reflection
    • Evidence-based retrospectives; contribution as claims with links.
  • 14.7 Getting ready for the final submission
  • 14.8 Validating an AI-drafted README validate a README that an assistant drafted.

Chapter Roadmap (3 of 3)

  • 14.9 Handover habits that mislead
  • 14.10 Try it yourself: hand the project over
  • 14.11 Questions to think with, and a transfer challenge
  • What to take away, and what comes next
    • Chapter 15: the recurring assistance workflows and their verification records.

Learning Outcomes (1 of 2)

After this chapter, you should be able to:

  1. Evaluate how a design accommodated a change, using the affected responsibilities, the test failures, the revised assumptions and review feedback, and explain why a small diff is not automatically a better design.
  2. Prioritise remaining release work, and choose between fixing, deferring and accepting a known issue.
  3. Produce reproducible handover instructions and check them by following them in a fresh copy.

Learning Outcomes (2 of 2)

  1. Trace requirements to implementation and tests, and keep that trace true with a check.
  2. Support retrospective conclusions with evidence, and write an individual reflection that explains your own contribution.
  3. Prepare a final submission without claiming evidence that does not exist.

Where This Chapter Fits and Where You Start

  • Supports all learning outcomes, LO1 to LO6: the final sprint and retrospective. Required reading: Martin (2002), a review of the chapters relevant to your project.
  • The descriptor says teams submit a repository link, a technical report and an individual reflection, give a demonstration and presentation, and that marks are moderated by individual contribution.
  • This chapter creates no new deadlines, weights or marking rules; the approved final-project instructions decide what you submit.
  • Entry version: the Chapter 13 version (the visitor change, version 0.13.1), or for your team’s project a Milestone 4 outcome with its remaining defects named.
  • Completed version: version 0.14.0. Its production code is unchanged: the companion lists only new and changed files.

How to Use This Chapter

  • Core: 14.1 to 14.5 and 14.7.
  • Working knowledge: 14.6 on contribution evidence. You are not asked to measure anyone; you are asked to know what each kind of evidence can and cannot show.
  • What you must do: B1 to B4 (the book’s project: running, tracing and diagnosis, the finished version is supplied) and T1 to T5 (your team’s project: your handover bundle, retrospective and individual reflection).
  • If you want more: B5 to B8 (rehearse removing the flag, validate an AI-drafted README, read a synthetic contribution table, close the gap the one-line experiment found), the review questions, the transfer challenge.

Opening Problem: The Morning of the Final Presentation

A classmate who was not on your team opens your repository link.

  1. The README says to run one command. It fails: the classmate’s machine has no mvn on its path, and the error says only “command not found”.
  2. The README promises one number of passing tests; the build reports another.
  3. The requirements table points at a test that was renamed last week.
  4. A reviewer asks which of the three of you wrote the approval-file adapter; the commit log says the person with the most commits wrote it. They did not.

The Defect Is in the Handover

  • None of these is a defect in the software. Each is a defect in the handover: the material that lets someone else set the project up, trust what it says, and understand who did what.
  • This chapter finishes the project the way Chapter 13 started the change: with evidence.
  • It is not a new feature sprint. The change was implemented in Chapter 13. Here we add no behaviour.
  • The one fix in the worked project is to the setup and the demonstration. Everything else is a decision, a document or a check.

14.1 Did the Design Absorb the Change?

Reviewing the outcome


“Did the Design Absorb the Change?” Is Too Vague

  • Break it into four questions, each with its own evidence.
  • Each evidence has a weakness; naming the weakness is part of the review.

The Four Questions

QuestionEvidenceWeakness
Which responsibilities were affected?Files changed and added, and what each doesCounts files and lines, not the importance of a line
What broke when the change started?Test failures after the smallest edit that introduces the changeShows what the tests cover, not everything that depends on the code
Which assumptions turned out wrong?The assumptions written in earlier chapters, checked against the changeOnly the assumptions someone wrote down
What did a reviewer say?Actual review comments and the responsesOnly as good as the review

Affected Responsibilities: The Numbers

Between the Chapter 11 and Chapter 13 versions, 23 files were added and 10 changed.

GroupFilesLines
New production files9399
Existing production files changed521 added, 3 removed
New test files10992
Existing test files changed310 added, 4 removed
New scripts, workflow and record4213
README and POM changed214 added, 8 removed

The New Production Files

  • Nine new production files hold 399 lines: the visitor policy, the approval record and its status, the gate, the approval port and its exception, the file adapter, the HTTP client and the demonstration program.
  • Line counts: 63 + 15 + 9 + 73 + 53 + 6 + 16 + 92 + 72.
  • Five existing production files changed, by a few lines each:
Existing fileChange
BorrowerCategory+1 −1
DirectoryReservationService+14
ReservationApplication+3 −1
LegacyDirectoryAdapter+1
LegacyDirectoryClient+2 −1

Figure 14.1: The Footprint of the Visitor Change

Existing code changed5 files, +21 -3 linesNew production code9 files, 399 linesBorrowerCategoryDirectoryReservationServiceReservationApplicationLegacyDirectoryAdapterLegacyDirectoryClientApprovalRecord, StatusVisitorGateVisitorReservationRulesHttpLegacyDirectoryClientApprovalSource, ExceptionFileApprovalSourceChapter13Demoimplemented by

What the Footprint Shows

  • The existing structure took the change by extension, which is what the abstractions of Chapters 5, 6 and 10 were for.
  • A new borrowing rule was one new class behind the policy contract. A new source of approval was one new port with an adapter.
  • Two things do not show in a line count:
    • the existing files that changed are the ones every request passes through;
    • nine new files are nine more things to read.

What Broke First: Experiments E1 and E2

Start the change in the smallest possible way and see what the tests say.

ExperimentEditResult
E1 (Chapter 11 version, 159 invocations)Add VISITOR to the enum only45 of 159 fail (1 failure, 44 errors); 89 lines say Missing policy: VISITOR
E2 (the same)The enum, VisitorReservationRules and the composition root5 of 159 fail, only in the three test files that build their own policy map; with four map entries added, all 159 pass

E1 and E2 in Detail

  • E1: the one failure is RepositoryBoundaryTest.coordinatorRequiresARepository, which expects a NullPointerException and gets an IllegalArgumentException.
  • E2: the five tests are in LegacyRegressionTest.chapter06 (through Chapter06Checks), BehaviourStrategyTest.injectedPolicyChangesDecisionWithoutChangingCoordinator and three tests in RepositoryBoundaryTest.
  • No assertion changed.
  • Everything constructed through the composition root fails together when the policy is missing there: one fix. Tests that spell out their own map fail separately: four fixes.

Is That Good or Bad?

  • Both. The design tells you, at construction time and with a precise message, everywhere that lists every category.
  • A safety net: the alternative would be a request that fails in production with a null policy.
  • A cost: every test that spells out the full policy map must be edited when a category is added.
  • B1 in the practical activity runs it the other way: remove the visitor policy from the composition root of the finished version: 73 of 235 fail (3 failures, 70 errors), in 14 test classes; roughly three in ten tests.

B1’s Failures by Test Class

Test classFailingTest classFailing
VisitorAcceptanceTest19NotificationPublisherTest3
DirectoryOutageIntegrationTest10NotificationMockitoTest3
LoanServiceTest9Chapter13JourneyE2ETest3
AvailabilityTest8RepositoryBoundaryTest1
ReserveEquipmentCommandTest4OverdueBridgeTest1
LegacyRegressionTest4AvailabilityTddTest1
DirectoryAdapterTest4
  • 146 lines of output say Missing policy: VISITOR (each failure is reported more than once).

Revised Assumptions (1 of 2)

Chapters 5 to 11 made statements that the change tested.

Assumption (where made)What the change showedVerdict
A policy answers a count and duration question and nothing else (Ch. 5, 6)Approval needs borrower, item, period and time; widening the policy would give it hidden state, so a separate gate was addedHeld, because the contract was not widened
Every category has a policy in one place (Ch. 6)True in production, but four test setups also spelled out the mapHeld, with a cost
The directory is a synthetic provider with no transport (Ch. 8)Chapter 13 added a real HTTP client and the failures a transport brings, including a timeout that must cover the whole answerContract held; behaviour extended

Revised Assumptions (2 of 2)

Assumption (where made)What the change showedVerdict
The count of retained reservations is the borrowing count (Ch. 7)CR-12 preserved it on purpose; AC11 pins itStill open (SD-12)
The overdue report knows two borrower kinds (Ch. 11)A late visitor reaches the “any other kind” rule and is fined at a rate nobody choseStill open (SD-11-1)
Notifications are one attempt, no retry (Ch. 6)The directory client follows the same ruleHeld and extended

A Small Diff Is Not Automatically a Better Design

  • The cheapest possible “implementation” of visitors: the directory adapter maps kind code V to the existing staff category. No gate, no register, no new policy. The diff is one line.
  • Experiment E3 (Chapter 13 version, 232 invocations): 4 of 232 fail, and all 203 unit invocations pass.
  • What fails are the tests that cross the real boundary: the three end-to-end journeys and DirectoryOutageIntegrationTest.aVisitorIsServedThroughTheRealDirectoryAndTheRealApprovalFile.

What E3 Shows in the Journey

  • With the flag off, visitor V1 is confirmed: the flag is ignored.
  • V2 is rejected only because CAM2 is already taken, with Rejected: item unavailable., not because it is unapproved.
  • A malformed register no longer stops anyone.
  • It is a smaller diff, and it does not implement the requirement.

Be Careful What E3 Shows

  • It shows this suite has a gap: no unit test checks the adapter’s mapping of the visitor code, so a one-line change to it passes the whole unit stage.
  • It does not show that unit tests cannot see such a change. A focused unit test of the adapter would catch it, and so did the integration tests.
  • A passing unit stage is evidence only about what the unit tests cover. A suite with a hole is found by trying a change that goes through the hole.
  • Neither the size of a diff nor a green unit stage is the measure of a design. The acceptance examples are. B8 asks you to close the gap.

The Opposite Case

  • A change larger than you expected is not a failure if each added file has a purpose the requirement needs.
  • It is a failure when the added files support code nobody asked for, or when existing files change in ways the change did not require.
  • The footprint above has no such file: the three changes to existing test files add an entry to a map, and nothing else.

Where the Design Bent

if (borrower.category() == BorrowerCategory.VISITOR) {
    Optional<String> rejection = visitorGate.rejection(borrower.id(), itemId, start, days);
    if (rejection.isPresent()) return rejection.get();
}
  • A category-specific branch in the entry point, in DirectoryReservationService.
  • For one special category it is fine and easier to read than an abstraction. At a second, it becomes a smell: the next step is a per-category pre-check chosen from a map, introduced as a refactoring with its own tests.
  • Record it as a known issue with its trigger (the second category), instead of fixing it now: KI-14.

Review Feedback

  • Real review feedback is evidence; constructed feedback is not. Use your team’s actual review comments and the responses.
  • the book’s project has no team, so the companion supplies a short, author-written, fictional review of the visitor change, labelled as such.
  • The task is to check each comment against the code, not to accept or reject it by tone.
  • No real peer review of this book’s project has taken place.

The Constructed Review, Checked

#CommentCheckVerdict
R1“DirectoryReservationService now branches on VISITOR. Will that grow?”One if on the category, delegating to the gateValid: KI-14, trigger a second category
R2“Where is the flag read in production?”Only Chapter13Demo reads lending.visitors and passes a boolean to VisitorGate; no static stateNo issue
R3“Is the old two-argument constructor safe for visitors?”ac12_... builds one and asserts the disabled rejectionCovered
R4“The README says 230 tests. I count 233.”The count appeared in both README and HANDOVERValid: a number stated in two places rots
  • R4 changed the version: the README now states no number, and handover-check.sh checks the numbers in HANDOVER.md.

14.2 Fix, Defer or Accept

Scope control in the last week


Everything Looks Improvable

  • In the final sprint every team sees things it could improve. Some are real defects. Some are polish. Many are new features in disguise.
  • If you act on all of them, you finish nothing and introduce faults close to the deadline.
  • Three questions sort them. Ask them in this order.

Three Questions

  1. Does it block a requirement or the demonstration? If yes, it is a blocker: fix it first or change the plan.
  2. Does fixing it need a decision that is not ours? If yes, it is deferred: record the question, the owner and what would trigger the decision.
  3. Is the cost and risk of changing it now worth the benefit? If not, it is accepted: say so in writing, as a limit, not as a secret.

Figure 14.2: Triage of a Known Issue

A knownissueBlocks a requirementor the demonstration?Needs a decisionthat is not ours?Worth the costand risk now?Fix now,with a checkDefer: record the question,the owner and the triggerAccept: state itas a limitnonoyesyesyesno

Reading the Triage

  • Every path ends in something written down.
  • “Fix now” ends with a check, so the fix is visible later.
  • “Accepted” is not “ignored”: the team decided, with a reason, and a reader can find the reason.

What the Project Did With Its Own List

  • The visitor project has fourteen known issues (docs/KNOWN-ISSUES.md).
  • One was fixed.
  • Seven are deferred: five wait for an owner’s decision, one for a JDK 17 and a hosted runner, one for the flag decision.
  • Six are accepted limits.
  • Every row has a decision, a reason and a next step.

The Fixed One

  • KI-1: the demonstration program needed a directory service that only the tests could start, so a new reader could not run it.
  • That is a blocker for a handover: a reader who cannot run the system cannot check anything else about it.
  • The fix added no behaviour. It added a small launcher (DemoRun) that starts the same test stub and runs the system twice, and a script, scripts/demo.sh.
  • It is covered by the dry run in scripts/handover-check.sh.

Deferred Issues (1 of 2)

IDItemWhy deferred
KI-2 (SD-11-1)A kind other than student or staff is fined at 1.00 a day with no grace and no cap; a late visitor now reaches it (AC18 pins it)Only the records office can say what the rate should be
KI-3 (SD-11-2)A loan within the grace period is listed with fine 0.00 and counted as overdueNeeds the owner’s answer to “should it appear at all?”
KI-4 (SD-11-3)A null kind throws NullPointerException, only for an overdue unreturned loanAsk whether the export can contain one
KI-5 (SD-11-4)Amounts use the default locale (0,50 under a German locale)Owner decides whether the separator follows the machine or the office

Deferred Issues (2 of 2)

IDItemWhy deferred
KI-6 (SD-12)The count rule counts every retained reservation, including returned onesPreserved on purpose by CR-12 and pinned by AC11; owner decides
KI-12No Java 17 run and no hosted CI run; action versions not re-checkedNeeds a JDK 17 and a hosted runner
KI-13The visitor flag is still in the codeDecided in ADR-014; removal rehearsed

Why Not Just Fix SD-11-1?

  • It is tempting to “fix” it by giving visitors a sensible rate.
  • But nobody on the team knows the right rate. Only the records office does.
  • A guess would be a new rule disguised as a bug fix, and it would hide the question and change AC18.
  • So it is deferred: the question, the owner, and the test that will change when the answer arrives are all written down.

Accepted Issues (1 of 2)

IDItemWhy accepted
KI-7One bad row in the approval register rejects the whole registerA silent skip could hide a lost approval or a duplicate; revisit with real use
KI-8The directory wait defaults to one second and was not measured; it bounds the whole lookup, body includedA setting (lending.directory.timeout.millis); measure before changing. Stalled-body case tested on a newer JDK only
KI-9No retry and no cached answer after an outageBy design (ADR-013); revisit after measured outages

Accepted Issues (2 of 2)

IDItemWhy accepted
KI-10The directory stub and the approval file are stand-insNeeds a provider contract test against a real service, which does not exist here
KI-11Concurrency (two writers of the register, simultaneous requests) was not consideredOut of scope; the register is a trusted sequential file
KI-14DirectoryReservationService branches on the visitor categoryFine for one special category; at a second, refactor with its own tests

Resist the Plausible Additions

Tempting additionWhy not now
Retry the directory call after a timeoutChanges the one-attempt contract of earlier chapters; nobody has measured an outage
Replace the approval file with JSON or a databaseNothing in the requirement needs it; adds a new boundary to test
Remove the flag to “clean up”The conditions for removal do not hold (14.3)
Give a visitor a late-fine rateA decision for the records office, not the team
Add a second category of visitorA new requirement: raise it as a change request, not as polish

The Costs of a Feature Built Before It Is Needed

Fowler names four:

  1. The cost of building it.
  2. The cost of the delay it causes.
  3. The cost of carrying its complexity.
  4. The cost of repairing it if its assumptions were wrong.
  • In the last week, the delay and the repair cost are the ones that hurt.

A Release Is “Done” by a Definition, Not a Feeling

  • The Scrum Guide defines a Definition of Done as the state an increment must reach to meet its quality measures; work that does not meet it cannot be part of the increment.
  • Write yours for the handover version, as a short list a check can confirm:
    • the pipeline passes;
    • the handover check passes;
    • no test is skipped;
    • every number in a document matches a run;
    • every decision record has a true status;
    • every known issue has a decision.

14.3 Writing It Down

Documents, decision records and release notes


One Document per Question

  • A new reader asks different questions at different times. One long document answers none of them well.
  • The project has six documents, and each is kept true in a different way.
  • Appendix C has forms for the requirements table and known-issues list (C.10), decision records (C.5) and release notes (C.11).

The Six Documents (1 of 2)

DocumentQuestion it answersHow it is kept true
README.mdWhat is this, and where do I start?Short; points to the others
docs/HANDOVER.mdHow do I set it up, run it and understand it in an hour?scripts/handover-check.sh follows it in a fresh copy
docs/REQUIREMENTS-TO-EVIDENCE.mdWhich test checks each requirement?TraceabilityTest fails if a named test or class is gone

The Six Documents (2 of 2)

DocumentQuestion it answersHow it is kept true
docs/KNOWN-ISSUES.mdWhat is wrong or missing, and what was decided?A reviewer; every row has a decision
docs/ADR-0NN-*.mdWhy is it built this way?Never edited after acceptance except its status; a new record supersedes it
docs/RELEASE-NOTES-*.mdWhat changed in this version?Written at release; one file per version
  • The cheapest document to maintain is one that a check keeps true. Wherever a statement can be checked by a script, write the script.

Decision Records

  • A record captures a decision, not a description of the code. Nygard proposes short records in the repository:
    • title, context (the forces at play), decision, status (proposed, accepted, or later superseded), consequences;
    • numbered in order and never reused; a reversed decision’s old record stays and is marked superseded.
  • This project’s records add three parts that earn their place: alternatives considered, how the decision was validated, and triggers for revisiting it.

ADR-014: Keep the Flag at Handover

ADR-013 set three conditions for removing the flag. ADR-014 checks each one:

Condition from ADR-013State
The pipeline has run both settingsTrue
The visitor contract has been accepted by an ownerNot yet
A decision to keep visitors enabled is recordedNot yet
  • Decision: keep the flag, default off, and rehearse the removal so the change is ready and its cost is known.
  • It does not remove the flag and does not edit ADR-013. Status: a model decision by the book’s author, not an institutional decision.

ADR-014: Alternatives and Consequences

  • Remove the flag now: rejected. It would turn an unaccepted teaching contract into permanent behaviour and remove the one switch that stops new visitor reservations without a new build.
  • Keep the flag with no removal plan: rejected. An owner-less flag is a permanent branch.
  • Replace the flag by run-time configuration: not adopted; outside the scope of the module.
  • Consequence: both paths stay tested (AC12, the flag-off journey, the gate’s disabled tests). A visitor can never be served unless someone sets the property to true.

Release Notes

  • A changelog is for people. Keep a Changelog groups changes as Added, Changed, Deprecated, Removed, Fixed and Security, gives each version its own dated entry and puts the newest first.
  • The notes for version 0.14.0 use three of those headings and add two of their own that a handover needs: “Behaviour you should know about” and “Not verified”.
  • The first line says no production behaviour changed: a statement a reader can check, and it was checked. Every file under src/main is byte for byte as in 0.13.1.

14.4 Keeping the Evidence True

Rehearsal and traceability


Rehearse a Risky Removal

  • Removing a flag looks like deleting an if. Rehearsed in a scratch copy, it touched eleven files, with 87 lines added and 131 removed.
  • The flag was a constructor parameter, a result text, a factory for the old two-argument constructor, a system property in the demonstration, AC12 which exists only for the flag, two other acceptance examples that mention it, a journey that tests the flag off, and two documents that explain it.
  • After the rehearsal: the full build 233 invocations, 0 failures, the pipeline passed, the handover check passed. The patch is stored in rehearsal/ and not applied.

The Eleven Files of the Rehearsal

FileChange
VisitorGate.javaRemoves DISABLED, the flag and the disabled() factory; adds noApprovals()
DirectoryReservationService.javaTwo-argument constructor uses VisitorGate.noApprovals()
Chapter13Demo.java, DemoRun.javaNo lending.visitors property; the demo runs once
VisitorGateTest.java, VisitorAcceptanceTest.javaFlag argument dropped; AC12 becomes “no register approves no visitor”; AC13 and AC16 renamed
Chapter13JourneyE2ETest.javaRemoves the flag-off journey, two remain
DirectoryOutageIntegrationTest.javadisabled() becomes noApprovals()
Requirements table, HANDOVER.md, handover-check.shNames and counts: 233 total, 205, 26 and 2

What the Rehearsal Also Taught

  • Retiring AC12 means a requirement in the specification disappears with the flag: “a disabled flag rejects explicitly” has no meaning without a flag. That is a change to the specification, and it needs a decision, not just a patch.
  • TraceabilityTest is why the table edits are in the patch: without them the build fails on the first stale test name, the check doing its job.
  • Prose is not edited by the patch: README.md, the rest of HANDOVER.md and ADR-013 still say the flag exists.
  • After removal, the rollback is a redelivery of the previous release, because there is no longer a switch.

Requirements to Evidence

  • Traceability is the chain from a requirement to the code that decides it and the test that checks it.
  • In a small project a table is enough: ten contract clauses and eighteen acceptance examples, each traced to a deciding class and a test, plus five known gaps (requirements with no evidence here).
  • The last link of the chain is the one that stops the table rotting.

Figure 14.3: The Chain

Requirementor exampleClass thatdecides itTest thatchecks itTraceabilityTest:does the namestill exist?Pipeline stagethat runs it

What TraceabilityTest Checks

CheckMethod
Every test named in the Evidence column exists as a methodeveryTestNamedInTheTableStillExists (also requires at least 29 named tests, so a deleted row cannot hide)
Every class named in the Decided by column exists under src/maineveryClassNamedAsDecidingStillExists
AC1 to AC18 each have exactly one row, and every ac<N>_ test appears in the tableeveryAcceptanceExampleHasExactlyOneRowAndEveryAcceptanceTestIsInTheTable
That a named test is the right evidenceNot checked: a reviewer decides

Does the Traceability Check Detect Faults?

FaultResult
A named test is renamedCaught: two of three methods fail
An acceptance example loses its rowCaught: two of three methods fail
A deciding class is renamed in the table (VisitorGuard)Caught
An acceptance test is added that no row mentionsCaught
A row names an extra test that existsNot caught; harmless
AC3 is pointed at the AC1 test, which existsNot caught: a wrong mapping only a reviewer finds
  • A script can check that a name exists. Only a reviewer can check that a name is the right evidence. Say so in the document.

14.5 Setup and a Demonstration That Anyone Can Reproduce

Write for someone who is not you


Write for Someone Who Is Not You

  • Your own machine has things installed that you have forgotten about. The only way to find them is to try the instructions somewhere else.
  • The handover document begins with what you need: a full JDK 17 or later with the jdk.jdeps module, Maven 3.9.11, a POSIX shell, network access for the first build.
  • Then three commands with their expected output, then how to see the system working.

A Script Can Follow the Document for You

  • scripts/handover-check.sh copies the project, without build output, into a new folder, runs the pipeline and the demonstration there, and compares what they print with what the document promises.
  • It ends with HANDOVER CHECK PASSED after twelve ok lines.
  • If a test is added and nobody updates the document, the script fails. It also fails if the document is edited to a number the run does not give.

Does the Handover Check Detect Faults?

FaultOutput
A test addedFAIL stage 2 ran 206 invocations: stage 2 shows 207; one problem
A word changed in the demonstration outputFAIL an outage is reported as unavailable; the pipeline fails too, because the journeys check the same line: three problems
HANDOVER.md edited to promise 232 invocationsFAIL HANDOVER.md promises 235 invocations in total; one problem
  • All three were caught. These are a few faults chosen by the author; they show the check notices a document that disagrees with a run, not one that is wrong in a way the script cannot see.

The Test Counts the Document Promises

StageChapter 13Chapter 14
2 Unit tests and architecture checks203206 (adds TraceabilityTest, 3)
3 Integration2626
4 End-to-end33
Total232235
  • mvn --batch-mode verify ends with Tests run: 235, Failures: 0, Errors: 0, Skipped: 0 and BUILD SUCCESS.

What the Dry Run Found (1 of 2)

Following the handover in a fresh folder found four things before any reader did.

FindingEffect on a new readerChange
D1. The setup assumed mvn was on the PATH; on the test machine it was not, and the failure said only command not foundThe reader stops at step 1The document starts with a tool check; scripts print a clear message and exit with status 2
D2. On a recent JDK, Maven 3.9.11 prints WARNING lines about sun.misc.Unsafe, and the mocking library prints a dynamic-agent warningA reader may think the build is brokenThe document lists “things you may see that are not errors”

What the Dry Run Found (2 of 2)

FindingEffect on a new readerChange
D3. The demonstration needed a directory service that only the tests could start (KI-1)The reader could not see the system workDemoRun and scripts/demo.sh
D4. The new clear message rejected a valid Maven path on Windows (a .cmd file that is not executable in the shell sense)The check meant to help would have blocked a correct setupAlso accept a file that exists
  • D4 was a fault in a fix written during this very exercise, found only because the fix was run in the setting it was meant to protect.
  • A document you have not followed is a guess about what it says.

What Did Not Break

  • A folder name with spaces was also tried, and worked.
  • That is a result, not a finding: say what you tried that did not break.

The Demonstration

  • A short, repeatable story with a fixed clock and fixed data.
  • scripts/demo.sh compiles, starts the local directory stub in one process, writes a small approval register, and runs the system twice: visitors disabled, then enabled.
  • The viewer sees the same requests produce different results, and an unreachable directory reported as unavailable, not as a missing borrower.
  • The handover suggests an outline of about ten minutes. Your instructions decide the length and format of your own demonstration.

Expected Demonstration Output

== visitors disabled ==
Visitors enabled: false
Student B1: Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Visitor V1: Rejected: visitor reservations are not enabled.
Visitor V2: Rejected: visitor reservations are not enabled.
Unknown U9: Rejected: borrower not found or inactive.
Outage BROKEN: Unavailable: Borrower directory unavailable
Reservations: 1
Notices: 1
  • The second run (== visitors enabled ==) confirms V1 as R2 on CAM2, rejects V2 with no valid sponsor approval, and ends with Reservations: 2, Notices: 2.

What the Dry Run Cannot Show

  • It ran on one operating system with one JDK and a local Maven repository that already held the dependencies.
  • It cannot show a first build over a slow network, a Java 17 runtime, a different shell, or a reader who misreads a sentence you thought was clear.
  • To cover the last, ask a real person from outside your team to follow the document while you watch and say nothing. Write down every place they hesitate. That is the test.

14.6 Retrospective and Individual Reflection

Evidence, not mood


A Retrospective Is Evidence-Based or It Is a Mood

  • The Scrum Guide’s purpose for the Retrospective: plan ways to increase quality and effectiveness; inspect individuals, interactions, processes, tools and the Definition of Done; discuss what went well and wrong; identify the most helpful changes.
  • Kerth’s Prime Directive asks the team to assume everyone did the best they could, given what they knew and the resources they had.
  • Together: look at the work and the process, not at blame.

Bring the Records

  • Make it evidence-based by bringing the sprint’s records: the board, the pipeline runs (passing and failing), the review comments, the defect list, the time between starting and finishing items (Chapter 12).
  • For each thing the team wants to say, ask: what record shows it?
  • Keep the number of actions small. Two actions with owners that are done beat ten that are not.

From Observation to Action

ObservationEvidenceActionOwner and date
“Reviews were slow”Two pull requests waited more than two days (board timestamps)Review before starting a new itemNamed person, next sprint
“The pipeline was flaky”One of ten runs failed with no code change (run list)Fix or quarantine the unstable testNamed person
“We rushed the end”Seven items moved to Done on the last dayPlan a smaller scope and a freezeWhole team

The Process Note, Revisited

  • Chapter 3 asked each team to say which parts of Scrum it used as defined and which it adapted for the course.
  • Now say what the evidence shows about each adaptation. For example: the course’s sprints end at milestone weeks and not at a fixed calendar interval; a student team is not a full-time team; the Product Owner is usually a teaching role.
  • For each adaptation: did it help, did it hurt, what would you change?
  • This is the closing of an earlier commitment. It needs no new terms.

Individual Contribution Is Evidence of Three Kinds

  • The descriptor assesses individual contribution through commits, pull-request reviews and peer evaluation, and says team marks are moderated by individual contribution.
  • Each of these is an input. None is the answer.
  • Next: what each can and cannot show.

What Each Kind of Evidence Can Show

EvidenceWhat it can showWhat it cannot show
CommitsThat a change was made, by which author identity, when, and what it touchedEffort, difficulty or importance. Commit size is a habit; pair work is recorded under one author unless named in a Co-authored-by line; formatting and regenerated files inflate counts; squash merges turn many commits into one
Pull-request reviewsThat someone read a change and what they said (comment, approval, request for changes)Whether the review was careful; an approval says the changes look ready to merge, nothing about how thoroughly it was checked
Peer evaluationHow teammates experienced each other’s work, including things no tool recordsObjectivity: affected by friendship, conflict and what people happened to see

How Little a Count Can Be Trusted

  • GitHub’s account of its contribution graph: a commit counts only when its author email is linked to the account, and only when it is on the default branch or reaches it through a merged pull request; commits made in a fork do not count until merged.
  • A person whose commits use the wrong email appears to have done nothing.

A Constructed Example (Synthetic)

A team of three finished its last sprint with these counts.

CommitsFormatting onlyLines changedPR reviews givenRequested changes
P1381741041
P2901,150115
P324362070
  • Team total: 71 commits and 2,180 changed lines.

Four Rankings, Three Orders

MeasureOrderShare
CommitsP1, P3, P2P1 has 38 of 71, more than half
Lines changedP2, P3, P1P1 has 410 of 2,180: 19%
Reviews givenP2, P3, P1
Reviews requesting changesP2, P1, P3
  • Those numbers describe habits, not contributions.
  • The underlying facts, which only people and the work itself can supply, might be: P1’s commits include seventeen formatting-only commits and several that undo each other, and P1 built the pipeline; P2 wrote the approval-file adapter in nine substantial commits and found two defects in review; P3 wrote the demonstration and the documents and did four pair sessions with P1, only two recorded.

No Ranking Is Offered, and None Could Be

  • The honest use of counts is as a pointer to the work to read.
  • “Why did P2 have so few commits?” is a question to ask; the answer is in the diff and the review.
  • A flawed alternative to avoid: Contribution: P1 54%, P3 34%, P2 13% (by commits). Habits are not contributions, and the same team has three other orders by other counts.

Write Your Contribution as Claims With Evidence

  • An individual reflection does not say “I did a lot”. It says what you did, where to see it, what you learned from it and what you would do differently.
  • These are prompts for thinking, not a rubric: your approved instructions say what the reflection must contain.
PartPrompts
What I didThree to five specific pieces of work, each with a link or path
What I decidedA design or process decision, the alternatives and why
What went wrongA real fault or misjudgement, how it was found, the fix, what changed

The Reflection Prompts, Continued

PartPrompts
How I used AIWhere, with what input, what you accepted or rejected, how you verified it
What I did not doHonest limits: work started and not finished, work you relied on others for
What I would changeOne thing about your own practice
  • Appendix C has templates for the retrospective (C.12) and decision records (C.5). Appendix E gives a fuller guide to contribution evidence (E.4), the final handover (E.5) and the reflection (E.6). This chapter stands on its own without them.

14.7 Getting Ready for the Final Submission

What is submitted, and what it must show


What the Submission Must Show

  • The descriptor says teams submit the complete system built across the sprints, a short technical report and an individual reflection, through a repository link, and give a demonstration and presentation followed by questions.
  • It also names five things the submission must show. Use them as the headings of an evidence index:
    1. clean object-oriented design;
    2. justified use of design patterns and architecture;
    3. automated tests with continuous integration;
    4. evidence of safe refactoring;
    5. adaptability to the injected change in requirements.

An Evidence Index for the Book’s Project (1 of 2)

Must showEvidenceWhat it does not show
Clean OO designResponsibilities and dependencies of Chapters 2, 5, 6, 10; the architecture testsThat it is the best design, or that anyone else reviewed it
Justified patterns and architectureChapters 9 and 10; ADR-010Any pattern the design does not use
Automated tests with CIThe test suite; the five-stage pipeline; the workflow fileA hosted CI run: the workflow has never run

An Evidence Index for the Book’s Project (2 of 2)

Must showEvidenceWhat it does not show
Safe refactoringChapter 11: characterisation tests, stage-by-stage refactoring, probeBehaviour on inputs the generator did not produce
Adaptability to the changeChapter 13 footprint and tests; the requirements table; the rehearsed flag removalWhether the visitor rule is the right policy

A Short Sequence

  1. Finish. The final sprint and workshop feedback are for finishing.
  2. Freeze. Decide a time after which only fixes to the handover and blockers are allowed.
  3. Run the handover check on the frozen version.
  4. Review independently. Use the free time before the demonstration to read the whole bundle as a stranger, fix what the dry run finds, and ask someone outside the team to follow it.
  5. Prepare the demonstration and the report from the evidence index, and write your reflection from your own work.

A Preparation Checklist (Not a Rubric) (1 of 2)

  • The repository link opens for a person who is not on the team, and the default branch is the version you mean.
  • A fresh copy builds with the documented commands and prints the documented numbers.
  • The pipeline has run, and you can show one passing run and one failing run you have read.
  • Every number in a document matches a run. Every claim names the evidence.

A Preparation Checklist (Not a Rubric) (2 of 2)

  • Every decision record has a true status. Every known issue has a decision.
  • The limits statement says what was not run: for the book’s project, a Java 17 runtime, a hosted CI run and any independent review.
  • Each person can say, with links, what they did.
  • The demonstration works from a fresh checkout with only the documented setup.
  • Do not claim a hosted run, a public deployment or a peer review that did not happen. A local run is evidence of a local run.

14.8 Validating an AI-drafted README

Validate a README that an assistant drafted


The Activity

For this activity
Your goalDecide which statements in an AI-drafted README are supported by the submitted version, and correct the rest
You start withThe README draft and the project at version 0.14.0
What is in boundsVerify every command, every feature claim and every limitation; edit only the draft
How to checkRun each command; read the code, tests and decision records each claim depends on
How to decideA verdict on each statement, and a corrected README

The Ready-Made Example (1 of 2)

Author-written fictional AI output, asked to “write a README for the project”:

# Campus Equipment Lending System

A Java 11 application for reserving campus equipment, with support for students, staff and visiting researchers.

## Quick start

1. Build: `mvn package`
2. Run: `java -jar target/equipment-lending.jar`
3. Check everything: `mvn verify` runs all 230 tests, which pass in under 30 seconds.

The Ready-Made Example (2 of 2)

## Features

- Visiting researchers are enabled by default. Set `-Dlending.visitors=false` to turn them off.
- Visitors may reserve CAM1 or CAM2 for one to three days when a sponsor approval matches.
- Sponsor approvals are read from a CSV file. The file is cached in memory for speed.
- If the borrower directory does not answer within one second, the system retries three
  times and then treats the borrower as a student.
- `scripts/pipeline.sh` runs five stages and deploys the application to the staging server.
- Architecture rules are enforced by `DependencyRulesTest`.
- The system is production-ready and safe for concurrent use.

## Demonstration

Run `bash scripts/demo.sh` to see visitors rejected and then approved.

The Task

  • Split it into separate statements. For each decide:
    • supported (name the evidence);
    • contradicted (name the evidence and write the correct statement);
    • not established (say what would be needed).
  • Run every command; do not decide from the text.
  • A statement that is plausible is not a statement that is true. A README that is partly right is more dangerous than one that is obviously wrong, because the reader stops checking.

Verdicts: Statements 1 to 6

#StatementVerdictEvidence
1A Java 11 applicationContradictedrelease is 17; javac --release 11 fails at LifecycleExamples.java: switch rules not supported
2Supports students, staff and visiting researchersSupportedBorrowerCategory, ADR-013
3Build: mvn packageSupportedProduces target/equipment-lending-0.14.0.jar
4Run: java -jar target/equipment-lending.jarContradictedUnable to access jarfile; with the right name: no main manifest attribute. Run java -cp target/classes edu.se506.lending.bootstrap.ReservationApplication
5mvn verify runs all 230 testsContradicted233 at the time of checking
6…under 30 secondsNot establishedAbout 21 seconds on one machine with cached dependencies: do not promise a time

Verdicts: Statements 7 to 11

#StatementVerdictEvidence
7Visitors enabled by default; -Dlending.visitors=false turns them offContradictedDefault is false; the demo prints Visitors enabled: false
8CAM1 or CAM2, one to three days, matching approvalSupportedC2, C3, C4 of the requirements table
9Approvals are read from a CSV fileContradictedTab-separated, fixed header, eight columns
10The file is cached in memoryContradictedtheFileIsReadAfreshOnEveryCall
11One second, retries three times, then treats the borrower as a studentContradicted (the one-second default is right)One attempt, no retry, no fallback; ...IsNotRetried asserts one request

Verdicts: Statements 12 to 16

#StatementVerdictEvidence
12pipeline.sh runs five stagesSupportedCompile, unit and architecture, integration, end-to-end, package
13…and deploys to the staging serverContradictedStage 5 only packages; the simulation copies a jar into a local folder
14Rules enforced by DependencyRulesTestPartly supportedChecks dependency direction (a separate test checks cycles), not every rule
15Production-ready and safe for concurrent useNot established, contradicted by the project’s own limitsKI-10 and KI-11
16bash scripts/demo.sh shows visitors rejected then approvedSupportedThe expected output above

Count the Verdicts

  • Of sixteen statements: six are supported or partly supported as written, eight are contradicted and two are not established.
  • Statements 5 and 6 split one sentence: this counts statements, not sentences.
  • The corrected README is ai-activity/README-corrected.md. Each command in it was run.

What Reading Reveals, and What Only a Run Does

  • A reader who knows the project notices the default of the flag and the CSV.
  • Nobody notices that java -jar fails without running it, because the sentence is exactly what a README usually says.
  • A draft in which half of the statements are contradicted reads as fluently as one that is right.
  • Notice also that the draft says nothing about limitations, and the corrected README adds them. Do not use a generated README to approve generated code.

14.9 Handover Habits That Mislead

Ten to avoid


Mistakes 1 to 5

MistakeConsequenceBetter action
The last week as a chance to add a featureLate faults; a handover describing a system that no longer existsTriage as fix, defer or accept; freeze
“Fixing” a suspected defect that needs an owner’s answerA guess becomes a rule; the question is lostDefer with the question, the owner and the test that will change
Judging a change by the size of its diffA tiny diff that fails the requirement looks goodJudge against the acceptance examples and across all stages
A README written from memoryCounts, commands and defaults that were once trueFollow it in a fresh copy; let a script check what it can
Documents nobody checksThey rot, and a reader trusts them anywayA check or a reviewer for each

Mistakes 6 to 10

MistakeConsequenceBetter action
Editing an old decision record to match what happenedThe reasons for the decision are lostA new record that supersedes or applies it
Removing a flag as tidy-upThe specification changes silently, and so does rollbackRehearse, decide, update the examples that mention the flag
Ranking teammates by commit countsHabits mistaken for contributionCounts point to the work; read the work
“I contributed a lot”Nothing can be checkedClaims with links, and honest limits
Claiming a hosted run, a review or a Java 17 run that did not happenThe submission loses credibility, and so do the true claimsSay what was not run

Diagnostic Alternative: A Number Stated in Two Places

README.md:    "The build executes 230 JUnit invocations ..."
HANDOVER.md:  "Expected: Tests run: 233 ..."
  • One is stale the moment a test is added, and a reader cannot tell which. State a number once, and have a script check it.

Diagnostic Alternatives: Edited Record and Guess

# ADR-013 ...
Status: Accepted
- Introduce one flag ... (edited on 20 October: the flag was removed)
// WRONG for a handover sprint: nobody knows the visitor rate.
private static final FinePolicy VISITOR = new FinePolicy(2, 0.50, 10.00);
  • The edit loses the reasons the team had at the time: write a new record. The constant looks like a repair of SD-11-1; it is a new rule, hides the question and changes AC18.
  • A claim that did not happen: CI: passing on GitHub Actions. The workflow has never run: say “workflow file written, not run”.

14.10 Try It Yourself

Hand the project over


What You Start With, and the Demonstration

  • You start with: the Chapter 14 version (version 0.14.0) with the companion’s files, the Chapter 13 material, and your team’s repository. B1 to B8 use the book’s project; T1 to T5 use your team’s project.
  • Instructor demonstration: run scripts/handover-check.sh and read it. Break one thing (add a test, or rename a word in the demonstration output) and run it again. Run TraceabilityTest after renaming a named test. Show the one-line adapter change that passes the unit stage and fails the integration stage.
  • Guided practice: classify four known issues of your choice as blocker, deferred or accepted, and say what evidence each decision rests on. Find the row of the requirements table you trust least.

The Book’s Project: B1 and B2

  • B1. Predict and measure what breaks. In a scratch copy, remove the visitor policy from the map in ReservationApplication.create. Predict how many invocations fail, in how many classes, and what the message says. Run, compare, say what it shows. Restore the original.
    • Good answer: most of the suite fails (73 of 235, 14 classes), every message names the missing policy; the design fails fast and precisely, and the cost is breadth. It does not show the code is correct.
  • B2. Triage. Take five items from docs/KNOWN-ISSUES.md; check each decision against the three questions and say whether you agree. Add one item you find yourself, with a decision.

B2: A Good Answer

ItemDecisionReason
KI-2 (SD-11-1, visitor late-fine rate)DeferNeeds the records office; any rate would be a guess
KI-7 (whole-file rejection of the register)AcceptA silent skip could hide a lost approval or a duplicate
KI-8 (one-second wait, not measured)AcceptA setting; measure before changing
KI-12 (no Java 17 or hosted CI run)DeferNeeds a JDK 17 and a hosted runner
KI-14 (the VISITOR branch)AcceptFine for one category; refactor at the second
  • A good new item comes from reading the project: DemoRun fixes the clock, so the demonstration cannot show an approval that expires.

The Book’s Project: B3 and B4

  • B3. Follow the handover. Run scripts/handover-check.sh. Then follow docs/HANDOVER.md by hand from a new folder, and list every place where you had to guess or where the text was wrong for your setup. Likely: where the project directory is, how to set CHAPTER_MVN, what the warnings mean, how long the build takes.
  • B4. Trace and break a row. Choose one contract clause, one acceptance example and one known gap. For each, open the named test and say whether it checks what the row claims. Rename one named test in a scratch copy, run TraceabilityTest, read the failure. Then name something the check cannot catch.
    • Example: C5 is checked by VisitorGateTest.theWindowIncludesItsStartAndExcludesItsEnd, which tests exactly the two edges and one instant before the start.

Your Team’s Project: T1 and T2

  • T1. Review your change. Fill in the four questions of 14.1 with real evidence: files and responsibilities affected, what failed when you started, the assumptions overturned, the review comments you actually received. Say where the design did not absorb the change cleanly.
  • T2. Triage your open items. A known-issues list with a decision for each item (fix, defer, accept), a reason, and an owner for the deferred ones. Fix only blockers. Freeze.

Your Team’s Project: T3 to T5

  • T3. Build the handover bundle. Setup instructions, a usage example, known limitations, test instructions, a requirements-to-evidence table and a demonstration outline. Dry run in a fresh copy, then have someone outside your team follow it while you watch. Record what they struggled with and fix it.
  • T4. Decide and record. At least two decision records: one about your flag or change (keep, remove or replace, with the conditions) and one other. Give each a true status.
  • T5. Retrospective, reflection and checklist. An evidence-based retrospective with at most three actions. Your individual reflection as claims with links. Complete the 14.6 checklist, marking each item complete or a gap.

If You Want More: B5 to B7

  • B5. Rehearse the removal. In a scratch copy apply rehearsal/remove-visitor-flag.patch (patch -p1), run the build (233 invocations) and the handover check; list the documents whose prose the patch does not edit: README.md, HANDOVER.md section 6, ADR-013, KNOWN-ISSUES.md (KI-13), and the descriptions in the requirements table.
  • B6. AI activity. Complete 14.8.
  • B7. Read a synthetic contribution table. Using the synthetic table, write two questions you would ask each team member and what you would read to answer each. Say what you would not conclude from the counts.

B8: Close the Gap That E3 Found

  • Write the focused unit test of LegacyDirectoryAdapter that fails when kind V is mapped to staff.
  • Use an in-memory client (no network) and check at least:
    • a row with kind V and status Y maps to BorrowerCategory.VISITOR, not staff or student;
    • a row with kind V and status N gives an empty result;
    • an unknown kind code throws DirectoryException and is never defaulted.
  • Then apply the one-line change and show that the unit stage now fails.
  • A weak answer adds a test that passes against the changed adapter too. The new tests raise the stage-2 count, so handover-check.sh and HANDOVER.md need their numbers updated: the handover check doing its job.

What You Should End Up With, and When You Are Done

  • What you should end up with: B1 to B4; for your team’s project, the change review, the triage list, the handover bundle with its dry-run record, the decision records, the retrospective with its actions, the individual reflection and the completed checklist.
  • Acceptance: every number in a document matches a run; the handover has been followed by someone other than its author in a fresh copy; every known issue has a decision; no feature added in the final sprint to fix an issue that needs an owner; every decision record has a true status; contribution is described with claims and links, not ranked by counts; the limits statement says what was not run.
  • Optional: write a second handover for a different reader, such as the person who will operate the system.

14.11 Questions to Think with, and a Transfer Challenge

Concept, diagnosis, transfer


Concept Questions

  1. Name the four questions of a change review and what each one’s evidence cannot show.
  2. A one-line change passes every unit test and fails four integration and end-to-end tests. What does that tell you about the change, and what does it tell you about the unit tests?
  3. What is the difference between deferring an issue and accepting it? Give an example of each from the project.
  4. What can a requirements-to-evidence check confirm, and what can it not?

Diagnosis Questions

  1. A teammate has 40 commits and another has 8. Name three questions you would ask before saying anything about their contributions, and something you would read for each.
  2. Your handover says “run mvn verify; expect a stated number of tests” and a colleague adds a test. List two things that could have noticed and the cost of each.

Guidance: Questions 1 to 4

  1. Responsibilities (counts do not show importance); what broke first (shows only what tests cover); assumptions (only those written down); reviewer comments (only as good as the review).
  2. The change is smaller than the requirement: it does not implement the flag, the register check or the failure behaviour. The unit tests cannot say so because none goes through the adapter with a real visitor. The stage that crosses the real boundary does: that is the reason to have it.
  3. Deferred waits for a decision that is not ours, so it carries an owner and a trigger (SD-11-1). Accepted is a limit we decided to live with, so it carries a reason and a statement (the unmeasured one-second wait; no retry).
  4. It can confirm a named test or class exists and each example has exactly one row. It cannot confirm that the named test is the right evidence: a reviewer does.

Guidance: Questions 5 and 6

  1. Are most of the 40 formatting, generated or reverted? (read the commit list.) What are the eight? (diffs and pull requests.) Who reviewed whom, and what did the reviews find? (review comments.) Was there pair work recorded as one author? (commit trailers, retrospective.) Say nothing about contribution until the artefacts are read.
  2. scripts/handover-check.sh compares the pipeline’s stage counts with what the handover promises; the cost is running the whole pipeline in a fresh copy, about a minute. A reviewer reading the pull request who knows the number is stated; the cost is attention and memory. The script is cheaper to repeat.

Transfer Challenge: ML Model Serving

A small team has built an ML model-serving service. Its final sprint ends in a week. Plan its handover:

  • a README for a stranger, the tool check and what the first run prints;
  • a demonstration with a fixed model and fixed data;
  • a requirements-to-evidence table;
  • a known-issues list that includes what the team did not evaluate about the model;
  • a decision record for the one flag that gates a model version;
  • what you would put in the limits statement.
  • Use synthetic model identifiers: no model needs to be trained.

Transfer Guidance (1 of 2)

ItemContent
README for a strangerWhat the service does; what it needs (runtime, build tool, a model file or a synthetic stand-in); three commands with expected output; where the decisions are
Tool checkRuntime and tool versions with the output they should print; what to do if one is missing; the warnings that are not errors
DemonstrationA fixed model identifier, fixed synthetic data, a fixed clock if time matters, and a script that runs the same requests with the gate off and on
Requirements to evidenceEach rule (only approved versions served externally) to the class that decides it and the test, plus a check that the names exist

Transfer Guidance (2 of 2)

ItemContent
Known issuesIncluding what was not evaluated about the model: accuracy, fairness, drift, behaviour on data unlike the synthetic data
Decision recordThe flag that gates a model version: conditions for removal, the owner, and what rollback does not undo (responses already served)
Limits statementNo real model evaluated; synthetic identifiers; no load test; no hosted run if none was made

What to Take Away, and What Comes Next

What to carry forward


Summary

  • A change is finished when someone else can see what it did, trust what the documents say, and run it.
  • Review the change with evidence, not feelings: responsibilities affected, failures caused, assumptions overturned, feedback received.
  • Sort what remains into fix, defer and accept; leave deferred questions with their owners.
  • Write the few documents a stranger needs; give each a check or a reviewer.
  • Follow your own instructions in a fresh copy, and let someone else follow them too.
  • Describe each person’s work as claims with links, never ranked by counts. Say what you did not run.

Where the Project Stands

  • The visitor project is handed over at version 0.14.0: its flag decided, its removal rehearsed, fourteen known issues with decisions, and two unresolved questions for an owner: SD-11-1 (the late-fine rate) and SD-12 (the count rule).
  • Chapter 15 collects the assistance workflows that recur through the book and the verification records that make them reviewable.

Sources and Further Reading

  1. Nygard: Documenting Architecture Decisions (2011). Keep a Changelog 1.1.0.
  2. Schwaber and Sutherland: The Scrum Guide (2020). Kerth: The Retrospective Prime Directive.
  3. Fowler: Yagni. Hodgson: Feature Toggles (aka Feature Flags).
  4. GitHub: documentation on contribution graphs, commits with multiple authors and pull request reviews.
  5. Martin: Agile Software Development (2002).
  • Full references and links are in the chapter text.

15 - Chapter 15: AI as a Software Engineering Assistant

Object Oriented Development and Agile Practices (SE506)

Chapter 15: AI as a Software Engineering Assistant

SE506: Object Oriented Development and Agile Practices

Dr. Polla Abdulhamid Fattah
Department of Computer Science & Artificial Intelligence
University of Kurdistan Hewlêr (UKH)


Chapter Roadmap (1 of 2)

  • 15.1 What an assistant gives you, and who stays accountable
    • The six-step workflow behind every AI activity in the book.
  • 15.2 Telling the assistant what you want: task and context
    • A prompt is a work order, not a requirement.
  • 15.3 Asking an assistant to explain: requirements and code
    • Classify explanations claim by claim.
  • 15.4 Candidate code and tests: a worked task
    • The worked task: two rejected attempts and an accepted version.
  • 15.5 Asking for a refactoring or a review
  • 15.6 How assistants fail: the patterns to recognise

Chapter Roadmap (2 of 2)

  • 15.7 Privacy, dependencies and where work came from
  • 15.8 Writing down what you checked: verification records
    • What evidence can and cannot show; a compact eight-part record.
  • 15.9 Doing all of this without a live service
  • 15.10 AI habits that mislead
  • 15.11 Try it yourself: the lab and your own task
  • 15.12 Questions to think with, and a transfer challenge
  • Summary and where to go next
    • The appendices collect what the chapters point to.

Learning Outcomes

After this chapter, you should be able to:

  1. Provide bounded context for an AI-assisted task: what it is, what it may use, what it must not do, and what counts as done.
  2. Evaluate candidate explanations, code and tests against evidence, claim by claim.
  3. Recognise unsupported APIs, invented methods, unnecessary abstractions and tests that repeat the code’s mistakes.
  4. Record useful provenance and verification evidence.
  5. Complete an equivalent exercise without live AI access.

Where This Chapter Fits and Where You Start

  • This is a reference chapter that belongs to no teaching week. It supports the module’s outcomes wherever an assistant is used and adds no separate assessed AI outcome.
  • It creates no new deadlines, weights or marking rules. Whether, and how, AI tools may be used in an assessment follows the module leader’s current rules.
  • Entry version: none. The worked example is a small separate project, the lab, that stands alone, compiles for Java 17 and needs only the pinned Maven toolchain of the earlier chapters.
  • The lending project is not changed: its handover version 0.14.0 stays as Chapter 14 left it.
  • The chapter is vendor-neutral. It does not teach prompt tricks, model internals, retrieval or agents.

How to Use This Chapter

  • Core: 15.1, 15.2, 15.4, 15.6 and 15.8.
  • Working knowledge: 15.3, 15.5 and 15.7. Learn the method; you do not need to memorise tools.
  • What you must do: B1 to B4 (the book’s project, the lab: running, tracing and diagnosis, everything supplied) and T1 to T3 (your team’s project: apply the method to your own work).
  • These steps consolidate what the “AI in practice” activities of Chapters 1 to 14 already ask. Do B1 to B4 once, where you first need them: alongside the first AI activity, or before your final handover.
  • If you want more: B5 to B7 and T4, the questions and the transfer challenge. Nothing required depends on a paid account.

Opening Problem: The Confident Note

It is late. You ask an assistant for a small method, and it returns tidy code with a confident note: “I tested this with Java 17 and it works.”

  1. It compiles on your laptop. The tests it wrote pass. You commit.
  2. The next morning the pipeline fails to compile: one call exists only from Java 21.
  3. A reviewer asks why the tests agree with the code when your teammate’s examples do not.
  4. Another asks where a dependency named in a pom.xml snippet came from, because nobody can find it.

What Was Missing

  • None of this needs a bad assistant. Each needs only an ordinary one and a missing habit.
  • The habit: treating the output as a candidate that someone else’s evidence must accept or reject.
  • This chapter collects that habit into a few workflows you have already practised in Chapters 1 to 14, shows them end to end on one small task, and says what to write down so the work can be reviewed.
  • AI-assisted development (an assistant helps you build software) is the topic. Software that contains an AI component, such as the borrower directory, is treated where it arises and, for a model, in Appendix F.

Before You Start: Five Rules (1 of 2)

  1. You are accountable. The assistant proposes. You decide, and you answer for what you commit.
  2. The requirement is yours. An assistant can ask good questions about it. It cannot decide what the equipment office wants.
  3. Keep the evidence independent. Check the output against something the assistant did not write: the requirement, examples you wrote first, the compiler, a trace by hand. Another assistant’s opinion is not independent evidence.

Before You Start: Five Rules (2 of 2)

  1. Share the minimum. No secrets and no personal data. Use synthetic data.
  2. Follow the current rules. Whether a given tool may be used on coursework or in an assessment is decided by the module leader’s current rules, not by this book.

15.1 What an Assistant Gives You, and Who Stays Accountable

Candidates, not answers


What an Assistant Gives You

  • An assistant can explain code, suggest an implementation, propose tests, point out risks in a change and draft documents.
  • Each is a candidate contribution: plausible text or code that may or may not be right.
  • GitHub’s documentation for one such tool says suggestions can look valid and still be incorrect, and the user must review and validate them before accepting. Users assume the risks of generated code: bugs, security flaws, intellectual-property questions.
  • That is one vendor’s statement about one tool. The discipline it describes is general.

Where the Decisions Stay

QuestionWho answers it
What should the system do?The requirement owner, with your questions. Not the assistant
Is this output correct?Evidence: compiler, examples you wrote first, tests, a trace. Not the assistant’s own claim
Is this the simplest design that works?You, in review
May this tool be used for this work?The module leader’s rules and your institution’s, not this book
What was accepted, and why?You, in a record

The Assistant’s Statement About Itself

  • “I tested it with Java 17” is a sentence, not a test.
  • The assistant’s statement about itself is a claim like any other.
  • Treat every such statement as something to check: an assistant that cannot run your build cannot have tested it under your settings, and one that can run something still ran what it chose.

A Workflow in Six Steps

1 Requirement andacceptance examples(yours)2 Bounded taskand context3 Candidateoutput4 Independentchecks5 Humanacceptance6 RecordfailspassesThe loop from the checks back to the taskis where most of the work happens.A failed candidate is information: keep it.

Reading Figure 15.1

  • The first box is yours: derived from the requirement, before any candidate exists, and it is the authority the checks refer to.
  • The loop from the checks back to the task is where most of the work happens.
  • A candidate that fails is information. Keep it.
  • Every AI activity in this book has this shape.

The Activities You Have Already Done (1 of 2)

ChapterWhat the candidate wasThe independent check
1An explanation of a small methodThe code and a hand trace
2An explanation of the coordinatorA trace and the supplied checks
3Suggested story assumptions and criteriaThe stated rules and scope
4An iteration planCapacity arithmetic and the gate output
5A “make this SOLID” proposalCompilation and exact-output checks
6A refactoring that claimed to decoupleThe source: which classes are named?
7A query and its testsThe reviewed examples and a wrong-boundary mutation

The Activities You Have Already Done (2 of 2)

ChapterWhat the candidate wasThe independent check
8Suggested structuresThe source and tests for unknown codes, outage and order
9Strategy or StateThe transition table and tests
10A dependency reviewActual imports and compiled dependencies
11A refactoring patchA hand trace, a scratch copy and the characterisation suite
12An explanation of metricsRecalculation
13An integration testFaults applied to the parser
14A READMERunning every command
  • The checks vary, and none of them is “ask another assistant”.

15.2 Telling the Assistant What You Want

Task and context


A Prompt Is a Work Order

  • A work order says what to do, with what, within what limits and in what form. It is useful, and it is not a substitute for the requirement.
  • If the requirement is unclear, the assistant fills the gap with a guess, and a fluent guess looks like an answer.
  • Keep the context small. More text is not more help, and every extra file is something the assistant may use and something you may have shared that you should not have.

The Five Parts of a Work Order (1 of 2)

PartWhat to sayExample from the lab
TaskOne sentence about what is wantedAdd earliestStart(from, days, searchDays) that returns the earliest start date the item could be reserved
ContextOnly the code and facts the task needsThe source of LoanPeriod; the bookings arrive as a List<LoanPeriod>
ConstraintsThe baseline and what is forbiddenJava 17; standard library only; no new dependencies

The Five Parts of a Work Order (2 of 2)

PartWhat to sayExample from the lab
OutputThe form of the answerThe complete AvailabilityFinder.java and nothing else
ScopeWhat it may touch, and what it may notDo not change LoanPeriod; do not reserve anything

The First Prompt of the Lab

You are helping with a small Java task in an existing project.

Task: add a class `AvailabilityFinder` in package `assist` with a method
`Optional<LocalDate> earliestStart(LocalDate from, int days, int searchDays)`. It returns
the earliest start date, on or after `from` and within `searchDays` days, on which an item
could be reserved for `days` days, given the periods for which the item is already booked.

Constraints: Java 17, standard library only, no new dependencies.
Output: the complete source of `AvailabilityFinder.java` and nothing else.
Scope: do not change `LoanPeriod`. Do not reserve anything.

Derive the Examples From the Requirement First

  • Before you prompt, write concrete examples of the behaviour you expect, with inputs and results. Derive them from the requirement and not from any code.
  • In the lab they are nine tests written before anything was requested.
  • What makes them independent evidence is how they were derived, not whether the assistant saw them.

Should the Examples Be in the Prompt?

  • A choice with a cost either way.
  • Sharing reviewed examples clarifies the task and often improves the output; they remain useful acceptance checks. But a pass on examples the assistant has seen shows only that it satisfied those examples, not behaviour beyond them.
  • In the lab the examples were not shared, so they could be used both ways: as acceptance checks, and to judge whether the code generalises.
  • The point is not that hiding examples is the rule. It is that the examples came first, from the requirement.

A Workable Practice in Three Parts

  1. State the requirement precisely in the prompt. Share reviewed examples if they help.
  2. Add boundary cases or properties that you derived independently, and keep some of them undisclosed when you want to judge whether the code generalises.
  3. Never derive the expected results and the implementation from the same unexamined assumption. That is what happens when an assistant writes the code and then its tests from that code.

Ambiguity Is Yours to Remove

  • The lab’s first prompt says the search is “within searchDays days”. That can mean:
    • “up to and including from + searchDays”, or
    • “searchDays candidate start dates, starting with from”.
  • The developer meant the second. The assistant chose the first, and its tests chose the same, so the code and the tests agreed with each other and disagreed with the developer.
  • The acceptance examples were the only thing that did not share the misreading. The requirement sentence was rewritten afterwards.
  • When an assistant guesses wrong, ask what in your requirement allowed the guess.

15.3 Asking an Assistant to Explain

Requirements and code


Explanations Are Lists of Claims

  • An assistant’s explanation of code is a list of claims, some about what the code shows and some about things the code does not show.
  • Break it into single claims and classify each:
    • Supported: the code, a test or a document shows it. Name the evidence.
    • Contradicted: the code shows the opposite. Name the evidence and write the correct statement.
    • Not established: the code does not say. Say what would be needed.
  • A useful prompt asks for exactly that separation, and it still needs checking.

A Constructed Explanation of VisitorGate.rejection

rejection first checks the feature flag and returns the “not enabled” text if visitors are off. It then checks that the item is CAM1 or CAM2. If the number of days is below one it returns an empty result without asking anyone, because the count and duration policy will reject it. Otherwise it reads the clock once, asks the approval source for the exact request, and rejects unless the record found matches the request and is valid at that instant. The gate caches the record for later requests and writes each rejection to the audit log. If the approval source throws, the gate returns the “no valid sponsor approval” rejection.

  • Eight statements, mixed. Three are wrong. Which?

Checking the Explanation (1 of 2)

#ClaimVerdictEvidence
1The flag is checked first and the “not enabled” text returnedSupportedFirst statement of rejection
2Then the item is checked against CAM1 or CAM2SupportedALLOWED_ITEMS = Set.of("CAM1", "CAM2")
3Fewer than one day returns empty without asking anyoneSupportedac4_... asserts the register is not read
4The clock is read onceSupportedoneRequestReadsTheClockOnce

Checking the Explanation (2 of 2)

#ClaimVerdictEvidence
5Exact request asked; rejected unless the record matches and is valid at that instantSupportedaSourceThatReturnsADifferentRequestDoesNotApprove; theWindowIncludesItsStartAndExcludesItsEnd
6The gate caches the recordContradictedNo field holds a record; fields are the source, the clock and the flag
7Each rejection is written to the audit logContradictedNo logger and no call that writes anywhere
8If the source throws, the gate returns the “no valid sponsor approval” rejectionContradictedanOperationalFailureOfTheSourceIsNotCaught

Why the Wrong Claims Are the Dangerous Ones

  • The incorrect ones are the most believable, because they describe things a gate like this might well do.
  • The third would have produced a wrong design decision about error handling: it is the difference between a rejection and a failure that Chapters 12 and 13 insist on.
  • Reading the code, a test and the class settles each in a minute.
  • Keep the accurate parts and correct the rest. Do not throw away an explanation because one sentence is wrong.

The Corrected Explanation

rejection returns an empty result when the request may go on to the coordinator, and a rejection text otherwise. It checks the flag first, then the item, then returns empty for fewer than one day (the policy rejects it). Otherwise it reads the clock once, asks the source for the exact request and rejects unless the record matches and is valid at that instant. It keeps no state and writes no log. If the source throws, the exception reaches the caller; nothing is reserved.


Requirements Work

  • An assistant can help you find gaps in a requirement: what happens at the boundary, what is not said, which cases conflict.
  • Use it to produce questions, and take the questions to whoever owns the requirement. Do not let it supply the rules.
  • Chapter 3’s exercise on a story and Chapter 11’s suspected defects had the same shape: the right output of an assistant was a question for an owner, not an answer.

15.4 Candidate Code and Tests

A worked task


The Worked Task

  • Given the periods for which an item is already booked, return the earliest start date on or after from, among searchDays candidate start dates beginning with from, on which it could be reserved for days days.
  • Periods are half-open: a booking that ends on a date does not block a new one that starts on that date.
  • Nothing is reserved. Invalid days or searchDays (below one) is an IllegalArgumentException; a null from or bookings list is a NullPointerException.
  • The developer’s nine acceptance examples EX1 to EX9 were written before prompting.

Acceptance Examples EX1 to EX5

Bookings: 20 to 22 and 24 to 26 October (half-open) unless stated.

IDRequestResult
EX1from 20, 2 days, window 1022 October (the end is exclusive)
EX2from 20, 3 days, window 1026 October (does not fit before the 24th)
EX3from 10, 2 days, window 10; and no bookings, from 10, 5 days, window 110 October
EX4from 20, 2 days, window 2none (the candidates are 20 and 21)
EX5from 20, 2 days, window 322 October (the third candidate is included); window 2 gives none

Acceptance Examples EX6 to EX9

IDRequestResult
EX6booking 18 to 20 only; from 20, 2 days, window 120 October
EX7days 0, window 0, a null from, a null listthe exceptions above
EX8the caller’s list gains a booking after constructionthe finder is unchanged
EX9one booking covering 1 to 31 October; from 5, 1 day, window 10none

One Acceptance Example in Code

/** CAM1 is booked for [20, 22) and [24, 26). */
private static AvailabilityFinder cam1() {
    return new AvailabilityFinder(List.of(period(20, 22), period(24, 26)));
}

@Test void ex4_theWindowCountsCandidateStartDatesAndNotDaysAfterFrom() {
    assertEquals(Optional.empty(), cam1().earliestStart(oct(20), 2, 2));
}

@Test void ex5_theLastCandidateInTheWindowIsIncluded() {
    assertEquals(Optional.of(oct(22)), cam1().earliestStart(oct(20), 2, 3));
    assertEquals(Optional.empty(), cam1().earliestStart(oct(20), 2, 2));
}

Attempt 1: The Constructed Response

  • Author-written fictional AI output, constructed to illustrate. Not a recording of a real model.
  • The response claims: “I tested it with Java 17 and it works” and contains:
List<LocalDate> free = from.datesUntil(from.plusDays(searchDays))
        .filter(start -> booked.stream()
                .noneMatch(b -> b.intersects(new LoanPeriod(start, start.plusDays(days)))))
        .toList();
return free.isEmpty() ? Optional.empty() : Optional.of(free.getFirst());

Attempt 1: Rejected by the Compiler

Compiled with the project’s baseline, --release 17, it fails with two errors:

AvailabilityFinder.java:18: error: cannot find symbol
  symbol:   method intersects(LoanPeriod)
  location: variable b of type LoanPeriod
AvailabilityFinder.java:20: error: cannot find symbol
  symbol:   method getFirst()
  location: variable free of type List<LocalDate>
  • Run it yourself: bash scripts/try-attempt.sh 1, or javac --release 17 by hand.

Two Different Failures

  • intersects is an invented method. LoanPeriod has overlaps; intersects is what a similar class elsewhere might have.
  • List.getFirst() exists in Java 21 and not in Java 17. It is an API newer than the baseline.
  • Compiled with plain javac on a JDK 21 or later and no --release, the second one compiles. In the experiment plain javac reported only the first error; javac --release 17 reported both.
  • If your only JDK is 17, both commands report both errors: the project’s command is the one that matters, and it fails as it should.

What --release Changes

  • --release restricts the API as well as the language level to the named release. --source and --target alone do not.
  • The project’s POM sets it to 17 through the compiler plugin’s release parameter.
  • The code “works on my machine” and breaks on the baseline.
  • The rule: compile and test with the project’s own commands, never with the assistant’s assurance or your own default settings.

Attempt 1 Under Different Settings

CommandResult
javac -d out1 *.java (JDK 21 or later, no --release)One error: intersects
javac --release 17 -d out2 *.javaTwo errors: intersects and getFirst
After changing only intersects to overlaps: plain javacNo error: it compiles
After the same change: javac --release 17One error: getFirst()
Maven (maven.compiler.release 17), try-attempt.sh 1Two errors, at [18,42] and [20,68]
  • After the invented method is fixed, the code compiles where it was written and fails on the baseline.

A Style Point, Not a Defect

  • The code builds a list of every free date just to take the first one. It is correct in spirit and does more work than needed.
  • That is a style point, not a defect, and it is the reason to read candidate code and not just run it.

Attempt 2: Compiles, Passes Its Own Tests, Still Wrong

  • The second work order names the two compiler findings and asks the assistant to keep the solution small and to write tests.
  • The constructed response: “The finder now delegates to an AvailabilityRule, so new rules can be added without changing it (open/closed principle). I added seven JUnit 5 tests and all of them pass. The behaviour matches your description.”
  • It compiles. Its seven tests pass. Run the developer’s acceptance examples:
ex4_theWindowCountsCandidateStartDatesAndNotDaysAfterFrom: expected: <Optional.empty> but was: <Optional[2026-10-22]>
ex5_theLastCandidateInTheWindowIsIncluded: expected: <Optional.empty> but was: <Optional[2026-10-22]>

The Loop Is One Step Too Far

  • for (int offset = 0; offset <= searchDays; offset++): the window examines searchDays + 1 candidates.
  • From the 20th with a window of 2 it examines 20, 21 and 22 and returns the 22nd.
  • One of the assistant’s own tests is called theSearchWindowIsInclusiveOfTheLastDay and asserts exactly that.
  • Run it against the correct implementation and it fails.
  • The generated tests were written from the generated code, so they confirm what the code does and say nothing about what it should do. “All tests pass” was true, and it was evidence about the wrong thing.

Attempt 2, Step by Step

RunResult
Attempt 2 with its own seven generated tests7 pass
Attempt 2 with the acceptance tests as writtenDoes not compile: reference to AvailabilityFinder is ambiguous at new AvailabilityFinder(null) in ex7
Attempt 2 with the acceptance tests after a one-line cast9 run, 2 fail: ex4_... and ex5_...
The 7 generated tests against the accepted finder6 pass and 1 fails: theSearchWindowIsInclusiveOfTheLastDay

An Abstraction Has a Price

  • Attempt 2 has two constructors, one taking a List and one taking an AvailabilityRule, so a bare null fits both: new AvailabilityFinder(null) became ambiguous, and the developer’s own test for a null argument no longer compiled.
  • The one-line cast used to run the examples changes only the argument’s type, not what the test asserts. Editing a test to suit the code is the wrong direction in general; here it is only so the logic failure can be seen. The right response is to remove the unneeded overload.

Size of the Abstractions

  • The interface has one implementation, built in one place by a factory called once. The task had one rule.
  • The assistant called it the open/closed principle. Chapter 5 said when that principle earns its cost; here nothing needs to be extended.
VersionTypesLinesDocumentation
Accepted finder138 (six of them documentation)Yes
Attempt 2 (AvailabilityFinder 30, AvailabilityRule 6, AvailabilityRules 13)349None

Why the Acceptance Examples Were Independent

  • They were derived from the requirement before any candidate existed, and not from code.
  • They were also not shared with the assistant, which let them be used a second way: to judge whether the output generalises.
  • Sharing them would have been legitimate. It would probably have produced a correct loop, and the pass would then have shown only that the assistant satisfied those examples.
  • The prompts show the point: prompt1.md says “within searchDays days” and nowhere says what that counts; prompt2.md asks for tests, which is where the shared misreading gets locked in.

Revision and Acceptance

  • The developer wrote the final version by hand, using attempt 2 only as a reminder of the loop shape, and rewrote the requirement sentence that had allowed the misreading.
  • The final class validates its arguments, copies the bookings so the caller’s list cannot change it, and calls LoanPeriod.overlaps.
  • It passes the nine acceptance examples. It does not reserve anything and does not touch the lending system.
  • mvn --batch-mode test ends with Tests run: 9, Failures: 0, Errors: 0, Skipped: 0.

The Accepted Finder

public Optional<LocalDate> earliestStart(LocalDate from, int days, int searchDays) {
    Objects.requireNonNull(from, "from");
    if (days < 1) throw new IllegalArgumentException("days must be at least 1");
    if (searchDays < 1) throw new IllegalArgumentException("searchDays must be at least 1");
    for (int offset = 0; offset < searchDays; offset++) {
        LocalDate start = from.plusDays(offset);
        LoanPeriod wanted = new LoanPeriod(start, start.plusDays(days));
        if (booked.stream().noneMatch(wanted::overlaps)) return Optional.of(start);
    }
    return Optional.empty();
}
  • The constructor does List.copyOf(Objects.requireNonNull(booked, "booked")).

Figure 15.2: The Worked Example

Prompt 1:task, context,constraints, scopeAttempt 1:invented method,Java 21 APIPrompt 2:name the findings,ask for testsAttempt 2:compiles, 7 generatedtests passDeveloper's version:one class, windowloop correctedAcceptedand recorded2 compile errorsEX4, EX5 fail9 examples pass

Reading Figure 15.2

  • Both rejected attempts are kept in the record, with the reason for each.
  • Attempt 1 failed to compile; attempt 2 compiled and passed its own tests but failed independent examples.
  • The route that worked: the developer’s version, informed by the failures, checked by examples written beforehand.

Do the Tests Detect Faults?

  • Ten single faults were applied to the accepted finder. Each was run against the developer’s acceptance examples and, separately, against the assistant’s generated tests.
CaughtSurvived
Acceptance examples (9 tests)8 of 102, both equivalent
Generated tests (6 that pass on correct code)4 of 106

The Ten Faults (1 of 2)

FaultAcceptance examplesGenerated tests
Loop runs one step too far (attempt 2’s own fault)Caught: EX4, EX5Survived
Loop stops one step earlyCaught: EX3, EX5, EX6Survived
A stay of days occupies days - 1 daysCaught: EX2, EX9Caught
The search skips from itselfCaught: EX3 to EX6Caught
noneMatch becomes allMatchCaught: seven examplesCaught: three tests

The Ten Faults (2 of 2)

FaultAcceptance examplesGenerated tests
Overlap treats an end date as occupiedCaught: five examplesCaught
days = 0 is acceptedSurvived (equivalent)Survived
searchDays = 0 is acceptedCaught: EX7Survived
The caller’s list is kept, not copiedCaught: EX8Survived
A null from is not rejected earlySurvived (equivalent)Survived

Why Two Faults Survive

  • With days = 0 the finder’s own check is gone, but LoanPeriod’s constructor throws IllegalArgumentException on a period that does not begin before it ends.
  • With a null from the early check is gone, but from.plusDays throws NullPointerException.
  • Each example asserts only the exception type. The faults change where the exception is raised and not what a caller sees: they are equivalent, and cannot be told apart from correct code by any test of the exception type.
  • The generated tests miss the very fault the assistant made, a window one step too long, because they were built to agree with it.
  • Ten faults chosen by the author on one small class say nothing about assistants in general.

15.5 Asking for a Refactoring or a Review

Questions, not verdicts


Ask for a Review as Questions

  • Assistants are good at producing a list of things worth looking at. They are poor at telling you which of them matters here, and a review that ends “looks good” settles nothing.
  • Ask for questions and risks, and decide for yourself:

List the behaviours of this method that a caller could depend on, and the inputs for which the behaviour is not obvious from the code. Do not suggest changes.


A Refactoring Must Say What Must Not Change

  • Chapter 11: a refactoring preserves intended observable behaviour.
  • A prompt for a refactoring should say so, name the boundaries that matter and keep the tests out of its reach: “do not change the tests; if a test fails, report it and do not edit it”.
  • When a patch arrives, apply its claims one at a time in a scratch copy and run the suite after each.
  • A patch that makes three claims and fails one test does not tell you which claim was wrong. Applied piece by piece, it does. Chapter 11’s patch changed a boundary, a lookup order and a default at once.

What a Review Can Check

  • A reviewer can check that a dependency points the right way (the compiled dependencies of Chapter 10), that a boundary is covered (Chapter 13), that a number in a document matches a run (Chapter 14), or that an explanation is consistent with the code.
  • These are checks someone can repeat.
  • “The assistant reviewed it and found no issues” is not one of them.

15.6 How Assistants Fail

The patterns to recognise


The Failures This Book Has Met

  • These are failures the book has met, with where. Recognising the pattern is quicker than diagnosing each instance.
  • Eleven patterns follow, each with a check that exposes it.

Failure Modes (1 of 3)

Failure modeWhat it looks likeSeen inA check that exposes it
Invented method or classA call to something that does not exist, named plausiblyLab attempt 1The compiler
API newer than the baselineCompiles on your default JDK, fails on the project’sLab attempt 1Compile with the project’s --release
Invented or wrong dependencyA coordinate that does not exist, or a real library you did not need15.7Look it up; read the build
Plausible but wrong boundaryA comparison one step offChapters 7, 11, 13; attempt 2Examples that sit on the boundary

Failure Modes (2 of 3)

Failure modeWhat it looks likeSeen inA check that exposes it
Tests that repeat the codeTests derived from the implementation agree with its mistakesChapters 7, 13; attempt 2Examples written beforehand; fault experiments
Unnecessary abstractionAn interface with one implementation, patterns for their own sakeChapters 5, 6, 8, 10; attempt 2Ask what change it makes easier
Silent policy changeA requirement decided inside a “tidy-up”Chapters 3, 11Diff the behaviour, not the text
Unsupported causal claim“Proves”, “caused”, “will definitely”Chapter 12Ask what evidence would show it

Failure Modes (3 of 3)

Failure modeWhat it looks likeSeen inA check that exposes it
Confident description of unseen codeBehaviour that the code does not haveChapters 1, 2; 15.3Read the code; trace
Self-reported verification“I tested it and it works”Lab attempts 1 and 2Run it yourself, under the project’s settings
Stale knowledgeA deprecated or removed API; an old configurationAnywhereOfficial documentation for your version

Two Worth a Sentence More

  • Silent policy change is dangerous because the code can be better, and the commit is no longer a refactoring: reviewers cannot tell which failures were intended.
  • Self-reported verification is a special case of confident wrongness. An assistant that cannot run your build cannot have tested it under your settings, and one that can run something still ran what it chose.

15.7 Privacy, Dependencies and Where Work Came from

What not to send; what not to trust


What Not to Send

  • With a remotely hosted tool, a prompt leaves your machine. Whether and how the service keeps or uses it is in its own terms, which differ and change: read them, do not assume.
  • A tool that runs locally may keep everything on your machine, but check how it is actually deployed: a local front end may still call a remote service.
  • A rule that works everywhere: send the minimum. No passwords, keys, tokens, personal data, real student or borrower records, or other people’s confidential material. Replace identifiers with invented ones.
  • Whether a tool may be used with course material is for the module leader’s rules and your institution’s policy.
  • OWASP’s list of risks for language-model applications names sensitive information disclosure.

A Prompt That Should Not Be Sent

Our reservation service is failing for Layla Hassan (student ID 2023-48817, [email protected]). Here is the config that fails; the directory password is in it: lending.directory.url=http://10.20.30.40:8080/borrowers, lending.directory.password=not-a-real-password-just-an-example. And here are the last 200 lines of our production log, which include other borrowers’ names. What is wrong?

  • Constructed example: all names, identifiers and the key are invented.

What the Prompt Discloses

In the first promptWhy it should not be sent
A student’s name, student ID and emailPersonal data about a real borrower
A configuration file with a passwordA credential. Anything secret in real life must not be pasted
An internal address and pathInformation about your infrastructure
200 lines of production log with other borrowers’ namesMore personal data, far more than the question needs
“What is wrong?”No hypothesis, no limit on scope

The Same Task, With the Minimum Context

A Java client calls GET <base>/borrowers/<key> and sometimes throws HttpTimeoutException after 1000 ms. Here is the method (no secrets, no real URL). Here are the three log lines that show the exception, with borrower keys replaced by B1, B2, B3. What are the possible causes? List them without choosing one.

  • It is smaller, safer and more useful: it names what is wrong and nothing else.
  • It states the symptom precisely (a timeout after 1000 ms) and asks for causes you will check, not for a verdict.

Verify Every Dependency

  • An assistant may suggest a library that does not exist. Researchers who tested sixteen code-generating models on 576,000 samples found hallucinated package names averaged at least 5.2% for the commercial models and 21.7% for the open-source models they tested, and noted that an attacker could register such a name with malicious code.
  • The percentages describe that study and its models, not yours. The habit is what matters.

The Dependency Checks (1 of 2)

CheckHow, for the book’s toolchainWhat it does not show
The coordinate existsAsk Maven Central for the artifact’s page and read the statusThat it is the library you meant, or safe
It is the project you think it isThe group ID belongs to the project’s own site; compare with its documentationThat the version has no defects
The version exists and is pinnedThe exact version’s POM returns 200; the project pins versionsCompatibility with your other dependencies

The Dependency Checks (2 of 2)

CheckHowWhat it does not show
You need itCan the standard library or an existing dependency do the job?
It is allowedIts licence, and your team’s rulesAnything about legal certainty
  • A status of 200 shows only that something is published under that name.

A Constructed Dependency Suggestion

Add these to your pom.xml and use Mocks.firstOf(list): org.mockito:mockito-fluent:5.20.0 and com.google.guava:guava:33.0.0-jre, both with scope test.

  • Task given: “Add a test helper that asserts the first element of a list.”
  • Status codes read from Maven Central on 13 October 2026. Nothing was downloaded.
CoordinateStatus
mockito-core, mockito-junit-jupiter200
mockito-fluent404
mockito-core:5.20.0 (the .pom)200
mockito-core:9.9.9404
guava, guava:33.0.0-jre200

The Decisions

  • mockito-fluent: reject. It does not exist under that group, and a plausible name is not a reason to search for a similar one. Even a real mocking library would be wrong: a helper that asserts the first element of a list does not need mocking.
  • guava:33.0.0-jre: reject. It exists, and nothing in the task needs it: list.get(0) and assertEquals do the job.
  • One suggestion was invented, and one was real and unnecessary. Both are failures, of different kinds.

Provenance and Disclosure

  • Provenance is where a piece of work came from. For AI-assisted work: that an assistant was used, what for, what you gave it, what you took from it and what you changed.
  • It matters because a reviewer or a successor needs it to judge the work, and because your institution may require it.
  • Say so plainly and follow the module leader’s rules and your team agreement. Whatever the rules, keep a record of tasks that mattered.
  • Questions of licence and ownership of generated code are unsettled and depend on the tool, the jurisdiction and the terms. This book gives no legal advice.

15.8 Writing Down What You Checked

Verification records


What Evidence Can Show (1 of 2)

EvidenceWhat it can showWhat it cannot
Requirement and examples written beforehandWhat was intendedThat your reading of the requirement is right
Hand trace on selected inputsThe code behaves as you think on those inputsBehaviour elsewhere
Compiler and type checker, under the project’s settingsSyntax, types, the API available at the baselineMeaning
TestsBehaviour on the cases testedCases not tested, or that the tests are good

What Evidence Can Show (2 of 2)

EvidenceWhat it can showWhat it cannot
Fault experiments on the testsThat the tests notice those faultsOthers; equivalent faults cannot be noticed
Traced dependencies (jdeps, the build’s own listing)What the code actually referencesWhether those references are wise
Human reviewJudgement that tools lackThoroughness, unless it is recorded
  • Do not use one assistant to approve another’s work and call the result verification. It is a second candidate opinion with the same weaknesses.

A Compact Record: Eight Parts (1 of 2)

PartContents
TaskWhat was asked and who owns the requirement
Tool and modelThe tool and model, or “none: constructed example”, and the date
Context givenWhat the assistant saw, and what was left out and why
Output receivedEach candidate in order, with accepted or rejected. Keep the rejected ones

A Compact Record: Eight Parts (2 of 2)

PartContents
DecisionWhat was accepted or rejected, and why
Changes madeWhat you changed by hand
VerificationThe checks, the commands, the results and the date; which checks are independent of the assistant
LimitsWhat the checks do not show and what was not run
  • The lab holds a template, a filled example, and scripts/check-record.sh that checks every heading is present and not empty. It cannot tell whether what is written is true: a reviewer must. Appendix C (C.13) has the blank record and a worked example.

A Useful Record, Not a Ritual

  • The ritual version says “reviewed and verified” and nothing else.
  • The useful one says:
    • attempt 1 failed to compile under --release 17 with two errors;
    • attempt 2’s seven generated tests passed while two acceptance examples failed;
    • ten faults were applied.
  • Write the record while you remember, and write it for someone who was not there.

The Filled Record: Task, Tool, Context

  • Task: add AvailabilityFinder.earliestStart(from, days, searchDays). The requirement and its nine acceptance examples were written by the developer before any prompt. The developer owns the requirement.
  • Tool and model: None: constructed example (author-written, not a recorded service response), 13 October 2026.
  • Context given: prompt 1 (task sentence, LoanPeriod source, constraints, output format, scope); prompt 2 (the two compiler findings, the request to keep it small and add tests). Not shared: the acceptance examples.

The Filled Record: Output and Decision

  • Output received:
    1. Attempt 1: rejected. It did not compile: LoanPeriod.intersects does not exist and List.getFirst() exists only from Java 21.
    2. Attempt 2: rejected. It compiled and its seven generated tests passed, but it added an interface and helper class the task did not need, its loop examined one candidate too many, one generated test asserted that behaviour, and it made new AvailabilityFinder(null) ambiguous.
  • Decision: neither attempt accepted. The developer wrote the final version by hand, using attempt 2 only as a reminder of the loop shape.

The Filled Record: Changes, Verification, Limits

  • Changes made: removed the interface and helper; fixed the loop to offset < searchDays; validated arguments; copied the list; discarded the generated tests; rewrote the requirement sentence.
  • Verification: javac --release 17 on attempt 1: two errors; attempt 2 with its own tests: 7 pass, with the acceptance tests: EX4 and EX5 fail; final version: 9 pass; ten faults: eight caught, two equivalent. Independent of the assistant’s claims: the compiler, the acceptance examples written beforehand, the fault experiment.
  • Limits: the examples are the developer’s reading of the requirement; compiled with --release 17 but run on a newer JDK, so no Java 17 runtime run is claimed; not connected to the lending system.

15.9 Doing All of This Without a Live Service

No live service required


Every AI Activity Can Be Done Without a Live Service

  • The supplied candidate. Each activity includes a constructed candidate output, labelled as such. The lab’s two attempts, the VisitorGate explanation, the privacy example and the dependency suggestion are all supplied.
  • Swap with a classmate. Each of you writes a plausible candidate for the same small task, deliberately including one or two flaws of the kinds in 15.6, and keeps a private list. Swap, find the flaws, compare lists. A flaw you did not find is as instructive as one you did.

More Offline Routes

  • Write both sides. Write the acceptance examples first, then a quick, careless implementation as if you were the assistant. Run the examples. This shows how an implementation that looks right can fail on a boundary.
  • Use a live assistant if you may and can. Only synthetic data and files you may share. Keep the actual output, including the attempts you reject. Treat its account of itself as a claim. Follow the module leader’s rules on assessments.
  • Constructed output is not a recording of a real model. It is written to illustrate a failure mode and should be labelled each time. Where a real transcript is captured, record the tool, the model and the date.

15.10 AI Habits That Mislead

Ten to avoid


Mistakes 1 to 5

MistakeConsequenceBetter action
Writing the requirement in the prompt and nowhere elseThe prompt becomes the only record; the assistant’s reading becomes the requirementWrite requirement and acceptance examples first, separately
Asking for code and tests togetherThe tests agree with the code’s mistakesExamples first; tests from the requirement; fault experiments
Accepting “I tested it”A claim stands in for a runRun it under the project’s settings
Compiling with your default settingsA newer API slips throughUse the project’s --release and build commands
Adding a suggested dependency without lookingInvented, unnecessary or hostile librariesLook it up, ask whether you need it, pin it

Mistakes 6 to 10

MistakeConsequenceBetter action
Pasting real data or secretsMay leave your machine, and stay in your history either waySynthetic data; the minimum context
Using another assistant to check the firstThe same weaknesses twiceIndependent evidence
Throwing away a whole explanation for one wrong sentenceThe correct parts are lostClassify claim by claim and correct
Keeping only the accepted answerA reviewer cannot see what was rejected, or whyKeep rejected attempts in the record
Writing “AI-assisted” and no moreNobody can judge the workA record: what, with what, what changed, what was checked

Diagnostic Alternatives

  • “Reviewed by another assistant” under Verification: a second candidate opinion, not independent evidence.
  • Accepting it because it compiled: attempt 1’s second error showed compiling is not enough, and attempt 2 compiled and was still wrong.
  • Tests derived from the implementation: theSearchWindowIsInclusiveOfTheLastDay agrees with the code and fails on correct code.
  • A record that says nothing: headings present, Verification reading “reviewed and verified”, Limits empty: the script flags empty headings, and a reviewer must flag empty claims.
  • A prompt that carries the requirement and the secrets: the privacy example.

15.11 Try It Yourself

The lab and your own task


What You Start With, and the Demonstration

  • You start with: the lab (Companion, Section 3), with its prompts, two constructed attempts, acceptance tests and record. B1 to B7 use the book’s project (the lab); T1 to T4 use your team’s project.
  • Instructor demonstration: compile attempt 1 with javac --release 17 and, if a JDK 21 or later is available, once more with plain javac; ask the class why the results differ. Then run attempt 2 against its own tests, and against the acceptance examples.
  • Guided practice: take one claim from the constructed VisitorGate explanation and classify it, naming the evidence. Take one statement an assistant has made about itself and say how you would check it.

The Book’s Project: B1 and B2

  • B1. Reject attempt 1. Run bash scripts/try-attempt.sh 1. Compile by hand with javac --release 17, read both errors; with a JDK 21 or later, compile once more without --release and compare. List each defect and its category from 15.6, and say what the claim “I tested it with Java 17” can and cannot mean.
  • B2. Diagnose attempt 2. Run its own tests (try-attempt.sh 2 generated), then the acceptance examples (try-attempt.sh 2 adapted). Find the failing line, name the shared misunderstanding between the code and its tests, and explain why the acceptance examples were independent evidence. Run the generated tests against the accepted finder and say what the one failure shows.

Expected Output of the Lab Scripts

CommandExpected
mvn --batch-mode testTests run: 9, Failures: 0, Errors: 0, Skipped: 0 and BUILD SUCCESS
try-attempt.sh 1BUILD FAILURE: two cannot find symbol errors at lines 18 and 20
try-attempt.sh 2 generatedTests run: 7, Failures: 0
try-attempt.sh 2 (acceptance)BUILD FAILURE: reference to AvailabilityFinder is ambiguous
try-attempt.sh 2 adaptedTests run: 9, Failures: 2: ex4_... and ex5_...
check-record.shRECORD CHECK PASSED
check-record.sh, only the template existsRECORD CHECK FAILED: nothing was checked, exit status 1

The Book’s Project: B3 and B4

  • B3. Make the tests bite. Apply three faults of your choice to the accepted finder in a scratch copy. Predict which acceptance examples fail, run them, compare. Find one fault that survives and say why.
    • Examples: offset < searchDays - 1, start.plusDays(days - 1), allMatch.
  • B4. Fill in a record. For a different, very small task of your own (a method you ask an assistant, or a constructed candidate from a classmate), complete a record using the template and run the checker.
    • A good record uses all eight headings, keeps rejected output, names at least one independent check and at least one limit. A weak one says “reviewed and verified” under Verification.

Your Team’s Project: T1 to T3

  • T1. Define one AI-assisted task before you prompt. A small, real task in your project: the requirement, the acceptance examples (derived from the requirement, before any candidate), the constraints and the scope. Decide which examples you will share and which you will keep undisclosed, and why.
  • T2. Run it and keep everything. A live assistant if the rules allow, or an offline route (15.9). Keep each attempt, including the ones you reject. Compile and test with the project’s own commands.
  • T3. Record it. Fill in the record, including the independent checks and the limits, put it with the change, and run the record checker.

If You Want More: B5 to B7 and T4

  • B5. Repeat the lab task with a live assistant, if you may. Which failure modes did it show? An assistant that gets it right the first time is a valid result: record that and the checks that showed it.
  • B6. Verify two dependencies. Look up the constructed suggestion’s coordinates, report the status of each and decide what to do about each.
  • B7. Swap and find. The classmate exercise of 15.9. Missing a flaw of the invented-method kind is common and instructive.
  • T4. Agree your team’s practice. A short note: which tools may be used (following the module leader’s rules), what may be shared with them, how work is disclosed and recorded, and who reviews AI-assisted changes.

What You Should End Up With, and When You Are Done

  • What you should end up with: B1 to B4; for your team’s project, one defined task with its acceptance examples, the attempts, a record, and a team note if you did T4.
  • Acceptance:
    • the acceptance examples were written before the candidate was requested;
    • every claim the assistant made about itself was checked or marked unchecked;
    • the check used the project’s own compiler settings;
    • a rejected attempt is in the record with its reason;
    • the record’s limits say what was not run;
    • no secrets or personal data were shared;
    • no live tool was used where the rules forbid it.
  • Optional: do the same task twice, once with the acceptance examples in the prompt and once without, and compare.

15.12 Questions to Think with, and a Transfer Challenge

Concept, diagnosis, transfer


Concept Questions

  1. Why is “I tested it with Java 17” not evidence? Name two checks that would be.
  2. An assistant writes a function and its tests, and they pass. Why does that not establish that the function is right, and what would?
  3. What does the --release option change that --source and --target alone do not?
  4. A dependency suggested by an assistant exists on Maven Central. What have you learned and what have you not?

Diagnosis Questions

  1. An explanation of a method says “the method caches the result.” The code has no field. What are your options for settling it, and what would you write down?
  2. A refactoring patch makes four claims and one test fails. How do you find which claim caused it, and why is the patch’s own summary not the way?

Guidance: Questions 1 to 4

  1. It is a sentence from a system that did not run your build and names no command or result. Evidence: compile and test with the project’s own command (mvn --batch-mode test, which sets release 17); run javac --release 17 on the file.
  2. Tests derived from the code agree with it, including where it is wrong: theSearchWindowIsInclusiveOfTheLastDay passes on attempt 2 and fails on correct code. Examples written from the requirement beforehand, and a fault experiment on the tests, would show it.
  3. --source/--target set the language level and class-file target. --release also restricts the API to the one that release defines, and cannot be combined with them.
  4. You have learned that something is published under that coordinate. Not that it is the library you meant, safe or licensed for your use, suited to your build, or needed.

Guidance: Questions 5 and 6

  1. Options: read the class for a field that holds a result (there is none); look for a test that depends on caching, and one that depends on its absence (theFileIsReadAfreshOnEveryCall); trace two calls by hand. Write down the claim, the evidence and the verdict: contradicted, with the line or test.
  2. Apply the claims one at a time in scratch copies and run the suite after each: the failing claim is the one whose copy fails. The patch’s summary is a claim of the same kind as the code and cannot tell you which of its own claims was the cause.

Transfer Challenge: ML Model Serving

A team building an ML model-serving service wants an assistant to write a data-validation function and its tests. Plan the task:

  • the requirement and acceptance examples (synthetic records);
  • the context to share and not to share;
  • what the independent checks are;
  • how you would check any suggested dependency;
  • what the record would say about what the checks do not show about the model;
  • what is different about AI in the product, such as the model itself, and why this chapter does not cover it.

Transfer Guidance (1 of 2)

ItemContent
Requirement and examplesThe validation rule stated precisely (what counts as missing, which ranges, what happens to a bad record). Examples on the boundaries, with synthetic records. Written first
Context to shareThe function signature, the record type, the constraints. Synthetic records only
Not to shareReal records, keys, tokens, the registry’s address, any real model or customer identifiers
Independent checksThe compiler under the project’s settings; the examples; a fault experiment on the tests; a trace on selected inputs

Transfer Guidance (2 of 2)

ItemContent
DependenciesLook up any suggested library; ask whether the standard library already does it; pin the version
RecordTask, tool and model, context, each attempt, decision, changes, verification, limits
What it does not show about the modelNothing here evaluates the model: accuracy, fairness, drift, behaviour on data unlike the synthetic data. Validating the data a model receives is not validating the model
AI in the productThe model is a component with its own failure modes, contracts and tests: a different topic. In this book it is the borrower directory: Chapters 6, 7, 8 and 13

What to Take Away, and What Comes Next

What to carry forward


Summary

  • An assistant gives you candidates. Your job:
    • define the task;
    • keep the requirement and the examples yours;
    • check the output against evidence the assistant did not write;
    • record what you accepted and why.
  • The failures are familiar: invented methods, APIs newer than the baseline, dependencies that do not exist, boundaries one step off, tests that repeat the code, abstractions nobody needs, policy changed in passing, claims of testing that nobody ran.
  • Each has an inexpensive check. None of them requires a live service to practise.

Where to Go Next

  • This is the last chapter of the book’s main sequence.
  • The appendices that follow collect material the chapters point to: notation and a programming refresher, tools and reproducible setup, engineering templates, project versions and a glossary, and a guide to critical reading and project evidence.

Sources and Further Reading

  1. GitHub Docs: Application card: GitHub Copilot inline suggestions (one vendor’s account of one tool).
  2. Oracle: List (Java SE 21 and 17); javac (Java SE 17).
  3. Apache Maven: maven-compiler-plugin: compile mojo (the release parameter).
  4. OWASP: Top 10 for LLM Applications (2025 edition).
  5. Spracklen et al.: We Have a Package for You!, arXiv 2406.10279.
  • Full references and links are in the chapter text.