JavaScript代码整齐之道

JavaScript代码整齐之道

整齐的代码不仅仅是让人看起来惬意,更主要的是遵照一些范例能够让你的代码更轻易保护,同时下降bug概率。

原文
clean-code-javascript,这里总结摘录出个人以为有协助的处所,也加入了一些本身的明白(有些笔墨我觉得保存原文更好,所以直接copy了),其他另有许多点没有列出来,感兴趣能够去看原文。别的这不是强制的代码范例,就像原文中说的,
These are guidelines and nothing more

1. 用定名的变量替代数组下标

// bad
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
  // 下标1,2不易于明白
  address.match(cityZipCodeRegex)[1],
  address.match(cityZipCodeRegex)[2]
);
// good
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
// 运用数组解构更好的定名变量
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);

2. 函数的参数最好<=2个,只管防备3个。

假如有许多参数就应用object通报,并运用解构。

注重
object
array这些援用范例的值是
mutable的。

3. 一个函数只做一件事。

优点在于compose, test, and reason about

4. 不要自行扩大原型

假如想扩大原型,能够先继续再增加要领,防备污染。

// bad
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};
// good
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

5. 用多态来替代前提语句

// bad
if (type === 'text') {
    // do something
} else if (type === 'select') {
    // do something else
}

我个人写这类代码的一种经常使用体式格局是:

const control = {
    text: {
        mapper() {},
        restore(){},
        name: 'this is a text field',
    },
    select: {
        mapper() {},
        restore(){},
        name: 'this is a select field',
    }
}

control[type].mapper();

实际上就是多态(polymorphism),也能够斟酌用class的体式格局,也许如许:

class Field {
    ...
}

class TextField extends Field {
    mapper(){}
    restore(){}
    name = 'this is a text field';
}

class SelectField extends Field {
    mapper(){}
    restore(){}
    name = 'this is a select field';
}

6. 运用gettersetter函数。

// bad
function makeBankAccount() {
  // ...

  return {
    balance: 0
    // ...
  };
}

const account = makeBankAccount();
account.balance = 100;
// good
function makeBankAccount() {
  // this one is private
  let balance = 0;

  // a "getter", made public via the returned object below
  function getBalance() {
    return balance;
  }

  // a "setter", made public via the returned object below
  function setBalance(amount) {
    // ... validate before updating the balance
    balance = amount;
  }

  return {
    // ...
    getBalance,
    setBalance
  };
}

const account = makeBankAccount();
account.setBalance(100);

你能够在gettersetter内里做许多事变而不须要修正每个.balance的处所。

7. Prefer composition over inheritance

只管用组合来替代继续,什么情况下用继续:

  • Your inheritance represents an “is-a” relationship and not a “has-a” relationship (Human->Animal vs. User->UserDetails).
  • You can reuse code from the base classes (Humans can move like all animals).
  • You want to make global changes to derived classes by changing a base class. (Change the caloric expenditure of all animals when they move).

8. SOLID

Single Responsibility Principle 单一职责准绳

There should never be more than one reason for a class to change,一个类被转变的缘由数目应当只管下降。假如一个类中功用太多,当你修正个中一点时会无法估量任何援用该类的模块所遭到的影响。

Open/Closed Principle 开放关闭准绳

用户能够在不修正内部完成的前提下自行扩大功用。比方有一个Http模块,内部会依据环境推断用哪一个adaptor。假如用户要增加adaptor就必需修正Http模块。

// bad
class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === "ajaxAdapter") {
      return makeAjaxCall(url).then(response => {
        // transform response and return
      });
    } else if (this.adapter.name === "nodeAdapter") {
      return makeHttpCall(url).then(response => {
        // transform response and return
      });
    }
  }
}

function makeAjaxCall(url) {
  // request and return promise
}

function makeHttpCall(url) {
  // request and return promise
}
// good
class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then(response => {
      // transform response and return
    });
  }
}

Liskov Substitution Principle 里式替代准绳

父类和子类应当能够被交流运用而不会失足。

// bad
class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderLargeRectangles(rectangles) {
  rectangles.forEach(rectangle => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
    rectangle.render(area);
  });
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

上面的Rectangle不能直接替代Square,由于会致使盘算面积毛病,斟酌将盘算面积的要领笼统出来:

class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach(shape => {
    const area = shape.getArea();
    shape.render(area);
  });
}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);

Interface Segregation Principle 接口断绝准绳

Clients should not be forced to depend upon interfaces that they do not use。举例来说,一个功用模块须要设想必需传的参数和可选参数,不应当强制用户运用可选参数。

Dependency Inversion Principle 依靠注入准绳

原文:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend upon details. Details should depend on abstractions.
// bad
class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryTracker {
  constructor(items) {
    this.items = items;

    // BAD: We have created a dependency on a specific request implementation.
    // We should just have requestItems depend on a request method: `request`
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

const inventoryTracker = new InventoryTracker(["apples", "bananas"]);
inventoryTracker.requestItems();

上面例子在于,InventoryTracker内部实例化了InventoryRequester,也就意味着high-level的模块须要晓得low-level模块的细节(比方实例化InventoryRequester须要晓得它的组织参数等,或者说须要import该模块,形成耦合)。

// good
class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ["WS"];
  }

  requestItem(item) {
    // ...
  }
}

// By constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses WebSockets.
const inventoryTracker = new InventoryTracker(
  ["apples", "bananas"],
  new InventoryRequesterV2()
);
inventoryTracker.requestItems();

直接传入low-level的实例而不须要斟酌它是怎样被实例化的,high-level只须要依靠笼统的接口就能够完成对子模块的挪用。

9. 诠释

Comments are an apology, not a requirement. Good code mostly documents itself. 好的代码是自诠释的。

参考:clean-code-javascript

    原文作者:TonyZhu
    原文地址: https://segmentfault.com/a/1190000018688196
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