Project 3

The Robust Refactor

Apologetic question: "How do we handle things going wrong?"

Project 3 — The Robust Refactor

“A program that hides its failures is a program nobody trusts.”

Chapter: 3 — Exception Handling Due: End of Week 3 Submit: A link to your work — an OnlineGDB project URL or a public GitHub repo URL — containing the refactored program, a notes.txt, and (for Hard) a log file from a sample bad run. See Coding 1’s online-coding workflow appendix for the workflow. Allowed tools: Pen and paper, the Java 17 API docs, this textbook, your fingers. Not yet allowed: AI assistance of any kind. Phase 1 is still AI-off. Refactoring brittle code is a thinking exercise, not a typing exercise.


The Setup

You will receive a deliberately brittle Java program — let’s call it VerseLookup (download VerseLookup.java). It is supposed to:

  • Read a CSV file of Bible verses (reference, text), one verse per line.
  • Accept a verse reference from the user via standard input.
  • Print the matching verse, or “not found” if none matches.
  • Loop until the user types quit.

It does all that. On the happy path. On the not-happy path, it does any of the following: it crashes when the CSV file is missing. It crashes when a line in the CSV is malformed. It crashes when the user types an empty line. It silently produces wrong output when two verses have similar references. It catches a generic Exception somewhere deep and swallows the error without telling anyone.

Your job: make it never crash, and make it fail informatively when something genuinely goes wrong.

The apologetic frame: how do we handle things going wrong? Coding 1’s Chapter 3 introduced this question gently. Coding 2’s Chapter 3 makes it concrete. Romans 8:28 doesn’t promise nothing goes wrong; it promises that things go to somewhere, that the brokenness has a response. The discipline of this project is the engineering version: things go wrong; the response is deliberate, not accidental.


Learning Targets

By completing this project, you will demonstrate that you can:

  • Identify every place a brittle program can crash.
  • Add try/catch/finally (or try-with-resources) at the right level — neither too narrow nor too broad.
  • Use throws declarations to push errors to the layer with enough context to handle them.
  • Write at least one custom exception type, with a useful message and a chained cause.
  • Chain exceptions when wrapping, so diagnostic information survives.
  • Distinguish user errors (graceful) from programmer errors (fail fast).
  • (Medium) Document your distinction in a notes.txt.
  • (Hard) Add a java.util.logging layer that produces a readable audit trail.

If you can do those eight things by Friday, you’ve earned a Normal completion.


What You Will Receive

The instructor will distribute a starter pack containing:

  • VerseLookup.java (download) — the brittle starter (~120 lines).
  • verses.csv (download) — a sample data file (~50 verses), some of which are deliberately malformed.
  • the starter README (download) — what the program is supposed to do, at the highest level. (Three or four sentences. Not a spec — your job will partly be to write the spec.)

The starter pack is on the course portal at the start of Week 3. If you can’t find it, ask immediately.

Coach’s Note — Resist the urge to rewrite the program from scratch. The point of this project is refactoring — preserving the existing behavior wherever it’s correct, and tightening up only the places where it isn’t. Rewriting is a different skill. We’ll rewrite plenty in Phase 2.


Normal Tier

Goal: A version of VerseLookup.java that never crashes, with proper try/catch, throws declarations, one custom exception type, and one example of exception chaining.

