您现在的位置是:网站首页 > 过度设计(一个按钮点击事件抽象出 5 层接口)文章详情

过度设计(一个按钮点击事件抽象出 5 层接口)

过度设计是前端开发中常见的问题,尤其在追求“架构优雅”时容易陷入陷阱。一个简单的按钮点击事件被拆分成多层接口,看似模块化,实则增加了维护成本。这种现象背后往往隐藏着对设计模式的误解或对“未来扩展”的过度担忧。

什么是过度设计

过度设计指为满足假想的未来需求,提前引入不必要的抽象层。比如点击按钮触发API请求的场景,本可以直截了当实现,却被拆分成:

  1. 视图层事件绑定
  2. 业务逻辑中介层
  3. 数据转换层
  4. API调用抽象层
  5. 错误处理装饰层
// 典型过度设计示例
class ButtonClickHandler {
  private apiService: APIService;
  private dataParser: DataParser;
  private errorDecorator: ErrorDecorator;

  constructor() {
    this.apiService = new APIService();
    this.dataParser = new DataParser();
    this.errorDecorator = new ErrorDecorator();
  }

  async handleClick(rawData: unknown) {
    try {
      const parsed = this.dataParser.normalize(rawData);
      const response = await this.apiService.fetch(parsed);
      return this.errorDecorator.wrap(response);
    } catch (err) {
      this.errorDecorator.log(err);
      throw new AdvancedError(err);
    }
  }
}

// 实际调用需要4层实例化
document.getElementById('btn').addEventListener('click', () => {
  const handler = new ButtonClickHandler();
  handler.handleClick({ foo: 'bar' });
});

过度设计的典型特征

1. 接口套娃现象

每个新需求都导致新增抽象层而非复用现有代码。例如表单验证场景:

// 初始版本
function validateForm(data) {
  return data.username.length > 0;
}

// "优化"后版本
interface Validator {
  validate: (data: any) => boolean;
}

class RequiredValidator implements Validator {
  validate(data) {
    return data != null;
  }
}

class LengthValidator implements Validator {
  constructor(private min: number) {}
  validate(data) {
    return data.length >= this.min;
  }
}

class ValidatorComposite {
  private validators: Validator[] = [];
  addValidator(v: Validator) {
    this.validators.push(v);
  }
  validate(data) {
    return this.validators.every(v => v.validate(data));
  }
}

// 使用时代码量暴增
const composite = new ValidatorComposite();
composite.addValidator(new RequiredValidator());
composite.addValidator(new LengthValidator(5));

2. 过度依赖设计模式

强行套用设计模式导致代码复杂度上升。比如为简单数据缓存引入代理模式:

interface DataService {
  fetchData(): Promise<any>;
}

class RealDataService implements DataService {
  async fetchData() {
    return api.get('/data');
  }
}

class DataServiceProxy implements DataService {
  private cache: Map<string, any> = new Map();
  constructor(private realService: DataService) {}

  async fetchData() {
    if (this.cache.has('data')) {
      return this.cache.get('data');
    }
    const data = await this.realService.fetchData();
    this.cache.set('data', data);
    return data;
  }
}

// 实际调用链
const proxy = new DataServiceProxy(new RealDataService());
proxy.fetchData();

过度设计的危害

1. 认知负荷剧增

新成员需要理解多层接口关系才能修改简单功能。下图展示了一个按钮点击涉及的调用链:

View → Controller → Service → Repository → HTTP Client
      ↳ Logger      ↳ Validator
            ↳ Error Mapper

2. 修改成本指数级增长

添加新字段需要修改多个层级:

  1. 更新DTO接口定义
  2. 修改数据转换层
  3. 调整验证规则
  4. 更新API契约
  5. 同步类型声明

3. 测试复杂度爆炸

原本单个单元测试现在需要mock多层依赖:

// 测试代码示例
it('should handle click', async () => {
  const mockApi = { fetch: jest.fn() };
  const mockParser = { normalize: jest.fn() };
  const mockDecorator = { wrap: jest.fn() };

  const handler = new ButtonClickHandler();
  handler.apiService = mockApi;
  handler.dataParser = mockParser;
  handler.errorDecorator = mockDecorator;

  await handler.handleClick({});
  
  expect(mockParser.normalize).toBeCalled();
  expect(mockApi.fetch).toBeCalled();
  expect(mockDecorator.wrap).toBeCalled();
});

合理设计的平衡点

1. 延迟抽象原则

当出现三次重复代码时才考虑抽象:

// 第一次实现
function fetchUser() {
  return axios.get('/user');
}

// 第二次类似需求
function fetchProducts() {
  return axios.get('/products');
}

// 第三次出现时再抽象
function createFetcher(endpoint) {
  return () => axios.get(endpoint);
}

2. 扁平化层级

将多层调用压缩到2-3层内:

// 优化后的数据获取
async function loadData() {
  try {
    const res = await fetch('/api/data');
    return await res.json();
  } catch (err) {
    showError(err);
    throw err;
  }
}

3. 实用主义设计

根据实际场景选择复杂度,例如表单处理:

// 简单表单
function handleSubmit(event) {
  const data = new FormData(event.target);
  if (!data.get('name')) return;
  postData(data);
}

// 复杂表单才引入验证层
class FormValidator {
  static validate(data) {
    // 复杂校验逻辑
  }
}

从过度设计到适度设计

1. 识别过度设计的信号

  • 修改简单功能需要通读5个以上文件
  • 接口定义比实现代码多3倍
  • 需要画UML图才能理解模块关系
  • 50%的代码是接口/抽象类

2. 重构策略

使用内联重构简化调用链:

// 重构前
class OrderService {
  private validator: OrderValidator;
  private notifier: OrderNotifier;

  placeOrder(order: Order) {
    this.validator.validate(order);
    const result = OrderRepository.save(order);
    this.notifier.notify(result);
    return result;
  }
}

// 重构后
function placeOrder(order: Order) {
  if (!order.items.length) throw new Error('Invalid order');
  const result = db.save(order);
  sendEmail(result.user);
  return result;
}

3. 架构演进建议

  1. 初期:直接过程式编程
  2. 中期:按功能划分模块
  3. 后期:仅在必要时引入抽象
graph TD
  A[简单脚本] --> B[功能模块]
  B --> C{需要抽象?}
  C -->|Yes| D[引入接口]
  C -->|No| E[保持现状]

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步