Java Guild course · Reference card
Reference · OO & design
Every choice in the submission, the principle behind it, and the counterargument you should raise before they do.
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.
IOException| Principle | Effective Java Item 73 — throw exceptions appropriate to the abstraction. |
|---|---|
| Cost of the alternative | IOException 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. |
| Principle | Oracle’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 alternative | Unchecked, 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. |
LibraryException as a common supertype| Principle | Polymorphism — let the caller choose its granularity. |
|---|---|
| Cost of the alternative | Every 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. |
Optional<Book> instead of null| Principle | Effective Java Item 55 — return Optional where absence is a normal outcome the caller must handle. |
|---|---|
| Cost of the alternative | A 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. |
| Counterargument | “Optional 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. |
void instead of boolean| Principle | One outcome, one mechanism. A return value no caller can act on is noise. |
|---|---|
| Cost of the alternative | The 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. |
LibraryRepository extracted from Library| Principle | SRP (“one reason to change”) and DIP — the domain depends on an abstraction, not on a file format. |
|---|---|
| Cost of the alternative | The 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. |
MenuOption enum instead of six int constants| Principle | Make illegal states unrepresentable. Type safety over primitive constants. |
|---|---|
| Cost of the alternative | A 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. |
Scanner and PrintStream| Principle | Dependency injection as a pattern, no framework required. |
|---|---|
| Cost of the alternative | Reaching 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. |
| Principle | Correct 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. |
| Principle | The strong exception guarantee — if an operation throws, the object is unchanged. |
|---|---|
| Cost of the alternative | books.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. |
| Principle | Prefer the real collaborator unless it is slow, non-deterministic, or hard to drive into the state you need. |
|---|---|
| Cost of the alternative | Mocking 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. |
| Principle | Work inside a stated constraint deliberately; change a supplied build only with a reason. |
|---|---|
| Cost of the alternative | Bumping the target might break an assessor building on an older JDK, for language sugar that is not needed. |
| Counterargument | “Book 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. |
| Term | What it means here |
|---|---|
| Characterisation test | A test recording what code currently does, so it can be changed safely. Feathers. Green on arrival by design. |
| Golden master / approval test | A characterisation test that compares whole output against an approved file rather than an inline expectation. |
| Strong exception guarantee | If it throws, the object is as it was. What the load reordering provides. |
| Tell, don’t ask | library.borrowBook(title), not library.searchBook(t).get().borrowBook(). Behaviour lives with the data. |
| Primitive obsession | Six int menu codes and a parallel string; a raw String title as identity. |
| Composition root | What main shrinks to: the one place that wires objects together. |
| Seam | A place you can change behaviour without editing the class. Feathers. The constructor parameters are the seam. |
| Silent data corruption | The semicolon bug. Worse than a crash because nothing prompts anyone to look. |
| SRP | One reason to change — not “one thing”. Say it Martin’s way; the misquote invites a follow-up. |