拒绝“屎山”代码:实战重构技巧指南

​“任何傻瓜都能写出计算机可以理解的代码。好的程序员能写出人类可以理解的代码。” —— 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 是什么意思?​如果我要再加一个条件,这个函数会不会爆炸?​三个月后的我,还能看懂这段代码吗?​ 保持代码整洁,是对自己,也是对团队最大的温柔。​ 如果你觉得这篇文章对你有帮助,欢迎点赞、收藏并关注我的博客!

    © 版权声明
    THE END
    喜欢就支持一下吧
    点赞9 分享
    评论 抢沙发

    请登录后发表评论

      暂无评论内容