系统重构:这次怎么落地的

这篇是系统重构的实操备忘,偏决策和限制条件。

技术债务评估

代码复杂度

# 使用 ESLint 检查代码复杂度
npx eslint --ext .js,.jsx src/ --format json > complexity-report.json

# 使用 SonarQube
sonar-scanner \
  -Dsonar.projectKey=myproject \
  -Dsonar.sources=src \
  -Dsonar.host.url=https://sonarqube.example.com

技术债务比率

// 计算技术债务比率
function calculateTechnicalDebt() {
  const totalLines = countTotalLines();
  const debtLines = countTechnicalDebtLines();
  
  return (debtLines / totalLines) * 100;
}

// 债务比率解释
// 0-5%: 良好
// 5-10%: 可以接受
// 10-20%: 需要关注
// >20%: 需要重构

代码重复率

# 使用 JSCPD 检测代码重复
npx jscpd src/ --format json > duplication-report.json

# 结果示例
{
  "statistics": {
    "totalLines": 10000,
    "duplicateLines": 1000,
    "percentage": 10
  }
}

重构策略

小步重构

// 重构前
function processUser(user) {
  if (user.age < 18) {
    return 'minor';
  } else if (user.age >= 18 && user.age < 65) {
    return 'adult';
  } else {
    return 'senior';
  }
}

// 重构:提取常量
const AGE_MINOR = 18;
const AGE_SENIOR = 65;

function processUser(user) {
  if (user.age < AGE_MINOR) {
    return 'minor';
  } else if (user.age < AGE_SENIOR) {
    return 'adult';
  } else {
    return 'senior';
  }
}

// 重构:提取方法
function getUserAgeGroup(age) {
  if (age < AGE_MINOR) return 'minor';
  if (age < AGE_SENIOR) return 'adult';
  return 'senior';
}

提取函数

// 重构前
function calculateOrderTotal(order) {
  let total = 0;
  for (let item of order.items) {
    if (item.discount) {
      total += item.price * item.quantity * (1 - item.discount);
    } else {
      total += item.price * item.quantity;
    }
  }
  if (order.coupon) {
    total = total * (1 - order.coupon.discount);
  }
  return total;
}

// 重构:提取函数
function calculateItemPrice(item) {
  const price = item.price * item.quantity;
  return item.discount ? price * (1 - item.discount) : price;
}

function applyCouponDiscount(total, coupon) {
  return coupon ? total * (1 - coupon.discount) : total;
}

function calculateOrderTotal(order) {
  const itemsTotal = order.items.reduce((sum, item) => 
    sum + calculateItemPrice(item), 0
  );
  
  return applyCouponDiscount(itemsTotal, order.coupon);
}

提取类

// 重构前
function validateUser(user) {
  if (!user.email || !isValidEmail(user.email)) {
    throw new Error('Invalid email');
  }
  if (!user.password || user.password.length < 8) {
    throw new Error('Password too short');
  }
  if (!user.age || user.age < 18) {
    throw new Error('User must be 18 or older');
  }
}

// 重构:提取类
class UserValidator {
  validateEmail(email) {
    if (!email || !isValidEmail(email)) {
      throw new Error('Invalid email');
    }
  }
  
  validatePassword(password) {
    if (!password || password.length < 8) {
      throw new Error('Password too short');
    }
  }
  
  validateAge(age) {
    if (!age || age < 18) {
      throw new Error('User must be 18 or older');
    }
  }
  
  validate(user) {
    this.validateEmail(user.email);
    this.validatePassword(user.password);
    this.validateAge(user.age);
  }
}

const validator = new UserValidator();
validator.validate(user);

架构重构

分层架构

// 重构前:所有逻辑都在一个文件
app.post('/api/users', async (req, res) => {
  // 验证逻辑
  if (!req.body.email) {
    return res.status(400).json({ error: 'Email required' });
  }
  
  // 业务逻辑
  const user = await db.query('SELECT * FROM users WHERE email = ?', [req.body.email]);
  if (user) {
    return res.status(400).json({ error: 'User already exists' });
  }
  
  const newUser = await db.query('INSERT INTO users SET ?', req.body);
  
  // 响应逻辑
  res.json(newUser);
});

// 重构:分层架构

// Controller 层
class UserController {
  constructor(userService) {
    this.userService = userService;
  }
  
  async create(req, res) {
    try {
      const user = await this.userService.create(req.body);
      res.json(user);
    } catch (error) {
      res.status(400).json({ error: error.message });
    }
  }
}

