Refactoring techniqueSimplifying Conditional Expressions
Introduce Assertion
State an assumption the code already relies on as an explicit check that fails fast, instead of leaving it in a comment or in the reader's head.
Problem
A block of code only works if some condition holds: a list is non-empty, a total is non-negative, a field was set earlier. Nothing checks it. When the assumption breaks, the code fails later and somewhere else, or silently produces wrong numbers.
Solution
Add an assertion at the point where the assumption must be true. It documents the requirement and stops execution with a clear message when it is violated.
Before
function allocatePayment(payment: Payment, invoices: Invoice[]): Allocation[] {
// invoices are sorted oldest first and none are already paid
const allocations: Allocation[] = [];
let remaining = payment.amountCents;
for (const inv of invoices) {
const applied = Math.min(remaining, inv.balanceCents);
allocations.push({ invoiceId: inv.id, amountCents: applied });
remaining -= applied;
if (remaining === 0) break;
}
return allocations;
}After
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(`Invariant failed: ${message}`);
}
function allocatePayment(payment: Payment, invoices: Invoice[]): Allocation[] {
assert(invoices.every((inv) => inv.balanceCents > 0), "invoices must be unpaid");
assert(
invoices.every((inv, i) => i === 0 || invoices[i - 1].dueDate <= inv.dueDate),
"invoices must be sorted by due date",
);
const allocations: Allocation[] = [];
let remaining = payment.amountCents;
for (const inv of invoices) {
const applied = Math.min(remaining, inv.balanceCents);
allocations.push({ invoiceId: inv.id, amountCents: applied });
remaining -= applied;
if (remaining === 0) break;
}
return allocations;
}Why
- The comment becomes executable and cannot go stale.
- A violation fails at its source with a message that names the broken rule.
- TypeScript's
asserts conditionsignature also narrows types after the call.
Steps
- Find an assumption stated in a comment, in a name, or implied by the code (division by a count,
[0]access, non-null!). - Write the assertion right where the assumption is first needed. Make the message name the rule. Run the tests; they should still pass because the assumption held.
- If a test now fails, you found a real bug or a wrong assumption. Fix one of them before going on.
- Delete the comment the assertion replaced.
Watch out
- Assertions are for programmer errors, not for input validation. User input, API payloads and file contents can legitimately be wrong; validate them and return errors or throw domain exceptions.
- Do not assert things the type system already guarantees.
- Keep the check cheap, or guard it behind a debug flag. The sort check above is O(n) on a hot path; drop it in production if profiling shows a cost.
- Some runtimes strip assertions in production (
python -O, Java without-ea). Do not put side effects inside them, and do not rely on them for security. - Removing an assertion has no named technique. Remove it only when the assumption is no longer needed.
Related
- Replaces explanatory Comments.
- When absence is a valid state rather than a broken assumption, use Introduce Null Object.
- For expected failure cases, see Replace Error Code with Exception.
Agent checklist
- The asserted condition is an internal invariant, not untrusted input.
- The assertion has no side effects.
- Its message names the rule that was broken.
- Existing tests still pass, proving the assumption held.
Treats these smells
See also
- Introduce Null ObjectReplace repeated null checks for a missing collaborator with an object that implements the same interface and does the safe default.
- Replace Exception with TestCheck a predictable condition before the call instead of catching the exception it would throw, keeping exceptions for real failures.
- Replace Error Code with ExceptionThrow a typed exception instead of returning a sentinel such as -1 or false, so failures cannot be silently ignored by callers.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.