“任何傻瓜都能写出计算机可以理解的代码。好的程序员能写出人类可以理解的代码。” —— Martin Fowler
你是否经历过这样的场景:打开三个月前自己写的代码,或者接手同事遗留的项目,看着满屏的 if-else 嵌套和不知所云的变量名,内心充满了绝望?这种难以维护的代码,我们俗称为“屎山”(Spaghetti Code)。今天,我们不谈枯燥的理论,而是通过一个真实的业务场景,手把手带你进行一次重构(Refactoring),展示如何将一段糟糕的代码变得优雅、整洁。
场景设定:购物车结算功能
假设我们有一个电商系统的结算函数,它的逻辑如下:
- 检查库存。
- 根据用户等级(普通、VIP、SVIP)计算折扣。
- 检查是否有优惠券。
- 如果是“双11”大促,还有额外折扣。
Refactoring 前:糟糕的实现
下面是典型的“面条代码”。它的问题在于:嵌套过深、魔术数字、职责不清。
function calculateTotal(cart, user, isSale) {
let total = 0;
if (cart.items.length > 0) {
for (let i = 0; i < cart.items.length; i++) {
// 检查库存
if (cart.items[i].qty <= cart.items[i].stock) {
let itemPrice = cart.items[i].price * cart.items[i].qty;
// 0 = 普通用户, 1 = VIP, 2 = SVIP
if (user.level === 1) {
itemPrice = itemPrice * 0.9;
} else if (user.level === 2) {
itemPrice = itemPrice * 0.8;
}
if (isSale) {
itemPrice = itemPrice - 5; // 大促减5块
}
total += itemPrice;
} else {
return "库存不足";
}
}
// 满100包邮
if (total < 100) {
total += 10;
}
return total;
} else {
return 0;
}
}
看着头晕吗?如果老板让你加一个“黑钻会员”等级,你是不是得在中间再塞一个 else if?这种代码非常脆弱,动一处很容易崩全局。
Refactoring 实战:三步走
我们将运用三个核心技巧来重构这段代码:卫语句(Guard Clauses)、策略模式(Strategy Pattern) 和 函数抽取(Extract Function)。
第一步:使用“卫语句”消除嵌套
深层嵌套是代码可读性的杀手。我们可以通过“尽早返回”来展平代码结构。如果购物车为空,直接返回 0。如果库存不足,直接抛出错误。
<!– end list –>
function calculateTotal(cart, user, isSale) {
if (!cart.items || cart.items.length === 0) return 0;
let total = 0;
for (const item of cart.items) {
if (item.qty > item.stock) {
throw new Error(`商品 ${item.name} 库存不足`);
}
// ... 计算逻辑 ...
}
// ...
}
第二步:消灭“魔术数字”与复杂判断
代码里的 0, 1, 2 是什么意思?除了写代码的人,没人知道。我们应该用常量或枚举来代替。同时,针对不同等级的折扣计算,我们可以用**对象映射(Object Map)**来代替冗长的 if-else。
const USER_LEVELS = {
NORMAL: 0,
VIP: 1,
SVIP: 2
};
const DISCOUNT_STRATEGIES = {
[USER_LEVELS.NORMAL]: (price) => price,
[USER_LEVELS.VIP]: (price) => price * 0.9,
[USER_LEVELS.SVIP]: (price) => price * 0.8,
};
第三步:单一职责原则(抽取函数)
主函数 calculateTotal 管得太宽了,它既管库存,又管打折,还管邮费。我们应该把这些逻辑拆分出去。
✨ Refactoring 后:优雅的实现
// 常量定义
const SHIPPING_COST = 10;
const FREE_SHIPPING_THRESHOLD = 100;
const SALE_DISCOUNT = 5;
const DISCOUNT_RATES = {
0: 1.0, // Normal
1: 0.9, // VIP
2: 0.8 // SVIP
};
/**
* 计算单个商品的小计(含折扣)
*/
function getItemSubtotal(item, userLevel, isSale) {
const basePrice = item.price * item.qty;
// 获取折扣率,默认为 1 (不打折)
const rate = DISCOUNT_RATES[userLevel] || 1.0;
let finalPrice = basePrice * rate;
// 大促立减
if (isSale) {
finalPrice = Math.max(0, finalPrice - SALE_DISCOUNT);
}
return finalPrice;
}
/**
* 检查库存
*/
function validateStock(cartItems) {
const outOfStockItem = cartItems.find(item => item.qty > item.stock);
if (outOfStockItem) {
throw new Error(`商品 "${outOfStockItem.name}" 库存不足`);
}
}
/**
* 主函数:计算购物车总价
*/
function calculateTotal(cart, user, isSale) {
// 1. 边界检查
if (!cart.items || cart.items.length === 0) return 0;
// 2. 前置校验
validateStock(cart.items);
// 3. 计算商品总额
let total = cart.items.reduce((sum, item) => {
return sum + getItemSubtotal(item, user.level, isSale);
}, 0);
// 4. 处理邮费
if (total < FREE_SHIPPING_THRESHOLD) {
total += SHIPPING_COST;
}
return parseFloat(total.toFixed(2)); // 保持精度
}
重构后的优势
1、可读性极高:主函数像读文章一样流畅(检查 -> 校验 -> 累加 -> 邮费)。
2、易于扩展:如果老板要加“黑钻会员”,只需要在 DISCOUNT_RATES 对象里加一行配置,完全不需要动主逻辑。
3、易于测试:我们可以单独为 getItemSubtotal 编写单元测试。
重构不是为了炫技,而是为了生存。当你下一次想写 if (status == 1) 的时候,请停下来想一想:这个 1 是什么意思?如果我要再加一个条件,这个函数会不会爆炸?三个月后的我,还能看懂这段代码吗? 保持代码整洁,是对自己,也是对团队最大的温柔。 如果你觉得这篇文章对你有帮助,欢迎点赞、收藏并关注我的博客!







暂无评论内容