Skip to content

Sticky Contract

You extracted a unit from a caller for legibility or testability. Over time, the unit’s signature, behavior, and side-effect shape become a contract that constrains future change — even though no consumer outside the original caller actually depends on the contract being stable.

Primarily cognitive: future changes to the unit feel risky, code review pushes back on signature changes, the unit’s shape is preserved out of caution rather than necessity. The cost shows up as drag on the original caller’s evolution.

  • The original caller could absorb a refactor that changes the unit’s shape, but the team treats the unit as load-bearing and does the change in a slower, more conservative way.
  • Tests at the unit boundary are preserved as if they were a published contract; new behavior is added by extending the contract rather than by reshaping it, accumulating cruft.
  • The unit becomes the kind of code people work around rather than work with. Multiple slightly-different helper extractions appear because nobody wants to disturb the original.
  • New consumers of the same logic end up duplicating it, because the existing unit’s shape doesn’t quite fit and changing it feels too risky.
  • You’re hesitating to change a unit’s signature even though only one caller exists.
  • A code review comment asks “are you sure this is backwards-compatible?” about a unit with one consumer in the same file.
  • New behavior is being shoehorned into the existing unit’s shape rather than reshaping the unit.
  • Units with one or two consumers but stable signatures over years, while the consumers themselves churn.
  • “Helper” or “utility” extractions of the same logic appearing in multiple places because the original was too rigid.
  • Changes to the original caller that touch lots of files for no clear reason — the unit’s contract is leaking constraint outward.
  • Treat extracted units inside a feature as implementation details until proven otherwise. Mark them as such (folder placement, naming, optionally a comment).
  • When changing the original caller, give yourself permission to also reshape its helpers in the same commit. Don’t treat extraction as a one-way door.
  • Don’t preserve unit-level tests when the unit’s shape is changing — rewrite or delete them. Tests are scaffolding, not contract.
  • If you find yourself preserving a contract for “future consumers that might exist,” ask whether those consumers actually exist. Almost always: they don’t.
  • The unit has multiple real consumers and a documented, owned contract. Stability is the value.
  • Intrinsic-contract code (Luhn check, parsers, etc.). The contract is the spec; preserve it.
  • Library code being prepared for publication. Stability of the public surface is exactly the deliverable.