Leecason / blog

https://leecason.github.io
1 stars 0 forks source link

Refactor #11

Open Leecason opened 5 years ago

Leecason commented 5 years ago

什么是重构

何时重构

Leecason commented 5 years ago

1.提炼函数

单一职责原则 (single responsibility principle)

在 JavaScript 开发中,大部分时间都在与函数打交道,所以函数应该有良好的命名,函数体内的逻辑清晰明了。如果一个函数过长,需要加注释才会让这个函数显得易读。那么这些函数就需要重构了。

如果在函数中有一段代码可以被独立出来,那我们最好把这部分代码放入一个独立的函数中。

好处: 1.避免出现超大函数 2.独立出来的函数有助于代码复用 3.独立出来的函数更容易被复写 4.独立出来的函数如果拥有一个良好的命名,它本身就起到了注释的作用

function getInfo () {
  ajax('url', function (data) {
    console.log(data.id);
    console.log(data.name);
  });
}

改为

function getInfo () {
  ajax('url', function (data) {
    printDetail(data);
  });
}

function printDetail (data) {
    console.log(data.id);
    console.log(data.name);
}
Leecason commented 5 years ago

2.合并重复的条件片段

如果一个函数体内有一些条件分支语句,而这些条件分支语句内部散布了一些重复的代码,那么就有必要进行合并去重

let paging = function (currentPage) {
  if (currentPage <= 0) {
    currentPage = 0;
    jump(currentPage);
  } else if (currentPage >= totalPage) {
    currentPage = totalPage;
    jump(currentPage);
  } else {
    jump(currentPage);
  }
}

改为

let paging = function (currentPage) {
  if (currentPage <= 0) {
    currentPage = 0;
  } else if (currentPage >= totalPage) {
    currentPage = totalPage;
  }

  jump(currentPage);
}
Leecason commented 5 years ago

3.把条件分支语句提炼成函数

复杂的条件分支语句是导致程序难以阅读和理解的重要原因,而且容易导致一个庞大的函数。

function getPrice (price) {
  let date = new Date();

  if (date.getMonth() >= 6 && data.getMonth <= 9) {
    return price * 0.8;
  }

  return price;
}

改为

function isSummer () {
  let date = new Date();

  return date.getMonth() >= 6 && data.getMonth <= 9;
}

function getPrice (price) {
  if (isSummer()) {
    return price * 0.8;
  }

  return price;
}
Leecason commented 5 years ago

4.合理使用循环

在函数体内,如果有些代码实际上负责的是一些重复性的工作,那么合理利用循环不仅可以完成同样的功能,还可以使代码量更少。

function createXHR () {
  let xhr;

  try {
    xhr = new ActiveXObject('MSXML2.XMLHttp.6.0');
  } catch (e) {
    try {
      xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
    } catch (e) {
      xhr = new ActiveXObject('MSXML2.XMLHttp');
    }
  }
  return xhr;
}

 改为

const versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];

function createXHR () {
  for (var i = 0, version; version = versions[i++];) {
    try {
      return new ActiveXObject(version);
    } catch (e) {

    }
  }
}
Leecason commented 5 years ago

5.提前让函数退出代替嵌套条件分支

嵌套的条件分支语句是代码维护者的噩梦。我们可以挑选一些条件分支,在进入这些条件后,就立即让这个函数退出。要做到这一点,有一个常见的技巧,就是在面对一个嵌套的 if 分支时,我们可以把外层 if 表达式进行反转。

function deleteObj (obj) {
  if (!obj.isReadOnly) {
    if (obj.isFolder) {
      deleteFolder(obj);
    } else if (obj.isFile) {
      deleteFile(obj);
    }
  }
}

改为

function deleteObj (obj) {
  if (obj.isReadOnly) return;

  if (obj.isFolder) {
    deleteFolder(obj);
    return;
  }

  if (obj.isFile) {
    deleteFile(obj);
    return;
  }
}
Leecason commented 5 years ago

6.传递对象参数代替过长的参数列表

有时候一个函数可能接收多个参数,而参数越多,函数就越难理解和使用。使用的人首先得搞明白全部参数的含义,在使用的时候,还要避免少传了某个参数或者把两个参数搞反了位置。而且如果我们想在第三个参数和第四个参数之间增加一个新的参数,就会涉及到许多代码的修改。这时我们就可以把参数都放入一个对象内,然后把该对象传入函数,函数需要的数据就可以自行从该对象里获取。不用再关心参数的数量和顺序,只需要保证参数对应的 key 值不变就可以了。

