重构遗留代码
概览
这项技能帮助您系统地重构遗留代码,以提高可维护性、可读性和性能,同时保持现有功能。它遵循安全重构的行业最佳实践,并进行全面测试。
何时使用
- 现代化过时的代码模式或弃用的API
- 减少现有代码库的技术债务
- 提高代码的可读性和可维护性
- 从单体代码中提取可重用组件
- 升级到更新的语言特性或框架
- 为新功能开发做准备
指令
1. 代码评估
首先,分析遗留代码以理解:
# 审查代码库结构
tree -L 3 -I 'node_modules|dist|build'
# 检查过时的依赖
npm outdated # 或 pip list --outdated, composer outdated, 等。
# 识别代码复杂性热点
# 使用工具:
# - SonarQube 用于代码异味
# - eslint 用于 JavaScript
# - pylint 用于 Python
# - RuboCop 用于 Ruby
评估清单:
- [ ] 识别过时的模式和API
- [ ] 定位紧密耦合的组件
- [ ] 查找重复的代码块
- [ ] 查看测试覆盖率差距
- [ ] 文档化当前行为和边缘情况
- [ ] 识别性能瓶颈
2. 建立安全网
重构前,确保您有全面的测试:
// 添加特性化测试以锁定当前行为
describe('LegacyFeature', () => {
it('should preserve existing behavior during refactoring', () => {
// 测试当前实现行为
const input = { /* 实际测试数据 */ };
const result = legacyFunction(input);
// 文档化预期输出
expect(result).toEqual({ /* 当前实际输出 */ });
});
});
测试策略:
- 添加关键路径的单元测试
- 创建组件交互的集成测试
- 文档化边缘情况和错误场景
- 设置测试覆盖率监控
- 每次重构步骤前运行测试
3. 增量重构
系统地应用重构模式:
提取函数/方法
// 之前:长且复杂的函数
function processUserData(user) {
// 50行混合验证、转换和业务逻辑
if (!user.email || !user.email.includes('@')) return null;
const normalized = user.email.toLowerCase().trim();
// ... 更复杂的逻辑
}
// 之后:提取的、专注的函数
function validateEmail(email) {
return email && email.includes('@');
}
function normalizeEmail(email) {
return email.toLowerCase().trim();
}
function processUserData(user) {
if (!validateEmail(user.email)) return null;
const email = normalizeEmail(user.email);
// 清晰、可读的流程
}
用多态替换条件语句
# 之前:复杂的条件逻辑
def calculate_price(customer_type, base_price):
if customer_type == 'regular':
return base_price
elif customer_type == 'premium':
return base_price * 0.9
elif customer_type == 'vip':
return base_price * 0.8
else:
return base_price
# 之后:多态方法
class PricingStrategy:
def calculate(self, base_price):
return base_price
class RegularPricing(PricingStrategy):
pass
class PremiumPricing(PricingStrategy):
def calculate(self, base_price):
return base_price * 0.9
class VIPPricing(PricingStrategy):
def calculate(self, base_price):
return base_price * 0.8
# 使用
pricing = pricing_strategies[customer_type]
price = pricing.calculate(base_price)
引入参数对象
// 之前:长参数列表
function createUser(
firstName: string,
lastName: string,
email: string,
phone: string,
address: string,
city: string,
state: string,
zip: string
) {
// ...
}
// 之后:参数对象
interface UserData {
firstName: string;
lastName: string;
email: string;
phone: string;
address: Address;
}
interface Address {
street: string;
city: string;
state: string;
zip: string;
}
function createUser(userData: UserData) {
// ...
}
4. 现代化模式
用现代等价物替换过时的模式:
用Promises代替Callbacks
// 之前:回调地狱
function fetchUserData(userId, callback) {
db.query('SELECT * FROM users WHERE id = ?', [userId], (err, user) => {
if (err) return callback(err);
db.query('SELECT * FROM orders WHERE user_id = ?', [userId], (err, orders) => {
if (err) return callback(err);
callback(null, { user, orders });
});
});
}
// 之后:Async/await
async function fetchUserData(userId) {
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
const orders = await db.query('SELECT * FROM orders WHERE user_id = ?', [userId]);
return { user, orders };
}
现代语言特性
// 之前:var 和字符串连接
var userName = user.firstName + ' ' + user.lastName;
var isActive = user.status === 'active' ? true : false;
// 之后:const/let 和模板字面量
const userName = `${user.firstName} ${user.lastName}`;
const isActive = user.status === 'active';
5. 减少依赖
打破紧密耦合:
# 之前:紧密耦合到特定实现
class OrderProcessor:
def __init__(self):
self.db = MySQLDatabase() # 紧密耦合
self.email = SendGridEmail() # 紧密耦合
def process_order(self, order):
self.db.save(order)
self.email.send(order.customer_email, "Order confirmed")
# 之后:依赖注入
class OrderProcessor:
def __init__(self, database, email_service):
self.db = database # 任何数据库实现
self.email = email_service # 任何电子邮件服务
def process_order(self, order):
self.db.save(order)
self.email.send(order.customer_email, "Order confirmed")
# 易于测试,使用mocks
processor = OrderProcessor(MockDatabase(), MockEmailService())
6. 文档化
记录重构决策:
## 重构日志
### 2025-01-15: 提取支付处理
**理由**:支付逻辑嵌入在订单控制器中(500行)
**变更**:
- 提取了具有单一责任的PaymentService
- 引入了PaymentGateway接口以增加灵活性
- 添加了全面的单元测试(95%覆盖率)
**破坏性变更**:无(仅限内部重构)
**性能影响**:订单处理时间提高了15%
### 2025-01-16: 用Async/Await替换回调
**理由**:用户认证流程中的回调地狱
**变更**:
- 将所有认证方法转换为async/await
- 使用try/catch简化错误处理
- 提高可读性(从150行减少到80行)
**破坏性变更**:函数签名更改(需要在调用代码中更新)
**迁移**:更新了控制器中的所有12个调用点
最佳实践
✅ 要做
- 增量重构:小的、可测试的变更
- 频繁运行测试:每次重构步骤后
- 经常提交:创建逻辑上的、原子的提交
- 保持现有测试通过:不要破坏功能
- 使用IDE重构工具:比手动编辑更安全
- 审查代码覆盖率:确保测试覆盖重构后的代码
- 记录决策:为什么,而不仅仅是什么
- 寻求同行评审:新的眼光能发现问题
❌ 不要做
- 将重构与新功能混合:分离关注点
- 无测试重构:破坏性变更的食谱
- 改变行为:重构应保持功能
- 重构大块代码:增加风险和审查难度
- 忽视代码异味:系统地解决它们
- 跳过文档:未来的维护者需要上下文
常见陷阱
1. 过度工程化
// ❌ 对于简单情况太复杂
class UserNameFormatterFactory {
createFormatter(type) {
return new UserNameFormatter(new FormattingStrategy(type));
}
}
// ✅ 适合简单情况
function formatUserName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
2. 过早优化
先关注可读性,然后根据分析优化瓶颈。
3. 破坏向后兼容性
在移除公共API之前使用弃用警告:
/** @deprecated Use createUser(userData) instead. Will be removed in v2.0 */
function createUserOld(firstName: string, lastName: string, email: string) {
console.warn('createUserOld is deprecated. Use createUser(userData)');
return createUser({ firstName, lastName, email });
}
测试策略
单元测试
describe('Refactored User Service', () => {
describe('validateEmail', () => {
it('should accept valid email formats', () => {
expect(validateEmail('user@example.com')).toBe(true);
});
it('should reject invalid email formats', () => {
expect(validateEmail('invalid')).toBe(false);
expect(validateEmail('')).toBe(false);
expect(validateEmail(null)).toBe(false);
});
});
});
集成测试
def test_refactored_order_processing():
"""确保重构后的代码保持端到端行为"""
# 安排
order = create_test_order()
processor = OrderProcessor(test_database, test_email_service)
# 行动
result = processor.process_order(order)
# 断言
assert result.status == 'completed'
assert test_database.orders.count() == 1
assert test_email_service.sent_count == 1
回归测试
运行完整的测试套件以确保没有意外的副作用。
重构模式参考
常见模式
- 提取方法/函数:将长函数分解为更小的函数
- 提取类:将相关功能分组
- 内联方法:移除不必要的间接性
- 移动方法:将方法放在适当的类中
- 重命名:使用描述性名称
- 替换魔法数字:使用命名常量
- 用多态替换条件语句:使用继承
- 引入参数对象:将相关参数分组
- 移除重复:DRY原则
- 简化条件逻辑:降低复杂性
工具与资源
静态分析工具
- JavaScript/TypeScript:ESLint, TSLint, SonarQube
- Python:Pylint, Flake8, Bandit
- Java:SonarQube, PMD, Checkstyle
- Ruby:RuboCop, Reek
- PHP:PHPStan, Psalm
IDE重构支持
- VS Code:内置重构命令
- JetBrains IDEs:全面的重构工具
- Eclipse:自动化重构
- Vim/Neovim:语言服务器重构操作
推荐阅读
- 《重构》 马丁·福勒
- 《与遗留代码共存》 迈克尔·费瑟斯
- 《代码整洁之道》 罗伯特·C·马丁
示例
完整重构示例
之前
// legacy-user-service.js - 200行复杂、耦合的代码
var UserService = {
createUser: function(fn, ln, em, ph, addr) {
if (!em || em.indexOf('@') === -1) {
return { error: 'Invalid email' };
}
var conn = mysql.createConnection(config);
conn.connect();
conn.query(
'INSERT INTO users (first_name, last_name, email, phone, address) VALUES (?, ?, ?, ?, ?)',
[fn, ln, em.toLowerCase(), ph, addr],
function(err, result) {
if (err) {
console.log(err);
return { error: 'Database error' };
}
// 发送欢迎电子邮件
var nodemailer = require('nodemailer');
var transporter = nodemailer.createTransport(emailConfig);
transporter.sendMail({
to: em,
subject: 'Welcome!',
html: '<h1>Welcome ' + fn + '!</h1>'
}, function(err, info) {
if (err) console.log(err);
});
conn.end();
return { id: result.insertId };
}
);
}
};
之后
// user-service.ts - 干净、可测试、可维护
interface UserData {
firstName: string;
lastName: string;
email: string;
phone: string;
address: string;
}
class UserService {
constructor(
private database: Database,
private emailService: EmailService,
private validator: Validator
) {}
async createUser(userData: UserData): Promise<User> {
this.validator.validateEmail(userData.email);
const normalizedData = this.normalizeUserData(userData);
const user = await this.database.users.create(normalizedData);
await this.sendWelcomeEmail(user);
return user;
}
private normalizeUserData(data: UserData): UserData {
return {
...data,
email: data.email.toLowerCase().trim()
};
}
private async sendWelcomeEmail(user: User): Promise<void> {
await this.emailService.send({
to: user.email,
subject: 'Welcome!',
template: 'welcome',
data: { firstName: user.firstName }
});
}
}
// validator.ts
class Validator {
validateEmail(email: string): void {
if (!email || !email.includes('@')) {
throw new ValidationError('Invalid email format');
}
}
}
// 易于测试
describe('UserService', () => {
it('should create user with valid data', async () => {
const mockDb = createMockDatabase();
const mockEmail = createMockEmailService();
const service = new UserService(mockDb, mockEmail, new Validator());
const user = await service.createUser({
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
phone: '555-0123',
address: '123 Main St'
});
expect(user.id).toBeDefined();
expect(mockDb.users.create).toHaveBeenCalled();
expect(mockEmail.send).toHaveBeenCalledWith(
expect.objectContaining({ to: 'john@example.com' })
);
});
});
实现的好处
- ✅ 可测试性:依赖注入,易于模拟
- ✅ 可读性:清晰、专注的方法
- ✅ 可维护性:单一责任原则
- ✅ 类型安全:TypeScript接口防止错误
- ✅ 可重用性:组件可以独立使用
- ✅ 错误处理:适当的异常处理
- ✅ 现代模式:Async/await, 依赖注入
清单
在考虑重构完成之前:
- [ ] 所有现有测试通过
- [ ] 为重构的代码添加了新测试
- [ ] 代码覆盖率保持或提高
- [ ] 公共API没有破坏性变更(或已适当记录)
- [ ] 性能基准测试显示没有回归
- [ ] 代码审查完成
- [ ] 文档更新
- [ ] 重构决策记录
- [ ] CI/CD流水线通过
- [ ] 在生产环境类似的环境进行阶段性部署以验证
支持
对于复杂的重构场景,考虑:
- 尽早并经常寻求同行评审
- 使用功能标志进行逐步推出
- 创建重构计划文档
- 安排专门的重构时间
- 部署后监控生产指标