优惠券代码优化思考

avatar
前端工程师 @豌豆公主

优惠券代码优化思考

image.png

代码优化,总感觉缺点味道,所有我把这次优化,说成了重构,事实上,这次优化,确实相当于重构了

什么是重构,为什么要重构

  1. 重构是在不改变软件可观测行为的前提下,调整代码结构,提高软件的可理解性降低变更成本
  2. 重构是一种经济使用行为,而非道德使然,如果它不能更快更好的开发,那么他是毫无意义的
  3. 代码的写法应该使人理解他所需的时间最小化,进而变更代码所需要的时间也会最小化

重构的原则

  1. 重构的目标:提高迭代效率 (经济驱动而不是道德驱动)
  2. 每一次提交代码,都应该使代码变得更好,先重构,再开发
  3. 增量式重构 = 自动化测试+持续集成+TDD驱动重构(待补充)
  • 每一次重构完成都应该提交代码,这样就可以在下一次重构出现问题的时候,迅速回退到上一次正常工作时的状态,这一点很有用!“
  • 这样的重构还有个好处,那就是可以保证代码随时都是可发布的状态,因为并没有影响到整体功能的运行。”

代码的坏味道

神秘的命名( Mysterious Name)

  1. 猜谜时间会越来越多

image.png

重复代码 (Repeat Code)

过长参数列表(Long Parameter List)

(() => {
  function priceRange(products, min, max, isOutSide) {
    if (isOutSide) {
      return products.filter((r) => r.price < min || r.price > max);
    } else {
      return products.filter((r) => r.price > min && r.price < max);
    }
  }

  const products = [
    { name: 'apple', price: 6 },
    { name: 'banana', price: 7 },
    { name: 'orange', price: 15 },
    { name: 'cookie', price: 0.5 },
  ];
  const range = { min: 5, max: 8 };
  const insidePriceProducts = priceRange(products, range.min, range.max, false);
  console.log('insidePriceProducts', insidePriceProducts);
})();
(() => {
  // 把 priceRange 的 isOutSide 标记参数移除了,让人疑惑的标记参数就被移除了,取而代之的是两个语义更加清晰的函数
  function priceOutSideRange(products, min, max) {
    return products.filter((r) => r.price < min || r.price > max);
  }

  function priceInsideRange(products, min, max) {
    return products.filter((r) => r.price > min && r.price < max);
  }
  const products = [
    { name: 'apple', price: 6 },
    { name: 'banana', price: 7 },
    { name: 'orange', price: 15 },
    { name: 'cookie', price: 0.5 },
  ];
  const range = { min: 5, max: 8 };
  const insidePriceProducts = priceInsideRange(products, range.min, range.max);
  console.log('insidePriceProducts', insidePriceProducts);
})();

(() => {
  // range 范围的判定还是需要花费一定时间理解,而 range 作为我们刚识别出来的一种结构,可以继续进行重构,就像这样。”
  class Range {
    constructor(min, max) {
      this.min = min;
      this.max = max;
    }
    outside(num) {
      return num < this.min || num > this.max;
    }
    inside(num) {
      return num > this.min && num < this.max;
    }
  }

  function priceInsideRange(products, range) {
    return products.filter((r) => range.inside(r.price));
  }

  const products = [
    { name: 'apple', price: 6 },
    { name: 'banana', price: 7 },
    { name: 'orange', price: 15 },
    { name: 'cookie', price: 0.5 },
  ];
  const insidePriceProducts = priceInsideRange(products, new Range(5, 8));
  console.log('insidePriceProducts', insidePriceProducts);
})();

过长函数(Long Function)

image.png

image.png

全局数据(Global Data)

// global.js
// ...
let userAuthInfo = {
  platform: 'pc',
  token: '',
};

export { userAuthInfo };

// main.js
userAuthInfo.token = localStorage.token;

// request.js
const reply1 = await login();
userAuthInfo.token = reply1.data.token;

// business.js
await request({ authInfo: userAuthInfo });

// 但是我现在可以在代码库的任何一个角落都可以修改 platform 和 token,而且没有任何机制可以探测出到底哪段代码做出了修改,这就是全局数据的问题
// 每当我们看到可能被各处的代码污染的数据,我们还是需要全局数据用一个函数包装起来,至少你就能看见修改它的地方,并开始控制对它的访问,这里我做个简单的封装
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
let userAuthInfo = {
  platform: 'pc',
  token: '',
};

function getUserAuthInfo() {
  return { ...userAuthInfo };
}

function setToken(token) {
  userAuthInfo.token = token;
}

export { getUserAuthInfo, setToken };

// main.js
setToken(localStorage.token);

// request.js
const reply = await login();
setToken(reply.data.token);

// business.js
await request({ authInfo: getUserAuthInfo() });
// 这样一来,通过对象引用就无法修改源对象了


优惠券优化

  • 神秘命名
  • 代码臃肿(组件化)
  • 细节思考,超前设计(样式居中,样式居中,内容过长的处理)

未使用 image.png 已使用 已过期

image.png

不可用

image.png

可用

image.png

image.png

image.png

命名规范

  • list 和detail

样式兼容性考虑

  • 内容多了的处理考虑

image.png