code-architecture-wrong-abstraction
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCode 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.
jsx
// ✅ Correct: Wait for the pattern to emerge
// First occurrence - just write it
const userTotal = items.reduce((sum, item) => sum + item.price, 0);
// Second occurrence - still duplicate
const cartTotal = products.reduce((sum, p) => sum + p.price, 0);
// Third occurrence - NOW consider abstraction
const calculateTotal = (items, priceKey = 'price') =>
items.reduce((sum, item) => sum + item[priceKey], 0);不要急于抽象,直到代码在至少3个地方出现。这样才能获得足够的上下文,区分真正的模式与偶然的相似性。
jsx
// ✅ 正确做法:等待模式浮现
// 第一次出现 - 直接编写代码
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
- 相同代码出现在3个及以上位置
- 模式已经稳定(需求清晰明确)
- 抽象能够简化代码理解
- 用例具备完全一致的行为,而非仅仅是结构相似
❌ 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
- 代码仅在1-2个位置出现
- 需求仍在不断变化
- 用例需要不同的行为(即使结构看起来相似)
- 抽象需要为差异添加参数/条件判断
The Wrong Abstraction Pattern
错误抽象的演化模式
This is how wrong abstractions evolve:
jsx
// 1️⃣ Developer A spots duplication and extracts it
function processData(data) {
return data.map(transform).filter(validate);
}
// 2️⃣ New requirement is "almost" compatible
function processData(data, options = {}) {
let result = data.map(options.customTransform || transform);
if (options.skipValidation) return result;
return result.filter(options.customValidate || validate);
}
// 3️⃣ More variations pile up...
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;
}
// ❌ Now it's incomprehensible spaghetti错误抽象通常是这样形成的:
jsx
// 1️⃣ 开发者A发现重复代码并将其提取出来
function processData(data) {
return data.map(transform).filter(validate);
}
// 2️⃣ 新需求“几乎”兼容现有逻辑
function processData(data, options = {}) {
let result = data.map(options.customTransform || transform);
if (options.skipValidation) return result;
return result.filter(options.customValidate || validate);
}
// 3️⃣ 更多变体不断叠加...
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
jsx
// Before: One bloated function trying to do everything
processData(users, { customTransform: formatUser, skipValidation: true });
processData(orders, { sort: true, sortFn: byDate, limit: 10 });
// After: Inline and simplify each use case
const formattedUsers = users.map(formatUser);
const recentOrders = orders.sort(byDate).slice(0, 10);
// Later: If true patterns emerge, abstract properly最快的解决方法是回退:
- 内联:将抽象逻辑重新嵌入每个调用处
- 删除:移除每个调用不需要的部分
- 接受:暂时保留代码重复以保证清晰性
- 重新提取:基于当前认知构建合适的抽象
jsx
// 之前:一个臃肿的函数试图处理所有事情
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 |
| 收益 | 隐性成本 |
|---|---|
| 代码复用 | 不相关模块间的意外耦合 |
| 单一可信源 | 多层间接调用掩盖Bug |
| 符合DRY原则 | 组织惯性让重构变得困难 |
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.
外观模式(Facade)将复杂子系统封装在简单接口后。它很有用,但过度使用时往往会变成错误抽象。
The Typography Component Trap
排版组件陷阱
tsx
// ❌ Facade that becomes limiting
<Typography variant="body" size="sm">Hello</Typography>
// What if you need <small> or <mark>?
// Now you must extend the facade first:
<Typography variant="body" size="sm" as="small">Hello</Typography> // Added prop
<Typography variant="body" size="sm" as="mark">Hello</Typography> // Another prop
// ❌ Facade keeps growing with every edge case
type TypographyProps = {
variant: 'h1' | 'h2' | 'body' | 'caption';
size: 'sm' | 'md' | 'lg';
as?: 'p' | 'span' | 'small' | 'mark' | 'strong' | 'em'; // Growing...
weight?: 'normal' | 'bold';
color?: 'primary' | 'secondary' | 'muted';
// ... more props for every HTML text feature
};tsx
// ❌ 逐渐受限的外观组件
<Typography variant="body" size="sm">Hello</Typography>
// 如果你需要<small>或<mark>标签怎么办?
// 现在你必须先扩展外观组件:
<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';
// ... 为每个HTML文本特性添加更多属性
};When Facade Works
外观模式适用场景
tsx
// ✅ Good: Facade encapsulates complex logic
<DatePicker
value={date}
onChange={setDate}
minDate={today}
/>
// Hides: localization, calendar rendering, keyboard nav, accessibility
// ✅ Good: Facade enforces design system constraints
<Button variant="primary" size="md">Submit</Button>
// Ensures consistent styling, no arbitrary colorstsx
// ✅ 良好用法:外观组件封装复杂逻辑
<DatePicker
value={date}
onChange={setDate}
minDate={today}
/>
// 隐藏了:本地化、日历渲染、键盘导航、无障碍支持
// ✅ 良好用法:外观组件强化设计系统约束
<Button variant="primary" size="md">Submit</Button>
// 确保样式一致性,不允许随意使用颜色When to Skip the Facade
何时跳过外观模式
tsx
// ✅ Sometimes native HTML is clearer
<small className="text-muted">Fine print</small>
<mark>Highlighted text</mark>
// vs forcing everything through a facade:
<Typography variant="small" highlight>...</Typography> // ❌ Overengineeredtsx
// ✅ 有时原生HTML更清晰
<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 |
| 适合使用外观模式的场景 | 适合跳过外观模式的场景 |
|---|---|
| 封装复杂逻辑(API、状态) | 封装简单HTML元素 |
| 强化设计约束 | 一次性样式需求 |
| 团队需要一致模式 | 初级开发者需要学习底层技术 |
| 行为稳定且定义明确 | 需求仍在演变 |
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 and manual updates across files is simpler than maintaining a leaky abstraction.
ctrl+f如果初级开发者必须:
- 学习外观组件API
- 还要学习底层技术
- 为边缘案例扩展外观组件
...那么这个外观组件只会增加学习成本,而非价值。有时用手动更新多个文件,比维护一个“漏水”的抽象更简单。
ctrl+fQuick 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
- 等待3次及以上出现后再抽象
- 让模式自然浮现
- 优先优化可变更性,而非符合DRY原则
- 测试具体功能,而非抽象逻辑
- 内联错误抽象并重新开始
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 |
| 方法 | 含义 | 适用场景 |
|---|---|---|
| DRY | Don't Repeat Yourself(不要重复自己) | 模式稳定后 |
| WET | Write Everything Twice(所有内容写两次) | 默认起始方式 |
| AHA | Avoid Hasty Abstractions(避免仓促抽象) | 指导原则 |