skip to content

Explain the Sprout Method, Sprout Class, Wrap Method and Wrap Class techniques for adding behaviour to untested legacy code, and when you would choose each.

level: middleimportance: should knowfreq 45%

answer

  1. New code born tested, old code barely touched
  2. Sprout = call new tested unit from one line
  3. Wrap Method = rename original, new method calls both
  4. Wrap Class = decorator wired at composition root
  5. Interleaved logic -> sprout, before/after -> wrap

basics

~20 s

All four let you add new, tested code without untangling the old code. Sprout puts the new logic in a new method or class and calls it from one place in the old code. Wrap renames the old method and puts a new method in its place that calls both, or puts the old object behind a decorator that adds behaviour.

solid answer

~60 s

These are Feathers' techniques for adding behaviour when you cannot yet get the surrounding code under test. Sprout Method: write the new logic as a new, fully tested method (often on the same class) and insert a single call at the right point in the ugly method — the only untested edit is one call line. Sprout Class: same idea but the new logic goes into a new class, chosen when the host class is untestable (heavy constructor) or the new responsibility does not belong there. Wrap Method: rename the original method, create a new method with the original name that calls the renamed original plus your new step — works only when the new behaviour must run before or after the whole original, not interleaved. Wrap Class (Decorator): a new class implementing the same interface holds the original, adds behaviour, and is injected at the composition root — the strongest option, since callers need no change and you can layer or remove it. Trade-off across all four: they leave the old mess intact and can add structural noise, so use them as a beachhead of tested code, not a permanent policy.

code

pseudocode · 14 lines
pseudocode
// --- Sprout Method: new logic tested separately, one call inserted ---
function postEntries(entries) {
  ... 300 untested lines ...
  deduped = removeDuplicates(entries)   // <-- the only untested edit
  ... 300 more untested lines ...
}
function removeDuplicates(entries) { /* new, fully unit-tested */ }

// --- Wrap Method: original renamed, new method keeps the old name ---
function postEntriesCore(entries) { ...original body unchanged... }
function postEntries(entries) {
  auditLog.record(entries)   // new, tested behaviour, runs before
  postEntriesCore(entries)
}

go deeper

for a junior

Describe sprouting (new tested method/class, one call added) and wrapping (rename original, new method with the old name calls both) with a concrete example.

for a middle

Add the selection rule — interleaved behaviour forces Sprout, before/after allows Wrap; untestable host forces Sprout Class — and note that the inserted glue line stays untested.

for a senior

Discuss Wrap Class as Decorator wired at the composition root, interface extraction cost, stacking/removability, and the duplication and drift risks of a sprout-only culture.

for a principal

Position these as a debt-containment policy: new code is always tested, legacy mass stops growing, and change points get characterized on the next visit — with metrics (coverage on changed lines) rather than a big-bang cleanup programme.

