Skip to content

Module 15 — Challenge Problems

Open problems. The three below have more than one defensible answer; the reasoning is assessed as much as the code.

They share one demand, and it is this module's hardest habit: every judgement is a decision with a cost attached, and every claim about an improvement is a number. "This follows SOLID" is not an answer here. "I chose this, it costs that, this alternative would have cost this instead, and here is what would make me reverse it" is.


Challenge 1 — Make one requirement cheap, and prove it [L6 Build]

The problem. Take your Lab 14 system and pick one plausible next requirement — a new kind of item, a new member category, a rule that depends on both, a hold queue, a fine that is capped per member. Then restructure the design so that this requirement touches as few places as possible.

What a good answer contains.

  • The requirement written down first, in one sentence, in the user's own terms. The design comes after; stating the requirement is what stops the exercise from becoming "add a pattern somewhere".
  • The change-cost trace before: every file, class and function the requirement would have touched, as a list and a number. This is the diagnosis, and it must be produced before the refactoring — a count taken afterwards is a justification.
  • The refactoring, with behaviour unchanged, demonstrated by comparing the program's output before and after. In a module about design, this is where most attempts fail: the new design changes one number in a report, or reorders a line, and the difference is never noticed because nobody diffed.
  • The change-cost trace after, in the same form, with both numbers printed next to each other.
  • One honest entry where the refactoring did not pay off. For example: adding a factory traded one switch for one class plus one registration line, which is roughly break-even at the first new kind and only wins at the second. A report where every change improved is a report where the count was done carelessly.
  • A named trigger for revisiting: what would make you undo part of it.

What would make it weak. A refactoring that reduces the count by moving the same conditional into a new class — the number of places to look is unchanged, and the reader now needs the new class too. A report that presents the final design without the before-count. And a behaviour difference that nobody checked for: a new design that quietly changes a return value is not a design improvement, it is a bug with a diagram.

Deciding it for yourself. Ask which axis actually changes: kinds of item, categories of member, rules, or nothing yet. The refactoring is worth doing along that axis and is speculation along any other. If you cannot name the axis from the requirements, the honest answer is "no refactoring yet", with the count that supports it.


Challenge 2 — Un-pattern a system, and keep the tests green [L7 Refactor]

The problem. Somebody else's subsystem is over-designed: an interface with one implementation, a factory that is a class wrapping one constructor call, an observer used for a fixed list of two subscribers, a strategy whose only variation is a single integer. Rebuild it as the simplest version that satisfies the same requirements — and keep every behaviour.

What a good answer contains.

  • The requirements, re-stated, and specifically: which variation does each abstraction claim to support, and does any requirement actually need it? An abstraction with no variation behind it is the finding, and saying so out loud is the hardest part of this challenge.
  • The simplified version, behaviourally identical, with the same inputs producing the same outputs, compared line by line rather than by inspection.
  • The change-cost check on the other side: for the requirement each abstraction was built for, count what it costs now that the abstraction is gone. If removing the factory makes a new kind cost six edits, the factory was justified and the answer is to keep it — and to say so. Rejecting a refactoring after measuring its cost is a correct outcome of this challenge, not a failed one.
  • A named condition that would bring the abstraction back, written down so the decision is not re-argued from scratch next time.
  • The honest score: how many types were removed, how many lines, and what the reader now has to hold in mind while reading the rest.

What would make it weak. Removing an abstraction because it looked unnecessary, without running the change-cost trace — the shortcut that makes simplification a form of vandalism. Equally weak: an answer with no removal at all, which has not engaged with the premise. If every abstraction here is justified, demonstrate it with the requirement each one buys, one by one.

Deciding it for yourself. Simplification is justified exactly when the variation is not real. Write the requirement that would exercise each abstraction as a sentence a user would say. If you cannot write one, that abstraction is a cost with no buyer — and the reverse test is the same test: if you can write it, and it is plausible, the abstraction stays.


Challenge 3 — Review a design you did not write, and be useful about it [L8 Evaluate]

The problem. Take a design of your own from earlier in the course — Module 8, 11 or 14 is the richest source — and review it as if it were a stranger's. Write the review a good colleague would want to receive: specific, fair, and about the code rather than the person.

What a good answer contains.

  • Findings tied to evidence: a line, a class, a behaviour, and the requirement that would be expensive as a result. "LoanService holds storage, the lending rule and the report; the fine-by-category requirement would touch all three, so the seam is between storage and policy."
  • At least one acknowledged strength, with the same standard of evidence. A review with no positives is as uninformative as one with no findings — and in a self-review it is usually the weaker discipline, which makes it worth practising.
  • A priority order. Not every finding is equally important, and a review that lists eleven equal problems has not judged any of them. Say which two are worth fixing and which can wait.
  • A counter-case engaged with, not a strawman. Before criticising a design decision, state the requirement it was made for and whether that requirement is plausible. Some decisions that read as mistakes were correct trades.
  • One recommendation you would implement yourself, in enough detail that a reader could begin — a seam, an interface split, a dependency injected.
  • The limits of the review: what you could not see, did not test, or assumed. Naming a review's own uncertainty is what separates a useful review from an authoritative-sounding one.

What would make it weak. Findings without evidence ("this class is too big"). Findings inflated to sound thorough. Inventing a defect to balance the count. Or the opposite failure: a review that concludes everything is fine, which on a first-attempt design from Module 8 is essentially impossible — if you found nothing, you have not yet looked at the requirements the design has to survive.

Deciding it for yourself. The measure of a review is what its reader can do afterwards. Before finishing, check whether a stranger could act on each finding without asking you what you meant. If a finding is unactionable, it is an opinion; either sharpen it with evidence or cut it.


Evaluating your own answer

Write these five lines before reading any suggested approach:

The requirement (or, for Challenge 2, the abstraction and what it claims to support):
What it touched, or claimed to touch, and the count:
What I changed, and what it cost:
The option I rejected, and its cost:
What would change my mind:

Then check it against four tests:

  1. Is there a requirement? Every design judgement in this module is made for a requirement. Without one there is nothing to evaluate, and "more flexible" is not a requirement.
  2. Is there a before-count? A count taken only after the refactoring measures nothing. The number that argues is the one produced before the code changed.
  3. Is the cost of the chosen option named? Every design here pays for what it buys — types, indirection, a lifetime question relocated, a reader who must follow one more hop. An answer with no cost has not finished the decision.
  4. Was the behaviour compared? This module's exit level is 8. Evaluation rests on evidence you produced: the output diffed, the counts, the type list. Anything asserted is a preference.

Levels: [L6 Build] · [L7 Refactor] · [L8 Evaluate] — the exit level is 8, and Challenge 3 is the whole of it: judging design, including your own, with findings a reader can act on and limits you are willing to state.


Next: Self-assessment — ten questions, including a prediction about what a new kind of item costs in two designs, and one asking you to name a pattern you should not have used.