前端编程设计原则

146 阅读5分钟

如何写出高质量的前端代码》学习笔记

1. 最少知识原则 (Law of Demeter)

1.1 定义

最少知识原则(LOD)强调一个对象应该对其他对象保持最少的了解,只与直接的朋友交谈。这个原则也被称为“迪米特法则”。

1.2 核心思想

  • 一个对象应该对其他对象有尽可能少的了解

  • 一个类/组件/方法应该只调用:

    • 自身的方法
    • 参数的方法
    • 自身创建对象的方法
    • 自身直接依赖对象的方法

1.3 示例

❌ 下面是一个违反最少知识原则的例子:

// 反面示例
class UserService {
  getOrderDetails(userId) {
    const user = this.userRepository.findById(userId);
    // 违反原则:直接访问用户对象内部的订单对象,再访问订单的详情
    return user.orders[0].orderDetails; 
  }
}

✅ 正确示例

// 正确示例 
class UserService {
  getOrderDetails(userId) {
    const user = this.userRepository.findById(userId);
    // 通过用户对象提供的方法获取订单详情
    return user.getLatestOrderDetails();
  }
}

class User {
  getLatestOrderDetails() {
    if(this.orders.length > 0) {
      return this.orders[0].getDetails();
    }
    return null;
  }
}

1.5 在前端开发中的应用

在前端组件开发中,应该:

❌ 避免跨层级访问组件

<!-- 错误示例 -->
<template>
  <div>
    <child-component ref="child">
      <grand-child ref="grandChild"></grand-child>
    </child-component>
  </div>
</template>

<script>
export default {
  methods: {
    // 错误:直接访问孙组件
    updateGrandChild() {
      this.$refs.child.$refs.grandChild.update();
    }
  }
}
</script>

✅ 正确的组件通信方式

<!-- 正确示例 -->
<template>
  <div>
    <child-component 
      @update="handleUpdate"
    ></child-component>
  </div>
</template>

<script>
export default {
  methods: {
    // 通过事件通信
    handleUpdate(data) {
      // 处理更新
    }
  }
}
</script>

2. DRY原则 (Don't Repeat Yourself)

2.1 定义

DRY原则要求系统中的每一个功能都应该有唯一的实现。如果出现重复的代码,就意味着我们需要将其抽象成一个可重用的部分。

2.2 常见的代码重复类型

  1. 完全重复:相同的代码块在多处出现
  2. 几乎重复:代码结构相似,只有少量差异
  3. 语义重复:实现相同功能的不同代码

2.3 示例

❌ 重复代码示例:

// 重复的验证逻辑
function validateEmail(email) {
  if(!email) return falsereturn /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(email);
}

function validateUserForm(data) {
  if(!data.email) return falsereturn /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(data.email);
}

function validateCompanyForm(data) {
  if(!data.contactEmail) return falsereturn /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(data.contactEmail);
}

✅ 改进后:

// 抽取公共验证方法
const validators = {
  email(value) {
    if(!value) return falsereturn /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(value);
  }
};

function validateUserForm(data) {
  return validators.email(data.email);
}

function validateCompanyForm(data) {
  return validators.email(data.contactEmail); 
}

2.4 实践建议

  1. 创建通用组件库
  2. 提取共用的工具函数
  3. 使用继承和组合来复用代码
  4. 建立代码评审机制,及时发现重复代码

3. KISS原则 (Keep It Simple, Stupid)

3.1 定义

KISS原则强调简单性应该是软件设计的核心目标。代码应该简单易懂,而不是复杂晦涩。

3.2 示例

❌ 过度复杂的实现:

// 复杂的实现
function getWeekday(date) {
  const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 
                'Thursday', 'Friday', 'Saturday'];
  let day = date.getDay();
  return days[((day % 7) + 7) % 7];
}

✅ 简化后的实现:

// 简单的实现
function getWeekday(date) {
  const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 
                'Thursday', 'Friday', 'Saturday'];
  return days[date.getDay()];
}

3.3 实践建议

  1. 避免过度工程化
  2. 使用清晰的命名
  3. 保持函数短小精悍
  4. 避免过早优化
  5. 及时重构复杂代码

