Project 2

Spec Before Code

Apologetic question: "What does it mean to make a promise and keep it?"

Project 2 — Spec Before Code

“The cheapest mistakes are the ones you find before you write the code that depends on them.”

Chapter: 2 — Contracts and Specifications Due: End of Week 2 Submit: A link to your work — an OnlineGDB project URL or a public GitHub repo URL — containing your spec, your code, and (if Medium or Hard) your tests. See Coding 1’s online-coding workflow appendix for the workflow. Allowed tools: Pen and paper, the official Java 17 API docs, this textbook, your fingers. Not yet allowed: AI assistance of any kind. Phase 1 is AI-off. If you use AI for the spec, you skip the very rep this project is designed to give you.


The Setup

A senior engineer keeps a promise. Not because someone made them promise — because they wrote the promise down, in language precise enough that breaking it is visible. That’s what a Javadoc spec is.

This week, you pick a small class from a short menu of options. You write the complete specification — class-level Javadoc, every method signature, every parameter constraint, every postcondition, every exception — before you write a single method body. Then you implement to the spec.

The apologetic frame: what does it mean to make a promise and keep it? The Lutheran confessional tradition has spent five hundred years writing down what it believes in language precise enough that future generations can tell whether the church is keeping faith with itself. The Augsburg Confession was a spec. The Formula of Concord was a spec. They were promises written down precisely enough to be enforceable, in a sense, against the people who came after. Your Javadocs are nothing as grand — but they are the same shape of artifact. Say what you will do. Then do it. Then make it visible whether you did.


Learning Targets

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

  • Write a complete Javadoc specification for a class before writing the implementation.
  • Articulate preconditions, postconditions, and invariants in unambiguous prose.
  • Document every parameter, return value, and thrown exception accurately.
  • Implement Java code that matches the spec exactly.
  • Enforce documented preconditions at the top of method bodies.
  • (Medium) Translate spec claims into JUnit tests, then pass the tests on first run.
  • (Hard) Refactor an implementation without changing its spec or its tests — the proof that a real spec is implementation-independent.

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


Choose Your Class

Pick one of the following three problem statements. Each is small enough to specify in a single afternoon and large enough to make the spec discipline pay off. Pick the one that grabs you.

Option A — Roster

A class that tracks the unique members of a study group. Each member is identified by name (a non-empty String). Each member has a timestamp of join (milliseconds since epoch, captured at add-time). The class supports:

  • Adding a member (no-op if already present).
  • Removing a member (no-op if not present).
  • Asking whether a name is present.
  • Asking for the current size.
  • Asking for all members in the order they joined.

Option B — EventLog

The class from §2.6 of the chapter. An append-only log of events. Each event has a category (a non-empty String), a message (a non-empty String), and a timestamp (captured at add-time). The class supports:

  • Adding an event.
  • Asking for all events in a given category, in insertion order.
  • Asking for the total count of events.
  • Asking for the count of events in a given category.

(If you pick B, you cannot copy the chapter’s spec verbatim — you must write your own. The chapter version is incomplete on purpose.)

Option C — VerseConcordance

A class that builds a concordance from a body of Bible verses. Callers add verses one at a time, each verse identified by a reference (e.g., "Romans 8:28") and a text. The class supports:

  • Adding a verse.
  • Looking up all references whose text contains a given word (case-insensitive).
  • Asking for the total number of verses.
  • Asking for the most common word across all added verses.

Decide what “word” means and document it. Decide what happens if the same reference is added twice and document it. The spec is the artifact.


Normal Tier

Goal: A complete spec, then a complete implementation that matches the spec, for the class you chose.

Required deliverables

  1. <ClassName>.java — your implementation. Every public method, constructor, and class itself has a complete Javadoc comment.

  2. spec.txt — a sibling file that contains:

    • The problem statement you chose (copy/paste from above).
    • A “Design decisions” section with 3-5 numbered bullets calling out the decisions your spec makes that the problem statement leaves open. (Examples: “Names are case-sensitive.” “Adding a null name throws NullPointerException.” “The list returned by getMembers is unmodifiable.”)
    • Your reflection — three sentences on what the act of writing the spec first revealed about the problem.
  3. <ClassName>Demo.java — a small main that creates an instance, calls every public method at least once, and prints the results so the grader can confirm the implementation works.

