代码重构艺术:从混乱到优雅的实战指南
展示怎样通过重构改造遗留代码,用全栈视角解析设计模式的实际应用
📋 目录
- 引言:为什么需要重构
- 重构的基本原则
- 常见的代码坏味道
- 重构技巧与设计模式
- 实战案例:遗留系统重构
- 重构的最佳实践
- 总结与思考
引言:为什么需要重构
1.1 什么是代码重构
代码重构(Refactoring)是在不改变代码外部行为的前提下,对代码内部结构进行调整和优化的过程。就像整理房间一样,我们重新组织代码,使其更清晰、更易维护。
"任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。" —— Martin Fowler
1.2 为什么要重构
技术债务的累积
// ❌ 技术债务示例:难以维护的代码 function processUserData(data: any) { if (data.type == 1) { // 100 行代码处理类型1 var result = data.name + data.age; if (data.status == "active") { // 又是 50 行代码 return result + " is active"; } } else if (data.type == 2) { // 另外 100 行代码处理类型2 } // ... 更多嵌套逻辑 }重构带来的价值
| 价值维度 | 具体收益 |
|---|---|
| 🔧可维护性 | 降低修改成本,减少 bug 引入 |
| 📖可读性 | 新人快速上手,团队协作更顺畅 |
| 🚀可扩展性 | 轻松添加新功能,应对需求变化 |
| ⚡性能 | 优化算法和数据结构,提升执行效率 |
| 🧪可测试性 | 更容易编写单元测试,提高代码质量 |
重构的基本原则
2.1 重构的黄金法则
1. 小步快跑,频繁提交
# 重构流程 1. 编写测试用例(如果没有) 2. 进行一个小的重构 3. 运行测试确保功能正常 4. 提交代码 5. 重复上述步骤2. 保持功能不变
重构的核心是改进代码结构,而不改变外部行为。
// ✅ 重构前后行为一致 // 重构前 function calculateTotal(items: any[]) { let total = 0; for (let i = 0; i < items.length; i++) { total += items[i].price * items[i].quantity; } return total; } // 重构后 function calculateTotal(items: OrderItem[]): number { return items.reduce((total, item) => { return total + item.price * item.quantity; }, 0); } interface OrderItem { price: number; quantity: number; }3. 测试先行
// 重构前先写测试 describe('calculateTotal', () => { it('应该正确计算订单总额', () => { const items = [ { price: 10, quantity: 2 }, { price: 20, quantity: 1 }, ]; expect(calculateTotal(items)).toBe(40); }); it('空订单应该返回0', () => { expect(calculateTotal([])).toBe(0); }); });2.2 何时应该重构
三次法则(Rule of Three)
"第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次再做类似的事,你就应该重构。"
// ❌ 第一次:直接实现 function sendEmailToUser(user: User) { const transporter = nodemailer.createTransport({...}); transporter.sendMail({ to: user.email, subject: 'Welcome', text: 'Welcome to our platform' }); } // ❌ 第二次:复制粘贴 function sendPasswordResetEmail(user: User) { const transporter = nodemailer.createTransport({...}); transporter.sendMail({ to: user.email, subject: 'Password Reset', text: 'Click here to reset password' }); } // ✅ 第三次:重构 class EmailService { private transporter: Transporter; constructor() { this.transporter = nodemailer.createTransport({...}); } async sendEmail(to: string, subject: string, content: string) { return this.transporter.sendMail({ to, subject, text: content }); } async sendWelcomeEmail(user: User) { return this.sendEmail( user.email, 'Welcome', 'Welcome to our platform' ); } async sendPasswordResetEmail(user: User, resetLink: string) { return this.sendEmail( user.email, 'Password Reset', `Click here to reset: ${resetLink}` ); } }常见的代码坏味道
3.1 重复代码(Duplicated Code)
问题示例
// ❌ 重复的验证逻辑 class UserController { async createUser(req: Request, res: Response) { if (!req.body.email) { return res.status(400).json({ error: 'Email is required' }); } if (!req.body.email.includes('@')) { return res.status(400).json({ error: 'Invalid email' }); } if (!req.body.password || req.body.password.length < 8) { return res.status(400).json({ error: 'Password too short' }); } // 创建用户逻辑 } async updateUser(req: Request, res: Response) { if (!req.body.email) { return res.status(400).json({ error: 'Email is required' }); } if (!req.body.email.includes('@')) { return res.status(400).json({ error: 'Invalid email' }); } // 更新用户逻辑 } }重构方案:提取公共方法
// ✅ 提取验证逻辑 class Validator { static validateEmail(email: string): string | null { if (!email) { return 'Email is required'; } if (!email.includes('@')) { return 'Invalid email format'; } return null; } static validatePassword(password: string): string | null { if (!password) { return 'Password is required'; } if (password.length < 8) { return 'Password must be at least 8 characters'; } return null; } } class UserController { async createUser(req: Request, res: Response) { const emailError = Validator.validateEmail(req.body.email); if (emailError) { return res.status(400).json({ error: emailError }); } const passwordError = Validator.validatePassword(req.body.password); if (passwordError) { return res.status(400).json({ error: passwordError }); } // 创建用户逻辑 } }3.2 过长函数(Long Method)
问题示例
// ❌ 一个函数做太多事情 async function processOrder(orderId: string) { // 1. 获取订单(10行代码) const order = await db.orders.findById(orderId); if (!order) throw new Error('Order not found'); // 2. 验证库存(20行代码) for (const item of order.items) { const product = await db.products.findById(item.productId); if (product.stock < item.quantity) { throw new Error('Insufficient stock'); } } // 3. 计算价格(15行代码) let total = 0; for (const item of order.items) { const product = await db.products.findById(item.productId); total += product.price * item.quantity; } if (order.couponCode) { const discount = await calculateDiscount(order.couponCode, total); total -= discount; } // 4. 处理支付(25行代码) const payment = await paymentGateway.charge({ amount: total, currency: 'USD', source: order.paymentMethod, }); // 5. 更新库存(15行代码) for (const item of order.items) { awai