// Choose the right level of abstraction by evaluating coupling cost versus duplication cost and change patterns. Use when: (1) asked to evaluate whether similar code should be abstracted or kept separate, (2) an extraction is suggested without analysis of whether similar pieces change together or independently, (3) an abstraction is nameable only as "common parts" rather than a domain concept, (4) justification cites aesthetics or "might need" rather than maintenance costs and change patterns.
| name | deciding-abstraction-level |
| description | Choose the right level of abstraction by evaluating coupling cost versus duplication cost and change patterns. Use when: (1) asked to evaluate whether similar code should be abstracted or kept separate, (2) an extraction is suggested without analysis of whether similar pieces change together or independently, (3) an abstraction is nameable only as "common parts" rather than a domain concept, (4) justification cites aesthetics or "might need" rather than maintenance costs and change patterns. |
Choose the right level of abstraction by evaluating the cost of interdependence against the cost of repetition.
When I consider introducing or avoiding an abstraction, I evaluate whether generalization serves actual needs or just serves an aesthetic preference for eliminating repetition.
Assess the repetition cost - I examine whether the repeated elements are actually causing problems like inconsistency or maintenance burden, or whether they're just visually similar, since repetition that doesn't hurt is often cheaper than premature unification.
Analyze change patterns - I review the history and likely future of the similar elements to see if these pieces change together for the same reasons or independently for different reasons, as things that change together belong together while things that change separately should stay separate even if they look similar.
Evaluate interdependence cost - I consider what the proposed abstraction would bind together—whether these things should share a fate or whether binding them will make future changes harder by forcing me to either complicate the shared form or separate it later.
Test the abstraction's clarity - I try to name the abstraction and explain what concept it represents, since if I can only describe it as "the common parts of X and Y" rather than as a meaningful domain concept, it's probably not a real abstraction yet.
Compare restructuring paths - I weigh whether it's easier to extract a unifying concept from concrete repeated instances when the pattern becomes clear, or to separate a wrong abstraction that's been built into the system, recognizing that the second path is usually much more expensive.
Recognize false similarity - I look for ways the similar elements might diverge, considering whether apparent repetition is actually two different concepts that happen to look alike right now but will evolve differently as requirements change.
Wait for the rule of three - I resist generalizing on the second instance and instead wait for the third occurrence to reveal the true pattern, as the first instance is a case, the second is coincidence, and the third confirms the pattern worth unifying.
I'm reviewing an API where two endpoints both validate email addresses, and I'm tempted to extract a shared validateEmail() function. However, examining the domains reveals that one endpoint validates user registration emails (which must not already exist and should have strict format requirements) while the other validates contact form emails (which just need basic format checking and can be duplicated). Looking at the change history, user validation has been updated three times for security requirements while contact validation hasn't changed in months. These are similar operations serving different purposes that will evolve independently. Keeping the validation logic separate in each endpoint means changes to registration security don't risk breaking the contact form, and each can be understood in its own context. The superficial similarity would create coupling where none should exist.
I notice three API endpoints that each implement retry logic for external service calls with identical exponential backoff algorithms, error handling, and logging. Each implementation has fifteen lines of code duplicated almost exactly, and I've seen bugs where one was updated but the others weren't, creating inconsistent behavior. The change history shows these three have been updated together every time retry behavior needed adjustment. I can name the abstraction clearly as retryWithBackoff() and it represents a genuine domain concept rather than just "shared code." Extracting this abstraction actually reduces coupling rather than creating it, because currently these endpoints are implicitly coupled through their duplicated implementation, but the coupling is invisible and fragile. The abstraction makes the shared behavior explicit and ensures consistency, while each endpoint remains free to compose it differently or pass different parameters.
I'm working on the first background job in a new system and I'm tempted to build a generic job framework with base classes, lifecycle hooks, error handling strategies, and retry mechanisms to handle future jobs elegantly. However, there's currently only one job type and no clarity on what the next ones will need. Building the framework now means making assumptions about what flexibility will be valuable, and those assumptions are likely wrong since they're based on imagination rather than actual requirements. Instead, I implement this single job concretely with straightforward error handling and logging specific to its needs. When the second job arrives, I resist the urge to abstract and note the differences. By the third job, the actual patterns have emerged, I can see which parts truly vary and which are genuinely common, and I can extract abstractions that serve real needs rather than hypothetical ones. The concrete implementations are easy to refactor once the pattern is clear, while a premature framework would be fighting me the whole time.