前言

最近抽空看完了《代码整洁之道》,并且在目前公司项目中深有感触。就像Jack Reecves所发表的《源码就是设计》:源码就是最好软件设计文档,而其他非代码性的文档只是源码的辅助。那么一份整洁的代码阅读起来就应该是行云流水如同精美好文一般。

具备什么特性?

  • 变量、函数、类、包、模块命名合理有意义
  • 代码结构(格式)清晰
  • 必要的合理的注释信息:代码能自解释只是理想,该注释的地方还是免不了
  • 合理的异常处理机制
  • 有效的日志信息
  • 遵循面向对象设计原则

接下来展示一些好的JS代码应该怎么做。

避免条件

这似乎是个不可能完成的任务。大多数人第一次听到这个的时候会说,“没有 if 语句我该怎么办?”回答是在多数情况下都可以使用多态来实现相同的任务。第二个问题通常是,“那太好了,不过我为什么要这么做呢?”答案在于我们之前了解过整洁的概念:一个函数应该只做一件事情。如果你的类和函数有 if 语句,就意味着你的函数做了更多的事。记住,只做一件事。

bad

class Airplane {
  //...
  getCruisingAltitude() {
    switch (this.type) {
      case '777':
        return getMaxAltitude() - getPassengerCount();
      case 'Air Force One':
        return getMaxAltitude();
      case 'Cessna':
        return getMaxAltitude() - getFuelExpenditure();
    }
  }
}

good

class Airplane {
  //...
}

class Boeing777 extends Airplane {
  //...
  getCruisingAltitude() {
    return getMaxAltitude() - getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  //...
  getCruisingAltitude() {
    return getMaxAltitude();
  }
}

class Cessna extends Airplane {
  //...
  getCruisingAltitude() {
    return getMaxAltitude() - getFuelExpenditure();
  }
}

不要过度优化

现在浏览器在运行时悄悄地做了很多优化工作。很多时候你的优化都是在浪费时间。这里有很好的资源 可以看看哪些优化比较缺乏。把它们作为目标,直到他们能固定下来的时候。

bad

// 在旧浏览器中,每次循环的成本都比较高,因为每次都会重算 `len`。
// 现在浏览器中,这已经被优化了。
for (var i = 0, len = list.length; i < len; i++) {
  // ...
}

good

for (var i = 0; i < list.length; i++) {
  // ...
}

用 Object.assign 设置默认对象

bad

var menuConfig = {
  title: null,
  body: 'Bar',
  buttonText: null,
  cancellable: true
}

function createMenu(config) {
  config.title = config.title || 'Foo'
  config.body = config.body || 'Bar'
  config.buttonText = config.buttonText || 'Baz'
  config.cancellable = config.cancellable === undefined ? config.cancellable : true;

}

createMenu(menuConfig);

good

var menuConfig = {
  title: 'Order',
  // User did not include 'body' key
  buttonText: 'Send',
  cancellable: true
}

function createMenu(config) {
  config = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);

  // 现在 config 等于: {title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true}
  // ...
}

createMenu(menuConfig);

不要写入全局函数

JavaScript 中全局污染是一件糟糕的事情,因为它可能和另外库发生冲突,然而使用你 API 的用户却不会知道——直到他们在生产中遇到一个异常。来思考一个例子:你想扩展 JavaScript 的原生 Array,使之拥有一个 diff 方法,用来展示两数据之前的区别,这时你会怎么做?你可以给 Array.prototype 添加一个新的函数,但它可能会与其它想做同样事情的库发生冲突。如果那个库实现的 diff 只是比如数组中第一个元素和最后一个元素的异同会发生什么事情呢?这就是为什么最好是使用 ES6 的类语法从全局的 Array 派生一个类来做这件事。

bad

Array.prototype.diff = function(comparisonArray) {
  var values = [];
  var hash = {};

  for (var i of comparisonArray) {
    hash[i] = true;
  }

  for (var i of this) {
    if (!hash[i]) {
      values.push(i);
    }
  }

  return values;
}

good

class SuperArray extends Array {
  constructor(...args) {
    super(...args);
  }

  diff(comparisonArray) {
    var values = [];
    var hash = {};

    for (var i of comparisonArray) {
      hash[i] = true;
    }

    for (var i of this) {
      if (!hash[i]) {
        values.push(i);
      }
    }

    return values;
  }
}

避免否定条件

bad

function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}

good

function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

删除不用的代码

不用的代码和重复的代码一样糟糕。在代码库中保留无用的代码是毫无道理的事情。如果某段代码用不到,那就删掉它!如果你以后需要它,仍然可以从代码库的历史版本中找出来。

不需要日志式的注释

记住,使用版本控制!没用的代码、注释掉的代码,尤其是日志式的注释。用 git log 来获取历史信息!

bad

/**
 * 2016-12-20: Removed monads, didn't understand them (RM)
 * 2016-10-01: Improved using special monads (JP)
 * 2016-02-03: Removed type-checking (LI)
 * 2015-03-14: Added combine with type-checking (JR)
 */
function combine(a, b) {
  return a + b;
}

good

function combine(a, b) {
  return a + b;
}

参考

有兴趣的同学可以去看看原版或者下方资料。

  1. 英文版

  2. 中文版


千万别成为大多数人