JavaScript
代碼整潔之道整潔的代碼不單單是讓人看起來舒服,更重要的是遵循一些規範可以讓你的代碼更容易維護,同時下降bug概率。javascript
原文
clean-code-javascript,這裏總結摘錄出我的以爲有幫助的地方,也加入了一些本身的理解(有些文字我感受保留原文更好,因此直接copy了),其餘還有不少點沒有列出來,感興趣能夠去看原文。另外這不是強制的代碼規範,就像原文中說的,
These are guidelines and nothing more
。
// 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個,儘可能避免3個。若是有不少參數就利用object
傳遞,並使用解構。java
注意object
array
這些引用類型的值是mutable
的。
好處在於compose, test, and reason about
。node
若是想擴展原型,能夠先繼承再添加方法,防止污染。git
// 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)); } }
// bad if (type === 'text') { // do something } else if (type === 'select') { // do something else }
我我的寫這種代碼的一種經常使用方式是:github
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
的方式,大概這樣:ajax
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'; }
getter
和setter
函數。// 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);
你能夠在getter
和setter
裏面作不少事情而不須要修改每個.balance
的地方。數組
儘可能用組合來代替繼承,什麼狀況下用繼承:promise
SOLID
There should never be more than one reason for a class to change
,一個類被改變的緣由數量應該儘量下降。若是一個類中功能太多,當你修改其中一點時會沒法估量任何引用該類的模塊所受到的影響。app
用戶能夠在不修改內部實現的前提下自行擴展功能。例若有一個Http
模塊,內部會根據環境判斷用哪一個adaptor
。若是用戶要添加adaptor
就必須修改Http
模塊。ide
// 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 }); } }
父類和子類應該能夠被交換使用而不會出錯。
// 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);
Clients should not be forced to depend upon interfaces that they do not use
。舉例來講,一個功能模塊須要設計必須傳的參數和可選參數,不該該強迫用戶使用可選參數。
原文:
// 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
只須要依賴抽象的接口就能夠完成對子模塊的調用。
Comments are an apology, not a requirement. Good code mostly documents itself. 好的代碼是自解釋的。