Refactor around a boundary
Change the structure of a program while keeping its public promises stable.
BY THE END, YOU CAN
- Distinguish refactoring from behavior changes
- Identify coupling and cohesion
- Introduce a storage boundary without speculative abstractions
Diagram description: Callers → Public function (call); Tests pin behavior → Public function; Public function → Old internals (before); Public function → New internals (after)
Preserve a promise while changing its shape
Refactoring changes internal structure without intentionally changing observable behavior.
It is easier when that behavior is captured by tests and a clear contract. Suppose Relay stores jobs in a dictionary today but will need a database tomorrow. First identify the operations its callers rely on: enqueue, claim, and acknowledge.
A boundary built around those operations is more stable than exposing the dictionary everywhere. Do not combine a broad rewrite, new features, and contract changes unless you can review their effects separately.
Follow reasons to change
Cohesion means related behavior lives together.
Coupling means one part knows details of another. If changing storage requires edits in input validation, scheduling, and output formatting, storage details have spread too far. Move that knowledge behind a small interface and pass the dependency to callers.
Keep business decisions independent from database mechanics where practical. This is not a request to create an interface for every class. Introduce a seam where you have a concrete variation, testing need, or source of change.
Take small reversible steps
Start by extracting a named operation without altering its logic.
Run the relevant checks. Redirect one caller through the new boundary and repeat. Once all callers use the boundary, remove obsolete paths.
Examine whether the new design actually reduced knowledge and made the next change easier. A design with more layers can still be more tightly coupled if those layers merely forward implementation details. In Relay, a repository that exposes raw database cursors to every caller has not fully hidden storage decisions.
COMMON FAILURE MODE
An abstraction that mirrors every method of one implementation often preserves its coupling while adding extra navigation.
TRY IT YOURSELF
Find every place a toy worker directly manipulates pending jobs. Propose three operations that express intent without exposing the underlying collection.
Make the call in the laboratory.
The two-question checkpoint, your notes, and your saved progress are in the laboratory. Free, with no account.
Open in the laboratory ↗CONNECT IT TO RELAY
Introduce a Relay storage boundary with an in-memory implementation. Keep scheduling policy outside the storage mechanics.