Required spec quality

Every method’s Javadoc must include:

  • A one-sentence summary ending with a period.
  • One @param per parameter, including any precondition (null-handling, range, format).
  • An @return for every non-void method, describing what the return value means (not just its type).
  • An @throws for every exception the method can throw (checked and unchecked alike, when meaningful).

Every class-level Javadoc must include:

  • A one-paragraph description of what the class is for.
  • At least one stated invariant the class maintains.
  • A thread-safety statement (one sentence — “not thread-safe; callers requiring concurrent access must provide external synchronization” is the standard default for this course).

Required code quality

  • Every documented precondition is enforced at the top of its method, with a clear exception (use Objects.requireNonNull(arg, "arg") for null checks; throw new IllegalArgumentException(...) for value-range checks).
  • Every documented postcondition is true after the method returns. (Implementation responsibility.)
  • The code compiles cleanly on Java 17. No warnings.

Grading rubric — Normal (out of 100)

CriterionPoints
spec.txt includes problem statement + 3-5 design decisions + reflection10
Class-level Javadoc includes purpose + invariant + thread-safety statement10
Every public method has a complete Javadoc (summary, params, return, throws)15
Every precondition is documented and enforced in the body15
Implementation matches the spec — postconditions hold for every method20
Demo.java exercises every public method10
Code compiles cleanly with no warnings on Java 1710
Submission link works and includes all required files5
Spec and code are consistent — no spec/body drift5

Medium Tier (+up to 25% extra credit)

Layer the following on top of a complete Normal.

M1. Spec → Tests → Code (in That Order)

Demonstrate the test-first discipline.

Add a file <ClassName>Test.java containing JUnit 5 tests. (Chapter 4 will teach JUnit formally; for now, the basic shape is enough — see the snippet below.)

The tests must:

  • Cover at least three test cases for every public method. (e.g., the constructor: one normal case, one edge case, one precondition-violation case that asserts the right exception is thrown.)
  • Be written before the implementation. Commit / save / timestamp the test file before the corresponding method body exists. (Use file timestamps to demonstrate this — the grader will check.)
  • All pass on the first run of the implementation.

A minimal JUnit 5 test looks like this:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class RosterTest {
    @Test
    void newRosterIsEmpty() {
        Roster r = new Roster();
        assertEquals(0, r.size());
    }

    @Test
    void addRejectsNull() {
        Roster r = new Roster();
        assertThrows(NullPointerException.class, () -> r.add(null));
    }
}

If you’re working in OnlineGDB and JUnit isn’t immediately available, see Appendix B for the lightweight setup.

M2. Edge-Case Spec

Add a section to spec.txt called “Edge Cases” listing at least five edge cases your class handles — what happens for each, and where in the code (file + method + line range) the handling lives. Examples:

  • Empty input collection.
  • Input of size 1.
  • Duplicate inputs.
  • Whitespace-only strings.
  • Maximum-int boundary values.

Tests must exist (in <ClassName>Test.java) for each edge case listed.


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

Hard tier requires Normal + Medium done well.

H1. The Spec-Independent Refactor

Take your working, Medium-tier implementation. Refactor it to be more elegant — clearer, smaller, more cohesive, or significantly different in approach — without changing the spec or the tests.

Examples of valid refactors:

  • Replace a for loop with a Stream operation (if you’ve seen those — optional this week).
  • Extract a helper method that several public methods share.
  • Switch from ArrayList to LinkedHashMap (or vice versa) if the new structure better matches the access patterns.
  • Replace nested ifs with a guard-clause pattern.

Submit:

  • <ClassName>.refactored.java — the refactored implementation.
  • A section in spec.txt called “Refactor Notes” with: (a) what changed, with brief before/after snippets, (b) why the new version is better, (c) confirmation that every test in <ClassName>Test.java still passes against the refactored code.