## The problem they solve You must add a feature inside a 600-line untested method whose class cannot be constructed in a test. Fully untangling it first may take days. These four techniques let the **new** code be born tested while touching the old code as little as possible. Terms: *host* = the existing untested method/class; *new behaviour* = what you must add. ## Sprout Method 1. Write the new behaviour as a **new method**, ideally taking simple parameters and returning a value (pure if possible). 2. Unit-test that method thoroughly — it is brand-new code with no legacy baggage. 3. In the host, insert **one call** to it at the correct point. The untested edit is a single line. Choose this when the new behaviour must run in the middle of the host's flow, and when the host class is at least constructible or the sprouted method is static/pure so its tests do not need the host at all. Downside: the host grows a little and the new method may reference host state, so it can end up needing the host anyway; if that happens, prefer Sprout Class. ## Sprout Class Same shape, but the new behaviour goes into a **new class**. Choose it when: - The host class cannot be instantiated in a test (constructor opens connections, requires a giant object graph), so even a new method on it would be untestable. - The new responsibility is conceptually different (a validator, a calculator, a policy) — you are seeding the design you eventually want. - The host class is already enormous and adding to it worsens the Large Class smell. The host edit becomes: create the new object (or receive it) and call it — still one or two lines. Downside: more classes, and "conceptual weight" — a reader must now follow a hop; if the new class is anaemic and only exists to dodge testability, that is design debt to revisit. ## Wrap Method Use when the new behaviour must run **before or after the entire** original method and does not need its internals. 1. Rename the original method (`postPayments` -> `postPaymentsCore`), using an automated rename so all callers update safely. 2. Create a new method with the **original name and signature** that calls the renamed original and your new, tested step. Callers are untouched because the public name and signature are preserved. A variant is to give the new behaviour its own name and have a third method call both, which avoids implying the new step is part of the old operation. Restriction: it cannot interleave. If your new logic must run *between* two statements inside the original, Wrap Method cannot express it — use Sprout. Risk: the new behaviour becomes invisible at the call site (`postPayments` now also emails). Naming and a test that pins the combined behaviour mitigate this. ## Wrap Class (Decorator) Use when the behaviour must apply to **all** users of a class, or must be composable/removable. 1. Ensure the class implements (or extract) an interface. 2. Write a new class implementing that interface, holding an instance of the original, delegating everything and adding your behaviour. 3. Change the **composition root** (wherever the object is constructed/wired) to build the decorator around the original. Properties: callers are untouched; the new class is fully unit-testable against a fake inner instance; decorators can be stacked (logging, retry, caching, audit) and removed by editing wiring only. This is the classic Decorator pattern applied as a legacy tactic. A related variant is *Wrap Class* where the new class does **not** implement the same interface but simply owns the original and exposes a new API — useful when only some call sites should get the new behaviour. Downsides: an interface with many methods forces lots of boilerplate delegation; a stack of decorators makes stack traces and debugging harder; if the class has no interface and callers depend on the concrete type, extracting one can ripple. ## Choosing between them | Situation | Technique | |---|---| | New logic runs mid-flow, host constructible | Sprout Method | | New logic runs mid-flow, host untestable or new responsibility | Sprout Class | | New logic runs strictly before/after whole operation, few callers, no interface | Wrap Method | | New logic applies to every caller, must be layered/toggled | Wrap Class (Decorator) | ## Honest trade-offs - **They do not clean anything.** The legacy method stays untested; you have merely stopped adding to the untested mass. Feathers is explicit that this is acceptable — the alternative is often no improvement at all — but a codebase that only ever sprouts accumulates a fringe of small classes around unchanged monoliths. - **Duplication risk.** A sprouted validator may duplicate validation already buried in the host; you did not read the host, so you may not know. - **Untested glue.** The one inserted call line is untested. Keep it trivial (no logic, no conditionals) so review can verify it by eye. - **Preferred alternative when cheap.** If dependency-breaking plus characterization tests is a couple of hours' work, do that instead and edit the method directly — sprout/wrap are for when it is not.

  • Your new behaviour must run in the middle of the legacy method AND needs three of its local variables. Sprout or Wrap?
    Sprout Method or Sprout Class: Wrap cannot interleave. Pass the needed values in as explicit parameters and return a result the host assigns back, keeping the new unit pure and testable. If passing them is awkward, that is a signal to first extract the surrounding block into a method (an automated Extract Method) so the boundary becomes clean.
  • When is Wrap Class clearly better than Wrap Method?
    When the behaviour must apply to every user of the type, when you want it composable or removable via configuration (logging, retry, caching, audit), or when you cannot safely rename the original because callers are numerous or outside your repository. The cost is needing an interface and boilerplate delegation.
  • Isn't sprouting just avoiding the real work?
    It is deliberately deferring it. The value is that the untested mass stops growing and each change leaves a tested island behind. The failure mode is never coming back: teams should pair sprouting with an explicit policy that touched code gets characterized and refactored when the change point next reopens.

Sprouting is grafting a healthy branch onto an old tree: the new growth is sound even though the trunk is not. Wrapping is putting a new outer shell around the whole tree — everything that touches it goes through the shell, and you can take the shell off again.

saying these in an interview costs you the question

  • Claiming Wrap Method can insert behaviour in the middle of the original method
  • Sprouting logic that duplicates behaviour already inside the untested host, without checking
  • Putting real logic in the inserted glue line so the untested part is non-trivial
  • Saying Sprout Class is always better than Sprout Method — it adds indirection when the host is perfectly constructible
  • Treating these techniques as a permanent architecture instead of a bridge to characterized, refactored code
  • Building a decorator but forgetting to rewire the composition root, so it never runs

context