名称: 重构 描述: 通过系统性的重构技术改进代码结构、可读性和可维护性,而不改变外部行为,技术包括提取函数、移除重复、简化条件语句和应用设计模式。在减少技术债务、提取函数或类、移除代码重复、简化复杂条件语句、重命名以提高清晰度、应用设计模式、改进代码组织、减少耦合、增加内聚或在结构改进期间保持测试覆盖时使用。
重构 - 在不改变行为的情况下改进代码结构
何时使用此技能
- 减少现有代码库中的技术债务
- 从长方法中提取函数
- 移除代码重复(DRY原则)
- 简化复杂的条件逻辑
- 重命名变量、函数以提高清晰度
- 应用设计模式以改进结构
- 改进代码组织和文件结构
- 减少模块之间的耦合
- 增加类/模块内的内聚
- 在重构期间保持测试覆盖
- 拆分大文件或函数
- 逐步现代化遗留代码
何时使用此技能
- 代码工作但难以理解、修改或测试。在保持功能性的同时改进结构。
- 在相关任务或功能开发时
- 在需要此专业知识的开发期间
使用时机: 代码工作但难以理解、修改或测试。在保持功能性的同时改进结构。
核心原则
- 行为保留 - 重构从不改变代码的功能,只改变实现方式
- 小步骤 - 进行微小更改,每一步后测试
- 测试先行 - 在重构前确保有全面的测试
- 一次只做一件事 - 不要将重构与功能添加混在一起
- 在添加功能时重构 - 让代码比你找到时更好
何时重构
触发点
- 在添加功能前 - 为新功能腾出空间
- 在代码审查期间 - 发现代码异味或令人困惑的部分
- 当修复错误时 - 通过更好的结构使错误不可能发生
- 童子军规则 - 总是让代码比你找到时更干净
红旗(代码异味)
✗ 函数长度超过约50行
✗ 深度嵌套的条件语句(>3层)
✗ 重复代码(复制粘贴编程)
✗ 不清晰的变量名(x, temp, data)
✗ 函数参数超过3个
✗ 上帝类(类做太多事情)
✗ 长参数列表
✗ 注释解释代码功能(代码应自文档化)
重构目录
1. 提取方法/函数
何时: 函数做太多事情或有复杂逻辑
// 之前 - 难以理解
function processOrder(order) {
// 计算总计
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
if (item.discount) {
total -= item.price * item.quantity * item.discount;
}
}
// 应用税收
const tax = total * 0.08;
total += tax;
// 检查库存
for (const item of order.items) {
const stock = inventory.get(item.id);
if (stock < item.quantity) {
throw new Error('库存不足');
}
}
return { total, tax };
}
// 之后 - 清晰的职责
function processOrder(order) {
validateInventory(order.items);
const subtotal = calculateSubtotal(order.items);
const tax = calculateTax(subtotal);
const total = subtotal + tax;
return { total, tax };
}
function calculateSubtotal(items) {
return items.reduce((sum, item) => {
const itemTotal = item.price * item.quantity;
const discount = item.discount ? itemTotal * item.discount : 0;
return sum + itemTotal - discount;
}, 0);
}
function calculateTax(amount) {
const TAX_RATE = 0.08;
return amount * TAX_RATE;
}
function validateInventory(items) {
for (const item of items) {
const stock = inventory.get(item.id);
if (stock < item.quantity) {
throw new InsufficientStockError(item.id, stock, item.quantity);
}
}
}
2. 用命名常量替换魔法数字
// 之前 - 这些数字什么意思?
if (user.age >= 18 && user.age <= 65) {
premium = basePrice * 1.0;
} else {
premium = basePrice * 1.5;
}
setTimeout(checkStatus, 60000);
// 之后 - 自文档化
const MIN_STANDARD_AGE = 18;
const MAX_STANDARD_AGE = 65;
const STANDARD_RATE_MULTIPLIER = 1.0;
const HIGH_RISK_RATE_MULTIPLIER = 1.5;
const STATUS_CHECK_INTERVAL_MS = 60 * 1000; // 1分钟
if (user.age >= MIN_STANDARD_AGE && user.age <= MAX_STANDARD_AGE) {
premium = basePrice * STANDARD_RATE_MULTIPLIER;
} else {
premium = basePrice * HIGH_RISK_RATE_MULTIPLIER;
}
setTimeout(checkStatus, STATUS_CHECK_INTERVAL_MS);
3. 简化条件表达式
// 之前 - 复杂嵌套条件
function getShippingCost(order) {
if (order.items.length > 0) {
if (order.total > 50) {
if (order.isPremium) {
return 0;
} else {
return 5;
}
} else {
if (order.isPremium) {
return 5;
} else {
return 10;
}
}
} else {
return 0;
}
}
// 之后 - 守卫子句和早期返回
function getShippingCost(order) {
if (order.items.length === 0) return 0;
if (order.isPremium && order.total > 50) return 0;
if (order.isPremium) return 5;
if (order.total > 50) return 5;
return 10;
}
// 更好 - 策略模式
const SHIPPING_RATES = {
premiumOverFifty: { cost: 0, applies: (o) => o.isPremium && o.total > 50 },
premium: { cost: 5, applies: (o) => o.isPremium },
standardOverFifty: { cost: 5, applies: (o) => o.total > 50 },
standard: { cost: 10, applies: () => true }
};
function getShippingCost(order) {
if (order.items.length === 0) return 0;
for (const rate of Object.values(SHIPPING_RATES)) {
if (rate.applies(order)) return rate.cost;
}
}
4. 提取变量以提高清晰度
// 之前 - 难以理解
if (
(platform === 'ios' && version >= 13) ||
(platform === 'android' && version >= 10) ||
(platform === 'web' && browserVersion >= 90)
) {
enableNewUI();
}
// 之后 - 意图揭示名称
const isIosSupportedVersion = platform === 'ios' && version >= 13;
const isAndroidSupportedVersion = platform === 'android' && version >= 10;
const isWebSupportedVersion = platform === 'web' && browserVersion >= 90;
const supportsNewUI =
isIosSupportedVersion ||
isAndroidSupportedVersion ||
isWebSupportedVersion;
if (supportsNewUI) {
enableNewUI();
}
5. 移除死代码
// 之前 - 杂乱无用的代码
function calculatePrice(item) {
let price = item.basePrice;
// 旧折扣系统(不再使用)
// if (item.category === 'electronics') {
// price *= 0.9;
// }
// 应用当前折扣
if (item.discount) {
price *= (1 - item.discount);
}
// 旧税收计算
// const oldTax = price * 0.05;
// 新税收计算
const tax = price * 0.08;
return price + tax;
}
// 之后 - 干净专注
function calculatePrice(item) {
let price = item.basePrice;
if (item.discount) {
price *= (1 - item.discount);
}
const TAX_RATE = 0.08;
const tax = price * TAX_RATE;
return price + tax;
}
6. 用守卫子句替换嵌套条件语句
// 之前 - 深度嵌套
function withdraw(account, amount) {
if (account.isActive) {
if (account.balance >= amount) {
if (amount > 0) {
if (!account.isFrozen) {
account.balance -= amount;
return { success: true, newBalance: account.balance };
} else {
return { success: false, error: '账户冻结' };
}
} else {
return { success: false, error: '无效金额' };
}
} else {
return { success: false, error: '资金不足' };
}
} else {
return { success: false, error: '账户不活跃' };
}
}
// 之后 - 守卫子句
function withdraw(account, amount) {
if (!account.isActive) {
return { success: false, error: '账户不活跃' };
}
if (account.isFrozen) {
return { success: false, error: '账户冻结' };
}
if (amount <= 0) {
return { success: false, error: '无效金额' };
}
if (account.balance < amount) {
return { success: false, error: '资金不足' };
}
account.balance -= amount;
return { success: true, newBalance: account.balance };
}
7. 用多态替换类型代码
// 之前 - 到处类型检查
class Employee {
constructor(type, salary) {
this.type = type; // 'engineer', 'manager', 'salesperson'
this.salary = salary;
}
calculateBonus() {
if (this.type === 'engineer') {
return this.salary * 0.1;
} else if (this.type === 'manager') {
return this.salary * 0.2;
} else if (this.type === 'salesperson') {
return this.salary * 0.15;
}
}
getTitle() {
if (this.type === 'engineer') {
return '软件工程师';
} else if (this.type === 'manager') {
return '工程经理';
} else if (this.type === 'salesperson') {
return '销售代表';
}
}
}
// 之后 - 多态
class Employee {
constructor(salary) {
this.salary = salary;
}
calculateBonus() {
throw new Error('必须由子类实现');
}
getTitle() {
throw new Error('必须由子类实现');
}
}
class Engineer extends Employee {
calculateBonus() {
return this.salary * 0.1;
}
getTitle() {
return '软件工程师';
}
}
class Manager extends Employee {
calculateBonus() {
return this.salary * 0.2;
}
getTitle() {
return '工程经理';
}
}
class Salesperson extends Employee {
calculateBonus() {
return this.salary * 0.15;
}
getTitle() {
return '销售代表';
}
}
8. 合并重复代码
// 之前 - 重复逻辑
function calculateEmployeeBonus(employee) {
let bonus = employee.salary * 0.1;
if (employee.yearsOfService > 5) {
bonus += 1000;
}
if (employee.hasTopPerformance) {
bonus *= 1.5;
}
return bonus;
}
function calculateContractorBonus(contractor) {
let bonus = contractor.salary * 0.1;
if (contractor.yearsOfService > 5) {
bonus += 1000;
}
if (contractor.hasTopPerformance) {
bonus *= 1.5;
}
return bonus;
}
// 之后 - 共享逻辑
function calculateBonus(person) {
let bonus = person.salary * 0.1;
if (person.yearsOfService > 5) {
bonus += 1000;
}
if (person.hasTopPerformance) {
bonus *= 1.5;
}
return bonus;
}
// 两者使用
const employeeBonus = calculateBonus(employee);
const contractorBonus = calculateBonus(contractor);
重构过程
逐步方法
1. 识别异味或改进机会
2. 确保测试存在并通过
3. 做一个小的重构更改
4. 运行测试 - 必须仍然通过
5. 提交(可选但推荐)
6. 重复步骤3-5直到满意
7. 最终测试运行
8. 代码审查
示例过程
// 原始代码
function process(data) {
let result = [];
for (let i = 0; i < data.length; i++) {
if (data[i].status === 'active' && data[i].age >= 18) {
result.push({
id: data[i].id,
name: data[i].name,
email: data[i].email
});
}
}
return result;
}
// 步骤1: 提取条件
function isEligible(item) {
return item.status === 'active' && item.age >= 18;
}
function process(data) {
let result = [];
for (let i = 0; i < data.length; i++) {
if (isEligible(data[i])) {
result.push({
id: data[i].id,
name: data[i].name,
email: data[i].email
});
}
}
return result;
}
// 测试 ✓
// 步骤2: 用数组方法替换循环
function process(data) {
let result = [];
data.forEach(item => {
if (isEligible(item)) {
result.push({
id: item.id,
name: item.name,
email: item.email
});
}
});
return result;
}
// 测试 ✓
// 步骤3: 提取转换
function transformToUser(item) {
return {
id: item.id,
name: item.name,
email: item.email
};
}
function process(data) {
let result = [];
data.forEach(item => {
if (isEligible(item)) {
result.push(transformToUser(item));
}
});
return result;
}
// 测试 ✓
// 步骤4: 使用filter和map
function process(data) {
return data
.filter(isEligible)
.map(transformToUser);
}
// 测试 ✓
// 最终结果 - 清晰、函数式、可测试
常见陷阱
❌ 无测试重构
如果测试不存在,先写测试!
否则,无法验证行为是否保留。
❌ 大爆炸式重构
不要一次性重写所有东西。
小步、渐进的变化更安全、更容易审查。
❌ 将重构与功能混在一起
分开提交:
- 提交1: 重构(无行为变化)
- 提交2: 添加功能(使用重构后的代码)
❌ 过度工程化
不要为“未来需求”增加复杂性
需要时重构,而不是推测性地重构。
YAGNI: 你不会需要它
工具
IDE重构工具
✓ 重命名(跨所有文件安全重命名)
✓ 提取方法/函数
✓ 提取变量
✓ 内联变量/函数
✓ 更改签名
✓ 移动到文件/模块
静态分析
# JavaScript/TypeScript
eslint --fix
prettier --write
# Python
pylint
black
# 查找代码异味
# PMD, SonarQube, CodeClimate
资源
记住: 重构是持续改进。每次接触代码时,让它变得更好一点。频繁的小重构比罕见的大重写更好。