Java Guild course · Lesson 0007
Lesson 0007 · the narrative
An assessor with forty submissions to mark will read your history before they read your code. Write it for them.
The brief asks for “a brief description of the approach taken”. Most candidates write a paragraph. You can do better: hand over a commit log where the approach is demonstrated, commit by commit, with the reasoning attached to the change it justifies.
A commit message is not a note to yourself. It is a message to a reviewer who was not there, explaining why the diff below it exists. The diff already says what changed; the message exists to say why.
Chris Beams’ seven rules are the standard reference. All seven are worth following, but two do the heavy lifting:
| Rule | Why it earns marks here |
|---|---|
| Subject line in the imperative mood, under 50 chars, no full stop | “Replace IOException with domain exceptions” completes the sentence “if applied, this commit will…”. Consistency signals someone who has worked on a team. |
| Body explains what and why, not how | This is where the marks are. The how is the diff. The why is the only thing you can contribute that the code cannot. |
fixed bug
Tells the reviewer nothing. They now have to read the diff and guess your intent.
Changed borrowBook to throw AlreadyBorrowedException instead of
IOException and removed the boolean return type
Better, but it is only the diff in English. A reviewer can already see that.
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
unreferenced.
Now the reviewer knows the defect, the severity, and that you noticed the unused classes were a hint. That last sentence is worth more than the diff.
Conventional Commits
prefixes each subject with a type: feat:, fix:,
refactor:, test:, docs:.
| Argument | |
|---|---|
| For | The type instantly tells a reviewer whether to expect behaviour change. refactor: is a promise the tests did not change — a promise they can check. |
| Against | It is a convention for repos with automated changelogs and semantic-version releases. This has neither. It can read as ceremony. |
Use it, consistently, and say so in one line of your README.
The value here is not tooling — it is that refactor: versus
fix: makes your make-it-work-then-make-it-right discipline
visible at a glance down the log. And “I followed Conventional Commits”
is a two-word answer to “how do you work in a team?”
What you must not do is use it for half the commits. Inconsistency is worse than either choice.
Here is the whole submission as a sequence. Each line is one commit, at green. Full messages are on the commit script reference card.
| # | Subject | Why it is its own commit |
|---|---|---|
| 1 | chore: add starter project as supplied | The most important commit in the repo. Everything after it is a visible diff against what Solirius handed over. |
| 2 | build: compile with release 11 and set a real groupId | Build hygiene, separate from any code change. |
| 3 | test: pin existing behaviour with a golden master | The safety net, before anything it protects. |
| 4 | test: add unit tests for Book and Library | The Graduate requirement, discharged and identifiable. |
| 5 | fix: replace IOException with the supplied domain exceptions | The headline change. Include the re-approved golden master here. |
| 6 | refactor: return Optional from searchBook | Behaviour-preserving; keep it out of commit 5. |
| 7 | fix: stop the saved format losing and corrupting data | Three related defects in one subsystem — one commit is right, three would fragment the story. |
| 8 | refactor: extract LibraryRepository from Library | The DIP move. Separate, so the reviewer can check no assertion changed. |
| 9 | refactor: extract LibraryConsole from Main | Pure extraction. Claim “golden master unchanged”. |
| 10 | fix: handle menu input that is not a number | Behaviour change, so not folded into commit 9. |
| 11 | feat: search by author as well as title | Bonus. Clearly marked as beyond the required tier. |
| 12 | feat: sort the book list by title or author | Bonus. |
| 13 | docs: document the approach, decisions and deviations | The README. Last, so it describes what actually exists. |
target/, .idea/ or
libraryState.txt. The supplied .gitignore
covers the first two. Add the third.git rebase -i before you package.git config user.name
and user.email. A log authored by root@localhost is a
bad first impression.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
git add -A
git commit
with the message:
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.
If you have already started editing, stash your work
(git stash won’t help before git init — copy the
files aside instead), commit the pristine starter first, then re-apply. That
baseline commit is worth the ten minutes of faff.
Why commit the unmodified starter before making any changes?
Git diffs the root commit fine, against the empty tree. Not a constraint.
This is the whole reason. Without it, the assessor sees one commit containing both their code and yours and cannot tell them apart. With it, git diff HEAD~12 is precisely and only your work — which is what they are marking.
Authorship is recorded as you, since you are making the commit. It preserves the code, not the credit — and your README should say the starter was supplied.
A genuine benefit, but a minor one. You would have that from any commit; the value here is specifically about the boundary between their work and yours.
Which belongs in a commit message body?
git show --stat already prints that, accurately and for free. Restating it by hand just creates something that can go stale.
The diff is the algorithm, and it cannot drift out of date. Prose describing how the code works belongs in the code, as a comment or a javadoc.
This is the content only you can supply. The code shows what it does now; nothing in the repository records what was wrong before or which alternatives you rejected. That reasoning is exactly what “the approach taken” means.
Interesting to you, irrelevant to a reviewer, and it invites a judgement about your speed that you do not want to raise.
You realise commit 5 contained a typo you fixed in commit 6. What should you do before submitting?
Right. The taboo on rewriting history applies to published branches other people have pulled. Nobody has ever seen this repository. Tidying it before it is read is the same courtesy as proofreading — and git rebase -i with fixup is exactly the tool.
The rule is “don’t rewrite shared history”, not “never rewrite history”. Reciting the shorter version here leaves noise in the artifact being marked.
Now three commits are involved in one small mistake, and you have drawn attention to it. Worse than either alternative.
This throws away the story. The commit-by-commit narrative is a large part of the value; collapsing it to protect one typo trades the asset for the blemish.
Chris Beams — “How to Write a Git Commit Message”. Fifteen minutes, and the highest ratio of value to length on the whole reading list. Read the worked examples at the end, not just the seven rules.
Paste me your git log before you package and I will
read it the way an assessor will — top to bottom, without opening a single diff,
and tell you whether the story holds together.