Java Guild course · Reference card

Reference · OO & design

Design Decisions & How To Defend Them

Every choice in the submission, the principle behind it, and the counterargument you should raise before they do.

The format of a good answer

Decision → principle → concrete cost of the alternative → the counterargument. Reciting a principle is a junior answer. Naming what the alternative would have cost in this codebase, and volunteering the strongest objection to your own choice, is a senior one.

The decisions

1. Domain exceptions instead of IOException

PrincipleEffective Java Item 73 — throw exceptions appropriate to the abstraction.
Cost of the alternativeIOException tells a caller this method touches a disk. It does not. And one type could not distinguish not found from already borrowed, which is why the original reported “Book not found” for a book it was holding.
Counterargument“Three exception classes for a toy app is over-engineering.” Answer: they were already in the repository, and the brief names exactly these three scenarios.

2. Checked, not unchecked

PrincipleOracle’s rule of thumb: “if a client can reasonably be expected to recover from an exception, make it a checked exception.” (Effective Java Item 70 agrees.)
Cost of the alternativeUnchecked, nothing forces the console to handle it, and a mistyped title escapes to main and kills the app — which is precisely the bug that was there.
Counterargument“Checked exceptions are a design mistake; modern Java avoids them.” Answer: a real position, and for a widely-used library API I might agree. Here the sole caller has an obvious recovery, and the supplied classes were already checked — changing that needs a reason and there isn’t one.

3. LibraryException as a common supertype

PrinciplePolymorphism — let the caller choose its granularity.
Cost of the alternativeEvery call site repeats identical catch blocks, and adding a fourth exception means editing all of them.
Counterargument“It invites catching too broadly and losing the distinction you just built.” Answer: fair. The subtypes still exist and are still thrown, so I have added an option rather than removed one.

4. Optional<Book> instead of null

PrincipleEffective Java Item 55 — return Optional where absence is a normal outcome the caller must handle.
Cost of the alternativeA null return is a check the compiler cannot enforce. It also gave a clean orElseThrow that removed the duplicated absence check from both borrowBook and returnBook.
CounterargumentOptional allocates, and the brief specified Book searchBook(String).” Answer: allocation is irrelevant at this scale, and the deviation is documented in the README. Know the limits: the javadoc's API Note says return types only — not fields, not parameters, and never wrapping a collection.

5. void instead of boolean

PrincipleOne outcome, one mechanism. A return value no caller can act on is noise.
Cost of the alternativeThe method can only ever return true, so if (library.borrowBook(t)) is a branch that cannot be taken.
Counterargument“The brief specified boolean.” Answer: it did, and I documented the deviation. Keeping an unreachable return to match a signature would be worse code.

6. LibraryRepository extracted from Library

PrincipleSRP (“one reason to change”) and DIP — the domain depends on an abstraction, not on a file format.
Cost of the alternativeThe Advanced tier asks for a portable file-based database and the pom already ships sqlite-jdbc. With the interface, that is one new class and no change to Library. Without it, a rewrite.
Counterargument“One implementation, so YAGNI.” Answer: the strongest objection, and usually right — but here the second implementation is named in the brief and its driver is already a dependency. That is a change I can name, not one I imagined.

7. MenuOption enum instead of six int constants

PrincipleMake illegal states unrepresentable. Type safety over primitive constants.
Cost of the alternativeA hand-written MENU string and six constants kept in step manually — add an option, forget the string, and the feature is invisible. The menu is now rendered from values(), so they cannot diverge.
Counterargument“An enum for six menu items is heavy.” Answer: it is fewer lines than what it replaced, and it removed a whole class of bug.

8. Constructor injection of Scanner and PrintStream

PrincipleDependency injection as a pattern, no framework required.
Cost of the alternativeReaching for System.in/System.out internally leaves no seam, so the menu loop cannot be tested at all.
Counterargument“You could just swap System.setOut in the test.” Answer: the golden master does exactly that, because it must drive main. But global mutable state in tests is fragile and blocks parallel execution; a constructor parameter is better where the design allows one.

9. Escaping rather than a rarer delimiter

PrincipleCorrect by construction, not by hoping the data behaves.
Cost of the alternative“Users probably won’t type a tab” is not a correctness argument. Escaping makes every character legal in every field.
Counterargument“Use JSON.” Answer: I’d prefer it, but the pom ships no JSON library and adding one to a supplied build is a bigger decision than twenty lines of escaping.

10. Build the replacement, then swap

PrincipleThe strong exception guarantee — if an operation throws, the object is unchanged.
Cost of the alternativebooks.clear() before opening the file means a failed load destroys the in-memory library. Verified: one book in, failed load, zero books out.
Counterargument“Extra allocation.” Answer: one list, versus silently losing a user’s data.

11. No Mockito

PrinciplePrefer the real collaborator unless it is slow, non-deterministic, or hard to drive into the state you need.
Cost of the alternativeMocking Library to verify borrowBook was called asserts your implementation, not your behaviour. Mocking streams is more code than ByteArrayOutputStream and asserts less.
Counterargument“It is in the pom, so they want to see it.” Answer: using a tool to signal familiarity is the wrong reason. Where I would use it: forcing a LibraryRepository to throw, to test the disk-failure path.

12. Staying on Java 11

PrincipleWork inside a stated constraint deliberately; change a supplied build only with a reason.
Cost of the alternativeBumping the target might break an assessor building on an older JDK, for language sugar that is not needed.
CounterargumentBook would be nicer as a record.” Answer: records need 16; javac --release 11 rejects them outright. And Book has a mutable borrowed flag, so it would be a poor record anyway. I did change source/target to release, which fixes a real compiler warning without changing the version.

Vocabulary — use these words precisely

TermWhat it means here
Characterisation testA test recording what code currently does, so it can be changed safely. Feathers. Green on arrival by design.
Golden master / approval testA characterisation test that compares whole output against an approved file rather than an inline expectation.
Strong exception guaranteeIf it throws, the object is as it was. What the load reordering provides.
Tell, don’t asklibrary.borrowBook(title), not library.searchBook(t).get().borrowBook(). Behaviour lives with the data.
Primitive obsessionSix int menu codes and a parallel string; a raw String title as identity.
Composition rootWhat main shrinks to: the one place that wires objects together.
SeamA place you can change behaviour without editing the class. Feathers. The constructor parameters are the seam.
Silent data corruptionThe semicolon bug. Worse than a crash because nothing prompts anyone to look.
SRPOne reason to change — not “one thing”. Say it Martin’s way; the misquote invites a follow-up.

Primary sources