onChange
Today I want to teach a practical pattern for general software engineering. A common trigger for this lesson is onChange.
What we are solving
The failure mode is usually not one big crash. It is a chain of small assumptions that drift over time. The goal is to keep one deterministic path first, then add flexibility after behavior is measurable.
Step 1: Define one stable contract
Write down what must always be true before and after each operation. This prevents hidden coupling and keeps retries safe.
type Job = { id: string; attempt: number; status: 'queued' | 'done' | 'failed' };
export function next(job: Job): Job {
return { ...job, attempt: job.attempt + 1, status: 'done' };
}
Step 2: Build the happy path before edge paths
Implement the smallest complete flow first. Avoid mixing fallback logic into the core path too early, or debugging will become guesswork.
Step 3: Add guardrails and recovery behavior
After the base flow is stable, add validation gates, explicit error reasons, and rollback-friendly operations.