Java Guild course · Reference card

Reference · git

The Commit Script

Thirteen commits, written out in full. Copy them, adjust to what you actually did, and never send a log that reads “wip”.

Rules

Setup

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

1 · the baseline

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.

2 · build hygiene

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".

3 · the safety net

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.

4 · the graduate requirement

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.

5 · the headline change

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.

6 · a refactor, kept separate

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.

7 · persistence

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.

8 · the DIP move

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.

9 · extracting the console

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.

10 · the input crash

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.

11–12 · bonus features

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.

13 · the README

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.

Reading your own log before you send

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
The test

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.

Primary source

Chris Beams — How to Write a Git Commit Message · Conventional Commits 1.0.0