code-architecture-wrong-abstraction

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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.
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:
  1. Inline the abstraction back into each caller
  2. Delete the portions each caller doesn't need
  3. Accept temporary duplication for clarity
  4. 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
最快的解决方法是回退
  1. 内联:将抽象逻辑重新嵌入每个调用处
  2. 删除:移除每个调用不需要的部分
  3. 接受:暂时保留代码重复以保证清晰性
  4. 重新提取:基于当前认知构建合适的抽象
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

抽象的隐性成本

BenefitHidden Cost
Code reuseAccidental coupling between unrelated modules
Single source of truthLayers of indirection obscure bugs
DRY complianceOrganizational 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 colors
tsx
// ✅ 良好用法:外观组件封装复杂逻辑
<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>  // ❌ Overengineered
tsx
// ✅ 有时原生HTML更清晰
<small className="text-muted">Fine print</small>
<mark>Highlighted text</mark>

// 相比强行通过外观组件处理所有内容:
<Typography variant="small" highlight>...</Typography>  // ❌ 过度设计

Facade Trade-offs

外观模式的权衡

Use Facade WhenSkip Facade When
Hiding complex logic (APIs, state)Wrapping simple HTML elements
Enforcing design constraintsOne-off styling needs
Team needs consistent patternsJuniors need to learn the underlying tech
Behavior is stable and well-definedRequirements are still evolving
适合使用外观模式的场景适合跳过外观模式的场景
封装复杂逻辑(API、状态)封装简单HTML元素
强化设计约束一次性样式需求
团队需要一致模式初级开发者需要学习底层技术
行为稳定且定义明确需求仍在演变

The Junior Developer Test

初级开发者测试

If a junior must:
  1. Learn the facade API
  2. Then learn the underlying technology anyway
  3. 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.
如果初级开发者必须:
  1. 学习外观组件API
  2. 还要学习底层技术
  3. 为边缘案例扩展外观组件
...那么这个外观组件只会增加学习成本,而非价值。有时用
ctrl+f
手动更新多个文件,比维护一个“漏水”的抽象更简单。

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
  • 等待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

核心理念

ApproachMeaningWhen to Use
DRYDon't Repeat YourselfAfter patterns stabilize
WETWrite Everything TwiceDefault starting point
AHAAvoid Hasty AbstractionsGuiding principle
方法含义适用场景
DRYDon't Repeat Yourself(不要重复自己)模式稳定后
WETWrite Everything Twice(所有内容写两次)默认起始方式
AHAAvoid Hasty Abstractions(避免仓促抽象)指导原则

References

参考资料