您现在的位置是:网站首页 > 代码异味与设计模式重构机会文章详情

代码异味与设计模式重构机会

代码异味是软件开发中常见的问题,它们暗示着代码可能存在设计缺陷或潜在的技术债务。通过识别这些异味并结合设计模式进行重构,可以显著提升代码的可维护性、可扩展性和可读性。JavaScript作为一门灵活的语言,尤其容易在不经意间引入代码异味,而设计模式则提供了成熟的解决方案。

重复代码

重复代码是最明显的代码异味之一。当相同的逻辑出现在多个地方时,不仅增加了维护成本,还容易导致修改遗漏。例如:

// 重复的验证逻辑
function validateUser(user) {
  if (!user.name || user.name.trim() === '') {
    throw new Error('用户名不能为空');
  }
  if (!user.email || !user.email.includes('@')) {
    throw new Error('邮箱格式不正确');
  }
}

function validateOrder(order) {
  if (!order.product || order.product.trim() === '') {
    throw new Error('产品名称不能为空');
  }
  if (!order.email || !order.email.includes('@')) {
    throw new Error('邮箱格式不正确');
  }
}

这种情况下,可以采用策略模式模板方法模式进行重构:

// 策略模式重构
const validators = {
  notEmpty: (value) => !!value && value.trim() !== '',
  isEmail: (value) => value.includes('@')
};

function validate(data, rules) {
  for (const [field, rule] of Object.entries(rules)) {
    if (!validators[rule](data[field])) {
      throw new Error(`${field}验证失败`);
    }
  }
}

// 使用方式
validate(user, {
  name: 'notEmpty',
  email: 'isEmail'
});

过长的函数

当一个函数超过50行代码时,就可能成为维护的噩梦。这种函数通常做了太多事情,违反了单一职责原则。例如:

function processOrder(order) {
  // 验证订单
  if (!order.id) throw new Error('缺少订单ID');
  if (!order.items || order.items.length === 0) throw new Error('订单项不能为空');
  
  // 计算总价
  let total = 0;
  order.items.forEach(item => {
    total += item.price * item.quantity;
  });
  
  // 应用折扣
  if (order.customer.isVIP) {
    total *= 0.9;
  }
  
  // 生成发票
  const invoice = {
    orderId: order.id,
    total,
    date: new Date()
  };
  
  // 发送通知
  if (order.customer.notificationPref === 'email') {
    sendEmail(order.customer.email, '您的订单已处理', `订单总价: ${total}`);
  } else if (order.customer.notificationPref === 'sms') {
    sendSMS(order.customer.phone, `您的订单${order.id}已处理`);
  }
  
  return invoice;
}

可以应用命令模式职责链模式将其分解:

class OrderProcessor {
  constructor() {
    this.steps = [
      new ValidationStep(),
      new PricingStep(),
      new DiscountStep(),
      new InvoiceGenerationStep(),
      new NotificationStep()
    ];
  }

  process(order) {
    return this.steps.reduce((result, step) => {
      return step.execute(order, result);
    }, {});
  }
}

class ValidationStep {
  execute(order) {
    if (!order.id) throw new Error('缺少订单ID');
    if (!order.items || order.items.length === 0) throw new Error('订单项不能为空');
    return order;
  }
}
// 其他步骤类类似...

过大的类

当一个类拥有太多属性和方法时,它可能承担了太多职责。例如:

class UserManager {
  constructor() {
    this.users = [];
  }
  
  addUser(user) { /*...*/ }
  removeUser(id) { /*...*/ }
  findUser(id) { /*...*/ }
  validateUser(user) { /*...*/ }
  sendWelcomeEmail(user) { /*...*/ }
  generateReport() { /*...*/ }
  backupUsers() { /*...*/ }
  restoreBackup() { /*...*/ }
}

这种情况适合使用单一职责原则外观模式进行重构:

class UserRepository {
  constructor() {
    this.users = [];
  }
  
  add(user) { /*...*/ }
  remove(id) { /*...*/ }
  find(id) { /*...*/ }
}

class UserValidator {
  validate(user) { /*...*/ }
}

class UserNotifier {
  sendWelcomeEmail(user) { /*...*/ }
}

class UserManagerFacade {
  constructor() {
    this.repository = new UserRepository();
    this.validator = new UserValidator();
    this.notifier = new UserNotifier();
  }
  
  registerUser(user) {
    this.validator.validate(user);
    this.repository.add(user);
    this.notifier.sendWelcomeEmail(user);
  }
}

过度使用基本类型

使用基本类型(如字符串、数字)来表示领域概念会导致代码缺乏表达力。例如:

function createOrder(productId, quantity, customerType) {
  // customerType可能是'regular', 'vip', 'premium'等
  // ...
}

可以使用对象字面量模式状态模式改进:

class Customer {
  constructor(type) {
    this.type = type;
  }
  
  get discountRate() {
    switch(this.type) {
      case 'vip': return 0.2;
      case 'premium': return 0.3;
      default: return 0;
    }
  }
}

function createOrder(productId, quantity, customer) {
  const discount = customer.discountRate;
  // ...
}

过度嵌套的条件语句

深层嵌套的if-else或switch语句难以理解和维护。例如:

function getShippingCost(order) {
  if (order.customer.isVIP) {
    return 0;
  } else {
    if (order.items.length > 5) {
      return 10;
    } else {
      if (order.total > 100) {
        return 5;
      } else {
        return 15;
      }
    }
  }
}

可以应用策略模式责任链模式重构:

const shippingRules = [
  {
    condition: order => order.customer.isVIP,
    cost: () => 0
  },
  {
    condition: order => order.items.length > 5,
    cost: () => 10
  },
  {
    condition: order => order.total > 100,
    cost: () => 5
  },
  {
    condition: () => true, // 默认情况
    cost: () => 15
  }
];