4. YAGNI原则 (You Aren't Gonna Need It)

4.1 定义

YAGNI原则建议开发者不要去实现当前不需要的功能。只实现目前需要的功能,而不是预测性地开发可能用到的功能。

4.2 示例

❌ 过度设计的例子:

// 过度设计的缓存类
class Cache {
  constructor() {
    this.cache = new Map();
    this.maxSize = 1000this.timeout = 3600this.strategy = 'LRU';
    // ... 更多复杂的配置
  }
  
  set(key, value, timeout, strategy) {
    // 复杂的缓存策略实现
  }
  
  get(key) {
    // 复杂的获取逻辑
  }
}

✅ 符合YAGNI的简单实现:

// 简单的缓存实现
class Cache {
  constructor() {
    this.cache = new Map();
  }
  
  set(key, value) {
    this.cache.set(key, value);
  }
  
  get(key) {
    return this.cache.get(key);
  }
}

4.3 实践建议

  1. 专注于当前需求
  2. 避免过度抽象
  3. 在需要时才添加功能
  4. 保持代码简单直接

5. SOLID原则

5.1 单一职责原则 (SRP)

每个类或模块应该只有一个改变的理由。

示例:

❌ 违反SRP的例子

// 违反SRP的例子
class UserManager {
  createUser() { /* ... */ }
  validateEmail() { /* ... */ }
  sendEmail() { /* ... */ }
  generateReport() { /* ... */ }
}

✅ 符合SRP的设计

// 符合SRP的设计
class UserManager {
  createUser() { /* ... */ }
  updateUser() { /* ... */ }
}

class EmailService {
  validateEmail() { /* ... */ }
  sendEmail() { /* ... */ }
}

class ReportGenerator {
  generateReport() { /* ... */ }
}

5.2 开闭原则 (OCP)

软件实体应该对扩展开放,对修改关闭。

示例:

// 支持扩展的通知系统
class Notifier {
  constructor(strategy) {
    this.strategy = strategy;
  }

  send(message) {
    return this.strategy.send(message);
  }
}

class EmailStrategy {
  send(message) {
    // 发送邮件
  }
}

class SMSStrategy {
  send(message) {
    // 发送短信
  }
}

// 扩展新的通知方式不需要修改现有代码
class PushStrategy {
  send(message) {
    // 发送推送
  }
}

5.3 里氏替换原则 (LSP)

子类必须能够替换其基类。

示例:

interface Shape {
  getArea(): number;
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  
  getArea(): number {
    return this.width * this.height;
  }
}

class Square implements Shape {
  constructor(private size: number) {}
  
  getArea(): number {
    return this.size * this.size;
  }
}

// 任何使用Shape的地方都可以使用Rectangle或Square
function printArea(shape: Shape) {
  console.log(shape.getArea());
}

5.4 接口隔离原则 (ISP)

客户端不应该依赖它不需要的接口。

示例:

// 违反ISP的接口
interface Worker {
  work(): voideat(): void;
}

// 符合ISP的接口拆分
interface Workable {
  work(): void;
}

interface Eatable {
  eat(): void;
}

interface Sleepable {
  sleep(): void;
}

class Human implements Workable, Eatable, Sleepable {
  work() { /* ... */ }
  eat() { /* ... */ }
  sleep() { /* ... */ }
}

class Robot implements Workable {
  work() { /* ... */ }
}

5.5 依赖倒置原则 (DIP)

高层模块不应该依赖低层模块,两者都应该依赖于抽象。

示例:

// 依赖抽象而不是具体实现
interface Database {
  save(data: any): voiddelete(id: string): void;
}

class MongoDB implements Database {
  save(data: any) { /* ... */ }
  delete(id: string) { /* ... */ }
}

class MySQL implements Database {
  save(data: any) { /* ... */ }
  delete(id: string) { /* ... */ }
}

// UserService依赖抽象的Database接口
class UserService {
  constructor(private db: Database) {}
  
  createUser(userData: any) {
    this.db.save(userData);
  }
}

6. 高内聚低耦合原则

6.1 定义

  • 高内聚:模块内部的元素应该紧密关联
  • 低耦合:模块之间的依赖应该最小化

6.2 示例

低内聚的组件:

<!-- 违反高内聚原则的组件 -->
<template>
  <div>
    <!-- 用户管理相关 -->
    <user-list></user-list>
    <user-form></user-form>
    
    <!-- 订单管理相关 -->
    <order-list></order-list>
    <order-form></order-form>
    
    <!-- 商品管理相关 -->
    <product-list></product-list>
    <product-form></product-form>
  </div>
</template>

高内聚的组件设计:

<!-- 用户管理组件 -->
<template>
  <div>
    <user-list></user-list>
    <user-form></user-form>
  </div>
</template>

<!-- 订单管理组件 -->
<template>
  <div>
    <order-list></order-list>
    <order-form></order-form>
  </div>
</template>

<!-- 商品管理组件 -->
<template>
  <div>
    <product-list></product-list>
    <product-form></product-form>
  </div>
</template>

6.3 实践建议

  1. 将相关的功能组织在一起
  2. 减少组件之间的直接依赖
  3. 使用事件总线或状态管理来处理组件通信
  4. 定期重构提高内聚性
  5. 使用依赖注入降低耦合

总结

这些编程原则不是硬性规定,而是指导方针。在实际开发中需要根据具体情况灵活运用,找到合适的平衡点。好的代码设计应该是:

  • 容易理解
  • 容易维护
  • 容易测试
  • 容易扩展
  • 可重用性高