Chapter 2 — Reps
Conditioning, not grading. Spec-writing reps this week.
Ground rules:
- Type every line yourself. No copy-paste of Javadoc templates from the internet.
- AI stays OFF. Phase 1. The whole point is to grow the spec muscle in your hands before Phase 2 attaches a machine to it.
- Read your specs out loud after writing them. Spec quality is a function of how cleanly they read.
- A spec that doesn’t constrain something is a spec that lies. “Adds an item” is not a spec — it’s a translation of the method name. Specs add information.
You’re working in OnlineGDB Java 17 or your local JDK. Either path; don’t switch mid-rep.
Reps 1–3: Read the Body, Write the Spec
All three bodies are in one runnable file (download Reps1to3.java) — they ship with no Javadoc on purpose, because writing it is the rep.
Rep 1 — Spec a Pure Function
Read this method. Write a complete Javadoc above it (purpose, @param, @return, @throws). The result should be precise enough that someone could reimplement the method from your Javadoc alone.
public static int gcd(int a, int b) {
if (b == 0) return Math.abs(a);
return gcd(b, a % b);
}
Things to think about: what about negative inputs? What about gcd(0, 0)? Does your spec describe these cases honestly?
Rep 2 — Spec a Mutator
Same drill. Write the Javadoc for this method.
public boolean addUnique(String name) {
if (name == null) return false;
if (members.contains(name)) return false;
members.add(name);
return true;
}
Pay attention to: what does the return value mean? What’s the postcondition on the members collection when the return is true? When it’s false? Is “null returns false” actually the right contract, or should it throw? (Your spec should document whatever the body actually does — but flag a comment if you think the body’s behavior is wrong.)
Rep 3 — Spec a Method That Lies
Read this method. Write its Javadoc as a careful reader of the body would write it — describing what the method actually does, not what its name suggests.
public int sizeOf(String s) {
int n = 0;
for (int i = 0; i < s.length(); i++) {
if (!Character.isWhitespace(s.charAt(i))) n++;
}
return n;
}
The name is sizeOf. The body counts non-whitespace characters. Your spec must describe the actual behavior. Then, as a separate note, suggest a better name.
Reps 4–5: Read the Spec, Write the Code
Both specs ship with stub bodies for you to fill in (download Reps4to5.java) — the stubs compile but do not yet satisfy the spec; that’s your job.
Rep 4 — Implement to a Spec
Below is a complete Javadoc for a method. Without looking up any reference implementation, write the method body.
/**
* Returns the first index in {@code arr} where the value is strictly
* greater than the given threshold, or {@code -1} if no such value exists.
*
* @param arr the array to search; must not be null
* @param threshold the value to compare against
* @return the smallest index {@code i} such that {@code arr[i] > threshold},
* or {@code -1} if no element exceeds the threshold
* @throws NullPointerException if {@code arr} is null
*/
public static int firstIndexAbove(int[] arr, int threshold) {
// your implementation here
}
Run a few hand-trace cases against your code. Does the postcondition hold? Does the precondition violation produce the documented exception? (Hint: dereferencing a null array’s .length will throw NullPointerException automatically — but the explicit check via Objects.requireNonNull is the better discipline.)
Rep 5 — Implement a Harder Spec
/**
* Returns the most frequently occurring element in the list. If multiple
* elements tie for the most occurrences, returns the one that appeared
* earliest in the list.
*
* @param items the list to scan; must not be null and must not be empty
* @return the most frequent element, with ties broken by first occurrence
* @throws NullPointerException if {@code items} is null
* @throws IllegalArgumentException if {@code items} is empty
*/
public static <T> T mostFrequent(List<T> items) {
// your implementation here
}
Decide on a data structure. (HashMap<T, Integer> is one natural choice.) Hand-trace your code against three test cases: [A, B, A], [A, B, A, B] (tie — your code should return A), and [A]. Confirm each behaves per the spec.
Reps 6–7: Spot the Liars
Rep 6 — Find the Mismatch
Each of the following has a Javadoc that lies about the body. Identify the discrepancy in each. (download Rep6Liars.java — all three liars, with a main that exposes two of them.)
(a)
/**
* Returns the sum of all positive numbers in the array.
*
* @param arr the array; must not be null
* @return the sum of positive elements
*/
public static int sumPositive(int[] arr) {
int total = 0;
for (int v : arr) total += v;
return total;
}
(b)
/**
* Removes and returns the first element of the list.
*
* @return the removed element, or null if the list is empty
*/
public String popFirst() {
return items.remove(0);
}
(c)
/**
* Returns an unmodifiable view of the underlying members list.
*/
public List<String> getMembers() {
return members;
}
For each, write one sentence: what does the body actually do, and what would you change — the spec or the body?
Rep 7 — Inconsistent Specs
Read these two method Javadocs (same class). What’s wrong?
/**
* Adds a name to the roster. If name is null, no-op.
*/
public void add(String name) { /* ... */ }
/**
* Removes a name from the roster.
* @throws NullPointerException if name is null
*/
public void remove(String name) { /* ... */ }
The two methods have inconsistent null-handling policies for the same parameter. Pick one policy. Rewrite both Javadocs (and describe what the body changes would be) so they agree.
Reps 8–9: Invariants
Rep 8 — Find the Invariants
For each class description, list at least three invariants the class must maintain. (No code required — just the invariants in prose.)
(a) A Stack<T> with push, pop, peek, size, and isEmpty.
(b) A RangeMap<V> that stores values for half-open intervals [low, high) and supports put(low, high, value) and get(point).
(c) A Roster that tracks unique members; add(name) does nothing if the name is already present.
Write your invariants as declarative statements: “the size is always equal to the number of successful add calls minus the number of successful remove calls,” not “you can’t add the same name twice.”
Rep 9 — The Constructor Invariant
Read this constructor. What invariant does it establish? What invariant does it fail to establish that it should? (download Rep9BankAccount.java — a runnable version whose main shows the missing checks letting bad state through.)
public BankAccount(String owner, double startingBalance) {
this.owner = owner;
this.balance = startingBalance;
}
Write the missing precondition checks (one or two lines each). Then write the corrected Javadoc.
Rep 10 — Spec a Class From Scratch
You are designing a class called WordCounter. Its job: callers give it strings (one at a time), and at any moment they can ask for the count of any particular word, or the most-common word, or the list of all unique words.
Without writing any implementation, write the complete spec for the class:
- The class-level Javadoc (purpose, invariants, thread-safety policy).
- The constructor’s Javadoc.
- The Javadoc for every public method.
You decide what the method signatures are. You decide whether words are case-sensitive. You decide what “word” means (whitespace-separated tokens? Letters only?). Document every decision.
When you’re done, put the spec aside. Implement the class from the spec. Then compare: did the implementation match the spec, or did the spec turn out to be incomplete?
Rep 11 — Spec From Another’s Code
Take a class from your Coding 1 work (your Apologist’s Card program is fine; one of your Account-style classes is better). Write a complete Javadoc spec for it retroactively — pretending you didn’t write it.
Then, with the spec in hand, look at the actual code. Where does the code violate the spec you just wrote? (Almost certainly somewhere — code written without a spec rarely satisfies one.)
This rep teaches you what it costs to skip the spec. The cost is the gap you just found.
Done? One Last Thing.
Open a fresh file. Write the complete Javadoc for the EventLog class described in §2.6 of the chapter — from memory, without looking back. Include the class header, constructor, add(category, message), getByCategory(category), and count().
Then open the chapter and compare. Where did you under-specify? Where did you forget a precondition? Where did you describe a postcondition imprecisely?
Specifications are a craft. You get better at them the way you get better at writing prose — by writing more of them and reading them back honestly.
Up next: Project 2 — Project 2: Spec Before Code.