Required changes

  1. Replace every place the program can crash with appropriate exception handling:

    • File-not-found, file-unreadable: catch IOException, print a useful message, exit cleanly (or, if the user input loop is already running, ask the user to choose a different file or quit).
    • Malformed CSV lines: skip the line and log a warning. Don’t crash the load.
    • Empty user input or null: ignore and re-prompt. Don’t crash.
    • Quit command: clean exit, not via an exception.
  2. Add at least one custom exception — for example, MalformedVerseException for a bad line in the CSV. The exception must:

    • Extend Exception (checked) or RuntimeException (unchecked) — your choice; document why in notes.txt.
    • Have at least the (String) and (String, Throwable) constructors.
    • Have a useful message that includes the bad line (or its line number).
  3. Use exception chaining at least once. Wherever you catch one exception type and throw another (e.g., catch IOException, throw a VerseLookupException), pass the original as the cause.

  4. Use try-with-resources for the file reader. Not try { ... } finally { reader.close(); }. The modern idiom.

  5. Add throws declarations on internal helper methods that genuinely cannot handle the exception themselves, pushing the responsibility up to the layer that can (usually main).

  6. Add Javadoc to every method whose error behavior changed, using @throws to document each exception that can escape. (Connection to Chapter 2: every change to error behavior is a change to the method’s contract.)

  7. notes.txt with at least:

    • The list of bugs/crashes you identified in the original program (at least 5 — the starter has at least 5 real failures).
    • The list of changes you made (one bullet per change, naming the affected method).
    • One paragraph reflection: what was hardest about deciding where to put each try/catch?

Required quality bar

  • The program never crashes with an uncaught exception, regardless of input.
  • The program fails informatively when something genuinely cannot be recovered (e.g., the data file doesn’t exist).
  • Every catch block does something — log, recover, rethrow with a useful message. No empty catches.

Grading rubric — Normal (out of 100)

CriterionPoints
notes.txt lists at least 5 real bugs/crashes in the starter10
Every identified crash is addressed in the refactored code15
At least one custom exception is defined with both standard constructors10
At least one place uses exception chaining (cause is passed)10
Try-with-resources used for file reading10
throws declarations used appropriately on internal methods10
No empty catch blocks; every catch does something useful10
Javadoc updated with @throws on methods whose error behavior changed10
Program never crashes on the included test cases10
Submission link works; all files present5

The grader will deliberately run the program with malformed input, missing files, and invalid commands. None of those should produce a stack trace at the terminal.


Medium Tier (+up to 25% extra credit)

Layer the following on top of a complete Normal.

M1. User Errors vs Programmer Errors

Add a section to notes.txt called “Error Classification” that lists every error case in the refactored program and classifies each as either:

  • User error — caused by user input or environmental conditions. Handled gracefully (informative message, recovery, retry).
  • Programmer error — caused by a bug in the code or a violation of an internal contract. Handled by fail-fast (throw an unchecked exception with a precise message; do not attempt recovery).

For each case, include:

  • The error case (what goes wrong).
  • The classification (user / programmer).
  • How the code responds (graceful recovery? fail-fast throw?).
  • One sentence justifying the classification.

The discipline being trained: most working programmers reach for try/catch reflexively for every error. The senior move is to catch only at the boundaries (user input, file I/O, network) and let internal contract violations bubble up loudly. Your code should reflect this discipline, and your notes.txt should articulate it.

M2. Input Validation Layer

Add a small validation method (or class) that runs at the input boundary and either accepts the user’s input (and returns a clean value) or rejects it (with a clear message asking again).

/**
 * Reads a verse reference from stdin, prompting until the user enters
 * something that looks like a valid reference (e.g., "Romans 8:28") or "quit".
 *
 * @return the validated reference, or null if the user typed "quit"
 */
public static String readReference(Scanner in) { /* ... */ }

This is what “graceful degradation at the boundary” looks like in code. Your main should be cleaner because the parsing logic now lives in one place.


Hard Tier (+up to 25% additional extra credit)

Hard tier requires Normal + Medium done well.

H1. The Logging Layer

Add a java.util.logging layer to the refactored program. Every meaningful event gets logged:

  • File load attempts (success and failure).
  • Each malformed CSV line skipped (warning, with line number and contents).
  • Each user query (info, with the queried reference).
  • Each exception caught (warning or severe, with stack trace).
  • Clean shutdown (info).

Configure the logger to write to a file as well as stdout. (Use java.util.logging.FileHandler — see Chapter 3 §3.9 “Logging” for the Logger basics and a FileHandler + addHandler setup snippet, and the FileHandler Javadoc for the full options.)

Run the program once against a deliberately bad data file (the instructor provides one in the starter, verses-broken.csv). Capture the resulting log file. Submit it as audit-bad-run.log.

The grader should be able to read your log file and trace exactly what happened during the bad run — which lines were rejected, what reference the user looked up, what error occurred and where it was caught, and how the program ultimately responded.