// Service 层
class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }
  
  async create(userData) {
    await this.validateEmail(userData.email);
    
    const existingUser = await this.userRepository.findByEmail(userData.email);
    if (existingUser) {
      throw new Error('User already exists');
    }
    
    return this.userRepository.create(userData);
  }
  
  async validateEmail(email) {
    if (!email) {
      throw new Error('Email required');
    }
  }
}

// Repository 层
class UserRepository {
  constructor(db) {
    this.db = db;
  }
  
  async findByEmail(email) {
    return this.db.query('SELECT * FROM users WHERE email = ?', [email]);
  }
  
  async create(userData) {
    return this.db.query('INSERT INTO users SET ?', userData);
  }
}

依赖注入

// 重构前:直接依赖
class OrderService {
  async create(orderData) {
    const db = require('./db');
    const paymentService = require('./payment-service');
    
    const order = await db.query('INSERT INTO orders SET ?', orderData);
    await paymentService.processPayment(order);
    
    return order;
  }
}

// 重构:依赖注入
class OrderService {
  constructor(db, paymentService) {
    this.db = db;
    this.paymentService = paymentService;
  }
  
  async create(orderData) {
    const order = await this.db.query('INSERT INTO orders SET ?', orderData);
    await this.paymentService.processPayment(order);
    
    return order;
  }
}

// 使用
const db = new Database();
const paymentService = new PaymentService();
const orderService = new OrderService(db, paymentService);

重构工具

重构检查清单

## 重构检查清单

### 重构前
- [ ] 有足够的测试覆盖
- [ ] 理解现有代码
- [ ] 识别重构范围
- [ ] 制定重构计划

### 重构中
- [ ] 小步重构
- [ ] 每步都有测试
- [ ] 保持代码可编译
- [ ] 及时提交代码

### 重构后
- [ ] 所有测试通过
- [ ] 代码审查完成
- [ ] 性能测试通过
- [ ] 文档更新

自动化重构

// 使用 Jest Snapshot 测试
describe('UserFormatter', () => {
  test('should format user correctly', () => {
    const user = { name: 'Alice', age: 30, email: '[email protected]' };
    const formatted = formatUser(user);
    
    expect(formatted).toMatchSnapshot();
  });
});

// 使用 Prettier 自动格式化
npx prettier --write src/**/*.js

// 使用 ESLint 自动修复
npx eslint --fix src/**/*.js

踩过的坑

坑一:重构范围太大

一次重构太多,导致难以回滚。

解决:小步重构,每次只改一个功能。

// 错误:一次重构太多
function refactorEverything() {
  refactorDataAccess();
  refactorBusinessLogic();
  refactorValidation();
  refactorLogging();
}

// 正确:小步重构
function refactorDataAccess() {
  // 只重构数据访问层
}

function refactorBusinessLogic() {
  // 下一步重构业务逻辑层
}

坑二:没有测试保护

重构破坏了现有功能,没有测试保护。

解决:重构前先写测试。

// 重构前:先写测试
describe('calculateOrderTotal', () => {
  test('should calculate total correctly', () => {
    const order = {
      items: [
        { price: 10, quantity: 2 },
        { price: 5, quantity: 1 }
      ]
    };
    
    expect(calculateOrderTotal(order)).toBe(25);
  });
});

// 然后重构
function calculateOrderTotal(order) {
  // 新的实现
}

坑三:过度重构

为了重构而重构,没有实际价值。

解决:只重构有价值的地方。

// 不必要的重构:只是改个变量名
// 旧代码
function processData(data) {
  return data.map(x => x * 2);
}

// 新代码
function transformData(data) {
  return data.map(item => item * 2);
}

// 如果旧代码已经清晰,这样的重构没有价值

写在最后

重构这东西,不是技术问题,是投资回报问题。

解决了

  • 维护成本降低
  • 开发效率提升
  • 代码质量提高

带来了

  • 短期成本
  • 学习成本
  • 风险

重构之前先评估:

  • 技术债务
  • 投资回报
  • 团队能力
  • 时间预算

不是所有代码都需要重构,有时候重写更简单。


这次系统重构花了一个月,从技术债务评估到重构策略。重构完成后,开发效率提升了 50%,Bug 数量减少了 40%。

版权声明: 本文首发于 指尖魔法屋-系统重构:这次怎么落地的https://blog.thinkmoon.cn/post/81-system-refactoring-technical-debt-strategy-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!