简介:为什么重构遗留代码是必要的
每个开发者迟早都会遇到遗留代码——过时、文档匮乏且常常难以阅读。在 JavaScript 和 Node.js 的世界中,这尤其常见:项目快速增长,技术不断变化,旧的代码部分变成了阻碍开发的"沼泽"。重构遗留代码不仅是一种心血来潮,更是保持应用性能、安全性和可扩展性的必要手段。
在本文中,我们将详细讲解如何安全高效地重构 JavaScript (Node.js) 中的遗留代码。您将了解关键阶段:分析、拆分为模块、替换过时结构以及测试。所有示例均使用现代 JavaScript (ES6+) 编写,并面向 Node.js 环境。
第一步:分析和理解遗留代码
在修改代码之前,您需要先理解它。遗留代码通常以"意大利面条式"风格编写:数百行的函数、全局变量和混杂的逻辑。从审计开始。
1.1. 创建依赖关系图
使用诸如 madge 或 dependency-cruiser 等工具来可视化模块之间的连接。这将帮助识别循环依赖和"上帝对象"。
// 示例:安装 madgenpm install -g madgemadge --image graph.svg src/1.2. 识别"风险区域"
找出最常更改或导致错误的代码部分。使用 Git 提交历史:
git log --oneline --follow src/legacyModule.js此外,注意以下几点:
- 超过 50 行的函数
- 全局变量(例如,不使用
let/const的var) - 回调地狱(嵌套的异步调用)
1.3. 为现有行为编写测试
没有测试,重构就是一颗定时炸弹。使用 jest 或 mocha 覆盖关键函数。测试应检查当前行为,而非"理想"行为。
// 遗留函数的测试示例const sum = require('./legacyModule').sum;test('sum adds two numbers', () => { expect(sum(1, 2)).toBe(3);});第二步:逐步改进结构
不要试图一次性重写所有内容。使用"扼杀藤"策略——逐步替换代码部分,保持向后兼容性。
2.1. 将单体拆分为模块
将逻辑相关的代码块提取到单独的文件中。例如,将数据库工作移到 db.js,将请求处理移到 handlers.js。
// 之前:一个 500 行的文件function handleRequest(req, res) { // 100 行逻辑 // 50 行数据库工作 // 30 行响应格式化}
// 之后:拆分为模块// db.jsexports.getUser = (id) => { /* ... */ };
// handlers.jsconst db = require('./db');exports.handleRequest = (req, res) => { const user = db.getUser(req.params.id); // 响应格式化};2.2. 将 var 替换为 let/const
这是一个简单但重要的步骤。var 具有函数作用域,常常导致错误。对变量使用 let,对常量使用 const。
// 之前var x = 10;if (true) { var x = 20; // 覆盖外部变量}
// 之后let x = 10;if (true) { let x = 20; // 局部变量}第三步:现代化异步代码
Node.js 中的遗留代码常常受"回调地狱"困扰。将嵌套的回调替换为 Promise 或 async/await。
3.1. 将回调转换为 Promise
对遵循 Node.js 风格(第一个参数为错误)的函数使用 util.promisify。
const fs = require('fs');const { promisi