| name | flpbalada-code-architecture-wrong-abstraction |
| description | Guides when to abstract vs duplicate code. Use this skill when creating shared utilities, deciding between DRY/WET approaches, or refactoring existing abstractions. |
Code Architecture: Avoiding Wrong Abstractions
Core Principle
Prefer duplication over the wrong abstraction. Wait for patterns to emerge before abstracting.
Premature abstraction creates confusing, hard-to-maintain code. Duplication is far cheaper to fix than unwinding a wrong abstraction.
The Rule of Three
Don't abstract until code appears in at least 3 places. This provides enough context to identify genuine patterns vs coincidental similarities.
const userTotal = items.reduce((sum, item) => sum + item.price, 0);
const cartTotal = products.reduce((sum, p) => sum + p.price, 0);
const calculateTotal = (items, priceKey = 'price') =>
items.reduce((sum, item) => sum + item[priceKey], 0);
When to Abstract
✅ Abstract When
- Same code appears in 3+ places
- Pattern has stabilized (requirements are clear)
- Abstraction simplifies understanding
- Use cases share identical behavior, not just similar structure
❌ Don't Abstract When
- Code only appears in 1-2 places
- Requirements are still evolving
- Use cases need different behaviors (even if structure looks similar)
- Abstraction would require parameters/conditionals for variations
The Wrong Abstraction Pattern
This is how wrong abstractions evolve:
function processData(data) {
return data.map(transform).filter(validate);
}
function processData(data, options = {}) {
let result = data.map(options.customTransform || transform);
if (options.skipValidation) return result;
return result.filter(options.customValidate || validate);
}
function processData(data, options = {}) {
let result = data;
if (options.preProcess) result = options.preProcess(result);
result = result.map(options.customTransform || transform);
if (!options.skipValidation) {
result = result.filter(options.customValidate || validate);
}
if (options.postProcess) result = options.postProcess(result);
if (options.sort) result = result.sort(options.sortFn);
return options.limit ? result.slice(0, options.limit) : result;
}
How to Fix Wrong Abstractions
The fastest way forward is back:
- Inline the abstraction back into each caller
- Delete the portions each caller doesn't need
- Accept temporary duplication for clarity
- Re-extract proper abstractions based on current understanding
processData(users, { customTransform: formatUser, skipValidation: true });
processData(orders, { sort: true, sortFn: byDate, limit: 10 });
const formattedUsers = users.map(formatUser);
const recentOrders = orders.sort(byDate).slice(0, 10);
Hidden Costs of Abstraction
| Benefit | Hidden Cost |
|---|
| Code reuse | Accidental coupling between unrelated modules |
| Single source of truth | Layers of indirection obscure bugs |
| DRY compliance | Organizational inertia makes refactoring painful |
Facade Pattern: When It Becomes a Wrong Abstraction
Facades wrap complex subsystems behind a simple interface. They're useful but often become wrong abstractions when overused.
The Typography Component Trap
<Typography variant="body" size="sm">Hello</Typography>
<Typography variant="body" size="sm" as="small">Hello</Typography>
<Typography variant="body" size="sm" as="mark">Hello</Typography>
type TypographyProps = {
variant: 'h1' | 'h2' | 'body' | 'caption';
size: 'sm' | 'md' | 'lg';
as?: 'p' | 'span' | 'small' | 'mark' | 'strong' | 'em';
weight?: 'normal' | 'bold';
color?: 'primary' | 'secondary' | 'muted';
};
When Facade Works
<DatePicker
value={date}
onChange={setDate}
minDate={today}
/>
<Button variant="primary" size="md">Submit</Button>
When to Skip the Facade
<small className="text-muted">Fine print</small>
<mark>Highlighted text</mark>
<Typography variant="small" highlight>...</Typography>
Facade Trade-offs
| Use Facade When | Skip Facade When |
|---|
| Hiding complex logic (APIs, state) | Wrapping simple HTML elements |
| Enforcing design constraints | One-off styling needs |
| Team needs consistent patterns | Juniors need to learn the underlying tech |
| Behavior is stable and well-defined | Requirements are still evolving |
The Junior Developer Test
If a junior must:
- Learn the facade API
- Then learn the underlying technology anyway
- Then extend the facade for edge cases
...the facade adds friction, not value. Sometimes ctrl+f and manual updates across files is simpler than maintaining a leaky abstraction.
Quick Reference
DO
- Wait for 3+ occurrences before abstracting
- Let patterns emerge naturally
- Optimize for changeability, not DRY compliance
- Test concrete features, not abstractions
- Inline bad abstractions and start fresh
DON'T
- Abstract based on structural similarity alone
- Add parameters/conditionals to force fit new use cases
- Preserve abstractions due to sunk cost fallacy
- Fear temporary duplication
Key Philosophies
| Approach | Meaning | When to Use |
|---|
| DRY | Don't Repeat Yourself | After patterns stabilize |
| WET | Write Everything Twice | Default starting point |
| AHA | Avoid Hasty Abstractions | Guiding principle |
References