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
The code, tests and model answers the decks quote.
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:
Explain why a functioning program may still be difficult to maintain.
Describe how object-oriented design, iterative delivery, testing and review contribute to a controlled change.
Read a small Java class and distinguish its actual behaviour from assumptions about the larger system.
Identify evidence needed to evaluate a proposed change or an AI-generated explanation.
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.
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)
Evidence
Can help establish
Does not establish by itself
Successful compilation
The source satisfies the compiler’s checks
The program implements the requested policy
Passing behavioural tests
The tested cases match their expected results
All relevant cases were identified correctly
Code review
Another person examined decisions, changes and risks
Every defect was discovered
Several Kinds of Evidence (2 of 2)
Evidence
Can help establish
Does not establish by itself
User demonstration
The workflow can be inspected against user expectations
Untested failure paths work correctly
Version history
The team can see what changed and why
Each 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 feedback
Useful 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 goal
Decide which statements the code actually supports
You start with
The class in Section 1.1 and its three stated rules
What is in bounds
Judge this one method; do not rewrite it
Activity 1A: Evaluate a Candidate Explanation (2 of 2)
For this activity
How to check
The code, a hand trace of two inputs, the stated rules
How to decide
Accept 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:
Whether the code supports it, contradicts it, or provides insufficient information.
The expression, input or operation that justifies your decision.
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
Claim
Verdict
Evidence
Count non-negative and below three
Supported
activeReservations >= 0 && activeReservations < 3
Duration from one to seven days, inclusive
Supported
requestedDays >= 1 && requestedDays <= 7
Returns a Boolean result
Supported
Return type boolean
Consults the equipment database
Contradicted
The 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:
inspect availability,
request a reservation,
confirm an eligible request,
issue the equipment,
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
Term
Meaning in the book
Borrower
A person seeking permission to borrow equipment
Equipment item
One individually identifiable piece of equipment
Reservation
A recorded arrangement for an item over a period, whose status matters
Loan
The actual issue of an item to a borrower, followed by its return
Loan period
The 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)
Reading
Main development in your understanding
Chapters 2 to 4
Objects and responsibilities; a backlog; a shared delivery workflow
Chapters 5 to 7
Improve responsibilities and dependencies; develop behaviour with useful automated tests
Chapters 8 to 10
Evaluate recurring design solutions; organise a modest application
How the Chapters Build on One Another (2 of 2)
Reading
Main development in your understanding
Chapters 11 to 13
Improve unfamiliar code; interpret project evidence; implement a changed requirement across integration boundaries
Chapter 14
Evaluate the result, document decisions, prepare a reproducible handover
Chapter 15
Consult 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
Chapter
What happens to the directory
6
Previews the contract
7
Shows how a test substitutes it
8
Builds the adapter that translates the provider’s codes
10
Places it in the application structure
13
Adds 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
Select one ordinary case and one case near a limit.
Trace the decisions.
Write the expected result.
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:
The decision you made.
The evidence you used.
The uncertainty that remains.
Describes an action
Explains 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)
Entry
Question
Claim and location
What exactly does the author recommend, and where?
Context
What problem and assumptions surround the advice?
Supporting example
How does the example demonstrate the claimed benefit?
The Reading Record (2 of 2)
Entry
Question
Limitation
Under what conditions might the cost outweigh the benefit?
Project application
What would change if we applied it to our system?
Further evidence
Which 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.
Call
Your predicted result
Your 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
Call
Result
Reason
mayRequest(0, 1)
true
Count and duration are within range
mayRequest(2, 7)
true
One more is allowed; seven days is inclusive
mayRequest(3, 1)
false
Three existing already reach the limit
mayRequest(1, 8)
false
Eight days exceeds the student limit
mayRequest(-1, 1)
false
A negative count is explicitly rejected
mayRequest(1, 0)
false
Duration 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.
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
Category
Existing count
Days
Decision
Student
2
7
Allow
Student
3
1
Reject
Student
0
8
Reject
Staff
4
14
Allow
Staff
5
1
Reject
Staff
0
15
Reject
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 question
Confident
Need 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
Give an example of software that produces the expected result today but is difficult to maintain. What makes the next change risky?
Explain the difference between an iteration and an increment using the equipment-lending project.
Why does passing a set of tests provide useful but limited evidence? Include an example of an incorrect expectation.
Distinguish a behaviour change from a refactoring. Why should a reviewer know which one is intended?
Diagnosis Questions
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.
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
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”.
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
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.
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
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.
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.
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:
Assign responsibilities to objects and explain the state they protect.
Distinguish encapsulation and information hiding from mechanically adding getters and setters.
Diagnose cohesion and coupling problems using concrete change scenarios.
Learning Outcomes (2 of 2)
Compare composition with inheritance and justify a modest design choice.
Draw and interpret a small UML class diagram that matches the code, using associations, multiplicity, aggregation, composition, inheritance and interface realisation appropriately.
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
Why is the conflict check concerned with an equipment item while the count is concerned with a borrower?
Could the list of stored reservations be changed from outside the class?
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
Class
Main role
State held
ReservationRules
Evaluate the numeric borrowing policy
No instance fields
LoanPeriod
Represent a valid period; answer overlap questions
Start date and exclusive end date
Reservation
Record a confirmed reservation
Three identifiers; a period
ReservationCoordinator
Process requests using policy and existing records
In-memory reservations; a rules object
The Starter Files
File
Role
ReservationRules.java
The unchanged numeric policy from Chapter 1
LoanPeriod.java
Valid dates and exclusive-end overlap semantics
Reservation.java
Immutable reservation details
ReservationCoordinator.java
The starting request workflow
Chapter02Demo.java
Three requests: success, overlap, adjacency
Chapter02Checks.java
Supplied 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
publicLoanPeriod(LocalDatestart,LocalDateendExclusive){this.start=Objects.requireNonNull(start,"start");this.endExclusive=Objects.requireNonNull(endExclusive,"endExclusive");if(!start.isBefore(endExclusive)){thrownewIllegalArgumentException("End must be after start.");}}
Missing date: NullPointerException. Zero-length or reversed interval: IllegalArgumentException.
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
publicStringreserve(StringborrowerId,StringitemId,LocalDatestart,intrequestedDays){if(borrowerId==null||borrowerId.isBlank()||itemId==null||itemId.isBlank()){thrownewIllegalArgumentException("Identifiers must not be blank.");}Objects.requireNonNull(start,"start");intactiveCount=0;for(Reservationexisting:reservations){if(existing.borrowerId().equals(borrowerId)){activeCount++;}}
The reserve Method: Policy and Conflict
if(!rules.mayRequest(activeCount,requestedDays)){return"Rejected: borrower limit or duration.";}LoanPeriodrequested=newLoanPeriod(start,start.plusDays(requestedDays));for(Reservationexisting: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
Reservationreservation=newReservation("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
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 request
Decision it affects
Current location
Change a borrowing limit
Numeric policy
ReservationRules
Add a maintenance gap between reservations
Availability policy and period use
Coordinator conflict check, possibly period rules
Diagnosis by Change Scenario (2 of 2)
Possible request
Decision it affects
Current location
Store records beyond process lifetime
Storage mechanism
Coordinator’s list and storage operations
Translate a confirmation message
Presentation wording
Coordinator’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 methodconfirmationFor.
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 Reservationhas 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.
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)
Line
Reading
Enforced by
Coordinator 1 to 1 Rules
Each coordinator uses one rules object; each rules object is used by one coordinator
private final ReservationRules rules = new ReservationRules();
Coordinator 1 to 0..* Reservation
Each coordinator stores zero or more; each reservation is stored by one coordinator
The list starts empty and has no upper limit
Try It 1: Answers (2 of 2)
Line
Reading
Enforced by
Reservation 0..* to 1 LoanPeriod
Each reservation refers to exactly one period; a period may be referred to by any number of reservations
requireNonNull(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
Relationships: When to Use Each (1 of 2)
Relationship
Read it as
Use it when
Association
One object is linked to another
The link is structural and lasting
Dependency
One class uses another without keeping it
A parameter or brief call is the only link
Aggregation
A whole groups parts that can exist without it
Parts can be shared or removed and still exist
Relationships: When to Use Each (2 of 2)
Relationship
Read it as
Use it when
Composition
A whole owns parts with no meaning without it
A part’s lifetime is tied to its owner
Inheritance
The subclass is a kind of the superclass
A real subtype relationship holds
Interface realisation
The class implements the interface’s contract
Several classes satisfy one contract callers rely on
Figure 2.2: A Notation Sampler
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).
PdfPrinterimplements 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:
Does one class implement a contract that other classes rely on? Realisation.
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).
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
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.
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 source
In the diagram
private final LocalDate start;
Attribute -LocalDate start
private final List<Reservation> reservations
Association to Reservation, 0..* at that end
private final ReservationRules rules
Association to ReservationRules, 1 at that end
A parameter, local variable or new X() used only inside a method
Dependency on X
From Code to Diagram (2 of 2)
In the Java source
In the diagram
class A extends B
Inheritance, triangle at B
class A implements I
Realisation, 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.
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)
Candidate
Decision
Reason
Workshop
Leave out
The whole system; no behaviour described for it
Locker
Class
Has a number, a log and a report
Tool
Class
A locker holds one
Technician
Class
Looks after lockers
Step 2: Candidates and Decisions (2 of 2)
Candidate
Decision
Reason
Inspection entry
Class
Has a date and a note; belongs to a locker
Date, note, number, name
Attributes
Plain values describing a class
Report
Operation of Locker
Something a locker does, not a thing with its own data
Plain text, other formats
ReportPrinter interface and PlainTextPrinter class
The 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
Pair
What decides it
Result
Technician and locker
Neither owns the other; a locker can be reassigned
Plain association
Locker and tool
Neither is part of the other
Plain association
Locker and inspection entry
An entry is meaningless without its locker
Composition, diamond at Locker
Locker and ReportPrinter
A locker uses a printer only while reporting
Dependency
PlainTextPrinter and ReportPrinter
The class implements the contract
Realisation
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
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
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)
#
Mistake
Why it is wrong
1
-Locker locker attribute in Technician
Repeats the line; implies one locker per technician
2
Multiplicities reversed on Technician to Locker
Says each locker has many technicians and each technician one locker
3
Filled diamond at Technician
Claims lockers die with their technician, but a locker can be reassigned
Try It 3: Answers (2 of 2)
#
Mistake
Correct notation
4
Locker <|-- InspectionEntry
Composition: filled diamond at Locker, 1 and 0..*
5
Locker ..> PlainTextPrinter and report(PlainTextPrinter printer)
Depend on ReportPrinter; type the parameter as ReportPrinter
6
ReportPrinter <|-- PlainTextPrinter
Dashed 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)
Mistake
Why it is wrong
Better habit
Multiplicity at the wrong end
The far number counts the far class; reversed numbers say the opposite
Read every line aloud in both directions
Arrowhead as ownership
An arrowhead shows who holds the reference, not who owns whom
Use 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 locker
Test “A is a kind of B” and its promises
Common Drawing Mistakes (2 of 3)
Mistake
Why it is wrong
Better habit
Solid line for realisation
Realisation is dashed; solid with a triangle is inheritance
Check the line style of every triangle
Filled diamond because the field is final
Composition claims a lifetime; final only fixes a reference
Ask whether the part can exist without the owner
Showing a relationship twice
-Locker locker plus a line to Locker repeats one fact
Show a field of a design class as a line only
Common Drawing Mistakes (3 of 3)
Mistake
Why it is wrong
Better habit
Depending on a concrete class when an interface exists
A client naming PlainTextPrinter cannot accept another format
Point the dependency at the interface
Drawing every class and member
The design question disappears among the detail
One diagram, one question, only the members it needs
A diagram that disagrees with the code
Readers trust the diagram
Run 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
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
Begin with the supplied starting files and a recorded baseline.
Confirm that the checks pass before editing.
Create a branch.
Perform only the extraction.
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 emptygit 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 goal
Compare an explanation of the coordinator with its actual control flow, state changes and side effects
You start with
The complete starting ReservationCoordinator and collaborators
What is in bounds
Judge the explanation only; do not change the code
Ground Rules for the Activity (2 of 2)
For this activity
How to check
Tracing the code, the three-request demonstration, the supplied checks
How to decide
Keep 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)
Claim
Judgement
Evidence
Validates identifiers
Supported within scope
Rejects null or blank identifiers; no catalogue check
Counts the borrower’s stored reservations
Supported
Loops over records, compares borrower identifiers
Applies the numeric rule
Supported
Calls ReservationRules.mayRequest
Checks conflicts for the same item
Supported
Matching item and overlapping period
Claim Classification (2 of 2)
Claim
Judgement
Evidence
Adds before returning a confirmation
Supported
add is called before the message is built
Sends email
Contradicted
No sender or delivery call; the method returns a string
Persists to a database
Contradicted
An in-memory ArrayList; no database operation
Final list: callers get a deeply immutable copy
Incorrect
final stops reassignment; List.copyOf is shallow
Concurrent booking is safe
Not established
Check 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 startingReservationCoordinator.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
Call
Outcome
Stored
Important step
B1 requests CAM1 from 20 Oct for two days
Confirmed as R1
1
Empty history has no conflict
B2 requests CAM1 from 21 Oct for one day
Rejected: unavailable
1
Overlaps R1 for the same item
B2 requests CAM1 from 22 Oct for one day
Confirmed as R2
2
Equality 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
What a Strong Kit Answer Shows
Requirement
Model decision
A kit holds one or more items; removing it does not delete them
Aggregation, 1..* at the items end
An item belongs to at most one kit
0..1 at the kit end
A record belongs to exactly one item and has no meaning without it
Composition from item to record: 1 at the item, 0..* at the records
A summary is produced through an exporter; other formats expected
Exporter 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.
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);returnconfirmationFor(reservation);}privateStringconfirmationFor(Reservationreservation){return"Confirmed: "+reservation.id()+" for "+reservation.borrowerId()+" on "+reservation.itemId()+" ["+reservation.period().start()+", "+reservation.period().endExclusive()+").";}
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
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?
Explain one local invariant and one system-level condition in the project. Why do they need different enforcement points?
Explain cohesion and coupling using one likely change to the coordinator. Why is reducing the total number of dependencies not a sufficient design goal?
Compare composition and inheritance for a reservation and its loan period. What would inheritance incorrectly suggest?
Diagnosis Questions
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.
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
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.
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
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.
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
Returning the mutable internal list allows clear or addoutside 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.
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.
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
Dev.java:Classes and Objects; Inheritance.
Oracle: Java SE 17 API for LocalDate, Objects and List.
Chacon and Straub:Pro Git: Recording Changes; Branching Workflows.
Git documentation:git diff and git switch.
Conventional Commits 1.0.0.
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:
Distinguish iterative development from incremental delivery.
Explain Agile values through concrete project decisions.
Compare Scrum’s framework with Kanban’s attention to workflow and flow.
Learning Outcomes (2 of 2)
Develop small user stories and evaluate them using INVEST.
Write acceptance criteria and distinguish them from a Definition of Done.
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
Uncertainty
Example in the equipment project
A useful next action
User need
Does the officer need an availability enquiry without reserving?
Discuss the workflow; demonstrate a small example
Domain rule
Can a borrower cancel after equipment has been issued?
Ask the responsible stakeholder; record the decision
Technical feasibility
Can the institution’s catalogue be accessed?
A bounded investigation with a concrete result
Delivery
Can 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 decision
Applying the values thoughtfully
A mistaken interpretation
Choosing a board
Select a tool that makes coordination easier
Assume installing a board creates collaboration
Recording the period convention
Keep a short, clear decision with examples
Discard documentation because the code exists
Handling a new borrowing rule
Discuss its purpose with the equipment officer
Let the team invent policy privately
Planning the next increment
Use current information; revise when needed
Stop 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
Make the current reservation flow reproducible.
Add an enquiry that does not change records.
Support a borrower’s list of active reservations.
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.
Terminology should clarify responsibilities and decisions.
User-story templates and story points are optional practices, not required Scrum elements.
Accountabilities
Element
Meaning
Product Owner
Accountable for product value and effective Product Backlog management
Developers
Create usable increments and plan their work
Scrum Master
Establishes Scrum and supports team effectiveness
The Sprint and Its Events (1 of 2)
Element
Meaning
Sprint
A fixed-length event of one month or less containing the other events
Sprint Planning
Establishes why the Sprint matters, the selected work and an approach
Daily Scrum
Developers inspect progress toward the Sprint Goal and adapt their plan
The Sprint and Its Events (2 of 2)
Element
Meaning
Sprint Review
Inspect the outcome with stakeholders and consider adaptations
Sprint Retrospective
Examine how the team worked and choose improvements
The Sprint Contains All the Events
Artefacts and Their Commitments
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
Column
Local meaning
Backlog
Candidate work, not yet selected to start
Ready
Small enough and clear enough to begin under the team’s local policy
Building
Implementation and associated checks are under way
Review
Work is awaiting or receiving review and verification
Done
The 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
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)
Question
Scrum emphasis
Kanban emphasis
What structures the work?
Goals, Sprints, defined accountabilities and events
Explicit workflow and control of work in progress
What is inspected?
Product outcomes and progress toward goals
How work moves, waits and finishes
Comparing Scrum and Kanban (2 of 2)
Question
Scrum emphasis
Kanban emphasis
Can a board help?
Yes, to make selected work visible
Yes, to visualise the workflow and its policies
Can they be used together?
A Scrum team can also manage flow explicitly
Flow 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)
Quality
Question to ask about this story
Independent
Can it provide an identifiable outcome without another unfinished story? If not, what is the dependency?
Negotiable
Which implementation details can still be discussed while preserving the need?
Valuable
Who benefits from this behaviour, and how?
INVEST as Questions (2 of 2)
Quality
Question to ask about this story
Estimable
Is the team clear enough about scope and uncertainty to discuss its size?
Small
Can the team implement and verify a useful version within its intended work period?
Testable
Can 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
Order
ID
Outcome or purpose
Relationship to existing code
1
US-01
Record an eligible, non-conflicting student reservation
Core behaviour exists; delivery and completion evidence must be reviewed
2
US-02
Inspect an item’s availability without reserving it
No dedicated query operation exists yet
3
EN-01
Make the existing checks reproducible in an automated build workflow
Planned 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
Criterion
Required observable behaviour
AC-01
An eligible request for an available item creates one stored reservation and returns its confirmation
AC-02
A request overlapping an existing reservation for the same item is rejected without changing stored records
AC-03
A request that begins at an existing reservation’s exclusive end, or ends exactly where one begins, is permitted when the other rules hold
AC-04
A borrower with three active reservations cannot add a fourth, even for another item
AC-05
One 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:
Relevant acceptance examples have been checked, including important rejection paths.
The code compiles and runs using the documented commands in the agreed environment.
The change has been reviewed and acceptance-blocking findings resolved.
A Working Definition of Done (2 of 2)
The work is integrated with the team’s shared codebase and existing checks still pass.
Necessary usage notes, domain decisions and known limitations are current.
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 condition
Example
Question answered
Acceptance criterion
An enquiry leaves reservations unchanged
Does this behaviour satisfy the story?
Definition of Done
The integrated change is reviewed and its checks pass
Does the increment meet the shared standard?
Local Ready policy
Known scope, example inputs, identified blocking questions
Is the item understood enough to begin?
From Criterion to Code: A Plain-Java Bridge
// AC-02: an overlapping request changes no records.Stringconflict=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
Authority, 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 goal
Decide which suggestions belong in the story, which do not and which need a stakeholder
You start with
US-01, AC-01 through AC-05, and Chapter 2’s scope
What is in bounds
Classify 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 check
The stated rules, the Chapter 2 code and checks, the story’s scope
How to decide
Accept, 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.
A request for the same item and an identical period must be refused.
A request that begins exactly when an existing period ends must be refused.
Every confirmation must be emailed within thirty seconds.
A refused request must leave stored reservations unchanged.
The Ready-Made Example (2 of 2)
Specify how start dates that are already in the past are treated.
A deposit payment must be taken before any reservation can be confirmed.
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)
Suggestion
Disposition
Reason
1. Refuse the same item and an identical period
Accept
An identical period overlaps; a useful special case of AC-02
2. Refuse a request beginning exactly when another ends
Reject for this story
Contradicts the exclusive-end convention and AC-03
3. Email every confirmation within thirty seconds
Reject for this story
Adds delivery infrastructure and a timing requirement not agreed
4. A refused request leaves records unchanged
Accept
Matches the state-preservation expectations of AC-02 and AC-05
Decision Log (2 of 2)
Suggestion
Disposition
Reason
5. Specify how past start dates are treated
Clarify
The policy sets no past-date restriction; do not invent one
6. Take a deposit before confirming
Reject for this story
A new business process and unapproved scope
7. Do cancelled reservations count toward the limit?
Clarify, link to US-04
Cancellation 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)
Order
ID
Item
Type
1
US-01
Record an eligible, non-conflicting student reservation
User story
2
US-02
Inspect availability without reserving an item
User story
3
EN-01
Automate the existing build and checks
Technical enabler
A Worked Six-Item Backlog (2 of 2)
Order
ID
Item
Type
4
US-03
Inspect one borrower’s active reservations
User story
5
US-04
Cancel a confirmed reservation under an agreed policy
User story
6
US-05
Reject unknown equipment identifiers using an agreed catalogue
User 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)
ID
Given and when
Expected observations
AQ-01
No stored reservations; inspect CAM1 for [20 Oct, 22 Oct)
Available within this record set; records unchanged
Same reservation; inspect CAM1 for [22 Oct, 23 Oct)
Available under the adjacency convention; records unchanged
US-02: Acceptance Examples (2 of 2)
ID
Given and when
Expected observations
AQ-04
CAM1 reserved; inspect a different known item MIC1 for the same period
Available if MIC1 has no conflict; records unchanged
AQ-05
Inspect an invalid period with equal or reversed dates
An 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)
Ready
Building
Review
Done
US-03, US-04
US-02
EN-01, blocked by a setup problem
US-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
Mistake
Consequence
Correction
Treating Agile as the absence of planning
Dependencies and deadlines remain invisible
Plan at useful detail; revise from evidence
Treating a story template as the full requirement
Eligibility and failure behaviour remain ambiguous
Add discussion, decisions and examples
Calling every item highest priority
The team has no usable order
Explain why one outcome precedes another
Mistakes 4 and 5
Mistake
Consequence
Correction
Treating acceptance criteria as the whole DoD
Correct examples hide unreviewed or unintegrated work
Check behaviour and shared quality conditions
Starting new work whenever review is blocked
Unfinished work accumulates
Make the blockage visible; help existing work finish
Mistakes 6 and 7
Mistake
Consequence
Correction
Treating AI suggestions as stakeholder decisions
Scope or policy changes without agreement
Classify each proposal and justify its disposition
Counting inherited working code as newly delivered work
Progress becomes misleading
Record the baseline and the remaining work honestly
3.12 Questions to Think with
Concept, diagnosis, transfer
Concept Questions
Explain how an iteration can improve an existing feature and how an increment adds usable capability. Give an equipment-lending example of each.
Choose one Agile value and explain a decision that respects both sides of its preference rather than discarding one side.
Distinguish Sprint Review, Sprint Retrospective and Daily Scrum using the decisions each helps the team make.
Explain the difference between acceptance criteria, Definition of Done and the local Ready-column policy.
Diagnosis Questions
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.
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
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.
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
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.
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
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.
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
Agile Manifesto authors:Manifesto for Agile Software Development and Principles behind the Agile Manifesto, 2001.
Schwaber and Sutherland:The Scrum Guide, November 2020.
Kanban Guides contributors:The Kanban Guide, May 2025.
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:
Define a project vision with explicit scope, assumptions and constraints.
Slice a feature into a small increment that supports an observable user outcome.
Form an iteration plan that includes implementation, review, verification and realistic capacity.
Learning Outcomes (2 of 2)
Apply a shared repository workflow with clear review responsibilities.
Run a starter build and checks, and demonstrate that the checking process detects a deliberate failure.
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.
No web or mobile implementation required for kickoff
Concurrent use
Sequential teaching example
No 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
Element
Example or behaviour
Borrower identifier
B1
Equipment identifier
CAM1
Start date
2026-10-20
Duration in days
2
Period explanation
Includes 20 and 21 October; ends before 22 October
Action
Request reservation
Scope note
Controlled demo; records last only for this session
The 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)
Element
Example or behaviour
Equipment identifier
CAM1 from the controlled set of sample items
Start date
2026-10-22
End date, exclusive
2026-10-23
Action
Check availability
Result
Available, unavailable, or an identifiable invalid-period outcome
Explanation
An 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
Begin with no reservations.
Submit B1, CAM1, 20 October, two days. Expect one stored reservation ending exclusively on 22 October.
Submit B2, CAM1, 21 October, one day. Expect rejection and the original record preserved.
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)
Order
Item
Current interpretation
1
US-01: Record an eligible reservation
Core behaviour exists; confirm interaction and delivery evidence
2
US-02: Inspect availability without reserving
New user outcome to refine and forecast
3
EN-01: Automate build and checks
Enabling work supplied in this chapter; hosted evidence still required
Preserve the Backlog’s History (2 of 2)
Order
Item
Current interpretation
4
US-03: Inspect a borrower’s active records
Filtering and interaction remain new work
5
US-04: Cancel a confirmed reservation
Authority, lifecycle and count effects need clarification
6
US-05: Reject unknown equipment identifiers
Depends 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
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.
Same reservation; example MIC1 for [20 Oct, 22 Oct)
Available; records unchanged
AQ-05
Equal or reversed start and end
Identifiable 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
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 work
Person-hours
Resolve the query contract and update examples
2
Implement the read-only enquiry behaviour
4
Add the thin interaction and invalid-input response
3
Add and run query checks alongside regression checks
3
Review, integrate, update notes and rehearse the demonstration
2
Selected work
14
Unallocated capacity within the 18-hour budget
4
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.
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.
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
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.Stringconflict=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.
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-latesttimeout-minutes:5steps:- uses:actions/checkout@v7- uses:actions/setup-java@v6with:distribution:temurinjava-version:'17'- name:Compile and checkrun: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
Begin with a passing run.
On a disposable branch or copy, change one acceptance check to expect two records after the first successful reservation.
That expectation is intentionally wrong: valid behaviour stores exactly one.
Run the same gate and expect a nonzero result identifying AC-01.
The Deliberate Failure
// originalrequire(coordinator.reservations().size()==1,"AC-01 one record");// intentionally wrong, on a practice branch onlyrequire(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)
Evidence
Question it should answer
Vision and scope
Whose outcome matters, and under which boundaries?
Epics, stories and ordered backlog
What is supported, what is next, what is uncertain?
Wireframes and acceptance examples
What will the officer do, see and change?
The Preparation Map (2 of 3)
Evidence
Question it should answer
Capacity and iteration plan
Why is the selected work plausible, including verification and review?
Team agreement and review record
How will changes be inspected and integrated?
Repository and README
Can another person obtain the intended revision and run it?
The Preparation Map (3 of 3)
Evidence
Question it should answer
Passing CI result
Did the configured service execute the gate successfully?
Deliberate failure and restored pass
Does a failed check produce failure, and is the baseline healthy again?
Decision and limitation notes
Which assumptions restrict the claims being made?
Trace a Few Meaningful Links
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 goal
Evaluate a proposed iteration plan against scope, capacity, behaviour and delivery evidence
You start with
The Chapter 3 backlog, the capacity figures in 4.4 and the Chapter 4 starter
What is in bounds
Review and replace the plan; do not implement any feature
Ground Rules for the Activity (2 of 2)
For this activity
How to check
Your capacity arithmetic; the code (is there a query method? does reserve change state?); the actual gate output
How to decide
A 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)
Proposal
Verdict
Reason and correction
Allocate all 22 hours to features
Reject
Coordination, review and uncertainty consume capacity; use the explicit budget
Deliver query, cancellation, mobile, database and email together
Reject
The selected goal is one query slice; the additions lack agreed requirements
One layer per member; integrate at the end
Revise
Ownership can help; final-day integration delays learning
Model Critique (2 of 3)
Proposal
Verdict
Reason and correction
Convert three story points into three hours
Reject
No valid conversion was established; estimate tasks from real understanding
Call reserve, then delete the record
Reject
A query must not change state; deletion is not even a supplied operation; eligibility differs
Defer failure cases
Reject
Invalid intervals and non-mutation are part of this outcome’s acceptance
Model Critique (3 of 3)
Proposal
Verdict
Reason and correction
Mark EN-01 complete when YAML is committed
Reject
Configuration must execute successfully and detect the intended failure
A green badge proves the whole story
Reject
The 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
Mistake
Why it fails
Better action
Count starter behaviour as newly delivered
Hides the actual change
Separate existing capability from new evidence or functionality
Slice only by technical layers
No usable end-to-end result
Select one observable journey and its necessary tasks
Fill all nominal capacity with features
Review, coordination and uncertainty go unfunded
Budget those activities explicitly
Mistakes 4 to 6
Mistake
Why it fails
Better action
Use a wireframe as proof of implementation
Confuses an agreed sketch with working behaviour
Label proposal and implementation separately
Treat an availability answer as a reservation
Omits eligibility and later state changes
Keep query and command outcomes distinct
Create CI configuration without running it
No evidence the gate executes
Inspect an actual run against the intended revision
Mistakes 7 and 8
Mistake
Why it fails
Better action
“Fix” a red job by ignoring its exit status
Conceals the defect
Correct the cause; preserve failure propagation
Invent review or meeting evidence
Makes the project state misleading
Report 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)
Write the vision, boundaries, assumptions and two decision records with review triggers.
Group the backlog into epics, preserve story identifiers, and distinguish existing capability from new work.
Refine US-02 (or an equivalent thin slice) with normal, boundary and rejection examples, including state effects.
Create an iteration goal and capacity plan using your actual availability, with coordination, review and uncertainty shown.
Independent Team Work (2 of 2)
Agree ownership, review and board policies. Apply them to one small repository change.
Create the starter files, run the gate and reproduce the scripted demonstration.
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.
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)
Field
Team entry
Repository and baseline revision
The actual repository and commit
Implemented behaviour
Only the behaviour present at that revision
Local environment and command
Java version, shell, command
Local check result
Observed exit status and relevant output
Hosted passing run
Run URL or retained log, tested revision, date
The Evidence Note (Blank Template) (2 of 2)
Field
Team entry
Deliberate failure
Practice revision, failed assertion, failed run
Restored pass
Restored revision and passing run
Independent review
Actual reviewer, finding, correction, follow-up
Unresolved questions
Owner, impact, next decision point
Known limitations
Scope boundaries relevant to interpreting the demo
A template, not completed evidence.
Feedback Questions (Not Document Counts)
Dimension
Useful feedback question
Scope
Can the team explain the conditions under which its claim is valid?
Slice
Does the result support one observable outcome, including necessary failures?
Planning
Is review and uncertainty accounted for, and can people collaborate when needed?
Traceability
Does the cited check actually exercise the criterion?
Workflow
Is there real evidence of review, corrections and the integrated result?
Honesty
Are 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
How do a vision, an iteration goal and an implementation task differ? Use the equipment project for each.
Why can a console-based journey be a vertical slice even when it has no database?
Why is total available person-time insufficient to establish a feasible iteration plan?
What does a passing starter check establish, and what does it leave unproven?
Diagnosis Questions
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.
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
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.
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
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.
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
Calling reserveviolates 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.
|| 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.
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 pressure
Main knowledge involved
Current location
Change confirmation wording
Presentation of a completed reservation
Coordinator’s private formatting method
Add a second count/duration policy
Borrower eligibility rules
Existing rule plus new selection behaviour
Change interval overlap semantics
Meaning of a loan period
LoanPeriod
Change Pressures (2 of 2)
Change pressure
Main knowledge involved
Current location
Preserve records across restart
Storage lifetime and retrieval
Coordinator’s in-memory list
Reject unknown equipment identifiers
Authority for equipment identity
Not 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
Category
Existing active reservations allowed before a successful request
Requested duration
Student
0, 1 or 2
1 through 7 days
Staff
0, 1, 2, 3 or 4
1 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
Three Kinds of Evidence, in This Order
The compiler’s.
Your own.
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.
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)
Smell
What to look for
More than one reason to change
A method that mixes eligibility, conflict search, record creation and message wording
Hidden construction
A class that creates its collaborators with new inside its own methods
The Four Smells (2 of 2)
Smell
What to look for
Repeated or similar logic
Two 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 concepts
Strings 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.
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.
publicfinalclassConfirmationFormatter{publicStringformat(Reservationreservation){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.
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
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 truemean?
A signature describes parameter and result types and answers none of these.
The BorrowingPolicy Contract (1 of 2)
Aspect
Promise
Accepted input domain
Every pair of Java int values can be passed
Invalid numeric values
Negative count or nonpositive duration returns false
Decision
true exactly when the inputs satisfy that implementation’s documented limits
The BorrowingPolicy Contract (2 of 2)
Aspect
Promise
Side effects
Does not create reservations, change a counter or perform external I/O
Repeatability
Same inputs and fixed policy configuration give the same result
Scope of approval
Only 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]:
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.
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)
Run the Chapter 4 gate and record the starting result.
Add the concrete formatter with the existing confirmation expression.
Replace the private formatting call and remove the now-unused private method.
A Practical Sequence (2 of 2)
Compile and rerun the original regression and acceptance checks.
Inspect the diff for unrelated changes and record what behaviour was preserved.
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
publicStringreserve(StringborrowerId,StringitemId,LocalDatestart,intrequestedDays,BorrowerCategorycategory){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:returnformatter.format(reservation);}
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 copy
Result
Staff count limit < 5 to <= 5
Caught: staff sixth rejected
Staff duration <= 14 to < 14
Caught: staff upper accepted boundaries
Staff duration <= 14 to <= 15
Caught: staff fifteen days rejected
Staff negative-count guard removed
Caught: negative count returns false
Do the New Checks Detect Faults? (2 of 4)
Fault introduced into a copy
Result
Student count limit < 3 to < 5
Caught: fourth rejected
Student duration <= 7 to <= 14
Caught: eight-day duration rejected
Selector arms swapped
Caught: fourth rejected
Old overload delegates with STAFF
Caught: fourth rejected
Do the New Checks Detect Faults? (3 of 4)
Fault introduced into a copy
Result
Confirmation text changed to Reserved:
Caught: exact first confirmation
Confirmation drops the end date
Caught: exact first confirmation
Storing the reservation skipped
Caught: one success stored
Count restricted to staff requests only
Caught: fourth rejected
Do the New Checks Detect Faults? (4 of 4)
Fault introduced into a copy
Result
Overlap check ignores the item
Caught: different item and seven-day limit allowed
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)
Decision
Benefit
Cost or remaining limitation
Extract a concrete formatter
Independent presentation changes get a home
Another object and file; no configurable presentation yet
Use a small policy interface
Names the count/duration contract shared by two rules
Extra types and selection; numeric parameterisation is also possible
Keep a selector in the coordinator
Intermediate stage is easy to inspect
New categories still require a coordinator edit
The Cost of Each Decision (2 of 2)
Decision
Benefit
Cost or remaining limitation
Retain direct construction
Keeps focus on SRP, OCP and contracts
Dependency assembly stays coupled until Chapter 6
Avoid coordinator inheritance
Prevents duplicating or overriding the workflow
Variation 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 goal
Judge a “make this SOLID” proposal against concrete requirements, contracts and costs
You start with
The Chapter 4 source, the staff-rule table in 5.1 and the policy contract in 5.4
What is in bounds
Review the proposal; do not apply it wholesale
Ground Rules for the Activity (2 of 2)
For this activity
How to check
Compilation (is ReservationRulesfinal?), the exact-output checks, the rejection and state checks
How to decide
A 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 change
Verdict
Concrete reason
An interface for every class
Not justified as stated
No second role or client need for immutable values; adds navigation
Coordinator factory and abstract base
Defer
Two rules do not require a factory hierarchy
Replace rejection strings with exceptions
Separate feature/API proposal
Existing callers and acceptance examples observe the current results
Model Review (2 of 3)
Proposed change
Verdict
Concrete reason
Staff rules extend student rules
Reject
ReservationRules is final; and staff approval is incompatible with the exact student predicate
Remove all conditionals so no file ever changes
Reject the promise
An implementation must still be selected somewhere
Model Review (3 of 3)
Proposed change
Verdict
Concrete reason
Shorten the confirmation to “Reserved”
Reject during Stage A
The exact output changes: no longer the promised extraction
Green build proves SOLID
Reject
Checks 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
Mistake
Counterexample or consequence
Better reasoning
SRP means one method per class
A one-method service can still mix policy and presentation
Identify independent change pressures
OCP forbids editing existing code
A new category still needs an explicit selection decision
State the protected axis and the remaining edit boundary
Same signature means safe subtype
PositiveOnlyHistory.add(-2) rejects valid base input
Evaluate the behavioural contract
Mistakes 4 to 6
Mistake
Counterexample or consequence
Better reasoning
Any different policy result violates LSP
Student and staff intentionally have different limits
Compare with the shared contract
Better wording is part of a refactoring
“Reserved” breaks the preserved result
Separate the presentation feature from extraction
A passed LSP demo validates the subtype
The harness may have successfully detected a violation
State what the demonstration observed
Mistakes 7 and 8
Mistake
Counterexample or consequence
Better reasoning
A category parameter verifies authority
A caller can supply STAFF without proving identity
Label the trusted-example boundary
More interfaces mean better design
Unused variations add navigation and decisions
Relate every boundary to a concrete need
Mistakes 9 and 10
Mistake
Counterexample or consequence
Better reasoning
Switch on every analyser rule
Dozens of low-value findings hide the one that matters
Choose a small rule set for a stated purpose and record why
Read an unchanged finding as a failed refactoring
A tool measures one thing, such as branching
Say 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
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.
Record a passing baseline, extract the formatter, rerun the original checks without altering their expected behaviour.
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
Add or use the boundary and cross-category checks. Explain what each group establishes.
Run the separate LSP example. Identify the violated promise and compare the composition-based repair with changing the base contract.
Review the complete diff with another person where available. Retain actual findings and corrections; label solo review honestly.
Independent Work: Steps 7 and 8
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.
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)
Element
Present responsibility
Decision in this chapter
ReservationCoordinator
Sequence validation, eligibility, conflict detection and storage
Preserve sequencing; delegate formatting and policy evaluation
ReservationRules
Student count/duration decision
Preserve predicate; implement the shared policy contract
LoanPeriod
Valid interval and overlap semantics
Leave unchanged
Model Responsibility Diagnosis (2 of 2)
Element
Present responsibility
Decision in this chapter
Reservation
Immutable accepted-record details
Leave unchanged
Confirmation expression
Render an accepted record as text
Extract a concrete formatter
Staff eligibility
New count/duration variation
Add 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)
Smell
Where it appears in reserve
Act now?
More than one reason to change
One method validates, counts, applies eligibility, searches for a conflict, creates and stores the record, and produces wording
Yes for wording (A) and eligibility (B); no for storage
Hidden construction
private final ReservationRules rules = new ReservationRules();
Only as far as Stage B needs; full choice is Chapter 6
Model Manual Smell Review (2 of 2)
Smell
Where it appears in reserve
Act now?
Repeated or similar logic
Two loops differ only in the test; rejections are two plain strings
No: two short loops are clearer than a new abstraction
Primitives standing for concepts
Identifiers and days are strings and an integer; rejection reasons are free text
No: 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
Source
Finding
Group
PMD CyclomaticComplexity
reserve has complexity 12 (threshold 10)
Useful
PMD DataClass
Reservation is a suspected data class
Low-value here
Manual reading
new ReservationRules() inside the coordinator
Missed by the tool
Manual reading
One string for two different reasons
Missed by the tool
Manual reading
Two similar loops
Not reported at default thresholds; leave them
Rule Sets Compared on the Same Five Files
PMD rule set
Findings on before
What they were
Lab set (eight design rules)
2
The two above
rulesets/java/quickstart.xml
5
All NoPackage
category/java/codestyle.xml
34
MethodArgumentCouldBeFinal, LocalVariableCouldBeFinal, NoPackage, OnlyOneReturn and similar
category/java/bestpractices.xml
4
SystemPrintln in the demonstration class
This is why the lab uses a small set.
Suggested Evidence
Evidence
What to record
Starting point
Actual version and passing original gate
Stage A
Relevant diff; original checks with unchanged expectations
Stage B
New rule table; policy contract; compatibility and staff-boundary results
LSP example
Valid base call sequence; subtype outcome; precise violated promise
Review
Actual 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 change
Separate policy classes
Configured numeric limits
Change staff days from 14
Edit the staff predicate
Edit the staff configuration
Add a third pair of simple limits
Add another policy
Supply another configuration
A qualitatively different eligibility rule
A new implementation can express it
May 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
Why is counting methods an unreliable way to apply SRP? Give one example from the running project.
Against which variation is the policy design relatively protected, and which selection code remains open to change?
How can two implementations return different booleans and still satisfy the same policy interface contract?
Why is extracting the formatter a refactoring while introducing staff limits is a feature change?
Diagnosis Questions
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?
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
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.
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
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.
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
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.
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.
Score
Policy A: pass at 50
Policy B: pass at 60
49
REVIEW
REVIEW
50
PASS
REVIEW
60
PASS
PASS
-1 or 101
exception
exception
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
Martin:The Single Responsibility Principle and The Open Closed Principle, 2014.
Oracle:Java Language Specification, Java SE 17, Chapter 9: Interfaces.
Liskov and Wing:A Behavioral Notion of Subtyping, 1994.
Fowler:Definition of Refactoring, 2004; Refactoring, 2nd ed., 2018.
Martin:Agile Software Development, 2002.
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:
Design an interface around what a particular client needs.
Explain ISP through a concrete dependency that is unnecessarily broad.
Distinguish DIP, dependency injection and inversion of control.
Learning Outcomes (2 of 2)
Construct a small object graph using constructor injection and an explicit composition root.
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 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.
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
Decision
Owner in this version
Reason
Whether the request is acceptable
Coordinator and selected borrowing policy
Depends on reservation rules and current records
How notification is represented locally
Sender implementation and formatter
Depends on the chosen output
Which concrete objects are used
Application composition root
Depends 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
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)
Example
Reservation result
Notification
Eligible available request
Exact confirmation; one record stored
One accepted record supplied to the sender
Same-item overlap
Existing rejection; records unchanged
No call
Invalid duration
Existing rejection; records unchanged
No call
Acceptance Examples Before Refactoring (2 of 2)
Example
Reservation result
Notification
Invalid identifier
Existing exception; records unchanged
No call
Expected output failure after acceptance
Confirmation plus failure suffix; record retained
One attempt; no automatic retry
Repeat of the accepted request
Overlap rejection
No 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
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. */@FunctionalInterfacepublicinterfaceBorrowerDirectory{Optional<Borrower>find(StringborrowerId);}
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.
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 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
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)
Term
Question answered in this example
DIP
Does the coordinator depend on the application’s notification role rather than a provider detail?
DI
Who supplies the sender, formatter and policy mapping?
IoC
Which component controls assembly or later calls into application code?
Terms and the Question Each Answers (2 of 2)
Term
Question answered in this example
DI container
What mechanism resolves registrations and manages selected object lifetimes?
Service locator
Does 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
staticfinalclassDemoContainer{privatefinalMap<Class<?>,Supplier<?>>providers=newHashMap<>();privatefinalMap<Class<?>,Object>instances=newHashMap<>();privatefinalSet<Class<?>>constructing=newHashSet<>();public<T>voidbind(Class<T>type,Supplier<?extendsT>provider){/* ... */}public<T>Tget(Class<T>type){if(instances.containsKey(type))returntype.cast(instances.get(type));Supplier<?>provider=providers.get(type);if(provider==null)thrownewIllegalArgumentException("Missing binding: "+type.getSimpleName());if(!constructing.add(type))thrownewIllegalStateException("Dependency cycle: "+type.getSimpleName());/* run the provider, cache, return */}}
The provider calls the same ordinary constructor. Only configuration code uses the container.
Trace the First Resolution
Registration stores a provider without running it.
The first coordinator resolution runs its provider, which requests the formatter and sender.
Those providers run; their instances are passed to the constructor.
The coordinator is cached.
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 goal
Judge whether a generated refactoring really separates configuration and policy from the reservation workflow
You start with
The coupled coordinator from 6.1, the notification requirement and Figure 6.1
What is in bounds
Evaluate 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 check
The coordinator’s source (which concrete classes does it name?), the recorder-based checks, the failure case
How to decide
A 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 change
Verdict
Reason
Type the field as NotificationSender
Useful but insufficient alone
It expresses a role, but concrete construction can remain
Read provider settings inside the coordinator
Reject
Environmental configuration still changes the workflow’s source
Construct the console sender inside the coordinator
Reject for the final design
The caller cannot select a recorder without editing the client
Model Critique (2 of 3)
Claim or change
Verdict
Reason
Add configure/export/delete to the sender role
Reject without a client need
They couple this client to unrelated capabilities
Catch every runtime exception, return ordinary confirmation
Reject
It hides programming defects; expected failure becomes indistinguishable from success
Model Critique (3 of 3)
Claim or change
Verdict
Reason
Inject a global container for future lookups
Reject
Real required collaborators become hidden behind a registry
Declare DIP and DI complete because an interface exists
Reject
Contract 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
Mistake
Why it is misleading
Better action
An interface-typed field proves DIP
Concrete construction or provider-specific types may remain
Inspect the actual source dependencies
DI means a framework
Ordinary constructors can receive dependencies
Explain assembly before adding tooling
Every class needs an interface
Many stable values have no variation
Name the client need and change pressure
Mistakes 4 to 6
Mistake
Why it is misleading
Better action
A sender must expose provider administration
The coordinator does not need those operations
Keep the application role narrow
A container singleton is thread-safe
Lifetime and concurrent access are separate
Inspect mutable state and execution model
Notification failure means reservation failure
Storage may already have succeeded
State the partial-success outcome explicitly
Mistakes 7 and 8
Mistake
Why it is misleading
Better action
One call means exactly-once delivery
Output may fail after partial or remote effects
Describe only the guarantee implemented
Old checks passed, so all compatibility is preserved
Construction changed and notification adds effects
Separate 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
Define the sender role and identify at least two operations it should not contain.
Move output construction and category-to-policy mapping to the composition root. Require valid collaborators in the constructor.
Migrate existing caller construction while preserving existing reservation assertions.
Independent Work: Steps 4 to 6
Demonstrate both console and recording outputs with the same coordinator implementation.
Verify no notification on rejection, one attempt after acceptance, and the chosen failure outcome without automatic retry.
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
Run the educational container walkthrough and explain shared state, missing registration and cycle detection.
Review the change and write a rationale for every new boundary. Retain real review findings.
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 concern
Evidence worth inspecting
Role is client-specific
Coordinator uses only the sender operation and the domain record
Provider choice is external
Concrete output construction is in assembly code, not the coordinator
Valid configuration
Null and missing-policy failures occur before request processing
Mapping is stable
Editing the caller’s map does not rewire an existing coordinator
Review Evidence Worth Inspecting (2 of 2)
Review concern
Evidence worth inspecting
Existing behaviour
Migrated setup with unchanged reservation assertions passes
Feature effects
Accepted record supplied once; rejection causes no send; expected failure retains state
Lifetime
Repeated container resolution shares the same coordinator
Claims are bounded
No 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)
Fault
Result
Notification sent before the record is stored
Caught: notification failure retains accepted record
catch (NotificationException) widened to catch (RuntimeException)
Caught: unexpected sender failure is not swallowed
The checks do not prove thread safety, durable output or remote delivery.
6.11 Questions to Think with
Concept and diagnosis
Concept Questions
Why does NotificationSender omit the recorder’s received() method?
How can constructor injection be present while the intended DIP boundary is still missing?
What does the composition root know that the coordinator should not know?
Why can sharing a coordinator through a container change application behaviour even when its methods are unchanged?
Diagnosis Questions
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.
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
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.
A constructor taking ConsoleNotificationSenderuses 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
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.
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
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.
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.
Example
Conversion state
Notification state
Valid input converts
One output produced
One completion result submitted
Conversion fails
No success reported
No notice submitted
Conversion succeeds, notifier fails
Output retained
Failure 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)
Chapter
Revision focus
2
Encapsulation, responsibilities, cohesion/coupling, composition and inheritance, UML class diagrams, Git history, branches and small reviewed changes
3
Agile values in decisions, iterative/incremental delivery, Scrum/Kanban distinctions, stories, acceptance criteria and Definition of Done
Revision Focus (2 of 2)
Chapter
Revision focus
4
Vision and scope, vertical slicing, capacity-aware planning, review workflow and meaningful CI evidence
5
SRP, 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
Martin:Design Principles and Design Patterns (2000); A Little Architecture (2016).
Fowler:Inversion of Control Containers and the Dependency Injection Pattern (2004); Inversion of Control (2005).
Oracle: Java SE 17 Map and PrintStream.
Spring:Introduction to the Spring IoC Container and Beans.
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:
Translate acceptance examples into focused tests with meaningful expected outcomes.
Demonstrate red-green-refactor and explain why the first failure is the intended one.
Control data, time and collaborators to make tests repeatable.
Learning Outcomes (2 of 2)
Choose a useful test double and distinguish state checks from interaction checks.
Diagnose weak or brittle tests and explain the limits of passing evidence.
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:
A working MVP flow that someone else can run from written instructions.
Acceptance examples mapped to named tests.
One red-green-refactor sequence recorded from your own code.
A short rationale for your object-oriented and SOLID choices.
Start with items 1 and 3.
Milestone 2 Checklist (2 of 2)
A justified test double or controlled clock.
A build result for the revision you demonstrate.
Real review feedback and a short retrospective with a next action.
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)
Example
Setup and action
Required observation
AQ-01
Empty schedule; enquire about CAM1 for a valid interval
Same booking; enquire about MIC1 for the same interval
Available for the different sample item
AQ-05
Equal or reversed interval
Identifiable 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
booleanavailable=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 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)
Category
Count
Days
Expected
STUDENT
0
1
true
STUDENT
2
7
true
STUDENT
3
1
false
STUDENT
0
8
false
STAFF
4
14
true
STAFF
5
1
false
STAFF
0
15
false
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
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
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
publicrecordLoan(StringreservationId,StringborrowerId,StringitemId,LocalDateborrowedOn,LocalDatereturnedOn){publicLoan{if(reservationId==null||reservationId.isBlank()||borrowerId==null||borrowerId.isBlank()||itemId==null||itemId.isBlank()){thrownewIllegalArgumentException("Loan identifiers must not be blank.");}Objects.requireNonNull(borrowedOn,"borrowedOn");if(returnedOn!=null&&returnedOn.isBefore(borrowedOn)){thrownewIllegalArgumentException("Return cannot precede checkout.");}}publicbooleanactive(){returnreturnedOn==null;}}
LoanService.checkOut
LocalDatetoday=LocalDate.now(clock);if(today.isBefore(reservation.period().start())||!today.isBefore(reservation.period().endExclusive())){thrownewIllegalStateException("Checkout must be within the reservation period.");}booleanitemAlreadyOut=loans.values().stream().anyMatch(loan->loan.active()&&loan.itemId().equals(reservation.itemId()));if(itemAlreadyOut)thrownewIllegalStateException("Item is already on loan.");
LoanService.returnLoan
Loancurrent=loans.get(reservationId);if(current==null||!current.active()){thrownewIllegalStateException("No active loan for this reservation.");}Loanreturned=newLoan(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)
Operation
Rule and state effect
Checkout
Reservation must exist; today’s date must be within its period
Repeat checkout
One reservation can create at most one loan, including after return
Item already out
A second active loan for the same item is rejected
The Lending Rules (2 of 2)
Operation
Rule and state effect
Return
Only an active loan can be returned; repeated return is rejected
Return date
Cannot precede the recorded checkout date
Observation
Loan 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)
Role
Purpose
Example in this project
Dummy
Supplies an argument the scenario should not use
A sender passed to an immediately rejected request
Stub
Produces a controlled answer or failure
A sender that throws the expected notification exception
Fake
A working simplified substitute
An in-memory output standing in for external delivery
Test Doubles (2 of 2)
Role
Purpose
Example in this project
Spy
Records calls for later inspection
The recorder, when a test examines received reservations
Mock
Supports verification of expected interactions
A 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.
// no notification after a rejectionassertEquals("Rejected: borrower limit or duration.",c.reserve("B1","CAM1",day,8));assertTrue(c.reservations().isEmpty());verifyNoInteractions(sender);// expected failure: record retained, exactly one attemptdoThrow(newNotificationException("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.
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:
Real system clock used instead of the supplied one
three LoanServiceTest methods
Clock zone ignored
clockZoneDeterminesTheCheckoutDate
Return date recorded as the checkout date
checkoutAndReturnUseTheSuppliedClock and the backward-clock test
Which Test Caught Which Fault (4 of 4)
Fault
Caught by
Loan no longer rejects a return before checkout
backwardClockCannotRecordReturnBeforeCheckout
Sender called twice on success
NotificationMockitoTest, 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)
Evidence
What it should establish
Working MVP demonstration
A coherent user outcome runs from documented setup
Acceptance mapping
Selected examples connect to executed tests
TDD sequence
Intended failure, implementation and protected refactoring are visible
Milestone 2 Evidence (2 of 3)
Evidence
What it should establish
OO/SOLID rationale
Collaborators and responsibilities have concrete reasons
Double choice
Controlled behaviour or interaction is justified
Build result
Actual tests ran against the intended revision
Milestone 2 Evidence (3 of 3)
Evidence
What it should establish
Review and retrospective
Real feedback, a change in understanding and a next action are recorded
Limitations
In-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.
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 goal
Separate a proposed implementation from the evidence that would accept or reject it
You start with
AQ-01 to AQ-05 as reviewed, and Chapter 6’s coordinator
What is in bounds
Evaluate 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 check
The reviewed examples, the labelled wrong-boundary mutation, the build result
How to decide
A 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)
Claim
Verdict
Independent check or correction
Query by reserve-and-remove
Reject
reserve 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 conflicts
Reject
The agreed interval is half-open; [20,22) and [22,23) do not overlap
No exception is sufficient
Reject
An always-true or always-false method can return normally and be wrong
Model Critique (2 of 2)
Claim
Verdict
Independent check or correction
Implementation and tests mutually prove correctness
Reject
Both may share the same mistaken convention
Coverage proves correctness
Reject
Execution does not validate expected values or sensitivity to defects
Use the real date and wait for tomorrow
Reject
Supply 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
Mistake
Consequence
Better action
Count a dependency-download error as TDD red
Behaviour was never examined
Reach the intended assertion failure
Write expected values by calling the implementation
Reproduces the same mistake
Use independently reviewed examples
Mock the object under test
Replaces the behaviour being investigated
Keep the subject real
Mistakes 4 to 6
Mistake
Consequence
Better action
Use real time for a deterministic date rule
Results change with date or zone
Inject an explicit clock
Treat every recorder as the same double role
Hides why it is used
Describe its role in the particular test
Verify private call counts
Breaks harmless refactoring
Observe public behaviour or meaningful boundary interactions
Mistakes 7 and 8
Mistake
Consequence
Better action
Remove old checks during tool migration
Loses existing evidence
Bridge or migrate them explicitly
Call the bounded demo production-ready
Conceals lifecycle and integration gaps
State 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
Reproduce the supplied TDD cycle, or conduct a small new one. Record the actual sequence.
Complete the remaining query examples, including side effects and adjacency.
Demonstrate the loan flow with controlled dates and rejection of an invalid return.
Independent Work: Steps 4 to 6
Choose a substitute for one collaborator and justify its role.
Run the labelled wrong-boundary implementation in a separate copy, identify the failing assertion and restore the source.
Run the full build and map selected acceptance examples to named tests.
Independent Work: Steps 7 to 9
Rehearse the MVP demonstration from documented instructions, obtain actual review feedback and write a short retrospective with a next action.
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.
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.
two invocations of checkoutOutsideHalfOpenPeriodIsRejected
Local date depends on clock zone
clockZoneDeterminesTheCheckoutDate
Backward time cannot predate checkout
backwardClockCannotRecordReturnBeforeCheckout
Suggested Evidence Packet (1 of 2)
Document
Useful content
Scope note
Selected query and loan rules; synthetic inputs; lifecycle limitations
TDD record
Actual red message, same test at green, refactoring diff, regression result
Acceptance mapping
Named tests tied to concrete examples
Double rationale
Which collaborator is replaced, why, and what observation it enables
Suggested Evidence Packet (2 of 2)
Document
Useful content
Mutation record
Isolated faulty comparison, selected failure and restored pass
Build result
Executed revision, tool versions, actual report
Demonstration
Reproducible steps from empty state through reserve, checkout, return
Review and retrospective
Actual 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
Why is “the method did not throw” insufficient evidence for an availability query?
What distinguishes the red stage of the worked cycle from a missing-dependency failure?
When does the notification recorder act as a spy, and why can a framework mock serve a similar purpose?
Why do both the instant and zone matter when testing a local-date checkout rule?
Diagnosis Questions
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.
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
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.
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
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.
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
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.
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
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
publicclassOverdueReminder{publicbooleanremind(StringborrowerEmail,LocalDatedueDate,StringitemName){if(LocalDate.now().isAfter(dueDate)){MailClientclient=newMailClient("smtp.campus.example",25);client.send(borrowerEmail,"Overdue: "+itemName,"Please return "+itemName+".");System.out.println("Reminder sent to "+borrowerEmail);returntrue;}returnfalse;}}
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”.
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
JUnit Team:JUnit 5 User Guide, 5.13.4.
Apache Maven: Surefire with the JUnit Platform; Maven 3.9.11; Compiler Plugin.
Fowler:Test Driven Development; Test Double (2006).
Oracle: Java SE 17 Clock; Record Classes.
Mockito Team: API documentation; Mockito 5.20.0.
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:
Identify a concrete construction or interface-compatibility problem.
Distinguish Factory Method, a simple factory and Abstract Factory by their responsibilities.
Build a validated immutable request through a staged construction interface.
Learning Outcomes (2 of 2)
Adapt provider records, absence and failures into an application-owned contract.
Compose decorators and predict the effects of order and exception handling.
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
Pattern
Concerned with
Factory Method, Abstract Factory, Builder
Construction decisions
Adapter
Connecting an existing interface to the one a client needs
Decorator
Attaching 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:
the current simple solution,
the change it handles poorly,
the selected boundary,
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.
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
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.
Named operations configure a mutable builder; build() produces an immutable request.
Configure, Validate, Publish
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)){thrownewIllegalArgumentException("Both dates or neither are required");}if(builder.from!=null&&!builder.from.isBefore(builder.toExclusive)){thrownewIllegalArgumentException("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 Borrowerwhen 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 outcome
Application outcome
Same identifier, kind S, status Y
Present student borrower
Same identifier, kind T, status Y
Present staff borrower
Null row
Empty result: borrower absent
Recognised kind, same identifier, status N
Empty result: borrower inactive
Agree the Translation Contract First (2 of 2)
Provider outcome
Application outcome
Unknown or null kind or status; mismatched identifier
DirectoryException: invalid provider data
Provider’s declared Failure
DirectoryException retaining the cause
Unexpected programming exception
Propagates; 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.
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");varfound=directory.find(borrowerId);if(found.isEmpty())return"Rejected: borrower not found or inactive.";Borrowerborrower=found.get();if(!borrowerId.equals(borrower.id())){thrownewDirectoryException("Directory returned a different borrower");}returncoordinator.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
Figure 8.2: Missing, Inactive, or Failed
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.
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 pressure
Candidate
Simpler alternative
Subclasses must create a workflow’s product
Factory Method
Inject a ready product into the workflow
Related product roles must vary together
Abstract Factory
One formatter or one assembly function
Many named options configure an immutable value
Builder
A clear constructor or named creation method
Compare by the Decision Isolated (2 of 2)
Observed pressure
Candidate
Simpler alternative
Provider interface and meanings differ
Adapter
Direct injection if the contracts already match
Optional responsibilities compose around one interface
Decorator
One 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 goal
Classify each suggested structure by what it actually does, and decide which are justified
You start with
The isolated pattern examples, the directory contract in 8.5 and the Chapter 6 notification contract
What is in bounds
Classify, 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 check
The source (is there a subclass hook?), the mapping table, tests for unknown codes, outage, immutability and decorator order
How to decide
A 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 claim
Diagnosis and correction
Renaming static create makes Factory Method
Names do not create a subclass hook: compare simpleExporter with ExportJob.createExporter
One product role requires Abstract Factory
No related set has been established: keep simple assembly
Catch every exception and return staff
Hides failure and grants an unsupported category
Solution (2 of 2)
Fictional claim
Diagnosis and correction
Wrap all senders with retry
Changes the one-attempt contract: keep retry isolated
Decorator order does not matter
Changes which layer observes each attempt: the two exact audit-history tests
A builder guarantees immutable products
Mutable 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 privilegescategory="S".equals(row.kind())?STUDENT:STAFF;// 2. turns provider or programming failures into absencecatch(RuntimeExceptionfailure){returnOptional.empty();}// 3. the product shares the builder's mutable listcolumns=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
Mistake
Consequence
Better question
Classify by names such as create or Factory
Misidentifies the extension mechanism
Which operation varies, and how?
Put provider codes inside borrowing policies
Couples business rules to provider representation
Where should translation live?
Treat absence and outage identically
Hides failure as an eligibility decision
Was a valid lookup completed?
Mistakes 4 to 6
Mistake
Consequence
Better question
Assume a family interface prevents all mixing
Overstates guarantees
Do concrete factories return compatible products?
Keep the builder’s mutable list
Previously built values can change
Does construction copy mutable state?
Assume decorator order is irrelevant
Changes audit meaning and failure visibility
Which calls pass through each layer?
Mistakes 7 and 8
Mistake
Consequence
Better question
Install retries without revisiting the contract
Duplicate attempts or effects
What outcome does the existing promise allow?
Require all patterns in the semester project
Complexity without a problem
Which 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
Integrate the borrower-directory boundary using the selected mapping contract.
Show staff and student duration outcomes without accepting a caller-supplied category at the new entry point.
Prove that missing, malformed and failed lookups create no reservations or notices.
Independent Work: Steps 4 to 6 (Optional)
Compare two designs for a second scenario: injected exporter versus Factory Method, or constructor versus Builder.
Write a problem-solution-consequence decision, including a justified non-use case.
Inspect the fictional AI answer and retain independent verification of your corrections.
Independent Work: Steps 7 to 9
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.
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.
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.
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
What distinguishes the worked Factory Method from simpleExporter?
Why are two unrelated construction methods insufficient justification for Abstract Factory?
Which property makes the built report request independent of subsequent builder changes?
How do Adapter and Decorator differ even though both may contain another object?
Diagnosis Questions
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.
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
ExportJob owns an inherited workflow and subclasses override a creation hook. simpleExporterselects and returns a product in an ordinary function. A method named create does not alone establish the former.
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
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.
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
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.
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
Gamma, Helm, Johnson, Vlissides:Design Patterns: Elements of Reusable Object-Oriented Software.
Gamma et al.: the Abstract Factory and Adapter publisher excerpts.
Oracle: Java SE 17 Optional and FilterInputStream.
JUnit Team: JUnit 5.13.4 User Guide.
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:
Distinguish an interchangeable policy from lifecycle-dependent behaviour.
Explain and test Strategy selection using the existing borrowing policies.
Define a transition table and implement a small State-based lifecycle.
Learning Outcomes (2 of 2)
Specify Observer ordering, subscription changes and failure behaviour.
Represent a request with Command and explain execution-time side effects.
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.
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
Borrowing limits may vary by borrower category.
A proposed workflow should allow cancellation before checkout but reject it after the item is on loan.
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
Question
Primary variation
Candidate
Which count/duration rule applies?
Chosen algorithm or policy
Strategy
Which action is legal now?
Current lifecycle position and transitions
State
Who reacts to this occurrence?
Registered interested recipients
Observer
What action should this request object execute?
Captured receiver and arguments
Command
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 status
Confirm
Check out
Return
Cancel
REQUESTED
CONFIRMED
Reject
Reject
CANCELLED
CONFIRMED
Reject
ON_LOAN
Reject
CANCELLED
ON_LOAN
Reject
Reject
RETURNED
Reject
RETURNED
Reject
Reject
Reject
Reject
CANCELLED
Reject
Reject
Reject
Reject
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
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.
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)
Situation
Selected outcome
No observers
Normal return; no delivery claim
Same observer object registered twice
Reject duplicate registration
Subscription closed
Remove that registration; repeated close is harmless
Subscribe or unsubscribe during a callback
Change subsequent publications, not the current snapshot
The Publisher’s Contract (2 of 2)
Situation
Selected outcome
Expected NotificationException
Retain failure and continue to later observers
Unexpected exception
Propagate immediately; later observers are not attempted
Recursive publication through the same publisher
Reject 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(Entryentry:List.copyOf(entries)){// snapshottry{entry.observer.onConfirmed(reservation);}catch(NotificationExceptionfailure){// expected failureif(aggregate==null)aggregate=newNotificationException("One or more observers failed");aggregate.addSuppressed(failure);}}if(aggregate!=null)throwaggregate;
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 aggregateNotificationException.
The coordinator returns its existing suffix: notification failed; reservation retained.
Figure 9.3: One Failing Observer
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.reserveimmediately 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
publicrecordReserveEquipmentCommand(DirectoryReservationServicereceiver,StringborrowerId,StringitemId,LocalDatestart,intdays)implementsCommand<String>{publicReserveEquipmentCommand{Objects.requireNonNull(receiver,"receiver");if(borrowerId==null||borrowerId.isBlank()||itemId==null||itemId.isBlank()){thrownewIllegalArgumentException("Identifiers must not be blank.");}Objects.requireNonNull(start,"start");}@OverridepublicStringexecute(){returnreceiver.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
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
Situation
First option
Pattern when justified
Two small fixed limit rules
One conditional or parameterised policy
Strategy for independent implementations
A small fixed transition table
Enum and explicit operations
State when grouping state-specific behaviour clarifies
One fixed notification output
Direct injected sender
Observer for a changing set of local recipients
One immediate reservation request
Direct service call
Command 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 goal
Decide whether each proposal fits a policy-selection problem or a lifecycle problem, and what each allows a caller to do
You start with
The lifecycle table in 9.3, the existing strategies, and the failure and replay behaviour in 9.4 and 9.5
What is in bounds
Compare 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 check
The transition table, the Strategy and State tests, the publisher and command tests
How to decide
A 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
Mistake
Consequence
Better action
Treat category policies as lifecycle positions
Confuses selection with permitted transitions
Identify what changes the behaviour
Expose an unrestricted state setter
Allows invalid jumps
Change state only through reviewed operations
Store context data in shared state objects
One reservation may affect another
Keep progress in each context
Mistakes 4 to 6
Mistake
Consequence
Better action
Catch all observer errors as expected failures
Hides programming defects
Specify and catch the declared failure type
Assume a thrown notification means no reservation exists
Misreads partial effects
Check the storage-before-publication sequence
Remove observers from a live iteration
Makes dispatch fragile
Define and test snapshot semantics
Mistakes 7 and 8
Mistake
Consequence
Better action
Treat a captured command as earlier permission
Bypasses current receiver state
Validate the use case at execution
Assume a pattern supplies retries or undo
Invents unimplemented guarantees
Define 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
Replace the repeated conditional in the isolated lifecycle with State delegation, preserving the table.
Show permitted and rejected actions, including unchanged status on failure and independent contexts.
Demonstrate the publisher’s order and expected-failure continuation. Construct a ReserveEquipmentCommandwithout executing it, show construction performs no reservation, then execute it.
Independent Work: Steps 4 to 6
Choose a second scenario and justify adopting or declining Strategy, Observer or Command against a simpler implementation.
Evaluate the fictional AI proposals and retain independent evidence.
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
Begin with the complete table and ConditionalReservation; run the table tests.
Introduce private State objects implementing the four operations: reject by default, override only permitted transitions.
The context delegates and accepts a returned successor only on success.
Run the same table against both; add the independent-context check.
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
Why is category-based borrowing policy a Strategy example while the proposed reservation lifecycle is a State example?
Why can stateless enum-based State objects be shared while reservation progress must remain separate?
What does snapshot iteration mean for an observer removed during publication?
What does a Command capture, and which decisions still belong to execution time?
Diagnosis Questions
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.
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
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.
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
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.
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
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.
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
Gamma, Helm, Johnson, Vlissides:Design Patterns: the Strategy, State, Observer and Command entries.
Oracle: Java SE 17 PropertyChangeSupport, Throwable and Runnable.
JUnit Team: JUnit 5.13.4 User Guide.
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:
Assign existing responsibilities to presentation, application, domain and infrastructure code.
Distinguish runtime calls from source-code dependency direction.
Explain entity identity, immutability and value equality without conflating them.
Locate a focused repository contract and its implementation.
Learning Outcomes (2 of 2)
Restructure a use case while checking its observable behaviour and compatibility limits.
Record an architecture decision and prepare evidence for peer review and Milestone 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:
A design pattern from Chapters 8 and 9 used where it removes a real problem, and one place you declined to use one.
A compact package diagram saying which packages may depend on which.
One restructuring change, with moves kept separate from logic changes.
Passing checks, including a dependency check or a documented review of imports.
Milestone 3 Checklist (2 of 2)
A short architecture decision record with alternatives, consequences and a revisit trigger.
Real peer review of that change, with the feedback addressed.
A demonstration and a retrospective with a next action.
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.
every 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).
ContractBorrowerDirectory and its failure DirectoryException: application.port. Callers must tell an outage from an absent borrower.
ImplementationLegacyDirectoryAdapter 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
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
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 request
Packages that change
What it shows
Staff may reserve for 21 days
domain (StaffReservationRules)
The policy change stays in one package
The provider renames its status code Y
infrastructure.directory
Provider vocabulary stays outside the application
The confirmation wording changes
application (ConfirmationFormatter)
Its users, including the console sender, see it without being edited
Five Change Requests (2 of 2)
Change request
Packages that change
What it shows
Reservations are stored in a file
infrastructure.persistence (a new class) and bootstrap
The coordinator is not edited: it names only the port
A third borrower category is added
domain, infrastructure.directory and bootstrap
One 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)
Package
Classes
Ca
Ce
I
domain
8
16
0
0.00
application.port
5
14
3
0.18
infrastructure.notification
3
7
3
0.30
application
6
9
4
0.31
Package Metrics (2 of 2)
Package
Classes
Ca
Ce
I
infrastructure.directory
2
2
1
0.33
infrastructure.persistence
1
1
1
0.50
presentation
1
1
1
0.50
bootstrap
7
0
7
1.00
From tools/PackageMetrics.java, run as java tools/PackageMetrics.java target/classes.
Instability as a Picture
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.
domaindeserves 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.
addrejects 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 addthrows 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)
Item
What to produce
What it should establish
1
Pattern note
A pattern used where it removes a real problem, with the reason; one place you declined
2
Compact package diagram
Actual responsibilities and declared dependency direction, matching your code
3
Restructuring diff or PR
What moved and what logic changed, kept apart
Preparing Evidence (2 of 3)
Item
What to produce
What it should establish
4
Regression result and dependency report
Existing examples still pass; references follow the chosen rules; any cycle is reported
A reviewer examined the change and the feedback was addressed
Preparing Evidence (3 of 3)
Item
What to produce
What it should establish
7
Demonstration and retrospective
What worked, what did not, one next action
8
Limits statement
What 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 claim
Finding
A runtime call to the in-memory store proves an application import of infrastructure
Incorrect: interface dispatch and source references are different relationships
Put the repository interface in infrastructure
Not justified: the consuming application would depend on an infrastructure-owned contract
Every entity needs a repository and service
No such requirement follows from identity; LoanPeriod has neither
Findings (2 of 2)
Fictional claim
Finding
An unmodifiable list makes every element immutable
Incorrect for mutable elements
Bootstrap must not import adapters
Incorrect for the selected composition role
Passing tests establishes peer review
Incorrect 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
Mistake
Consequence
Better action
Treat package names as enforced architecture
Public types can still form forbidden dependencies
State and check a dependency policy
Confuse runtime calls with source imports
Misdiagnoses interface-based dispatch
Trace both relationships separately
Move provider codes into the domain
Couples rules to an external representation
Keep translation in the adapter
Duplicate eligibility in presentation
Inconsistent entry points
Delegate the use case
Mistakes 5 to 8
Mistake
Consequence
Better action
Call immutable objects automatically value-equal
Misreads equals and collections
Inspect and test equality semantics
Add generic CRUD to every class
Invents unsupported lifecycle operations
Add only needed storage capabilities
Construct adapters inside application services
Hides concrete dependencies
Assemble them explicitly at bootstrap
Claim passing tests prove peer review
Substitutes one type of evidence for another
Record actual review separately
Mistakes 9 to 11
Mistake
Consequence
Better action
Grade a package by its instability number
A coarse number from few classes; a muddled package can score well
Use it to pick what to examine, then reason about responsibility
Report an isolated package as I = 0 or I = 1
The formula is undefined when Ca + Ce = 0
Report it as undefined and explain why
Accept a cycle because the layers look right
Two packages in one layer can still depend on each other
Check 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.
Class
Package
Must not refer to
StaffReservationRules
domain
Any application, infrastructure or presentation class
LegacyDirectoryAdapter
infrastructure.directory
presentation, bootstrap, an isolated example
DirectoryReservationService
application
Any infrastructure class
InMemoryReservationRepository
infrastructure.persistence
presentation, bootstrap
ReservationApplication
bootstrap
Nothing 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 InMemoryReservationRepositoryinsideReservationCoordinator. 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 typeprivatefinalReservationRepositoryrepository=newInMemoryReservationRepository();// WRONG: callers could change the store without add's checkspublicList<Reservation>findAll(){returnbackingList;}
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 firstrecords.put(reservation.id(),reservation);// WRONG: the console now owns a policy the domain already decidesif(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)
Fault
Before the three added tests
With the final tests
Duplicate id replaces the original; modifiable snapshot copy; live snapshot view; HashMap instead of insertion order
All four caught
Unchanged
Explicit null check removed in the repository
Survived: still throws NullPointerException, only the message differs
Survives (equivalent behaviour)
Stores after notification; reservations() modifiable copy; identifier skips a number; conflict check ignores stored records
All four caught
Unchanged
Do the Tests Detect Faults? (2 of 3)
Fault
Before the three added tests
With the final tests
Null repository accepted
Survived: no test constructed a coordinator without one
Caught by coordinatorRequiresARepository
One repository shared by every session
Caught
Unchanged
Console prints without a newline; swallows directory failures
Both caught
Unchanged
Console duplicates the seven-day rule
Survived: the console test used only a student
Caught by the ten-day staff test
Do the Tests Detect Faults? (3 of 3)
Fault
Before the three added tests
With the final tests
PeriodValue validation removed; LoanPeriod gains value equality
Both caught
Unchanged
Seven dependency-direction violations
All caught by the direction test
Unchanged
directory and notification packages refer to each other
Survived: each reference is individually permitted
Caught by the cycle test
A class name held in a string
Survived: jdeps reads compiled references
Survives: a documented limit
Of the 25 faults, 20 caught before the three tests were added and 23 by the final tests.
10.13 Questions to Think with, and a Transfer Challenge
Concept, diagnosis, transfer
Concept Questions
Why can the coordinator call an infrastructure object without depending on its concrete class?
What distinguishes an application service from a domain policy and a repository?
Why does an immutable reservation still have entity identity, and why does that not automatically define Java equality?
What can the jdeps check establish, and what does it leave for review?
Diagnosis Questions (1 of 2)
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.
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)
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.
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
The coordinator refers to ReservationRepository; bootstrap supplies an implementation; at runtime dispatch reaches the concrete method. Static and runtime diagrams answer different questions.
A service coordinates a use case; a policy expresses a rule; a repository provides access to stored records through a stated contract.
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.
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
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.
Two requests can both observe a free interval and append different IDs. Unique IDs do not make eligibility, conflict check and insertion atomic.
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.
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
Fowler:Presentation Domain Data Layering; Stafford: Service Layer; Hieatt and Mee: Repository.
Oracle: the Java 17 jdeps command; Java SE 17 List.
Cockburn:Hexagonal Architecture.
Evans:Domain-Driven Design Reference (2015), pp. 10 to 17.
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:
Diagnose a smell from a concrete difficulty of change, instead of treating it as a verdict.
Distinguish a refactoring from a behaviour change, and state what must stay the same.
Write characterisation tests that record current behaviour, including behaviour that looks wrong.
Learning Outcomes (2 of 2)
Create a seam that removes a hidden dependency, so that code can be tested.
Apply small refactorings (Extract Method, Extract Class, Move Method, Rename) with a check after each step and a way back.
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
Observe.
Pin what you observe.
Reshape the structure in small checked steps.
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
Observe: run the code and read it with a specific question in mind. You need not understand all of it.
Make the smallest edit that lets you test it: a seam. Keep it mechanical, so the compiler and a quick read can check it.
Pin: characterisation tests that record what the code does now.
Reshape: refactor in small steps, tests after each.
Only then change behaviour, in a separate step with its own tests.
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";returnout;}
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
Observation
How you know
Status
Returned loans are skipped
Read: continue on returned
Known
Fines start after a grace period and are capped
Read: two nested comparisons per kind
Known
Neither "S" nor "T" pays 1.00 a day, no grace, no cap
Read: the final else
Known what, unknown whether intended
Separate What You Know From What You Guess (2)
Observation
How you know
Status
The output depends on the date the program runs
Read: LocalDate.now()
Known
Amounts print with a comma in some locales
String.format without a locale; %f localises the separator
Likely; confirm by running
A loan two days late is listed with a fine of 0.00 and counted
The count is incremented inside late > 0
Likely; 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)
Smell
The 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 method
To change the fine you must read the loop, the formatting and the totals too
Smells in the Report (2 of 3)
Smell
The difficulty it causes
Hidden dependency on the system clock
The output differs every day: no test can compare it with a fixed answer
Hidden dependency on the default locale
The 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)
Smell
The difficulty it causes
A string kind compared with equals
An unexpected kind falls into the final branch without anyone deciding that
String += in a loop
Not 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)
Isolate the code so it can run in a test (often the hardest part).
Write a test with a placeholder expectation, a value you know to be wrong.
Run it and read the actual result from the failure message.
How to Write One (2 of 2)
Record that result as the expectation, after you have looked at it.
Rename the test to say what you now understand the code does.
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
Class
Meaning
What you do
Expected
Matches the rule you were told
Keep the test
Surprising
You did not expect it, but it may be intended
Keep the test; ask the owner
Suspected defect
It looks wrong
Keep the test, mark it clearly, record the question; do not fix it here
Unknown
You cannot tell
Keep the test and record the question
What the Report Does: Fines by Days Late
Observed Fines (1 of 2)
Kind
Days late
Fine
Comment
S
1, 2
0.00
Listed and counted with no fine (grace)
S
3
0.50
First fine
S
10
4.00
S
22, 23, 100
10.00
The cap is reached at 22 days and holds
T
1 to 5
0.00
Listed and counted with no fine (grace)
T
6
0.25
First fine
T
25, 26, 100
5.00
The cap is reached at 25 days and holds
Observed Fines (2 of 2)
Kind
Days late
Fine
Comment
X (unknown)
1
1.00
Suspected defect: no grace
X (unknown)
100
100.00
Suspected defect: no cap
s (lower case)
3
3.00
Suspected defect: a typo in the data costs six times the student fine
blank
2
2.00
Same 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"})voidotherKindsPayTheFullRateWithNoGraceOrCap(Stringkind,intdaysLate,Stringfine){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 dependency
Where it is
How to isolate it
The system clock
LocalDate.now() inside build
A parameter seam: add build(rows, today); the old method calls it
The default locale
String.format("%.2f", ...)
Pin the locale in the test and restore it afterwards
Standard output
print writes to System.out
Capture System.out in one test and restore it afterwards
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
doublefine=fineFor(r.kind,late);out+=r.item+" "+r.borrower+" "+late+" day(s) late, fine "+money(fine)+"\n";...privatestaticdoublefineFor(Stringkind,longlate){/* the three branches, moved as they were */}privatestaticStringmoney(doubleamount){returnString.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.
privatestaticfinalFinePolicySTUDENT=newFinePolicy(2,0.50,10.00);privatestaticfinalFinePolicySTAFF=newFinePolicy(5,0.25,5.00);/** Suspected defect, preserved on purpose: any other kind pays 1.00 a day
* with no grace and no cap. */privatestaticfinalFinePolicyANY_OTHER_KIND=newFinePolicy(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 code
FinePolicy.fineFor(daysLate) with the same numbers
if (late > 2) not taken: fine stays 0
daysLate <= graceDays returns 0
fine = (late - 2) * 0.50; if (fine > 10) fine = 10;
Check it algebraically, then let the tests confirm the algebra for the inputs they cover.
Figure 11.2: After Stage 3
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 OverdueReportholds three FinePolicy values; the dashed arrow means it only usesLoanRow.
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.
longdaysLate=ChronoUnit.DAYS.between(row.due,today);if(daysLate>0){doublefine=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
Commit
Kind
What the reviewer checks
1. Add a date parameter to build (seam)
Mechanical edit before tests
The old method delegates and nothing else moved
2. Add characterisation tests and the differential test
Tests only
Every expectation was run and reviewed; surprises commented
3. Extract fineFor and money
Refactoring
The moved code is identical; all tests pass
4. Extract FinePolicy
Refactoring
The algebra; all tests pass; the odd policy is visible
5. Rename and tidy
Refactoring
No 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
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
Field
Entry for the report
Item
Overdue report: fine rules duplicated, hidden clock and locale
Evidence
Grace 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
Decision
Paid down: seam, tests and refactoring in five commits
Remaining
Three 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 behaviour
Class
Question
A loan within grace is listed with fine 0.00 and counted
Surprising (SD-11-2)
Should it appear at all?
Student fines start day 3, 0.50 a day, stop at 10.00
Expected
None
Staff fines start day 6, 0.25 a day, stop at 5.00
Expected
None
Any other kind pays 1.00 a day, no grace, no cap
Suspected defect (SD-11-1)
Do other kinds exist? Reject them?
The Pinned Behaviours and Questions for the Owner (2 of 2)
Pinned behaviour
Class
Question
A loan due today, due later, or returned is not listed
Expected
None
A null kind throws NullPointerException, only for an overdue unreturned loan
Unknown (SD-11-3)
Can the export contain a null kind?
Amounts use the default locale: 0,50 under Locale.GERMANY
Surprising (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 goal
Decide whether a proposed refactoring patch preserves behaviour, using evidence rather than the patch’s own claims
You start with
The refactored OverdueReport from 11.5, the characterisation suite and the differential test
What is in bounds
Evaluate 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 check
Trace the boundary case by hand; apply each claim to a scratch copy; run the suite; read each failing test
How to decide
A 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
longdaysLate=ChronoUnit.DAYS.between(row.due,today);FinePolicypolicy=row.kind.equals("T")?STAFF:STUDENT;if(!row.due.isAfter(today)){doublefine=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)
Claim
Finding
!row.due.isAfter(today) is equivalent to daysLate > 0
False 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 check
A 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)
Claim
Finding
Unknown kinds fall back to the student policy
A 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 preserved
True 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
Mistake
Consequence
Better action
Fix a suspected defect during a refactoring
One commit cannot be reviewed as “no behaviour change”
Pin it, record the question, fix it later in its own commit
Leave a test changing the locale or System.out unrestored
Unrelated tests fail far away
Restore global state in finally or @AfterEach
Refactor several things per commit
Failures cannot be attributed
One refactoring per commit, tests between
Read “all tests pass” as “behaviour identical”
Untested behaviour was never protected
State what the tests cover and what they do not
Refactor everything that smells
Time spent where nothing will change
Pick smells that block a change you must make
Mistakes 9 to 11
Mistake
Consequence
Better action
Change the shape of a public type other programs use
Hidden callers break
Keep signatures and formats others depend on
Trust the seam edit because it compiled
The one untested edit could still have changed behaviour
Run the original beside the seamed version on a few rows, then pin
Accept “equivalent” for a boundary rewrite
isAfter and a day count differ exactly on the due date
Trace 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
Input
Observed
Class
Two rows for the same item, both overdue
Two lines, in input order, fines added
Expected
A student exactly 22 days late, then 23
10.00 for both; the cap is reached at 22
Expected (a boundary worth pinning)
A row with a blank item name
A line that starts with a space
Surprising: 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)
Area
Faults tried
Result
Student rules
Grace 2 to 3; rate 0.50 to 0.60; cap 10 to 9
All three caught
Staff rules
Grace 5 to 4; rate 0.25 to 0.30; cap 5 to 6
All three caught
Other kinds
One grace day; a cap of 10; lower-case s accepted as a student
All three caught
Do the Tests Detect Faults? (2 of 4)
Area
Faults tried
Result
Policy arithmetic
Math.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 rules
Due today counted as overdue; returned loans not skipped; count off by two; total not accumulated
All four caught
Zero-fine patches
Zero-fine loans skipped; fine computed first and zero-fine loans skipped
Both caught
Do the Tests Detect Faults? (3 of 4)
Area
Faults tried
Result
The AI claims
!isAfter; policy looked up first; unknown kinds as students; all three together
All four caught
Hidden dependencies
Amounts always formatted with a point; header uses the system date; print writes to standard error; short entry point uses yesterday
All four caught
Date arithmetic
Dates subtracted the wrong way round; days late from Period.getDays()
Both caught
Do the Tests Detect Faults? (4 of 4)
Area
Faults tried
Result
Bridge
The due day is the exclusive end; returned flag inverted; mismatched loan not detected; null kind passed through
All four caught
Service
A source failure becomes an empty report; null snapshot as empty; system date instead of the clock; clock zone ignored; snapshot cached between reports
All 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
Why is a characterisation test not the same as a regression test, and what does a passing characterisation test tell you?
What is a seam, and why is the date parameter a better first edit than rewriting the method?
Why should a suspected defect be pinned rather than fixed during a refactoring?
What can a differential test show that a table of examples cannot, and what can it not show?
Diagnosis Questions (1 of 2)
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.
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)
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
A regression test protects agreed-correct behaviour; a characterisation test records whatever the code does today. A pass means unchanged, not right.
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.
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.
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
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.
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.
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.
Chapter
Revision focus
6
Interfaces and ISP, dependency direction, constructor injection, composition root, what a container automates
7
Red-green-refactor, test anatomy, stubs, fakes, spies and mocks, controlled time, weak tests
Quiz 2 Revision Pointers (2 of 3)
Chapter
Revision focus
8
Factory Method, Abstract Factory, Builder, Adapter, Decorator, and choosing the simpler alternative
9
Strategy, State, Observer and Command, partial failure, and choosing a pattern by the variation it handles
10
Layers 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
Fowler:Definition of Refactoring; the Refactoring catalogue; Technical Debt; Code Smell; Legacy Seam.
Feathers:Characterization Testing; Testing Effectively With Legacy Code; Working Effectively with Legacy Code (2004).
Oracle: Java SE 17 Formatter, Clock, ChronoUnit and Period.
JUnit: User Guide 5.13.4.
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:
Define a measurement boundary and calculate lead time, cycle time, throughput, work in progress and work-item age.
Distinguish velocity from item throughput and explain why neither ranks individual contribution.
Reconcile burn-down and burn-up observations with additions, removals and completed work.
Learning Outcomes (2 of 2)
Use a small body of evidence to frame a conditional forecast and identify missing information.
Evaluate a technical-health or debt claim without confusing activity with quality or causation.
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.
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)
Field
Definition for the synthetic history
Unit
One uniquely identified backlog item; IDs A to H
Accepted
Item admitted to the tracked scope
Started
First transition into implementation
Done
Accepted against the team’s Definition of Done, including required checks and review
Removed
Explicitly taken out of scope without completion
The Measurement Contract (2 of 3)
Field
Definition for the synthetic history
Reporting interval
[2026-10-01T00:00:00Z, 2026-10-11T00:00:00Z)
Duration
Elapsed 24-hour days; waiting and weekends remain included
Snapshot
State immediately after events at the stated timestamp
Completion cohort
Items whose Done event falls within the reporting interval
The Measurement Contract (3 of 3)
Field
Definition for the synthetic history
Exclusions
No 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)
ID
Illustrative work
Points
Accepted
Started
Done
Removed
A
Reservation boundary examples
3
01
01
03
-
B
Directory mapping clarification
5
01
02
06
-
C
Confirmation wording
2
01
03
04
-
D
Command replay protection examples
3
01
05
08
-
The Complete Issue History (2 of 2)
ID
Illustrative work
Points
Accepted
Started
Done
Removed
E
Report failure investigation
5
01
07
-
-
F
Optional export format
2
01
-
-
08
G
Snapshot consistency checks
3
04
06
10
-
H
Review-notes improvement
2
06
08
-
-
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
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.
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 day
Scope points
Completed points
Remaining points
WIP items
01
20
0
20
1
02
20
0
20
2
03
20
3
17
2
04
23
5
18
1
05
23
5
18
2
06
25
10
15
2
The Daily Snapshots (2 of 2)
Snapshot day
Scope points
Completed points
Remaining points
WIP items
07
25
10
15
3
08
23
13
10
3
09
23
13
10
3
10
23
16
7
2
11
23
16
7
2
The Burn-Up
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.
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
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.
Keep provenance for each PR: URL or ID, opened timestamp, merged timestamp, branch. Do not guess midnight if the UI shows only relative dates.
Map the data:accepted and started both to the PR opened instant; done to the merged instant; leave points and removed blank.
Calculate, then manually verify the longest duration.
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 untilwas 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)
Claim
Evidence to seek
Cautious response
Approval changes will touch many unrelated classes
Trace actual category, directory, wiring and reservation references
Review the boundary before estimating
Reports are unreliable
Reproduce a specific wrong or unavailable report
Distinguish SD-11 policy uncertainty from an implementation failure
Claims and Cautious Responses (2 of 2)
Claim
Evidence to seek
Cautious response
Builds are slowing feedback
Timed comparable builds, environment, wait causes
Try one targeted improvement and observe later runs
A package is difficult to change
Dependency evidence and affected responsibilities
Refer 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)
Area
Planned impact
Evidence needed in Chapter 13
Borrower and directory
Add a visitor category and explicit external mapping
Known visitor, unknown code and existing category cases
Request orchestration
Validate a matching approval before a visitor reservation
Success; denied, expired, mismatched and unavailable outcomes
Data boundary
Parse the read-only approval register
Real temporary-file parsing and failure checks
Planned Impact (2 of 2)
Area
Planned impact
Evidence needed in Chapter 13
Policies and wiring
Add the three-day limit and all required collaborators
Boundary tests and composition that includes every category
Existing reservation, loan and report behaviour
Retain old results and SD-11 status
Existing regression suite plus targeted old-path examples
Delivery configuration
One visitor-enablement flag
Both 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)
ID
Book contract
CR12-1
A 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-2
Visitors 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-3
Reuse the student count limit: fewer than three retained reservations (the existing convention)
The Behaviour and Data Decisions (2 of 3)
ID
Book contract
CR12-4
One matching trusted approval for the exact tuple (borrowerId, itemId, start, endExclusive), nonblank sponsor, status APPROVED
CR12-5
One request-time instant from an injected Clock; valid when validFrom <= now < validUntil; equality at expiry is invalid
CR12-6
Missing match, DENIED, or out-of-window: expected rejection; no reservation, no confirmation
CR12-7
Unreadable 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)
ID
Book contract
CR12-8
Once approval and existing checks succeed, create the reservation with existing confirmation behaviour; an expected notification failure leaves the reservation in place
CR12-9
One flag visitingResearchersEnabled, default false; disabled visitor requests are rejected without reading the register; student and staff requests never need approval
CR12-10
Approval 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.
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)
Example
Input difference
Expected observation
AC1
CAM1, two days, matching approval valid at request time
One reservation; existing confirmation; one notice
AC2
CAM2, three days, its own matching record
Allowed; upper boundary
AC3
Four days
Reject; no state or notice change
AC4
Zero or negative days
Reject; no state or notice change
AC5
MIC1 with matching approval
Reject: outside the visitor allowlist
AC6
Missing approval or DENIED
Expected rejection; no change
The Acceptance Matrix (2 of 3)
Example
Input difference
Expected observation
AC7
Approval for V2, CAM2 or a different end date
Does not authorise this tuple
AC8
now == validFrom
Valid if all else holds
AC9
now == validUntil, or before validFrom
Rejected
AC10
Unreadable file, malformed row or duplicate key
Operational failure; no change
AC11
Three retained reservations exist
Reject the count limit even if loans were returned
AC12
Visitor feature disabled
Explicit disabled result; approval source not called
The Acceptance Matrix (3 of 3)
Example
Input difference
Expected observation
AC13
Existing student or staff request, either flag setting
Existing behaviour; approval source not called
AC14
Approved item overlaps an existing reservation
Existing availability rejection
AC15
Valid visitor, expected notification failure
Reservation retained
AC16
Reservation created, then approval expires or flag turns off
Checkout and return stay available
AC17
Unknown directory code or inactive visitor
Existing semantics; never default to student
AC18
Report generated after a visitor loan exists
Existing 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.
Add recognised visitor path, policies and explicit wiring
Valid visitor with a controlled approval source; old categories unchanged
C12-3
Implement and test the actual approval-file adapter
Temporary-file success and malformed, missing, duplicate failures; no mock replacing the reader under test
The Ordered Backlog (2 of 2)
ID
Ordered work
Exit evidence
C12-4
Complete matching, time and failure protection
AC boundary and operational-failure evidence
C12-5
One flag; integrate CLI and demo
Both settings; old path; existing reservations usable after disable
C12-6
Full regression, real boundary checks, delivery workflow
Observed 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 claim
Judgement
Six items and 18 points delivered
Contradicted: F is removed, not Done. Five items and 16 points
Mean cycle is two after subtracting blocking
Contradicted: the defined mean is 2.8; subtracting would give 2.4, a different measure
The day 04 rise proves productivity declined
Not established: G adds three while C completes two; the line rises by one
The day 08 fall proves five points delivered
Contradicted: D completes three; F removes two
Verdicts (2 of 2)
Candidate claim
Judgement
Legacy architecture caused the waiting
Not established: one mapping clarification is recorded
Remaining work will definitely finish in 0.44 iterations
Unsupported forecast: the division is roughly right; comparability, capacity and uncertainty are missing
Rank members by commits
Reject: 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
Mistake
Consequence
Correction
Treat removed work as Done
Inflates delivery and hides scope decisions
Retain a separate removal event
Divide by all accepted items when averaging completed duration
Treats unfinished cases as if their duration were known
Use the completed cohort; report open ages separately
Drop waiting from cycle time without saying so
Changes the question; delay disappears
Keep elapsed time; define any extra measure separately
Infer work-item completion from commits
Counts workflow traces as delivered value
Match the unit and event to the evidence
Mistakes 5 to 7
Mistake
Consequence
Correction
Use an incomplete PR sample as repository throughput
Understates or selectively counts completions
Enumerate the full interval or label a sample
Convert one velocity observation into a deadline
Hides variation, new scope and uncertainty
State conditional assumptions; revisit with new evidence
Add visitors by defaulting unknown categories to students
Bypasses the explicit new rule
Preserve 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
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.
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
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.
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.
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
How do lead time and cycle time differ under this chapter’s contract? Why must the boundaries travel with the reported number?
How can a burn-down fall without an equivalent amount of work being delivered?
Why can equal throughput coexist with different cycle times and different customer experiences?
Why does a passing regression suite support preserving old behaviour without proving the new approval requirement?
Diagnosis Questions
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?
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
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.
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.
A carried-in seven-day item and a newly completed one-day item count equally. Recipients experience different waiting.
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
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.
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
Kanban Guides:The Kanban Guide, May 2025.
Schwaber and Sutherland:The Scrum Guide, November 2020.
Fowler:Technical Debt Quadrant (2009).
Forsgren et al.:The SPACE of Developer Productivity, ACM Queue 19(1), 2021.
Git Project:git-log documentation. Oracle: Java SE 17 Duration.
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:
Choose unit, integration and end-to-end checks for different risks, and say what each cannot show.
Explain the scope of a contract test and write one that several implementations must pass.
Implement a changed requirement with regression protection and a clear separation between expected rejections and operational failures.
Learning Outcomes (2 of 2)
Configure a modest delivery pipeline, run it and read a failure in it.
Distinguish continuous delivery from continuous deployment, and label a simulated deployment as one.
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:
The sponsor register exported by the office has a stray column, so a visitor request ends in an exception.
The borrower directory takes eleven seconds to answer, and the officer’s screen freezes.
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 wrong
Where
Cheapest check that can notice
Validity window off by one at its edges
Approval record
Unit test with a controlled instant
A visitor served without a matching approval
Visitor gate and service
Acceptance examples with a controlled approval source
Stray column or a bad date in the register
File adapter
Integration test with a real temporary file
Risks and the Cheapest Check (2 of 2)
What could go wrong
Where
Cheapest check that can notice
Directory slow, down or answering nonsense
HTTP client
Integration test against a local stub service
Flag read in the demonstration but not in the build
Whole program
End-to-end journey in a separate process
Old behaviour changed by accident
Existing code
The existing regression suite
Each check has a limit. Next: what each one cannot show.
What Each Check Cannot Show
Check
What it cannot show
Unit test with a controlled instant
That the register file is read correctly
Acceptance examples with a controlled source
That the real register has the shape the source expects
Integration test with a real temporary file
That the office’s real export looks like the test file
Integration test against a local stub
That the real provider behaves like the stub
End-to-end journey in a separate process
Behaviour at scale, or with real users
Existing regression suite
Anything 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
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.
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 sees
What the client does
200 with one line, kind and status
Returns the row
404
Returns no row: the borrower is absent
500 or any other status
Fails: the directory could not answer
No complete answer within the bounded wait, even if the headers arrived
Fails with a timeout
The Directory Failure Policy (2 of 2)
What the client sees
What the client does
Connection refused
Fails
A body that is not exactly two fields
Fails: a guess is worse than a failure
An unknown kind code
Fails 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.
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 Chapter13Demoas 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)
Stage
What runs
Why here
1 Compile
mvn compile
Nothing else can run without it
2 Unit tests and architecture checks
Everything not tagged integration or e2e
Fast, controlled, no outside resources
3 Integration tests
Tests tagged integration
Real file system, real HTTP client, local stub
The Five Stages (2 of 2)
Stage
What runs
Why here
4 End-to-end journeys
Tests tagged e2e
Needs compiled classes and a free port
5 Package
mvn package, tests skipped because stages 2 to 4 passed
Produces the release candidate
What a Failure Means at Each Stage
Stage
If it fails
1 Compile
The code does not build
2 Unit and architecture
A rule or a boundary in our own code is wrong
3 Integration
An adapter does not meet its boundary
4 End-to-end
The pieces are not joined correctly, or a setting is not read
5 Package
The package cannot be built
Figure 13.2: The Pipeline of This Chapter
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 2mvn test -Dgroups=integration # stage 3mvn test -Dgroups=e2e # stage 4mvn 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.
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
Find the first failing stage and ignore everything after it.
Read the name of the first failing test before the stack trace: the name says what the test promised.
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.
Builds the jar.
Copies it into a local build/staging folder as the current release.
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.
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:
the released variant is accepted;
both settings have passed in the pipeline for the release candidate;
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
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 DirectoryReservationServicebefore 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.
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:
Your released requirement change, implemented from the smallest vertical slice outwards, behind one flag, with acceptance examples.
Regression evidence from your existing suite, with any necessary changes to test setups disclosed.
Integration evidence for at least one genuine boundary, including its failure cases.
An observed pipeline run, and a failing run that you have read.
What You Should Be Able to Show (2 of 2)
A delivery step, simulated or real, with a smoke test and a rollback.
Flag evidence: both settings tested and a removal plan.
A demonstration and a retrospective with a next action.
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
Item
What to produce
What it should establish
1
Change record and acceptance examples
Requirement and each example traceable to a test
2
Regression result
Old behaviour intact; changed test setups listed
3
Boundary tests
Real file, service or store used; both outcomes checked
4
Pipeline output
Which stage ran which checks; one real failure read correctly
5
Delivery record
Candidate smoke-tested; a rollback tried
6
Flag evidence
Both paths tested; a named condition for removal
7
Retrospective
What worked, what did not, one next action
8
Limits statement
E.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 goal
Decide what a proposed integration test actually verifies, and repair it so it verifies what its name claims
You start with
The approval adapter, the visitor gate and the integration tests of 13.2
What is in bounds
Judge and repair the proposed test; do not weaken the adapter or its other tests
How to check
Introduce one fault into the parser in a scratch copy and see which tests notice
How to decide
A 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.
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
Question
Answer
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
Run
Proposed test
Repaired test
No fault
Passes
Passes
Fault A: validFrom and validUntil columns read the wrong way round
Passes
Fails: ApprovalSourceException: Row 2 is not a valid approval record.
Fault B: a broken row skipped instead of rejecting the file
Passes
Fails: 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
Mistake
Consequence
Better action
Mock the boundary and call the test an integration test
Passes whatever the real adapter does
Use the real file or a controlled local service, and fault-test it
Test only the successful response
An outage or bad data is found in the demonstration
Test missing, slow, malformed and refused answers
Report an unreachable service as “not found”
A real borrower is told they do not exist
Fail the request; say the service could not answer
Add a timeout without deciding what happens next
Waits less long and still behaves unpredictably
Write the policy: one attempt, no retry, no fallback
Mistakes 5 to 8
Mistake
Consequence
Better action
Default an unknown category to the nearest known one
A visitor becomes a student without anyone deciding it
Treat an unknown code as a failure
Let an end-to-end journey check everything
Slow, brittle, duplicates other tests
Check the lines that carry the journey’s meaning
Use real clocks, ports or paths in tests
Runs differ by day, machine or luck
Inject the clock, take a free port, use a temporary directory
Assume a timeout covers the whole call
A trickling response holds the caller as long as the server likes
Bound the whole operation; test the stall you did not think of
Mistakes 9 to 11
Mistake
Consequence
Better action
A stage that can select no tests
A mistyped tag gives a green stage that checked nothing
Fail a stage that runs nothing
Treat a green pipeline as proof
Security and unseen behaviour are not examined
State what the stages check and what they do not
Switch a flag on by default, or never remove it
The old path rots and the code keeps both
Default 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{returnfetch(key);}catch(IOException|InterruptedExceptionfailure){returnnull;}// WRONG: an unknown kind is given the nearest known category.default->BorrowerCategory.STUDENT;// WRONG: no timeout; the request waits for ever.HttpRequestrequest=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.publicstaticbooleanvisitingResearchersEnabled=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
Example
Deciding production code
Observable result
Stage
AC9 (expiry boundary)
ApprovalRecord.validAt, called from VisitorGate.rejection
Rejected: no valid sponsor approval. at validUntil; no reservation
2
AC10 (operational failure)
FileApprovalSource.readAll throws; nothing catches it on the request path
ApprovalSourceException; no reservation or notice
2 and 4
AC12 (the flag)
VisitorGate.rejection, the first check
Rejected: visitor reservations are not enabled.; register not read
2 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
Why can a unit test not show that a file adapter parses the real register? What does it show instead?
What does the directory contract test say, and what can it never show about the real provider?
Why is “not found” the wrong answer when the directory times out? Name two other defaults the policy refuses.
What is the difference between continuous delivery and continuous deployment, and which does simulate-release.sh imitate?
Diagnosis Questions
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?
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.
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
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.
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.
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.
Delivery keeps software releasable at any time; deployment releases every passing change automatically. The script imitates delivery only.
Guidance: Questions 5 to 7
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.
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.
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)
Risk
Check
Approval rule: only approved versions served externally
Unit 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 duplicate
Integration 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 nonsense
Integration 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)
Risk
Check
Flag on in the demonstration and off in the build
One end-to-end journey that starts the serving process with each setting
Old behaviour changed
The 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.
GitHub:Workflow syntax for GitHub Actions. Maven:Using JUnit 5 Platform in Surefire.
Oracle:HttpRequest.Builder, HttpClient, com.sun.net.httpserver (Java SE 17). JUnit:User Guide 5.13.4.
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:
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.
Prioritise remaining release work, and choose between fixing, deferring and accepting a known issue.
Produce reproducible handover instructions and check them by following them in a fresh copy.
Learning Outcomes (2 of 2)
Trace requirements to implementation and tests, and keep that trace true with a check.
Support retrospective conclusions with evidence, and write an individual reflection that explains your own contribution.
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.
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”.
The README promises one number of passing tests; the build reports another.
The requirements table points at a test that was renamed last week.
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
Question
Evidence
Weakness
Which responsibilities were affected?
Files changed and added, and what each does
Counts files and lines, not the importance of a line
What broke when the change started?
Test failures after the smallest edit that introduces the change
Shows 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 change
Only the assumptions someone wrote down
What did a reviewer say?
Actual review comments and the responses
Only as good as the review
Affected Responsibilities: The Numbers
Between the Chapter 11 and Chapter 13 versions, 23 files were added and 10 changed.
Group
Files
Lines
New production files
9
399
Existing production files changed
5
21 added, 3 removed
New test files
10
992
Existing test files changed
3
10 added, 4 removed
New scripts, workflow and record
4
213
README and POM changed
2
14 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.
Five existing production files changed, by a few lines each:
Existing file
Change
BorrowerCategory
+1 −1
DirectoryReservationService
+14
ReservationApplication
+3 −1
LegacyDirectoryAdapter
+1
LegacyDirectoryClient
+2 −1
Figure 14.1: The Footprint of the Visitor Change
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.
Experiment
Edit
Result
E1 (Chapter 11 version, 159 invocations)
Add VISITOR to the enum only
45 of 159 fail (1 failure, 44 errors); 89 lines say Missing policy: VISITOR
E2 (the same)
The enum, VisitorReservationRules and the composition root
5 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 class
Failing
Test class
Failing
VisitorAcceptanceTest
19
NotificationPublisherTest
3
DirectoryOutageIntegrationTest
10
NotificationMockitoTest
3
LoanServiceTest
9
Chapter13JourneyE2ETest
3
AvailabilityTest
8
RepositoryBoundaryTest
1
ReserveEquipmentCommandTest
4
OverdueBridgeTest
1
LegacyRegressionTest
4
AvailabilityTddTest
1
DirectoryAdapterTest
4
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 showed
Verdict
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 added
Held, 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 map
Held, 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 answer
Contract held; behaviour extended
Revised Assumptions (2 of 2)
Assumption (where made)
What the change showed
Verdict
The count of retained reservations is the borrowing count (Ch. 7)
CR-12 preserved it on purpose; AC11 pins it
Still 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 chose
Still open (SD-11-1)
Notifications are one attempt, no retry (Ch. 6)
The directory client follows the same rule
Held 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.
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
#
Comment
Check
Verdict
R1
“DirectoryReservationService now branches on VISITOR. Will that grow?”
One if on the category, delegating to the gate
Valid: 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 state
No issue
R3
“Is the old two-argument constructor safe for visitors?”
ac12_... builds one and asserts the disabled rejection
Covered
R4
“The README says 230 tests. I count 233.”
The count appeared in both README and HANDOVER
Valid: 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
Does it block a requirement or the demonstration? If yes, it is a blocker: fix it first or change the plan.
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.
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
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)
ID
Item
Why 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 overdue
Needs 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 loan
Ask 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)
ID
Item
Why deferred
KI-6 (SD-12)
The count rule counts every retained reservation, including returned ones
Preserved on purpose by CR-12 and pinned by AC11; owner decides
KI-12
No Java 17 run and no hosted CI run; action versions not re-checked
Needs a JDK 17 and a hosted runner
KI-13
The visitor flag is still in the code
Decided 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)
ID
Item
Why accepted
KI-7
One bad row in the approval register rejects the whole register
A silent skip could hide a lost approval or a duplicate; revisit with real use
KI-8
The directory wait defaults to one second and was not measured; it bounds the whole lookup, body included
A setting (lending.directory.timeout.millis); measure before changing. Stalled-body case tested on a newer JDK only
KI-9
No retry and no cached answer after an outage
By design (ADR-013); revisit after measured outages
Accepted Issues (2 of 2)
ID
Item
Why accepted
KI-10
The directory stub and the approval file are stand-ins
Needs a provider contract test against a real service, which does not exist here
KI-11
Concurrency (two writers of the register, simultaneous requests) was not considered
Out of scope; the register is a trusted sequential file
KI-14
DirectoryReservationService branches on the visitor category
Fine for one special category; at a second, refactor with its own tests
Resist the Plausible Additions
Tempting addition
Why not now
Retry the directory call after a timeout
Changes the one-attempt contract of earlier chapters; nobody has measured an outage
Replace the approval file with JSON or a database
Nothing 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 rate
A decision for the records office, not the team
Add a second category of visitor
A new requirement: raise it as a change request, not as polish
The Costs of a Feature Built Before It Is Needed
Fowler names four:
The cost of building it.
The cost of the delay it causes.
The cost of carrying its complexity.
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)
Document
Question it answers
How it is kept true
README.md
What is this, and where do I start?
Short; points to the others
docs/HANDOVER.md
How 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.md
Which test checks each requirement?
TraceabilityTest fails if a named test or class is gone
The Six Documents (2 of 2)
Document
Question it answers
How it is kept true
docs/KNOWN-ISSUES.md
What is wrong or missing, and what was decided?
A reviewer; every row has a decision
docs/ADR-0NN-*.md
Why is it built this way?
Never edited after acceptance except its status; a new record supersedes it
docs/RELEASE-NOTES-*.md
What 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-013
State
The pipeline has run both settings
True
The visitor contract has been accepted by an owner
Not yet
A decision to keep visitors enabled is recorded
Not 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
File
Change
VisitorGate.java
Removes DISABLED, the flag and the disabled() factory; adds noApprovals()
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
What TraceabilityTest Checks
Check
Method
Every test named in the Evidence column exists as a method
everyTestNamedInTheTableStillExists (also requires at least 29 named tests, so a deleted row cannot hide)
Every class named in the Decided by column exists under src/main
everyClassNamedAsDecidingStillExists
AC1 to AC18 each have exactly one row, and every ac<N>_ test appears in the table
A deciding class is renamed in the table (VisitorGuard)
Caught
An acceptance test is added that no row mentions
Caught
A row names an extra test that exists
Not caught; harmless
AC3 is pointed at the AC1 test, which exists
Not 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?
Fault
Output
A test added
FAIL stage 2 ran 206 invocations: stage 2 shows 207; one problem
A word changed in the demonstration output
FAIL 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 invocations
FAIL 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
Stage
Chapter 13
Chapter 14
2 Unit tests and architecture checks
203
206 (adds TraceabilityTest, 3)
3 Integration
26
26
4 End-to-end
3
3
Total
232
235
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.
Finding
Effect on a new reader
Change
D1. The setup assumed mvn was on the PATH; on the test machine it was not, and the failure said only command not found
The reader stops at step 1
The 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 warning
A reader may think the build is broken
The document lists “things you may see that are not errors”
What the Dry Run Found (2 of 2)
Finding
Effect on a new reader
Change
D3. The demonstration needed a directory service that only the tests could start (KI-1)
The reader could not see the system work
DemoRun 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 setup
Also 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
Observation
Evidence
Action
Owner and date
“Reviews were slow”
Two pull requests waited more than two days (board timestamps)
Review before starting a new item
Named person, next sprint
“The pipeline was flaky”
One of ten runs failed with no code change (run list)
Fix or quarantine the unstable test
Named person
“We rushed the end”
Seven items moved to Done on the last day
Plan a smaller scope and a freeze
Whole 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
Evidence
What it can show
What it cannot show
Commits
That a change was made, by which author identity, when, and what it touched
Effort, 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 reviews
That 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 evaluation
How teammates experienced each other’s work, including things no tool records
Objectivity: 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.
Commits
Formatting only
Lines changed
PR reviews given
Requested changes
P1
38
17
410
4
1
P2
9
0
1,150
11
5
P3
24
3
620
7
0
Team total: 71 commits and 2,180 changed lines.
Four Rankings, Three Orders
Measure
Order
Share
Commits
P1, P3, P2
P1 has 38 of 71, more than half
Lines changed
P2, P3, P1
P1 has 410 of 2,180: 19%
Reviews given
P2, P3, P1
Reviews requesting changes
P2, 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.
Part
Prompts
What I did
Three to five specific pieces of work, each with a link or path
What I decided
A design or process decision, the alternatives and why
What went wrong
A real fault or misjudgement, how it was found, the fix, what changed
The Reflection Prompts, Continued
Part
Prompts
How I used AI
Where, with what input, what you accepted or rejected, how you verified it
What I did not do
Honest limits: work started and not finished, work you relied on others for
What I would change
One 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:
clean object-oriented design;
justified use of design patterns and architecture;
automated tests with continuous integration;
evidence of safe refactoring;
adaptability to the injected change in requirements.
An Evidence Index for the Book’s Project (1 of 2)
Must show
Evidence
What it does not show
Clean OO design
Responsibilities and dependencies of Chapters 2, 5, 6, 10; the architecture tests
That it is the best design, or that anyone else reviewed it
Justified patterns and architecture
Chapters 9 and 10; ADR-010
Any pattern the design does not use
Automated tests with CI
The test suite; the five-stage pipeline; the workflow file
Chapter 13 footprint and tests; the requirements table; the rehearsed flag removal
Whether the visitor rule is the right policy
A Short Sequence
Finish. The final sprint and workshop feedback are for finishing.
Freeze. Decide a time after which only fixes to the handover and blockers are allowed.
Run the handover check on the frozen version.
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.
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 goal
Decide which statements in an AI-drafted README are supported by the submitted version, and correct the rest
You start with
The README draft and the project at version 0.14.0
What is in bounds
Verify every command, every feature claim and every limitation; edit only the draft
How to check
Run each command; read the code, tests and decision records each claim depends on
How to decide
A 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
#
Statement
Verdict
Evidence
1
A Java 11 application
Contradicted
release is 17; javac --release 11 fails at LifecycleExamples.java: switch rules not supported
2
Supports students, staff and visiting researchers
Supported
BorrowerCategory, ADR-013
3
Build: mvn package
Supported
Produces target/equipment-lending-0.14.0.jar
4
Run: java -jar target/equipment-lending.jar
Contradicted
Unable to access jarfile; with the right name: no main manifest attribute. Run java -cp target/classes edu.se506.lending.bootstrap.ReservationApplication
5
mvn verify runs all 230 tests
Contradicted
233 at the time of checking
6
…under 30 seconds
Not established
About 21 seconds on one machine with cached dependencies: do not promise a time
Verdicts: Statements 7 to 11
#
Statement
Verdict
Evidence
7
Visitors enabled by default; -Dlending.visitors=false turns them off
Contradicted
Default is false; the demo prints Visitors enabled: false
8
CAM1 or CAM2, one to three days, matching approval
Supported
C2, C3, C4 of the requirements table
9
Approvals are read from a CSV file
Contradicted
Tab-separated, fixed header, eight columns
10
The file is cached in memory
Contradicted
theFileIsReadAfreshOnEveryCall
11
One second, retries three times, then treats the borrower as a student
Contradicted (the one-second default is right)
One attempt, no retry, no fallback; ...IsNotRetried asserts one request
Verdicts: Statements 12 to 16
#
Statement
Verdict
Evidence
12
pipeline.sh runs five stages
Supported
Compile, unit and architecture, integration, end-to-end, package
13
…and deploys to the staging server
Contradicted
Stage 5 only packages; the simulation copies a jar into a local folder
14
Rules enforced by DependencyRulesTest
Partly supported
Checks dependency direction (a separate test checks cycles), not every rule
15
Production-ready and safe for concurrent use
Not established, contradicted by the project’s own limits
KI-10 and KI-11
16
bash scripts/demo.sh shows visitors rejected then approved
Supported
The 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
Mistake
Consequence
Better action
The last week as a chance to add a feature
Late faults; a handover describing a system that no longer exists
Triage as fix, defer or accept; freeze
“Fixing” a suspected defect that needs an owner’s answer
A guess becomes a rule; the question is lost
Defer with the question, the owner and the test that will change
Judging a change by the size of its diff
A tiny diff that fails the requirement looks good
Judge against the acceptance examples and across all stages
A README written from memory
Counts, commands and defaults that were once true
Follow it in a fresh copy; let a script check what it can
Documents nobody checks
They rot, and a reader trusts them anyway
A check or a reviewer for each
Mistakes 6 to 10
Mistake
Consequence
Better action
Editing an old decision record to match what happened
The reasons for the decision are lost
A new record that supersedes or applies it
Removing a flag as tidy-up
The specification changes silently, and so does rollback
Rehearse, decide, update the examples that mention the flag
Ranking teammates by commit counts
Habits mistaken for contribution
Counts point to the work; read the work
“I contributed a lot”
Nothing can be checked
Claims with links, and honest limits
Claiming a hosted run, a review or a Java 17 run that did not happen
The submission loses credibility, and so do the true claims
Say what was not run
Diagnostic Alternative: A Number Stated in Two Places
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.privatestaticfinalFinePolicyVISITOR=newFinePolicy(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
Item
Decision
Reason
KI-2 (SD-11-1, visitor late-fine rate)
Defer
Needs the records office; any rate would be a guess
KI-7 (whole-file rejection of the register)
Accept
A silent skip could hide a lost approval or a duplicate
KI-8 (one-second wait, not measured)
Accept
A setting; measure before changing
KI-12 (no Java 17 or hosted CI run)
Defer
Needs a JDK 17 and a hosted runner
KI-14 (the VISITOR branch)
Accept
Fine 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.mdby 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
Name the four questions of a change review and what each one’s evidence cannot show.
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?
What is the difference between deferring an issue and accepting it? Give an example of each from the project.
What can a requirements-to-evidence check confirm, and what can it not?
Diagnosis Questions
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.
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
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).
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.
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).
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
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.
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)
Item
Content
README for a stranger
What 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 check
Runtime and tool versions with the output they should print; what to do if one is missing; the warnings that are not errors
Demonstration
A 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 evidence
Each 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)
Item
Content
Known issues
Including what was not evaluated about the model: accuracy, fairness, drift, behaviour on data unlike the synthetic data
Decision record
The flag that gates a model version: conditions for removal, the owner, and what rollback does not undo (responses already served)
Limits statement
No 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
Nygard:Documenting Architecture Decisions (2011). Keep a Changelog 1.1.0.
Schwaber and Sutherland:The Scrum Guide (2020). Kerth:The Retrospective Prime Directive.
GitHub: documentation on contribution graphs, commits with multiple authors and pull request reviews.
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:
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.
Evaluate candidate explanations, code and tests against evidence, claim by claim.
Recognise unsupported APIs, invented methods, unnecessary abstractions and tests that repeat the code’s mistakes.
Record useful provenance and verification evidence.
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.”
It compiles on your laptop. The tests it wrote pass. You commit.
The next morning the pipeline fails to compile: one call exists only from Java 21.
A reviewer asks why the tests agree with the code when your teammate’s examples do not.
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)
You are accountable. The assistant proposes. You decide, and you answer for what you commit.
The requirement is yours. An assistant can ask good questions about it. It cannot decide what the equipment office wants.
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)
Share the minimum. No secrets and no personal data. Use synthetic data.
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
Question
Who 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
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)
Chapter
What the candidate was
The independent check
1
An explanation of a small method
The code and a hand trace
2
An explanation of the coordinator
A trace and the supplied checks
3
Suggested story assumptions and criteria
The stated rules and scope
4
An iteration plan
Capacity arithmetic and the gate output
5
A “make this SOLID” proposal
Compilation and exact-output checks
6
A refactoring that claimed to decouple
The source: which classes are named?
7
A query and its tests
The reviewed examples and a wrong-boundary mutation
The Activities You Have Already Done (2 of 2)
Chapter
What the candidate was
The independent check
8
Suggested structures
The source and tests for unknown codes, outage and order
9
Strategy or State
The transition table and tests
10
A dependency review
Actual imports and compiled dependencies
11
A refactoring patch
A hand trace, a scratch copy and the characterisation suite
12
An explanation of metrics
Recalculation
13
An integration test
Faults applied to the parser
14
A README
Running 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)
Part
What to say
Example from the lab
Task
One sentence about what is wanted
Add earliestStart(from, days, searchDays) that returns the earliest start date the item could be reserved
Context
Only the code and facts the task needs
The source of LoanPeriod; the bookings arrive as a List<LoanPeriod>
Constraints
The baseline and what is forbidden
Java 17; standard library only; no new dependencies
The Five Parts of a Work Order (2 of 2)
Part
What to say
Example from the lab
Output
The form of the answer
The complete AvailabilityFinder.java and nothing else
Scope
What it may touch, and what it may not
Do 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
State the requirement precisely in the prompt. Share reviewed examples if they help.
Add boundary cases or properties that you derived independently, and keep some of them undisclosed when you want to judge whether the code generalises.
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)
#
Claim
Verdict
Evidence
1
The flag is checked first and the “not enabled” text returned
Supported
First statement of rejection
2
Then the item is checked against CAM1 or CAM2
Supported
ALLOWED_ITEMS = Set.of("CAM1", "CAM2")
3
Fewer than one day returns empty without asking anyone
Supported
ac4_... asserts the register is not read
4
The clock is read once
Supported
oneRequestReadsTheClockOnce
Checking the Explanation (2 of 2)
#
Claim
Verdict
Evidence
5
Exact request asked; rejected unless the record matches and is valid at that instant
No field holds a record; fields are the source, the clock and the flag
7
Each rejection is written to the audit log
Contradicted
No logger and no call that writes anywhere
8
If the source throws, the gate returns the “no valid sponsor approval” rejection
Contradicted
anOperationalFailureOfTheSourceIsNotCaught
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.
ID
Request
Result
EX1
from 20, 2 days, window 10
22 October (the end is exclusive)
EX2
from 20, 3 days, window 10
26 October (does not fit before the 24th)
EX3
from 10, 2 days, window 10; and no bookings, from 10, 5 days, window 1
10 October
EX4
from 20, 2 days, window 2
none (the candidates are 20 and 21)
EX5
from 20, 2 days, window 3
22 October (the third candidate is included); window 2 gives none
Acceptance Examples EX6 to EX9
ID
Request
Result
EX6
booking 18 to 20 only; from 20, 2 days, window 1
20 October
EX7
days 0, window 0, a null from, a null list
the exceptions above
EX8
the caller’s list gains a booking after construction
the finder is unchanged
EX9
one booking covering 1 to 31 October; from 5, 1 day, window 10
none
One Acceptance Example in Code
/** CAM1 is booked for [20, 22) and [24, 26). */privatestaticAvailabilityFindercam1(){returnnewAvailabilityFinder(List.of(period(20,22),period(24,26)));}@Testvoidex4_theWindowCountsCandidateStartDatesAndNotDaysAfterFrom(){assertEquals(Optional.empty(),cam1().earliestStart(oct(20),2,2));}@Testvoidex5_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:
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
Command
Result
javac -d out1 *.java (JDK 21 or later, no --release)
One error: intersects
javac --release 17 -d out2 *.java
Two errors: intersects and getFirst
After changing only intersects to overlaps: plain javac
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
Run
Result
Attempt 2 with its own seven generated tests
7 pass
Attempt 2 with the acceptance tests as written
Does not compile: reference to AvailabilityFinder is ambiguous at new AvailabilityFinder(null) in ex7
Attempt 2 with the acceptance tests after a one-line cast
9 run, 2 fail: ex4_... and ex5_...
The 7 generated tests against the accepted finder
6 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.
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.mdasks 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
publicOptional<LocalDate>earliestStart(LocalDatefrom,intdays,intsearchDays){Objects.requireNonNull(from,"from");if(days<1)thrownewIllegalArgumentException("days must be at least 1");if(searchDays<1)thrownewIllegalArgumentException("searchDays must be at least 1");for(intoffset=0;offset<searchDays;offset++){LocalDatestart=from.plusDays(offset);LoanPeriodwanted=newLoanPeriod(start,start.plusDays(days));if(booked.stream().noneMatch(wanted::overlaps))returnOptional.of(start);}returnOptional.empty();}
The constructor does List.copyOf(Objects.requireNonNull(booked, "booked")).
Figure 15.2: The Worked Example
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.
Caught
Survived
Acceptance examples (9 tests)
8 of 10
2, both equivalent
Generated tests (6 that pass on correct code)
4 of 10
6
The Ten Faults (1 of 2)
Fault
Acceptance examples
Generated tests
Loop runs one step too far (attempt 2’s own fault)
Caught: EX4, EX5
Survived
Loop stops one step early
Caught: EX3, EX5, EX6
Survived
A stay of days occupies days - 1 days
Caught: EX2, EX9
Caught
The search skips from itself
Caught: EX3 to EX6
Caught
noneMatch becomes allMatch
Caught: seven examples
Caught: three tests
The Ten Faults (2 of 2)
Fault
Acceptance examples
Generated tests
Overlap treats an end date as occupied
Caught: five examples
Caught
days = 0 is accepted
Survived (equivalent)
Survived
searchDays = 0 is accepted
Caught: EX7
Survived
The caller’s list is kept, not copied
Caught: EX8
Survived
A null from is not rejected early
Survived (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 mode
What it looks like
Seen in
A check that exposes it
Invented method or class
A call to something that does not exist, named plausibly
Lab attempt 1
The compiler
API newer than the baseline
Compiles on your default JDK, fails on the project’s
Lab attempt 1
Compile with the project’s --release
Invented or wrong dependency
A coordinate that does not exist, or a real library you did not need
15.7
Look it up; read the build
Plausible but wrong boundary
A comparison one step off
Chapters 7, 11, 13; attempt 2
Examples that sit on the boundary
Failure Modes (2 of 3)
Failure mode
What it looks like
Seen in
A check that exposes it
Tests that repeat the code
Tests derived from the implementation agree with its mistakes
Chapters 7, 13; attempt 2
Examples written beforehand; fault experiments
Unnecessary abstraction
An interface with one implementation, patterns for their own sake
Chapters 5, 6, 8, 10; attempt 2
Ask what change it makes easier
Silent policy change
A requirement decided inside a “tidy-up”
Chapters 3, 11
Diff the behaviour, not the text
Unsupported causal claim
“Proves”, “caused”, “will definitely”
Chapter 12
Ask what evidence would show it
Failure Modes (3 of 3)
Failure mode
What it looks like
Seen in
A check that exposes it
Confident description of unseen code
Behaviour that the code does not have
Chapters 1, 2; 15.3
Read the code; trace
Self-reported verification
“I tested it and it works”
Lab attempts 1 and 2
Run it yourself, under the project’s settings
Stale knowledge
A deprecated or removed API; an old configuration
Anywhere
Official 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 prompt
Why it should not be sent
A student’s name, student ID and email
Personal data about a real borrower
A configuration file with a password
A credential. Anything secret in real life must not be pasted
An internal address and path
Information about your infrastructure
200 lines of production log with other borrowers’ names
More 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)
Check
How, for the book’s toolchain
What it does not show
The coordinate exists
Ask Maven Central for the artifact’s page and read the status
That it is the library you meant, or safe
It is the project you think it is
The group ID belongs to the project’s own site; compare with its documentation
That the version has no defects
The version exists and is pinned
The exact version’s POM returns 200; the project pins versions
Compatibility with your other dependencies
The Dependency Checks (2 of 2)
Check
How
What it does not show
You need it
Can the standard library or an existing dependency do the job?
It is allowed
Its licence, and your team’s rules
Anything 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.
Coordinate
Status
mockito-core, mockito-junit-jupiter
200
mockito-fluent
404
mockito-core:5.20.0 (the .pom)
200
mockito-core:9.9.9
404
guava, guava:33.0.0-jre
200
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)
Evidence
What it can show
What it cannot
Requirement and examples written beforehand
What was intended
That your reading of the requirement is right
Hand trace on selected inputs
The code behaves as you think on those inputs
Behaviour elsewhere
Compiler and type checker, under the project’s settings
Syntax, types, the API available at the baseline
Meaning
Tests
Behaviour on the cases tested
Cases not tested, or that the tests are good
What Evidence Can Show (2 of 2)
Evidence
What it can show
What it cannot
Fault experiments on the tests
That the tests notice those faults
Others; equivalent faults cannot be noticed
Traced dependencies (jdeps, the build’s own listing)
What the code actually references
Whether those references are wise
Human review
Judgement that tools lack
Thoroughness, 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)
Part
Contents
Task
What was asked and who owns the requirement
Tool and model
The tool and model, or “none: constructed example”, and the date
Context given
What the assistant saw, and what was left out and why
Output received
Each candidate in order, with accepted or rejected. Keep the rejected ones
A Compact Record: Eight Parts (2 of 2)
Part
Contents
Decision
What was accepted or rejected, and why
Changes made
What you changed by hand
Verification
The checks, the commands, the results and the date; which checks are independent of the assistant
Limits
What 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:
Attempt 1: rejected. It did not compile: LoanPeriod.intersects does not exist and List.getFirst() exists only from Java 21.
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
Mistake
Consequence
Better action
Writing the requirement in the prompt and nowhere else
The prompt becomes the only record; the assistant’s reading becomes the requirement
Write requirement and acceptance examples first, separately
Asking for code and tests together
The tests agree with the code’s mistakes
Examples first; tests from the requirement; fault experiments
Accepting “I tested it”
A claim stands in for a run
Run it under the project’s settings
Compiling with your default settings
A newer API slips through
Use the project’s --release and build commands
Adding a suggested dependency without looking
Invented, unnecessary or hostile libraries
Look it up, ask whether you need it, pin it
Mistakes 6 to 10
Mistake
Consequence
Better action
Pasting real data or secrets
May leave your machine, and stay in your history either way
Synthetic data; the minimum context
Using another assistant to check the first
The same weaknesses twice
Independent evidence
Throwing away a whole explanation for one wrong sentence
The correct parts are lost
Classify claim by claim and correct
Keeping only the accepted answer
A reviewer cannot see what was rejected, or why
Keep rejected attempts in the record
Writing “AI-assisted” and no more
Nobody can judge the work
A 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.
BUILD FAILURE: two cannot find symbol errors at lines 18 and 20
try-attempt.sh 2 generated
Tests run: 7, Failures: 0
try-attempt.sh 2 (acceptance)
BUILD FAILURE: reference to AvailabilityFinder is ambiguous
try-attempt.sh 2 adapted
Tests run: 9, Failures: 2: ex4_... and ex5_...
check-record.sh
RECORD CHECK PASSED
check-record.sh, only the template exists
RECORD 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.
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
Why is “I tested it with Java 17” not evidence? Name two checks that would be.
An assistant writes a function and its tests, and they pass. Why does that not establish that the function is right, and what would?
What does the --release option change that --source and --target alone do not?
A dependency suggested by an assistant exists on Maven Central. What have you learned and what have you not?
Diagnosis Questions
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?
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
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.
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.
--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.
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
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.
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)
Item
Content
Requirement and examples
The 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 share
The function signature, the record type, the constraints. Synthetic records only
Not to share
Real records, keys, tokens, the registry’s address, any real model or customer identifiers
Independent checks
The compiler under the project’s settings; the examples; a fault experiment on the tests; a trace on selected inputs
Transfer Guidance (2 of 2)
Item
Content
Dependencies
Look up any suggested library; ask whether the standard library already does it; pin the version
Record
Task, tool and model, context, each attempt, decision, changes, verification, limits
What it does not show about the model
Nothing 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 product
The 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
GitHub Docs:Application card: GitHub Copilot inline suggestions (one vendor’s account of one tool).
Oracle:List (Java SE 21 and 17); javac (Java SE 17).
Apache Maven:maven-compiler-plugin: compile mojo (the release parameter).
OWASP:Top 10 for LLM Applications (2025 edition).
Spracklen et al.:We Have a Package for You!, arXiv 2406.10279.
Full references and links are in the chapter text.