function getShippingCost(order) {
  const rule = shippingRules.find(r => r.condition(order));
  return rule.cost();
}

数据泥团

当一组数据总是一起出现时,它们应该被封装成对象。例如:

// 散落在各处的相关数据
const userName = '张三';
const userAge = 30;
const userEmail = 'zhang@example.com';

function sendBirthdayGreeting(name, age, email) {
  // ...
}

应该使用对象封装

class User {
  constructor(name, age, email) {
    this.name = name;
    this.age = age;
    this.email = email;
  }
  
  sendBirthdayGreeting() {
    // ...
  }
}

const user = new User('张三', 30, 'zhang@example.com');
user.sendBirthdayGreeting();

发散式变化

当一个类因为不同原因需要频繁修改时,就存在发散式变化问题。例如:

class ReportGenerator {
  generatePDF(data) { /*...*/ }
  generateHTML(data) { /*...*/ }
  generateCSV(data) { /*...*/ }
  
  validateData(data) { /*...*/ }  // 验证逻辑
  fetchData() { /*...*/ }        // 数据获取逻辑
}

可以应用桥接模式分离抽象和实现:

// 抽象部分
class Report {
  constructor(dataFetcher, validator, formatter) {
    this.dataFetcher = dataFetcher;
    this.validator = validator;
    this.formatter = formatter;
  }
  
  generate() {
    const data = this.dataFetcher.fetch();
    this.validator.validate(data);
    return this.formatter.format(data);
  }
}

// 实现部分
class PDFFormatter {
  format(data) { /*...*/ }
}

class DataValidator {
  validate(data) { /*...*/ }
}

霰弹式修改

与发散式变化相反,当需要对多个类做相同修改时,就存在霰弹式修改。例如多个类中都包含日志逻辑:

class OrderService {
  constructor() {
    this.logger = new Logger();
  }
  
  createOrder() {
    this.logger.log('Creating order');
    // ...
  }
}

class UserService {
  constructor() {
    this.logger = new Logger();
  }
  
  createUser() {
    this.logger.log('Creating user');
    // ...
  }
}

可以使用装饰器模式或**面向切面编程(AOP)**重构:

function withLogging(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`Calling ${name} with`, args);
    const result = original.apply(this, args);
    console.log(`Called ${name}, result`, result);
    return result;
  };
  return descriptor;
}

class OrderService {
  @withLogging
  createOrder() {
    // ...
  }
}

过度耦合

当类之间关系过于紧密时,修改一个类会影响其他类。例如:

class Order {
  constructor() {
    this.notifier = new EmailNotifier();
  }
  
  confirm() {
    // ...
    this.notifier.sendConfirmation();
  }
}

可以应用依赖注入控制反转解耦:

class Order {
  constructor(notifier) {
    this.notifier = notifier;
  }
  
  confirm() {
    // ...
    this.notifier.sendConfirmation();
  }
}

// 使用时
const order = new Order(new EmailNotifier());
// 或
const order = new Order(new SMSNotifier());

临时字段

当一个对象的某些字段只在特定情况下使用时,这些字段就是临时字段。例如:

class Customer {
  constructor() {
    this.discountCalculationData = null; // 只在计算折扣时使用
  }
  
  calculateDiscount() {
    this.discountCalculationData = this.fetchDiscountData();
    // 复杂的计算逻辑...
  }
}

可以使用引入外加函数策略模式重构:

class DiscountCalculator {
  constructor(customer) {
    this.customer = customer;
  }
  
  calculate() {
    const data = this.fetchDiscountData();
    // 计算逻辑...
  }
  
  fetchDiscountData() {
    // ...
  }
}

// 使用
const discount = new DiscountCalculator(customer).calculate();

不恰当的继承

继承被过度使用时,会导致脆弱的类层次结构。例如:

class Animal {
  eat() { /*...*/ }
  sleep() { /*...*/ }
}

class Bird extends Animal {
  fly() { /*...*/ }
}

class Penguin extends Bird {
  // 企鹅不会飞,但继承了fly方法
}

可以应用组合优于继承原则和策略模式重构:

class Animal {
  constructor(movementStrategy) {
    this.movement = movementStrategy;
  }
  
  move() {
    this.movement.execute();
  }
}

const flyMovement = {
  execute: () => console.log('Flying')
};

const swimMovement = {
  execute: () => console.log('Swimming')
};

const bird = new Animal(flyMovement);
const penguin = new Animal(swimMovement);

过度使用注释

当代码需要大量注释才能理解时,通常意味着代码本身不够清晰。例如:

// 检查用户是否有权限
// 参数user是用户对象
// 返回true如果有权限,否则false
function checkPermission(user) {
  // 管理员有全部权限
  if (user.role === 'admin') return true;
  
  // 普通用户需要检查权限列表
  if (user.permissions) {
    // 查找是否有需要的权限
    return user.permissions.some(p => p === 'requiredPermission');
  }
  
  // 默认无权限
  return false;
}

可以通过提取方法引入解释性变量重构:

function checkPermission(user) {
  return isAdmin(user) || hasRequiredPermission(user);
}

function isAdmin(user) {
  return user.role === 'admin';
}

function hasRequiredPermission(user) {
  const requiredPermission = 'requiredPermission';
  return user.permissions?.includes(requiredPermission) ?? false;
}

重构的权衡

虽然设计模式提供了强大的工具来消除代码异味,但过度使用模式也会导致代码过于复杂。关键在于识别真正的痛点,而不是为了使用模式而重构。例如,对于简单的条件语句,直接使用if-else可能比引入策略模式更合适。重构应该以实际需求为导向,而不是纯粹的理论完美。

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

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