function setInfo (id, name, address, sex, mobile) {
  ...
}

setInfo(000, 'sven', 'chengdu', 'male', '135...');

改为

setInfo (info) {
  ...
}

setInfo({
  id: 000,
  name: 'sven',
  address: 'chengdu',
  sex: 'male',
  mobile: '135...'
});
Leecason commented 5 years ago

7.尽量减少参数数量

如果调用一个函数时需要传入多个参数,那么这个函数是让人望而生畏的,我们必须要搞清楚这些参数代表的含义,而如果一个函数如果不需要传入任何参数就可以使用,这种是函数是受欢迎的。实际开发中向函数传入参数不可避免,但我们应该尽量减少函数接收的参数数量。

Leecason commented 5 years ago

8.少用三目运算符

有些程序员喜欢用三目运算符来代替传统的 if、else。理由是性能高,代码少。即使三目运算符的效率真的比 if、else 高,这点差距也是可以忽略的。实际开发中即使把一段代码循环100w次,三用三目运算符和 if、else 的时间开销也处在同一级别。

同样,相比损失的代码可读性和可维护性,三目运算符节省的代码量也可以忽略不计,让 js 文件加载更快的办法有很多,如压缩、缓存、使用 CDN 等。不应该把注意力只放在使用三目运算符节省的字符数量上。如果条件分支逻辑简单清晰,是可以使用三目运算符的。

let global = typeof window !== "undefined" ? window : this;

如果条件分支逻辑非常复杂,最好还是按部就班的使用 if、else,一是阅读容易,二是修改的时候更加方便。

Leecason commented 5 years ago

9.合理使用链式调用

在 JavaScript 中,可以很容易的实现方法的链式调用,即让方法调用结束后返回自身。 使用链式调用的方式并不会造成太多阅读上的困难,也确实能省下一些字符和中间变量,节省的字符同样是忽略不计的。缺点就是在调试的时候不方便,其中一条链中有问题必须先拆开才能定位错误。 如果该链结构相对稳定,后期不易发生修改,那么应该使用链式调用。

new User().setId(000).setName('sven');

如果该链容易发生变化,为了调试和维护,应该使用普通调用。

let user = new User();
user.setId(000);
user.setName('sven');
Leecason commented 5 years ago

10.分解大型类

面向对象设计鼓励将行为分布在合理数量的更小对象中。


function Spirit (name) {
  this.name = name;
}

Spirit.prototype.attack = function (type) {
  if (type === 'waveBoxing') {
    console.log('使用波动拳')
  } else if (type === 'whirlKick') {
    console.log('使用旋风腿');
  }
}

改为

function Attack (spirit) {
  this.spirit = spirit;
}

Attack.prototype.start = function (type) {
  return this.list[type].call(this);
}

Attack.prototype.list = {
  waveBoxing () {
    console.log('使用波动拳');
  },
  whirlKick () {
    console.log('使用旋风腿');
  }
}

function Spirit (name) {
  this.name = name;
  this.attackObj = new Attack(this);
}

Spirit.prototype.attack = function (type) {
  this.attackObj.start(type);
}
Leecason commented 5 years ago

11.用 return 退出多重循环

假设在函数体内有一个两重循环语句,我们需要在内层循环中判断,当达到某个临界条件时退出外层的循环,我们可以引入一个控制标记变量。

function func () {
  let flag = false;

  for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
      if (i * j > 30) {
        flag = true;
        break;
      }
    }

    if (flag) {
      break;
    }
  }
}

第二种做法是设置循环标记。

function func () {
  outerloop:

  for (let i = 0; i < 10; i++) {
    innerloop:
    for (let j = 0; j < 10; j++) {
      if (i * j > 30) {
        break outerloop;
      }
    }
  }
}

更简单的做法是在需要中止循环的时候直接退出整个方法。

function func () {
  for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
      if (i * j > 30) {
        return;
      }
    }
  }
}

用 return 直接退出方法会带来一个问题,如果在循环之后还有一段代码需要被执行,我们提前退出了整个方法,后面的代码就得不到被执行的机会,为了解决这个问题,我们可以把循环后面的代码直接放到 return 后面,如果代码较多,应该提炼成一个单独的函数。

function func () {
  for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
      if (i * j > 30) {
        print(i)
        return;
      }
    }
  }
}