The Trap of the Wrong Abstraction
Two pieces of code look similar. Extracting them into a shared function seems clean—duplication eliminated, problem solved.
Then a new requirement arrives. The shared function almost works, so you add a parameter. Another requirement: add an if statement. Then another. What started as a clean extraction becomes a knot—multiple use cases jammed into one function, held together by boolean flags and conditional logic:
function updateRecord(record, isAdmin = false, sendEmail = true, forceOverwrite = false) {
if (isAdmin && !forceOverwrite) {
// ... logic for admin ...
}
if (sendEmail) {
// ... logic for email ...
}
// ... more flags ...
}
The code wasn't coupled because it was actually related. It was coupled because it looked the same on a Tuesday afternoon.
The path out looks wrong at first: copy and paste. Inline the shared logic back into each place that uses it and let them diverge. Once they're separated, you can see what each one actually does, what it needs, and where it's heading. The right abstraction—if there is one—becomes visible after weeks of real use, not minutes of speculation.
Until then, a little duplication is easier to live with than a function that's quietly doing three different jobs.