H2. Stretch Reflection

Add a final section to notes.txt called “What This Project Taught Me” — 200-400 words. Specifically address:

  • One thing you changed your mind about while doing the refactor.
  • One place where you initially overcaught (catch too broad) and how you narrowed it.
  • One place where you initially undercaught (didn’t catch at all) and how you found the failure.

This reflection is graded for honesty and specificity. A generic reflection (“I learned that exception handling is important”) gets no points. A specific one (“I initially caught Exception in the CSV parser because I didn’t know what could throw; once I traced the actual exceptions, I narrowed it to IOException and MalformedVerseException and let everything else propagate”) earns full credit.


Submission

Submit one URL via the course portal:

  • OnlineGDB project with all required files, or
  • GitHub repo link with all required files.

What the linked project must contain

For Normal:

  • VerseLookup.java (your refactored version)
  • verses.csv (the original data — do not modify it; the grader runs against the same data)
  • notes.txt (bugs identified + changes made + reflection)
  • Your custom exception classes (each in its own .java file)

For Medium, add:

  • notes.txt section on error classification (user vs programmer errors)
  • The input-validation method/class

For Hard, add:

  • The logging layer configured in the code
  • audit-bad-run.log (output from running against the broken data file)
  • notes.txt section on what the project taught you

Hints

  • “Where do I start?” Run the original program. Try to break it. Type garbage. Delete the data file. Pass an empty line. Each crash you produce is one item on your bugs list. Once your list has at least 5 items, you’re ready to refactor.

  • “My try block is huge.” That’s a sign you’re catching at the wrong layer. Move the try closer to the line that can actually throw. A try block should usually wrap one or two operations, not a whole method.

  • “My catch block re-throws the same thing it caught.” Then you didn’t need to catch it. Remove the try/catch and let it propagate (declaring throws on the method if it’s checked).

  • “I’m catching Exception because I don’t know what else to do.” Look at the Javadoc of every method you call inside the try. Each one lists what it can throw. Catch those specific types.

  • “My custom exception feels useless.” A custom exception is only worth defining if (a) the standard types don’t capture what you mean, and (b) someone might want to catch your specific type without catching everything. If both aren’t true, use a standard type.

  • “How long should this take?” Normal: 4-6 hours. Medium: add 2-3 hours. Hard: add 3-5 hours for logging + reflection. If you’re past 14 hours, message the instructor.


What Mastery Looks Like (Beyond the Rubric)

The rubric tells you what to do. Here’s what to aim for:

A great refactor looks like less code than the original, or roughly the same — but the failure modes are all explicit and handled. If your refactor added 50% more lines, you’re catching too much; tighten it.

A great refactor has a main method that reads almost like a script — “open file, parse, prompt user, look up, print.” All the error handling lives in helper methods or at the boundary, not cluttering up the high-level flow.

A great notes.txt reads like a senior engineer’s pull request description. The reader understands what changed, why each change was made, and what the engineer thought about while doing it.

A great Hard-tier log is useful. A reader unfamiliar with the program can follow it and reconstruct what happened. Logs that just say “WARNING: something happened” are no logs at all.


When You’re Done

  1. Run the program against the original data. Confirm normal cases work.
  2. Run it against the broken data. Confirm no crash; confirm helpful error output.
  3. Run it with empty input, with quit, with non-existent reference. Confirm graceful behavior.
  4. Re-read notes.txt. Does it match what’s in the code?
  5. (Hard) Open audit-bad-run.log. Can you, the author, reconstruct the bad run from it alone? Could a colleague?
  6. Submit.
  7. Read Chapter 4. Next week, we prove the robust code is robust — with tests.

Coach’s Note — This is the project where Phase 1’s compounding starts to show. You used Chapter 1’s reading skill to find the bugs. You used Chapter 2’s spec discipline to decide what the corrected behavior should be. You used Chapter 3’s exception toolkit to make it happen. By the end of Phase 1 you’ll routinely use all eight chapters in one project. This week is the first taste.

See you on Monday.