新聞中心
介紹
作者根據(jù)Robert C. Martin《代碼整潔之道》總結(jié)了適用于JavaScript的軟件工程原則《Clean Code JavaScript》。

為西平等地區(qū)用戶(hù)提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及西平網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都做網(wǎng)站、成都網(wǎng)站建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、西平網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶(hù)提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶(hù)的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
本文是對(duì)其的翻譯。
不必嚴(yán)格遵守本文的所有原則,有時(shí)少遵守一些效果可能會(huì)更好,具體應(yīng)根據(jù)實(shí)際情況決定。這是根據(jù)《代碼整潔之道》作者多年經(jīng)驗(yàn)整理的代碼優(yōu)化建議,但也僅僅只是一份建議。
軟件工程已經(jīng)發(fā)展了50多年,至今仍在不斷前進(jìn)?,F(xiàn)在,把這些原則當(dāng)作試金石,嘗試將他們作為團(tuán)隊(duì)代碼質(zhì)量考核的標(biāo)準(zhǔn)之一吧。
最后你需要知道的是,這些東西不會(huì)讓你立刻變成一個(gè)優(yōu)秀的工程師,長(zhǎng)期奉行他們也并不意味著你能夠高枕無(wú)憂(yōu)不再犯錯(cuò)。千里之行,始于足下。我們需要時(shí)常和同行們進(jìn)行代碼評(píng)審,不斷優(yōu)化自己的代碼。不要懼怕改善代碼質(zhì)量所需付出的努力,加油。
變量
使用有意義,可讀性好的變量名
反例:
var yyyymmdstr = moment().format('YYYY/MM/DD');正例:
var yearMonthDay = moment().format('YYYY/MM/DD');
使用ES6的const定義常量
反例中使用”var”定義的”常量”是可變的。
在聲明一個(gè)常量時(shí),該常量在整個(gè)程序中都應(yīng)該是不可變的。
反例:
var FIRST_US_PRESIDENT = "George Washington";
正例:
const FIRST_US_PRESIDENT = "George Washington";
對(duì)功能類(lèi)似的變量名采用統(tǒng)一的命名風(fēng)格
反例:
getUserInfo(); getClientData(); getCustomerRecord();
正例:
getUser();
使用易于檢索名稱(chēng)
我們需要閱讀的代碼遠(yuǎn)比自己寫(xiě)的要多,使代碼擁有良好的可讀性且易于檢索非常重要。閱讀變量名晦澀難懂的代碼對(duì)讀者來(lái)說(shuō)是一種相當(dāng)糟糕的體驗(yàn)。 讓你的變量名易于檢索。
反例:
// 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();
}
使用說(shuō)明變量(即有意義的變量名)
反例:
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);
不要繞太多的彎子
顯式優(yōu)于隱式。
反例:
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);
});
避免重復(fù)的描述
當(dāng)類(lèi)/對(duì)象名已經(jīng)有意義時(shí),對(duì)其變量進(jìn)行命名不需要再次重復(fù)。
反例:
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';
}
避免無(wú)意義的條件判斷
反例:
function createMicrobrewery(name) {
var breweryName;
if (name) {
breweryName = name;
} else {
breweryName = 'Hipster Brew Co.';
}
}正例:
function createMicrobrewery(name) {
var breweryName = name || 'Hipster Brew Co.'
}
函數(shù)
函數(shù)參數(shù) (理想情況下應(yīng)不超過(guò)2個(gè))
限制函數(shù)參數(shù)數(shù)量很有必要,這么做使得在測(cè)試函數(shù)時(shí)更加輕松。過(guò)多的參數(shù)將導(dǎo)致難以采用有效的測(cè)試用例對(duì)函數(shù)的各個(gè)參數(shù)進(jìn)行測(cè)試。
應(yīng)避免三個(gè)以上參數(shù)的函數(shù)。通常情況下,參數(shù)超過(guò)兩個(gè)意味著函數(shù)功能過(guò)于復(fù)雜,這時(shí)需要重新優(yōu)化你的函數(shù)。當(dāng)確實(shí)需要多個(gè)參數(shù)時(shí),大多情況下可以考慮這些參數(shù)封裝成一個(gè)對(duì)象。
JS定義對(duì)象非常方便,當(dāng)需要多個(gè)參數(shù)時(shí),可以使用一個(gè)對(duì)象進(jìn)行替代。
反例:
function createMenu(title, body, buttonText, cancellable) {
...
}正例:
var menuConfig = {
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}
function createMenu(menuConfig) {
...
}
函數(shù)功能的單一性
這是軟件功能中最重要的原則之一。
功能不單一的函數(shù)將導(dǎo)致難以重構(gòu)、測(cè)試和理解。功能單一的函數(shù)易于重構(gòu),并使代碼更加干凈。
反例:
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();
}
函數(shù)名應(yīng)明確表明其功能
反例:
function dateAdd(date, month) {
// ...
}
let date = new Date();
// 很難理解dateAdd(date, 1)是什么意思正例:
function dateAddMonth(date, month) {
// ...
}
let date = new Date();
dateAddMonth(date, 1);
函數(shù)應(yīng)該只做一層抽象
當(dāng)函數(shù)的需要的抽象多余一層時(shí)通常意味著函數(shù)功能過(guò)于復(fù)雜,需將其進(jìn)行分解以提高其可重用性和可測(cè)試性。
反例:
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...
})
}
移除重復(fù)的代碼
永遠(yuǎn)、永遠(yuǎn)、永遠(yuǎn)不要在任何循環(huán)下有重復(fù)的代碼。
這種做法毫無(wú)意義且潛在危險(xiǎn)極大。重復(fù)的代碼意味著邏輯變化時(shí)需要對(duì)不止一處進(jìn)行修改。JS弱類(lèi)型的特點(diǎn)使得函數(shù)擁有更強(qiáng)的普適性。好好利用這一優(yōu)點(diǎn)吧。
反例:
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);
});
}
采用默認(rèn)參數(shù)精簡(jiǎn)代碼
反例:
function writeForumComment(subject, body) {
subject = subject || 'No Subject';
body = body || 'No text';
}正例:
function writeForumComment(subject = 'No subject', body = 'No text') {
...
}
使用Object.assign設(shè)置默認(rèn)對(duì)象
反例:
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: "Foo", body: "Bar", buttonText: "Baz", cancellable: true}
// ...
}
createMenu(menuConfig);
不要使用標(biāo)記(Flag)作為函數(shù)參數(shù)
這通常意味著函數(shù)的功能的單一性已經(jīng)被破壞。此時(shí)應(yīng)考慮對(duì)函數(shù)進(jìn)行再次劃分。
反例:
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);
}
避免副作用
當(dāng)函數(shù)產(chǎn)生了除了“接受一個(gè)值并返回一個(gè)結(jié)果”之外的行為時(shí),稱(chēng)該函數(shù)產(chǎn)生了副作用。比如寫(xiě)文件、修改全局變量或?qū)⒛愕腻X(qián)全轉(zhuǎn)給了一個(gè)陌生人等。
程序在某些情況下確實(shí)需要副作用這一行為,如先前例子中的寫(xiě)文件。這時(shí)應(yīng)該將這些功能集中在一起,不要用多個(gè)函數(shù)/類(lèi)修改某個(gè)文件。用且只用一個(gè)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'];
不要寫(xiě)全局函數(shù)
在JS中污染全局是一個(gè)非常不好的實(shí)踐,這么做可能和其他庫(kù)起沖突,且調(diào)用你的API的用戶(hù)在實(shí)際環(huán)境中得到一個(gè)exception前對(duì)這一情況是一無(wú)所知的。
想象以下例子:如果你想擴(kuò)展JS中的Array,為其添加一個(gè)diff函數(shù)顯示兩個(gè)數(shù)組間的差異,此時(shí)應(yīng)如何去做?你可以將diff寫(xiě)入Array.prototype,但這么做會(huì)和其他有類(lèi)似需求的庫(kù)造成沖突。如果另一個(gè)庫(kù)對(duì)diff的需求為比較一個(gè)數(shù)組中收尾元素間的差異呢?
使用ES6中的class對(duì)全局的Array做簡(jiǎn)單的擴(kuò)展顯然是一個(gè)更棒的選擇。
反例:
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;
}
}
采用函數(shù)式編程
函數(shù)式的編程具有更干凈且便于測(cè)試的特點(diǎn)。盡可能的使用這種風(fēng)格吧。
反例:
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)) {
// ...
}
避免條件判斷
這看起來(lái)似乎不太可能。
大多人聽(tīng)到這的第一反應(yīng)是:“怎么可能不用if完成其他功能呢?”許多情況下通過(guò)使用多態(tài)(polymorphism)可以達(dá)到同樣的目的。
第二個(gè)問(wèn)題在于采用這種方式的原因是什么。答案是我們之前提到過(guò)的:保持函數(shù)功能的單一性。
反例:
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();
}
}
避免類(lèi)型判斷(part 1)
JS是弱類(lèi)型語(yǔ)言,這意味著函數(shù)可接受任意類(lèi)型的參數(shù)。
有時(shí)這會(huì)對(duì)你帶來(lái)麻煩,你會(huì)對(duì)參數(shù)做一些類(lèi)型判斷。有許多方法可以避免這些情況。
反例:
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'));
}
避免類(lèi)型判斷(part 2)
如果需處理的數(shù)據(jù)為字符串,整型,數(shù)組等類(lèi)型,無(wú)法使用多態(tài)并仍有必要對(duì)其進(jìn)行類(lèi)型檢測(cè)時(shí),可以考慮使用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;
}
避免過(guò)度優(yōu)化
現(xiàn)代的瀏覽器在運(yùn)行時(shí)會(huì)對(duì)代碼自動(dòng)進(jìn)行優(yōu)化。有時(shí)人為對(duì)代碼進(jìn)行優(yōu)化可能是在浪費(fèi)時(shí)間。
這里可以找到許多真正需要優(yōu)化的地方
反例:
// 這里使用變量len是因?yàn)樵诶鲜綖g覽器中,
// 直接使用正例中的方式會(huì)導(dǎo)致每次循環(huán)均重復(fù)計(jì)算list.length的值,
// 而在現(xiàn)代瀏覽器中會(huì)自動(dòng)完成優(yōu)化,這一行為是沒(méi)有必要的
for (var i = 0, len = list.length; i < len; i++) {
// ...
}正例:
for (var i = 0; i < list.length; i++) {
// ...
}
刪除無(wú)效的代碼
不再被調(diào)用的代碼應(yīng)及時(shí)刪除。
反例:
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');
對(duì)象和數(shù)據(jù)結(jié)構(gòu)
使用getters和setters
JS沒(méi)有接口或類(lèi)型,因此實(shí)現(xiàn)這一模式是很困難的,因?yàn)槲覀儾](méi)有類(lèi)似public和private的關(guān)鍵詞。
然而,使用getters和setters獲取對(duì)象的數(shù)據(jù)遠(yuǎn)比直接使用點(diǎn)操作符具有優(yōu)勢(shì)。為什么呢?
- 當(dāng)需要對(duì)獲取的對(duì)象屬性執(zhí)行額外操作時(shí)。
- 執(zhí)行
set時(shí)可以增加規(guī)則對(duì)要變量的合法性進(jìn)行判斷。 - 封裝了內(nèi)部邏輯。
- 在存取時(shí)可以方便的增加日志和錯(cuò)誤處理。
- 繼承該類(lèi)時(shí)可以重載默認(rèn)行為。
- 從服務(wù)器獲取數(shù)據(jù)時(shí)可以進(jìn)行懶加載。
反例:
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);
讓對(duì)象擁有私有成員
可以通過(guò)閉包完成
反例:
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
類(lèi)
單一職責(zé)原則 (SRP)
如《代碼整潔之道》一書(shū)中所述,“修改一個(gè)類(lèi)的理由不應(yīng)該超過(guò)一個(gè)”。
將多個(gè)功能塞進(jìn)一個(gè)類(lèi)的想法很誘人,但這將導(dǎo)致你的類(lèi)無(wú)法達(dá)到概念上的內(nèi)聚,并經(jīng)常不得不進(jìn)行修改。
最小化對(duì)一個(gè)類(lèi)需要修改的次數(shù)是非常有必要的。如果一個(gè)類(lèi)具有太多太雜的功能,當(dāng)你對(duì)其中一小部分進(jìn)行修改時(shí),將很難想象到這一修夠?qū)Υa庫(kù)中依賴(lài)該類(lèi)的其他模塊會(huì)帶來(lái)什么樣的影響。
反例:
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()) {
// ...
}
}
}
開(kāi)/閉原則 (OCP)
“代碼實(shí)體(類(lèi),模塊,函數(shù)等)應(yīng)該易于擴(kuò)展,難于修改?!?/p>
這一原則指的是我們應(yīng)允許用戶(hù)方便的擴(kuò)展我們代碼模塊的功能,而不需要打開(kāi)js文件源碼手動(dòng)對(duì)其進(jìn)行修改。
反例:
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);
}
}
利斯科夫替代原則 (LSP)
“子類(lèi)對(duì)象應(yīng)該能夠替換其超類(lèi)對(duì)象被使用”。
也就是說(shuō),如果有一個(gè)父類(lèi)和一個(gè)子類(lèi),當(dāng)采用子類(lèi)替換父類(lèi)時(shí)不應(yīng)該產(chǎn)生錯(cuò)誤的結(jié)果。
反例:
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);
接口隔離原則 (ISP)
“客戶(hù)端不應(yīng)該依賴(lài)它不需要的接口;一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴(lài)應(yīng)該建立在最小的接口上。”
在JS中,當(dāng)一個(gè)類(lèi)需要許多參數(shù)設(shè)置才能生成一個(gè)對(duì)象時(shí),或許大多時(shí)候不需要設(shè)置這么多的參數(shù)。此時(shí)減少對(duì)配置參數(shù)數(shù)量的需求是有益的。
反例:
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() {}
}
});
依賴(lài)反轉(zhuǎn)原則 (DIP)
該原則有兩個(gè)核心點(diǎn):
1. 高層模塊不應(yīng)該依賴(lài)于低層模塊。他們都應(yīng)該依賴(lài)于抽象接口。 2. 抽象接口應(yīng)該脫離具體實(shí)現(xiàn),具體實(shí)現(xiàn)應(yīng)該依賴(lài)于抽象接口。
反例:
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();
使用ES6的classes而不是ES5的Function
典型的ES5的類(lèi)(function)在繼承、構(gòu)造和方法定義方面可讀性較差。
當(dāng)需要繼承時(shí),優(yōu)先選用classes。
但是,當(dāng)在需要更大更復(fù)雜的對(duì)象時(shí),最好優(yōu)先選擇更小的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() {}
}
使用方法鏈
這里我們的理解與《代碼整潔之道》的建議有些不同。
有爭(zhēng)論說(shuō)方法鏈不夠干凈且違反了德米特法則,也許這是對(duì)的,但這種方法在JS及許多庫(kù)(如JQuery)中顯得非常實(shí)用。
因此,我認(rèn)為在JS中使用方法鏈?zhǔn)欠浅:线m的。在class的函數(shù)中返回this,能夠方便的將類(lèi)需要執(zhí)行的多個(gè)方法鏈接起來(lái)。
反例:
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();
優(yōu)先使用組合模式而非繼承
在著名的設(shè)計(jì)模式一書(shū)中提到,應(yīng)多使用組合模式而非繼承。
這么做有許多優(yōu)點(diǎn),在想要使用繼承前,多想想能否通過(guò)組合模式滿(mǎn)足需求吧。
那么,在什么時(shí)候繼承具有更大的優(yōu)勢(shì)呢?這取決于你的具體需求,但大多情況下,可以遵守以下三點(diǎn):
- 繼承關(guān)系表現(xiàn)為”是一個(gè)”而非”有一個(gè)”(如動(dòng)物->人 和 用戶(hù)->用戶(hù)細(xì)節(jié))
- 可以復(fù)用基類(lèi)的代碼(“Human”可以看成是”All animal”的一種)
- 希望當(dāng)基類(lèi)改變時(shí)所有派生類(lèi)都受到影響(如修改”all animals”移動(dòng)時(shí)的卡路里消耗量)
反例:
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;
}
// ...
}
測(cè)試
一些好的覆蓋工具.
一些好的JS測(cè)試框架
單一的測(cè)試每個(gè)概念
反例:
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);
});
});
并發(fā)
用Promises替代回調(diào)
回調(diào)不夠整潔并會(huì)造成大量的嵌套。ES6內(nèi)嵌了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);
})
Async/Await是較Promises更好的選擇
Promises是較回調(diào)而言更好的一種選擇,但ES7中的async和await更勝過(guò)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);
}
}
錯(cuò)誤處理
錯(cuò)誤拋出是個(gè)好東西!這使得你能夠成功定位運(yùn)行狀態(tài)中的程序產(chǎn)生錯(cuò)誤的位置。
別忘了捕獲錯(cuò)誤
對(duì)捕獲的錯(cuò)誤不做任何處理是沒(méi)有意義的。
代碼中try/catch的意味著你認(rèn)為這里可能出現(xiàn)一些錯(cuò)誤,你應(yīng)該對(duì)這些可能的錯(cuò)誤存在相應(yīng)的處理方案。
反例:
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!
}
不要忽略被拒絕的promises
理由同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!
});
格式化
格式化是一件主觀的事。如同這里的許多規(guī)則一樣,這里并沒(méi)有一定/立刻需要遵守的規(guī)則??梢栽谶@里完成格式的自動(dòng)化。
大小寫(xiě)一致
JS是弱類(lèi)型語(yǔ)言,合理的采用大小寫(xiě)可以告訴你關(guān)于變量/函數(shù)等的許多消息。
這些規(guī)則是主觀定義的,團(tuán)隊(duì)可以根據(jù)喜歡進(jìn)行選擇。重點(diǎn)在于無(wú)論選擇何種風(fēng)格,都需要注意保持一致性。
反例:
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 {}
調(diào)用函數(shù)的函數(shù)和被調(diào)函數(shù)應(yīng)放在較近的位置
當(dāng)函數(shù)間存在相互調(diào)用的情況時(shí),應(yīng)將兩者置于較近的位置。
理想情況下,應(yīng)將調(diào)用其他函數(shù)的函數(shù)寫(xiě)在被調(diào)用函數(shù)的上方。
反例:
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();
注釋
只對(duì)存在一定業(yè)務(wù)邏輯復(fù)制性的代碼進(jìn)行注釋
注釋并不是必須的,好的代碼是能夠讓人一目了然,不用過(guò)多無(wú)謂的注釋。
反例:
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;
}
}
不要在代碼庫(kù)中遺留被注釋掉的代碼
版本控制的存在是有原因的。讓舊代碼存在于你的history里吧。
反例:
doStuff(); // doOtherStuff(); // doSomeMoreStuff(); // doSoMuchStuff();
正例:
doStuff();
不需要版本更新類(lèi)型注釋
記住,我們可以使用版本控制。廢代碼、被注釋的代碼及用注釋記錄代碼中的版本更新說(shuō)明都是沒(méi)有必要的。
需要時(shí)可以使用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;
}
避免位置標(biāo)記
這些東西通常只能代碼麻煩,采用適當(dāng)?shù)目s進(jìn)就可以了。
反例:
////////////////////////////////////////////////////////////////////////////////
// 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() {
// ...
}
避免在源文件中寫(xiě)入法律評(píng)論
將你的LICENSE文件置于源碼目錄樹(shù)的根目錄。
反例:
/* 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() {
// ...
} 文章標(biāo)題:JavaScript編程風(fēng)格指南
本文路徑:http://www.fisionsoft.com.cn/article/cdgiiho.html


咨詢(xún)
建站咨詢(xún)
