做者根據Robert C. Martin《代碼整潔之道》總結了適用於JavaScript的軟件工程原則《Clean Code JavaScript》。javascript
本文是對其的翻譯。html
本文項目地址:github.com/alivebao/cl…java
沒必要嚴格遵照本文的全部原則,有時少遵照一些效果可能會更好,具體應根據實際狀況決定。這是根據《代碼整潔之道》做者多年經驗整理的代碼優化建議,但也僅僅只是一份建議。node
軟件工程已經發展了50多年,至今仍在不斷前進。如今,把這些原則看成試金石,嘗試將他們做爲團隊代碼質量考覈的標準之一吧。git
最後你須要知道的是,這些東西不會讓你馬上變成一個優秀的工程師,長期奉行他們也並不意味着你可以高枕無憂再也不犯錯。千里之行,始於足下。咱們須要時常和同行們進行代碼評審,不斷優化本身的代碼。不要害怕改善代碼質量所需付出的努力,加油。es6
反例:github
var yyyymmdstr = moment().format('YYYY/MM/DD');複製代碼
正例:編程
var yearMonthDay = moment().format('YYYY/MM/DD');複製代碼
回到目錄設計模式
反例中使用"var"定義的"常量"是可變的。api
在聲明一個常量時,該常量在整個程序中都應該是不可變的。
反例:
var FIRST_US_PRESIDENT = "George Washington";複製代碼
正例:
const FIRST_US_PRESIDENT = "George Washington";複製代碼
反例:
getUserInfo();
getClientData();
getCustomerRecord();複製代碼
正例:
getUser();複製代碼
咱們須要閱讀的代碼遠比本身寫的要多,使代碼擁有良好的可讀性且易於檢索很是重要。閱讀變量名晦澀難懂的代碼對讀者來講是一種至關糟糕的體驗。
讓你的變量名易於檢索。
反例:
// 525600 是什麼?
for (var i = 0; i < 525600; i++) {
runCronJob();
}複製代碼
正例:
// Declare them as capitalized `var` globals.
var MINUTES_IN_A_YEAR = 525600;
for (var i = 0; i < MINUTES_IN_A_YEAR; i++) {
runCronJob();
}複製代碼
反例:
const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);複製代碼
正例:
const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
const match = cityStateRegex.match(cityStateRegex)
const city = match[1];
const state = match[2];
saveCityState(city, state);複製代碼
顯式優於隱式。
反例:
var locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
doStuff();
doSomeOtherStuff();
...
...
...
// l是什麼?
dispatch(l);
});複製代碼
正例:
var locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
doStuff();
doSomeOtherStuff();
...
...
...
dispatch(location);
});複製代碼
當類/對象名已經有意義時,對其變量進行命名不須要再次重複。
反例:
var Car = {
carMake: 'Honda',
carModel: 'Accord',
carColor: 'Blue'
};
function paintCar(car) {
car.carColor = 'Red';
}複製代碼
正例:
var Car = {
make: 'Honda',
model: 'Accord',
color: 'Blue'
};
function paintCar(car) {
car.color = 'Red';
}複製代碼
反例:
function createMicrobrewery(name) {
var breweryName;
if (name) {
breweryName = name;
} else {
breweryName = 'Hipster Brew Co.';
}
}複製代碼
正例:
function createMicrobrewery(name) {
var breweryName = name || 'Hipster Brew Co.'
}複製代碼
限制函數參數數量頗有必要,這麼作使得在測試函數時更加輕鬆。過多的參數將致使難以採用有效的測試用例對函數的各個參數進行測試。
應避免三個以上參數的函數。一般狀況下,參數超過兩個意味着函數功能過於複雜,這時須要從新優化你的函數。當確實須要多個參數時,大多狀況下能夠考慮這些參數封裝成一個對象。
JS定義對象很是方便,當須要多個參數時,可使用一個對象進行替代。
反例:
function createMenu(title, body, buttonText, cancellable) {
...
}複製代碼
正例:
var menuConfig = {
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}
function createMenu(menuConfig) {
...
}複製代碼
這是軟件功能中最重要的原則之一。
功能不單一的函數將致使難以重構、測試和理解。功能單一的函數易於重構,並使代碼更加乾淨。
反例:
function emailClients(clients) {
clients.forEach(client => {
let clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}複製代碼
正例:
function emailClients(clients) {
clients.forEach(client => {
emailClientIfNeeded(client);
});
}
function emailClientIfNeeded(client) {
if (isClientActive(client)) {
email(client);
}
}
function isClientActive(client) {
let clientRecord = database.lookup(client);
return clientRecord.isActive();
}複製代碼
反例:
function dateAdd(date, month) {
// ...
}
let date = new Date();
// 很難理解dateAdd(date, 1)是什麼意思複製代碼
正例:
function dateAddMonth(date, month) {
// ...
}
let date = new Date();
dateAddMonth(date, 1);複製代碼
當函數的須要的抽象多於一層時一般意味着函數功能過於複雜,需將其進行分解以提升其可重用性和可測試性。
反例:
function parseBetterJSAlternative(code) {
let REGEXES = [
// ...
];
let statements = code.split(' ');
let tokens;
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
})
});
let ast;
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
})
}複製代碼
正例:
function tokenize(code) {
let REGEXES = [
// ...
];
let statements = code.split(' ');
let tokens;
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
})
});
return tokens;
}
function lexer(tokens) {
let ast;
tokens.forEach((token) => {
// lex...
});
return ast;
}
function parseBetterJSAlternative(code) {
let tokens = tokenize(code);
let ast = lexer(tokens);
ast.forEach((node) => {
// parse...
})
}複製代碼
永遠、永遠、永遠不要在任何循環下有重複的代碼。
這種作法毫無心義且潛在危險極大。重複的代碼意味着邏輯變化時須要對不止一處進行修改。JS弱類型的特色使得函數擁有更強的普適性。好好利用這一優勢吧。
反例:
function showDeveloperList(developers) {
developers.forEach(developers => {
var expectedSalary = developer.calculateExpectedSalary();
var experience = developer.getExperience();
var githubLink = developer.getGithubLink();
var data = {
expectedSalary: expectedSalary,
experience: experience,
githubLink: githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach(manager => {
var expectedSalary = manager.calculateExpectedSalary();
var experience = manager.getExperience();
var portfolio = manager.getMBAProjects();
var data = {
expectedSalary: expectedSalary,
experience: experience,
portfolio: portfolio
};
render(data);
});
}複製代碼
正例:
function showList(employees) {
employees.forEach(employee => {
var expectedSalary = employee.calculateExpectedSalary();
var experience = employee.getExperience();
var portfolio;
if (employee.type === 'manager') {
portfolio = employee.getMBAProjects();
} else {
portfolio = employee.getGithubLink();
}
var data = {
expectedSalary: expectedSalary,
experience: experience,
portfolio: portfolio
};
render(data);
});
}複製代碼
反例:
function writeForumComment(subject, body) {
subject = subject || 'No Subject';
body = body || 'No text';
}複製代碼
正例:
function writeForumComment(subject = 'No subject', body = 'No text') {
...
}複製代碼
反例:
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);複製代碼
正例:
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 now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);複製代碼
這一般意味着函數的功能的單一性已經被破壞。此時應考慮對函數進行再次劃分。
反例:
function createFile(name, temp) {
if (temp) {
fs.create('./temp/' + name);
} else {
fs.create(name);
}
}複製代碼
正例:
function createTempFile(name) {
fs.create('./temp/' + name);
}
function createFile(name) {
fs.create(name);
}複製代碼
當函數產生了除了「接受一個值並返回一個結果」以外的行爲時,稱該函數產生了反作用。好比寫文件、修改全局變量或將你的錢全轉給了一個陌生人等。
程序在某些狀況下確實須要反作用這一行爲,如先前例子中的寫文件。這時應該將這些功能集中在一塊兒,不要用多個函數/類修改某個文件。用且只用一個service完成這一需求。
反例:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];複製代碼
正例:
function splitIntoFirstAndLastName(name) {
return name.split(' ');
}
var name = 'Ryan McDermott'
var newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];複製代碼
在JS中污染全局是一個很是很差的實踐,這麼作可能和其餘庫起衝突,且調用你的API的用戶在實際環境中獲得一個exception前對這一狀況是一無所知的。
想象如下例子:若是你想擴展JS中的Array,爲其添加一個diff
函數顯示兩個數組間的差別,此時應如何去作?你能夠將diff寫入Array.prototype
,但這麼作會和其餘有相似需求的庫形成衝突。若是另外一個庫對diff的需求爲比較一個數組中收尾元素間的差別呢?
使用ES6中的class對全局的Array作簡單的擴展顯然是一個更棒的選擇。
反例:
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;
}複製代碼
正例:
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;
}
}複製代碼
函數式的編程具備更乾淨且便於測試的特色。儘量的使用這種風格吧。
反例:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
var totalOutput = 0;
for (var i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}複製代碼
正例:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
var totalOutput = programmerOutput
.map((programmer) => programmer.linesOfCode)
.reduce((acc, linesOfCode) => acc + linesOfCode, 0);複製代碼
反例:
if (fsm.state === 'fetching' && isEmpty(listNode)) {
/// ...
}複製代碼
正例:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}複製代碼
反例:
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}複製代碼
正例:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}複製代碼
這看起來彷佛不太可能。
大多人聽到這的第一反應是:「怎麼可能不用if完成其餘功能呢?」許多狀況下經過使用多態(polymorphism)能夠達到一樣的目的。
第二個問題在於採用這種方式的緣由是什麼。答案是咱們以前提到過的:保持函數功能的單一性。
反例:
class Airplane {
//...
getCruisingAltitude() {
switch (this.type) {
case '777':
return getMaxAltitude() - getPassengerCount();
case 'Air Force One':
return getMaxAltitude();
case 'Cessna':
return getMaxAltitude() - getFuelExpenditure();
}
}
}複製代碼
正例:
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();
}
}複製代碼
JS是弱類型語言,這意味着函數可接受任意類型的參數。
有時這會對你帶來麻煩,你會對參數作一些類型判斷。有許多方法能夠避免這些狀況。
反例:
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.peddle(this.currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location('texas'));
}
}複製代碼
正例:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}複製代碼
若是需處理的數據爲字符串,整型,數組等類型,沒法使用多態並仍有必要對其進行類型檢測時,能夠考慮使用TypeScript。
反例:
function combine(val1, val2) {
if (typeof val1 == "number" && typeof val2 == "number" ||
typeof val1 == "string" && typeof val2 == "string") {
return val1 + val2;
} else {
throw new Error('Must be of type String or Number');
}
}複製代碼
正例:
function combine(val1, val2) {
return val1 + val2;
}複製代碼
現代的瀏覽器在運行時會對代碼自動進行優化。有時人爲對代碼進行優化多是在浪費時間。
反例:
// 這裏使用變量len是由於在老式瀏覽器中,
// 直接使用正例中的方式會致使每次循環均重複計算list.length的值,
// 而在現代瀏覽器中會自動完成優化,這一行爲是沒有必要的
for (var i = 0, len = list.length; i < len; i++) {
// ...
}複製代碼
正例:
for (var i = 0; i < list.length; i++) {
// ...
}複製代碼
再也不被調用的代碼應及時刪除。
反例:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
var req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');複製代碼
正例:
function newRequestModule(url) {
// ...
}
var req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');複製代碼
JS沒有接口或類型,所以實現這一模式是很困難的,由於咱們並無相似public
和private
的關鍵詞。
然而,使用getters和setters獲取對象的數據遠比直接使用點操做符具備優點。爲何呢?
set
時能夠增長規則對要變量的合法性進行判斷。反例:
class BankAccount {
constructor() {
this.balance = 1000;
}
}
let bankAccount = new BankAccount();
// Buy shoes...
bankAccount.balance = bankAccount.balance - 100;複製代碼
正例:
class BankAccount {
constructor() {
this.balance = 1000;
}
// It doesn't have to be prefixed with `get` or `set` to be a getter/setter
withdraw(amount) {
if (verifyAmountCanBeDeducted(amount)) {
this.balance -= amount;
}
}
}
let bankAccount = new BankAccount();
// Buy shoes...
bankAccount.withdraw(100);複製代碼
能夠經過閉包完成
反例:
var Employee = function(name) {
this.name = name;
}
Employee.prototype.getName = function() {
return this.name;
}
var employee = new Employee('John Doe');
console.log('Employee name: ' + employee.getName()); // Employee name: John Doe
delete employee.name;
console.log('Employee name: ' + employee.getName()); // Employee name: undefined複製代碼
正例:
var Employee = (function() {
function Employee(name) {
this.getName = function() {
return name;
};
}
return Employee;
}());
var employee = new Employee('John Doe');
console.log('Employee name: ' + employee.getName()); // Employee name: John Doe
delete employee.name;
console.log('Employee name: ' + employee.getName()); // Employee name: John Doe複製代碼
如《代碼整潔之道》一書中所述,「修改一個類的理由不該該超過一個」。
將多個功能塞進一個類的想法很誘人,但這將致使你的類沒法達到概念上的內聚,並常常不得不進行修改。
最小化對一個類須要修改的次數是很是有必要的。若是一個類具備太多太雜的功能,當你對其中一小部分進行修改時,將很難想象到這一修夠對代碼庫中依賴該類的其餘模塊會帶來什麼樣的影響。
反例:
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials(user)) {
// ...
}
}
verifyCredentials(user) {
// ...
}
}複製代碼
正例:
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()) {
// ...
}
}
}複製代碼
「代碼實體(類,模塊,函數等)應該易於擴展,難於修改。」
這一原則指的是咱們應容許用戶方便的擴展咱們代碼模塊的功能,而不須要打開js文件源碼手動對其進行修改。
反例:
class AjaxRequester {
constructor() {
// What if we wanted another HTTP Method, like DELETE? We would have to
// open this file up and modify this and put it in manually.
this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
}
get(url) {
// ...
}
}複製代碼
正例:
class AjaxRequester {
constructor() {
this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
}
get(url) {
// ...
}
addHTTPMethod(method) {
this.HTTP_METHODS.push(method);
}
}複製代碼
「子類對象應該可以替換其超類對象被使用」。
也就是說,若是有一個父類和一個子類,當採用子類替換父類時不該該產生錯誤的結果。
反例:
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 {
constructor() {
super();
}
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);
let area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20.
rectangle.render(area);
})
}
let rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);複製代碼
正例:
class Shape {
constructor() {}
setColor(color) {
// ...
}
render(area) {
// ...
}
}
class Rectangle extends Shape {
constructor() {
super();
this.width = 0;
this.height = 0;
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Shape {
constructor() {
super();
this.length = 0;
}
setLength(length) {
this.length = length;
}
getArea() {
return this.length * this.length;
}
}
function renderLargeShapes(shapes) {
shapes.forEach((shape) => {
switch (shape.constructor.name) {
case 'Square':
shape.setLength(5);
case 'Rectangle':
shape.setWidth(4);
shape.setHeight(5);
}
let area = shape.getArea();
shape.render(area);
})
}
let shapes = [new Rectangle(), new Rectangle(), new Square()];
renderLargeShapes(shapes);複製代碼
「客戶端不該該依賴它不須要的接口;一個類對另外一個類的依賴應該創建在最小的接口上。」
在JS中,當一個類須要許多參數設置才能生成一個對象時,或許大多時候不須要設置這麼多的參數。此時減小對配置參數數量的需求是有益的。
反例:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.animationModule.setup();
}
traverse() {
// ...
}
}
let $ = new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
animationModule: function() {} // Most of the time, we won't need to animate when traversing.
// ...
});複製代碼
正例:
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() {
// ...
}
}
let $ = new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
options: {
animationModule: function() {}
}
});複製代碼
該原則有兩個核心點:
反例:
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);
});
}
}
class InventoryRequester {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
let inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();複製代碼
正例:
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.
let inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
inventoryTracker.requestItems();複製代碼
典型的ES5的類(function)在繼承、構造和方法定義方面可讀性較差。
當須要繼承時,優先選用classes。
可是,當在須要更大更復雜的對象時,最好優先選擇更小的function而非classes。
反例:
var Animal = function(age) {
if (!(this instanceof Animal)) {
throw new Error("Instantiate Animal with `new`");
}
this.age = age;
};
Animal.prototype.move = function() {};
var Mammal = function(age, furColor) {
if (!(this instanceof Mammal)) {
throw new Error("Instantiate Mammal with `new`");
}
Animal.call(this, age);
this.furColor = furColor;
};
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function() {};
var Human = function(age, furColor, languageSpoken) {
if (!(this instanceof Human)) {
throw new Error("Instantiate Human with `new`");
}
Mammal.call(this, age, furColor);
this.languageSpoken = languageSpoken;
};
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function() {};複製代碼
正例:
class Animal {
constructor(age) {
this.age = age;
}
move() {}
}
class Mammal extends Animal {
constructor(age, furColor) {
super(age);
this.furColor = furColor;
}
liveBirth() {}
}
class Human extends Mammal {
constructor(age, furColor, languageSpoken) {
super(age, furColor);
this.languageSpoken = languageSpoken;
}
speak() {}
}複製代碼
這裏咱們的理解與《代碼整潔之道》的建議有些不一樣。
有爭論說方法鏈不夠乾淨且違反了德米特法則,也許這是對的,但這種方法在JS及許多庫(如JQuery)中顯得很是實用。
所以,我認爲在JS中使用方法鏈是很是合適的。在class的函數中返回this,可以方便的將類須要執行的多個方法連接起來。
反例:
class Car {
constructor() {
this.make = 'Honda';
this.model = 'Accord';
this.color = 'white';
}
setMake(make) {
this.name = name;
}
setModel(model) {
this.model = model;
}
setColor(color) {
this.color = color;
}
save() {
console.log(this.make, this.model, this.color);
}
}
let car = new Car();
car.setColor('pink');
car.setMake('Ford');
car.setModel('F-150')
car.save();複製代碼
正例:
class Car {
constructor() {
this.make = 'Honda';
this.model = 'Accord';
this.color = 'white';
}
setMake(make) {
this.name = name;
// NOTE: Returning this for chaining
return this;
}
setModel(model) {
this.model = model;
// NOTE: Returning this for chaining
return this;
}
setColor(color) {
this.color = color;
// NOTE: Returning this for chaining
return this;
}
save() {
console.log(this.make, this.model, this.color);
}
}
let car = new Car()
.setColor('pink')
.setMake('Ford')
.setModel('F-150')
.save();複製代碼
在著名的設計模式一書中提到,應多使用組合模式而非繼承。
這麼作有許多優勢,在想要使用繼承前,多想一想可否經過組合模式知足需求吧。
那麼,在何時繼承具備更大的優點呢?這取決於你的具體需求,但大多狀況下,能夠遵照如下三點:
反例:
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
// ...
}
// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super();
this.ssn = ssn;
this.salary = salary;
}
// ...
}複製代碼
正例:
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}
// ...
}複製代碼
反例:
const assert = require('assert');
describe('MakeMomentJSGreatAgain', function() {
it('handles date boundaries', function() {
let date;
date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
date.shouldEqual('1/31/2015');
date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});複製代碼
正例:
const assert = require('assert');
describe('MakeMomentJSGreatAgain', function() {
it('handles 30-day months', function() {
let date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
date.shouldEqual('1/31/2015');
});
it('handles leap year', function() {
let date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
});
it('handles non-leap year', function() {
let date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});複製代碼
回調不夠整潔並會形成大量的嵌套。ES6內嵌了Promises,使用它吧。
反例:
require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', function(err, response) {
if (err) {
console.error(err);
}
else {
require('fs').writeFile('article.html', response.body, function(err) {
if (err) {
console.error(err);
} else {
console.log('File written');
}
})
}
})複製代碼
正例:
require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then(function(response) {
return require('fs-promise').writeFile('article.html', response);
})
.then(function() {
console.log('File written');
})
.catch(function(err) {
console.error(err);
})複製代碼
Promises是較回調而言更好的一種選擇,但ES7中的async和await更賽過Promises。
在能使用ES7特性的狀況下能夠儘可能使用他們替代Promises。
反例:
require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then(function(response) {
return require('fs-promise').writeFile('article.html', response);
})
.then(function() {
console.log('File written');
})
.catch(function(err) {
console.error(err);
})複製代碼
正例:
async function getCleanCodeArticle() {
try {
var request = await require('request-promise')
var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
var fileHandle = await require('fs-promise');
await fileHandle.writeFile('article.html', response);
console.log('File written');
} catch(err) {
console.log(err);
}
}複製代碼
錯誤拋出是個好東西!這使得你可以成功定位運行狀態中的程序產生錯誤的位置。
對捕獲的錯誤不作任何處理是沒有意義的。
代碼中try/catch
的意味着你認爲這裏可能出現一些錯誤,你應該對這些可能的錯誤存在相應的處理方案。
反例:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}複製代碼
正例:
try {
functionThatMightThrow();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
}複製代碼
理由同try/catch
.
反例:
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
});複製代碼
正例:
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
});複製代碼
格式化是一件主觀的事。如同這裏的許多規則同樣,這裏並無必定/馬上須要遵照的規則。能夠在這裏完成格式的自動化。
JS是弱類型語言,合理的採用大小寫能夠告訴你關於變量/函數等的許多消息。
這些規則是主觀定義的,團隊能夠根據喜歡進行選擇。重點在於不管選擇何種風格,都須要注意保持一致性。
反例:
var DAYS_IN_WEEK = 7;
var daysInMonth = 30;
var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
var Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}複製代碼
正例:
var DAYS_IN_WEEK = 7;
var DAYS_IN_MONTH = 30;
var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
var artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}複製代碼
當函數間存在相互調用的狀況時,應將二者置於較近的位置。
理想狀況下,應將調用其餘函數的函數寫在被調用函數的上方。
反例:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
lookupMananger() {
return db.lookup(this.employee, 'manager');
}
getPeerReviews() {
let peers = this.lookupPeers();
// ...
}
perfReview() {
getPeerReviews();
getManagerReview();
getSelfReview();
}
getManagerReview() {
let manager = this.lookupManager();
}
getSelfReview() {
// ...
}
}
let review = new PerformanceReview(user);
review.perfReview();複製代碼
正例:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
perfReview() {
getPeerReviews();
getManagerReview();
getSelfReview();
}
getPeerReviews() {
let peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
getManagerReview() {
let manager = this.lookupManager();
}
lookupMananger() {
return db.lookup(this.employee, 'manager');
}
getSelfReview() {
// ...
}
}
let review = new PerformanceReview(employee);
review.perfReview();複製代碼
註釋並非必須的,好的代碼是可以讓人一目瞭然,不用過多無謂的註釋。
反例:
function hashIt(data) {
// The hash
var hash = 0;
// Length of string
var length = data.length;
// Loop through every character in data
for (var i = 0; i < length; i++) {
// Get character code.
var char = data.charCodeAt(i);
// Make the hash
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash = hash & hash;
}
}複製代碼
正例:
function hashIt(data) {
var hash = 0;
var length = data.length;
for (var i = 0; i < length; i++) {
var char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash = hash & hash;
}
}複製代碼
版本控制的存在是有緣由的。讓舊代碼存在於你的history裏吧。
反例:
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();複製代碼
正例:
doStuff();複製代碼
記住,咱們可使用版本控制。廢代碼、被註釋的代碼及用註釋記錄代碼中的版本更新說明都是沒有必要的。
須要時可使用git log
獲取歷史版本。
反例:
/** * 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;
}複製代碼
正例:
function combine(a, b) {
return a + b;
}複製代碼
這些東西一般只能代碼麻煩,採用適當的縮進就能夠了。
反例:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
let $scope.model = {
menu: 'foo',
nav: 'bar'
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
let actions = function() {
// ...
}複製代碼
正例:
let $scope.model = {
menu: 'foo',
nav: 'bar'
};
let actions = function() {
// ...
}複製代碼
將你的LICENSE
文件置於源碼目錄樹的根目錄。
反例:
/* The MIT License (MIT) Copyright (c) 2016 Ryan McDermott Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE */
function calculateBill() {
// ...
}複製代碼
正例:
function calculateBill() {
// ...
}複製代碼