程式碼規範整理

Nana_Jia發表於2020-12-28

變數、常量

駝峰(使用有意義且可發音)命名變數、函式名、方法名
大寫命名常量(單詞間用“”)
使用解釋變數(佔位符)【

使用預設引數代替短路或條件

函式、方法

使用物件做函式引數(理性情況引數個數為2個或更少)
forEach前先過濾,不在forEach中做判斷(函式應該只做一件事)
函式應該只有一個抽象
刪除重複程式碼
使用Object.assign設定預設物件
不要將標示(用作判斷的東西)用作函式引數
不寫入全域性函式
支援函數語言程式設計而不是指令式程式設計
封裝條件語句
避免否定條件句
避免條件句(使用多型性來實現相同的任務)
避免型別檢查(typeof、instanceof)(可藉助typescript)
不要過度優化
刪除死程式碼

物件和資料結構

使用getters和setters
使物件具有私有成員(可以通過閉包實現、private)

類(Class)

與ES5普通功能相比,更喜歡ES2015/ES6級別
使用方法連結(return this)
選擇組合而不是繼承
實體

單一責任原則(SRP)

正如Clean程式碼中所述,“一個類的更改原因不應該超過一個”。將一個類塞滿大量功能是很誘人,例如當你在你的航班上只能帶一個手提箱。這樣做的問題是,你的類在概念上沒有凝聚力,它會給它很多改變的理由。儘可能減少更改類的次數非常重要。這一點很重要,因為如果一個類中有太多的功能,而您修改了其中的一部分,那麼很難理解這將如何影響程式碼庫中的其他依賴模組。

Bad:

class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}

Good:

class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}

class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}

開/關原理(OCP)

正如bertrandmeyer所說,“軟體實體(類、模組、函式等)應該是開放的,但是對於修改是關閉的。”這意味著什麼呢?這個原則基本上是說,您應該允許使用者在不改變現有程式碼的情況下新增新功能。
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替代原理(LSP)

這是一個非常簡單的概念的可怕術語。它的正式定義是“如果s是T的一個子型別,那麼T型別的物件可以被s型別的物件替換(即,s型別的物件可以替換T型別的物件),而不會改變該程式的任何期望屬性(正確性、執行的任務等)”,這是一個更可怕的定義。
最好的解釋是,如果有父類和子類,那麼基類和子類可以互換使用,而不會得到錯誤的結果。這可能仍然令人困惑,所以讓我們看看經典的方形矩形示例。從數學上講,正方形是一個矩形,但如果通過繼承使用“is-a”關係對其進行建模,則很快就會遇到麻煩。
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);

Good:

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);

介面隔離原理

JavaScript沒有介面,所以這個原則並不像其他原則那樣嚴格適用。然而,即使在JavaScript缺少型別系統的情況下,它也非常重要和相關。
ISP宣告“不應該強迫客戶端依賴於他們不使用的介面。”介面是JavaScript中的隱式契約,因為duck型別。
在JavaScript中演示這一原理的一個很好的例子是針對需要大型設定物件的類。不要求客戶端設定大量的選項是有益的,因為大多數時候他們不需要所有的設定。將它們設為可選有助於防止出現“胖介面”。
Bad:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.settings.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  animationModule() {} // Most of the time, we won't need to animate when traversing.
  // ...
});

Good:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.options = settings.options;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }

  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  options: {
    animationModule() {}
  }
});

依賴倒置原理(DIP)

這一原則闡述了兩個基本事項:
高階模組不應依賴於低階模組。兩者都應該依賴於抽象。
抽象不應依賴細節。細節應該依靠抽象。
這一點一開始可能很難理解,但如果您使用過AngularJS,您就會看到依賴注入(DI)形式的這一原則的實現。雖然它們不是完全相同的概念,但是DIP阻止高階模組瞭解其低階模組的細節並設定它們。它可以通過DI實現這一點。這樣做的一個巨大好處是減少了模組之間的耦合。耦合是一種非常糟糕的開發模式,因為它使程式碼很難重構。
如前所述,JavaScript沒有介面,因此依賴的抽象是隱式契約。也就是說,一個物件/類向另一個物件/類公開的方法和屬性。在下面的示例中,隱式約定是InventoryTracker的任何請求模組都將具有一個requestItems方法。
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();

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();

測試

每個測試的單一概念
併發
使用Promises,而不是callbacks(回撥)
使用Async/Await比使用Promises程式碼更乾淨

錯誤處理

不要忽略捕捉到的錯誤
不要忽視被拒絕(失敗)的Promises(請求)
格式化
使用一致的大寫字母
函式呼叫方和被呼叫方應接近(保持函式呼叫方在被呼叫方的上面)

註釋

只註釋具有業務邏輯複雜性的內容
不要在程式碼庫中留下注釋掉的程式碼
不需要日誌註釋
避免使用位置標記

相關文章