The spec must not change. The tests must not change. That is the proof that your spec was a real, implementation-independent contract — not a description of the particular code you happened to write the first time.

If any test had to be modified to accommodate the refactor, your spec was leaky. Tighten the spec, rerun the original test, and try the refactor again.

H2. Two Implementations, Same Spec

Alternative Hard challenge (instead of H1):

Provide two implementations of your class — <ClassName>A.java and <ClassName>B.java — that use meaningfully different data structures or algorithms internally. Both must satisfy the same spec and pass the same test suite.

Add a section in spec.txt discussing the tradeoffs between the two implementations (time complexity, space complexity, code clarity).


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:

  • <ClassName>.java
  • <ClassName>Demo.java
  • spec.txt

For Medium, add:

  • <ClassName>Test.java

For Hard, add either:

  • <ClassName>.refactored.java + refactor notes in spec.txt (H1), or
  • <ClassName>A.java + <ClassName>B.java + tradeoff notes in spec.txt (H2).

The grader will read spec.txt first, then the code, then run the demo (and, for Medium/Hard, the tests).


Hints

  • “How long should the Javadoc be?” Long enough to answer every question a caller could have. Short enough that nothing in it is filler. The chapter’s EventLog.add Javadoc is a good length target: about 8 lines for a method that does one thing well.

  • “My spec keeps changing while I implement.” That’s the point. Every change is one bug you didn’t ship. Update both the spec and the code. The pair must stay consistent.

  • “I implemented first and then wrote the spec.” Submit anyway, but mark your spec.txt reflection honestly: “I implemented first and then specced.” You’ll lose Medium-tier credit (which requires test-first), but you’ll get honest credit for Normal. Lying about the order is the single fastest way to fail this project’s spirit; honesty is graded.

  • “My tests are catching bugs in my implementation, not bugs in my spec.” Good — that’s what tests are for. Update the implementation, rerun. If a test ever catches a spec bug (the test passes but the documented behavior is wrong), update the spec.

  • “How long should this take?” Normal: 3-5 hours. Medium: add 2-3 hours for the tests. Hard: add 2-4 hours for the refactor or alternate implementation. If you’re past 12 hours, something is off — message the instructor.


What Mastery Looks Like (Beyond the Rubric)

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

A great spec reads like the API documentation for a piece of the standard library. You should be able to put your spec.txt next to a section of the official java.util docs and find them similar in tone, density, and discipline.

A great spec answers questions you didn’t realize were questions. The reader finishes your getByCategory spec and never has to wonder, “what if there are no matching events?” — because you already answered it.

A great Hard-tier refactor reads like a different programmer wrote it. Two solutions to the same problem, written by two different careful people, often look quite different. If your refactor looks essentially identical to the original, you didn’t refactor — you cosmetically tweaked.

A great spec.txt reflection paragraph identifies one specific thing that surprised you about writing the spec first. Generic reflections (“I learned that planning is important”) get no credit. Specific reflections (“I assumed getByCategory would return a List, but writing the spec made me realize I had to decide whether the list was mutable, and the right answer was no”) are the rep.


When You’re Done

  1. Open spec.txt. Read it out loud, all the way through. Does every sentence pull weight?
  2. Open <ClassName>.java. Pick any three method bodies. Re-read the Javadoc, then the body, and ask: does the body keep every promise the Javadoc made?
  3. Run <ClassName>Demo.java. Confirm the output is what your spec describes.
  4. If Medium or Hard: run <ClassName>Test.java. All green?
  5. Submit.
  6. Read Chapter 3. Next week, we make the contracts enforceable with proper exception handling.

Coach’s Note — Project 2 is the first project where the artifact you submit is not just code. The spec is graded. The reflection is graded. Honesty about the process is graded. This is the project that starts training you to be the kind of engineer whose PRs other engineers actually want to review. Take it seriously and the rest of Phase 1 gets easier.

See you on Monday.