Java Guild course · Reference card
Reference · git
Thirteen commits, written out in full. Copy them, adjust to what you actually did, and never send a log that reads “wip”.
user.name and user.email before commit 1.git rebase -i before packaging. Nobody has pulled
this branch, so rewriting it is proofreading, not vandalism.cd ~/Desktop/java-guild-candidate-test-intermediate-main
git init -b main
git config user.name "Tom Spencer"
git config user.email "tomspencerlondon@gmail.com"
echo "libraryState.txt" >> .gitignore
chore: add starter project as supplied
Baseline commit of the unmodified Solirius starter, so that every
subsequent change is visible as a diff against what was handed over.
build: compile with release 11 and set a real groupId
maven.compiler.source/target produce a warning on modern JDKs:
location of system modules is not set in conjunction with -source 11
--release 11 is recommended instead
release 11 also guarantees the code is compiled against the Java 11
API, not merely to its bytecode level. The Java version itself is
unchanged: the project still targets 11, so no record types, sealed
types or switch expressions are used anywhere in the submission.
groupId was left as the literal placeholder "groupId".
test: pin existing behaviour with a golden master
The changes that follow touch every class, including the exception
types and the persistence format. This captures what the application
does today across eleven scenarios, driven through main(), so that
unintended changes appear as a diff rather than as a surprise.
The approved baseline deliberately records four crashes and one data
corruption bug as current behaviour. It asserts what the program does,
not what it should do; the unit tests added next specify that.
Written by hand rather than with ApprovalTests.Java, to avoid adding a
dependency to a supplied pom for twenty-five lines of comparison.
test: add unit tests for Book and Library
Covers the Graduate requirement; the project had no src/test directory
and "mvn test" reported "No tests to run".
Cases are chosen so that a plausible wrong implementation would fail:
borrowing twice, returning a book that was never borrowed, and a
two-book library where exactly one is on loan. A test that only borrows
a fresh book would pass against an implementation that ignores the
borrowed flag entirely.
These pass against the existing code, so they characterise rather than
drive it. Behaviour that does not yet exist is driven test-first in the
commits that follow.
fix: replace IOException with the supplied domain exceptions
borrowBook and returnBook signalled every failure with IOException,
although neither performs any I/O, and collapsed two distinct cases
into one branch: borrowing an already-borrowed book reported
"Book not found" for a book the library was holding.
Wires up AlreadyBorrowedException, BookNotFoundException and
NotBorrowedException, which were present in the repository but
referenced by nothing, and adds LibraryException as a common supertype
so the console can report any domain failure uniformly.
They remain checked exceptions: the caller has a real recovery, which
is to report the message and continue.
The boolean return values are dropped. With failure signalled by an
exception both methods could only ever return true, so the value was
one no caller could act on. This deviates from the signatures in the
brief and is recorded in the README.
Golden master re-approved here: three stack traces become three
messages, and the application now survives to save its state.
refactor: return Optional from searchBook
searchBook returned null for an absent title, so every caller had to
remember a check the compiler could not enforce.
Returning Optional also gives borrowBook and returnBook a shared
requireBook helper via orElseThrow, removing the duplicated absence
check from both.
No behaviour change: the golden master is unchanged by this commit.
fix: stop the saved format losing and corrupting data
Three defects in save/load, each covered by a test added here:
- Titles containing the ';' separator were split across fields, so
"Coming Home; A Novel" by Rosamunde Pilcher reloaded as "Coming Home"
by " A Novel", losing the author entirely and without any error.
Fields are now escaped, so any character is legal in any field.
- A truncated line raised ArrayIndexOutOfBoundsException from parts[2].
The field count is validated and reported with its line number.
- loadLibraryState cleared the collection before opening the file, so a
missing or corrupt file destroyed the in-memory library. The
replacement list is built first and swapped in only once the whole
file has parsed, giving the strong exception guarantee.
Storage failures now raise LibraryStorageException rather than
IOException, so the console handles them alongside domain failures.
refactor: extract LibraryRepository from Library
Library was both a collection of books and a file format, so it had two
reasons to change.
The domain now depends on a LibraryRepository interface, with
FileLibraryRepository holding the flat-file implementation. The
Advanced tier of the brief asks for a portable file-based database and
the pom already declares sqlite-jdbc; with this interface that becomes
an added class rather than a change to Library.
No behaviour change. The tests moved to FileLibraryRepositoryTest but
no assertion was altered, and the golden master is unchanged.
refactor: extract LibraryConsole from Main
Main read System.in and wrote System.out from a 90-line static method,
so the menu loop had no seam a test could reach.
LibraryConsole takes its Library, Scanner and PrintStream as
constructor parameters, which lets a test drive it with byte arrays and
assert on the exact output a user would see. Main is reduced to wiring:
load state, run the console, save state.
No behaviour change: the golden master is unchanged by this commit.
fix: handle menu input that is not a number
Typing any non-digit at the menu raised InputMismatchException from
Scanner.nextInt and terminated the application, discarding everything
added during that session, since state is only written on a clean exit.
The choice is now read a line at a time and matched against MenuOption.
Reading a line rather than guarding with hasNextInt avoids the second
trap: hasNextInt consumes nothing, so a loop on it spins forever unless
the offending token is discarded separately.
MenuOption also replaces the six int constants and the hand-maintained
MENU string. The menu is rendered from values(), so an option cannot
exist without appearing on screen.
feat: search by author as well as by title
Beyond the Intermediate tier. Listed under the Advanced requirements
and cheap once searchBook returns Optional.
feat: sort the book list by title or author
Beyond the Intermediate tier. Comparators are exposed as constants on
Book so the ordering is named and testable rather than inline.
docs: document the approach, decisions and deviations
Covers how to build and run, the approach taken, the defects found in
the supplied code, the design decisions and their trade-offs, the two
places the implementation deviates from the brief's stated signatures,
and what was deliberately left unbuilt.
Written last so that it describes what the code actually does.
git log --oneline # does the story read top to bottom?
git log --stat | head -60 # is each commit one logical change?
git diff HEAD~12 --stat # everything you changed, in one view
git status --short # must be empty before packaging
Read only the subject lines, in order. If someone who has never seen the repository could describe what you did and why from those thirteen lines alone, the log is doing its job.
Chris Beams — How to Write a Git Commit Message · Conventional Commits 1.0.0