測(cè)試代碼質(zhì)量的唯一方式:別人看你代碼時(shí)說(shuō) f * k 的次數(shù)。
成都創(chuàng)新互聯(lián)公司是一家專注于成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作與策劃設(shè)計(jì),寧武網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)10余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:寧武等地區(qū)。寧武做網(wǎng)站價(jià)格咨詢:028-86922220
代碼質(zhì)量與其整潔度成正比。干凈的代碼,既在質(zhì)量上較為可靠,也為后期維護(hù)、升級(jí)奠定了良好基礎(chǔ)。
本文并不是代碼風(fēng)格指南,而是關(guān)于代碼的可讀性、復(fù)用性、擴(kuò)展性探討。
我們將從幾個(gè)方面展開(kāi)討論:
用有意義且常用的單詞命名變量
Bad:
const yyyymmdstr = moment().format('YYYY/MM/DD');
Good:
const currentDate = moment().format('YYYY/MM/DD');
保持統(tǒng)一
可能同一個(gè)項(xiàng)目對(duì)于獲取用戶信息,會(huì)有三個(gè)不一樣的命名。應(yīng)該保持統(tǒng)一,如果你不知道該如何取名,可以去 codelf 搜索,看別人是怎么取名的。
Bad:
getUserInfo(); getClientData(); getCustomerRecord();
Good:
getUser()
每個(gè)常量都該命名
可以用buddy.js 或者ESLint 檢測(cè)代碼中未命名的常量。
Bad:
// 三個(gè)月之后你還能知道 86400000 是什么嗎?
setTimeout(blastOff, 86400000);
Good:
const MILLISECOND_IN_A_DAY = 86400000;
setTimeout(blastOff, MILLISECOND_IN_A_DAY);
可描述
通過(guò)一個(gè)變量生成了一個(gè)新變量,也需要為這個(gè)新變量命名,也就是說(shuō)每個(gè)變量當(dāng)你看到他第一眼你就知道他是干什么的。
Bad:
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/;
saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1],
ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);
Good:
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/;
const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || [];
saveCityZipCode(city, zipCode);
直接了當(dāng)
Bad:
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// 需要看其他代碼才能確定 'l' 是干什么的。
dispatch(l);
});
Good:
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});
避免無(wú)意義的前綴
如果創(chuàng)建了一個(gè)對(duì)象 car,就沒(méi)有必要把它的顏色命名為 carColor。
Bad:
const car = {
carMake: 'Honda',
carModel: 'Accord',
carColor: 'Blue'
};
function paintCar(car) {
car.carColor = 'Red';
}
Good:
const car = {
make: 'Honda',
model: 'Accord',
color: 'Blue'
};
function paintCar(car) {
car.color = 'Red';
}
使用默認(rèn)值
Bad:
function createMicrobrewery(name) {
const breweryName = name || 'Hipster Brew Co.';
// ...
}
Good:
function createMicrobrewery(name = 'Hipster Brew Co.') {
// ...
}
函數(shù)
參數(shù)越少越好
如果參數(shù)超過(guò)兩個(gè),使用 ES2015/ES6 的解構(gòu)語(yǔ)法,不用考慮參數(shù)的順序。
Bad:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
Good:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});
只做一件事情
這是一條在軟件工程領(lǐng)域流傳久遠(yuǎn)的規(guī)則。嚴(yán)格遵守這條規(guī)則會(huì)讓你的代碼可讀性更好,也更容易重構(gòu)。如果違反這個(gè)規(guī)則,那么代碼會(huì)很難被測(cè)試或者重用。
Bad:
function emailClients(clients) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
Good:
function emailActiveClients(clients) {
clients
.filter(isActiveClient)
.forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
顧名思義
看函數(shù)名就應(yīng)該知道它是干啥的。
Bad:
function addToDate(date, month) {
// ...
}
const date = new Date();
// 很難知道是把什么加到日期中
addToDate(date, 1);
Good:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);
只需要一層抽象層
如果函數(shù)嵌套過(guò)多會(huì)導(dǎo)致很難復(fù)用以及測(cè)試。
Bad:
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
});
});
const ast = [];
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
});
}
Good:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const ast = lexer(tokens);
ast.forEach((node) => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
tokens.push( /* ... */ );
});
});
return tokens;
}
function lexer(tokens) {
const ast = [];
tokens.forEach((token) => {
ast.push( /* ... */ );
});
return ast;
}
刪除重復(fù)代碼
很多時(shí)候雖然是同一個(gè)功能,但由于一兩個(gè)不同點(diǎn),讓你不得不寫兩個(gè)幾乎相同的函數(shù)。
要想優(yōu)化重復(fù)代碼需要有較強(qiáng)的抽象能力,錯(cuò)誤的抽象還不如重復(fù)代碼。所以在抽象過(guò)程中必須要遵循 SOLID 原則(SOLID 是什么?稍后會(huì)詳細(xì)介紹)。
Bad:
function showDeveloperList(developers) {
developers.forEach((developer) => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach((manager) => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}
Good:
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience,
};
switch(employee.type) {
case 'develop':
data.githubLink = employee.getGithubLink();
break
case 'manager':
data.portfolio = employee.getMBAProjects();
break
}
render(data);
})
}
對(duì)象設(shè)置默認(rèn)屬性
Bad:
const 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);
Good:
const menuConfig = {
title: 'Order',
// 'body' key 缺失
buttonText: 'Send',
cancellable: true
};
function createMenu(config) {
config = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
// config 就變成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);
不要傳 flag 參數(shù)
通過(guò) flag 的 true 或 false,來(lái)判斷執(zhí)行邏輯,違反了一個(gè)函數(shù)干一件事的原則。
Bad:
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
Good:
function createFile(name) {
fs.create(name);
}
function createFileTemplate(name) {
createFile(`./temp/${name}`)
}
避免副作用(第一部分)
函數(shù)接收一個(gè)值返回一個(gè)新值,除此之外的行為我們都稱之為副作用,比如修改全局變量、對(duì)文件進(jìn)行 IO 操作等。
當(dāng)函數(shù)確實(shí)需要副作用時(shí),比如對(duì)文件進(jìn)行 IO 操作時(shí),請(qǐng)不要用多個(gè)函數(shù)/類進(jìn)行文件操作,有且僅用一個(gè)函數(shù)/類來(lái)處理。也就是說(shuō)副作用需要在唯一的地方處理。
副作用的三大天坑:隨意修改可變數(shù)據(jù)類型、隨意分享沒(méi)有數(shù)據(jù)結(jié)構(gòu)的狀態(tài)、沒(méi)有在統(tǒng)一地方處理副作用。
Bad:
// 全局變量被一個(gè)函數(shù)引用
// 現(xiàn)在這個(gè)變量從字符串變成了數(shù)組,如果有其他的函數(shù)引用,會(huì)發(fā)生無(wú)法預(yù)見(jiàn)的錯(cuò)誤。
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];
Good:
var name = 'Ryan McDermott';
var newName = splitIntoFirstAndLastName(name)
function splitIntoFirstAndLastName(name) {
return name.split(' ');
}
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
避免副作用(第二部分)
在 JavaScript 中,基本類型通過(guò)賦值傳遞,對(duì)象和數(shù)組通過(guò)引用傳遞。以引用傳遞為例:
假如我們寫一個(gè)購(gòu)物車,通過(guò) addItemToCart() 方法添加商品到購(gòu)物車,修改 購(gòu)物車數(shù)組。此時(shí)調(diào)用 purchase() 方法購(gòu)買,由于引用傳遞,獲取的 購(gòu)物車數(shù)組 正好是最新的數(shù)據(jù)。
看起來(lái)沒(méi)問(wèn)題對(duì)不對(duì)?
如果當(dāng)用戶點(diǎn)擊購(gòu)買時(shí),網(wǎng)絡(luò)出現(xiàn)故障, purchase() 方法一直在重復(fù)調(diào)用,與此同時(shí)用戶又添加了新的商品,這時(shí)網(wǎng)絡(luò)又恢復(fù)了。那么 purchase() 方法獲取到 購(gòu)物車數(shù)組 就是錯(cuò)誤的。
為了避免這種問(wèn)題,我們需要在每次新增商品時(shí),克隆 購(gòu)物車數(shù)組 并返回新的數(shù)組。
Bad:
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};
Good:
const addItemToCart = (cart, item) => {
return [...cart, {item, date: Date.now()}]
};
不要寫全局方法
在 JavaScript 中,永遠(yuǎn)不要污染全局,會(huì)在生產(chǎn)環(huán)境中產(chǎn)生難以預(yù)料的 bug。舉個(gè)例子,比如你在 Array.prototype 上新增一個(gè) diff 方法來(lái)判斷兩個(gè)數(shù)組的不同。而你同事也打算做類似的事情,不過(guò)他的 diff 方法是用來(lái)判斷兩個(gè)數(shù)組首位元素的不同。很明顯你們方法會(huì)產(chǎn)生沖突,遇到這類問(wèn)題我們可以用 ES2015/ES6 的語(yǔ)法來(lái)對(duì) Array 進(jìn)行擴(kuò)展。
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));
}
}
比起命令式我更喜歡函數(shù)式編程
函數(shù)式變編程可以讓代碼的邏輯更清晰更優(yōu)雅,方便測(cè)試。
Bad:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}
Good:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
let totalOutput = programmerOutput
.map(output => output.linesOfCode)
.reduce((totalLines, lines) => totalLines + lines, 0)
封裝條件語(yǔ)句
Bad:
if (fsm.state === 'fetching' && isEmpty(listNode)) {
// ...
}
Good:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
盡量別用“非”條件句
Bad:
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}
Good:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
避免使用條件語(yǔ)句
Q:不用條件語(yǔ)句寫代碼是不可能的。
A:絕大多數(shù)場(chǎng)景可以用多態(tài)替代。
Q:用多態(tài)可行,但為什么就不能用條件語(yǔ)句了呢?
A:為了讓代碼更簡(jiǎn)潔易讀,如果你的函數(shù)中出現(xiàn)了條件判斷,那么說(shuō)明你的函數(shù)不止干了一件事情,違反了函數(shù)單一原則。
Bad:
class Airplane {
// ...
// 獲取巡航高度
getCruisingAltitude() {
switch (this.type) {
case '777':
return this.getMaxAltitude() - this.getPassengerCount();
case 'Air Force One':
return this.getMaxAltitude();
case 'Cessna':
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}
Good:
class Airplane {
// ...
}
// 波音777
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
// 空軍一號(hào)
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
// 賽納斯飛機(jī)
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
避免類型檢查(第一部分)
JavaScript 是無(wú)類型的,意味著你可以傳任意類型參數(shù),這種自由度很容易讓人困擾,不自覺(jué)的就會(huì)去檢查類型。仔細(xì)想想是你真的需要檢查類型還是你的 API 設(shè)計(jì)有問(wèn)題?
Bad:
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location('texas'));
}
}
Good:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}
避免類型檢查(第二部分)
如果你需要做靜態(tài)類型檢查,比如字符串、整數(shù)等,推薦使用 TypeScript,不然你的代碼會(huì)變得又臭又長(zhǎng)。
Bad:
function combine(val1, val2) {
if (typeof val1 === 'number' && typeof val2 === 'number' ||
typeof val1 === 'string' && typeof val2 === 'string') {
return val1 + val2;
}
throw new Error('Must be of type String or Number');
}
Good:
function combine(val1, val2) {
return val1 + val2;
}
不要過(guò)度優(yōu)化
現(xiàn)代瀏覽器已經(jīng)在底層做了很多優(yōu)化,過(guò)去的很多優(yōu)化方案都是無(wú)效的,會(huì)浪費(fèi)你的時(shí)間,想知道現(xiàn)代瀏覽器優(yōu)化了哪些內(nèi)容,請(qǐng)點(diǎn)這里。
Bad:
// 在老的瀏覽器中,由于 `list.length` 沒(méi)有做緩存,每次迭代都會(huì)去計(jì)算,造成不必要開(kāi)銷。
// 現(xiàn)代瀏覽器已對(duì)此做了優(yōu)化。
for (let i = 0, len = list.length; i < len; i++) {
// ...
}
Good:
for (let i = 0; i < list.length; i++) {
// ...
}
刪除棄用代碼
很多時(shí)候有些代碼已經(jīng)沒(méi)有用了,但擔(dān)心以后會(huì)用,舍不得刪。
如果你忘了這件事,這些代碼就永遠(yuǎn)存在那里了。
放心刪吧,你可以在代碼庫(kù)歷史版本中找他它。
Bad:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Good:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
用 get、set 方法操作數(shù)據(jù)
這樣做可以帶來(lái)很多好處,比如在操作數(shù)據(jù)時(shí)打日志,方便跟蹤錯(cuò)誤;在 set 的時(shí)候很容易對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)…
Bad:
function makeBankAccount() {
// ...
return {
balance: 0,
// ...
};
}
const account = makeBankAccount();
account.balance = 100;
Good:
function makeBankAccount() {
// 私有變量
let balance = 0;
function getBalance() {
return balance;
}
function setBalance(amount) {
// ... 在更新 balance 前,對(duì) amount 進(jìn)行校驗(yàn)
balance = amount;
}
return {
// ...
getBalance,
setBalance,
};
}
const account = makeBankAccount();
account.setBalance(100);
使用私有變量
可以用閉包來(lái)創(chuàng)建私有變量
Bad:
const Employee = function(name) {
this.name = name;
};
Employee.prototype.getName = function getName() {
return this.name;
};
const 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
Good:
function makeEmployee(name) {
return {
getName() {
return name;
},
};
}
const employee = makeEmployee('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
在 ES2015/ES6 之前,沒(méi)有類的語(yǔ)法,只能用構(gòu)造函數(shù)的方式模擬類,可讀性非常差。
Bad:
// 動(dòng)物
const Animal = function(age) {
if (!(this instanceof Animal)) {
throw new Error('Instantiate Animal with `new`');
}
this.age = age;
};
Animal.prototype.move = function move() {};
// 哺乳動(dòng)物
const 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 liveBirth() {};
// 人類
const 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 speak() {};
Good:
// 動(dòng)物
class Animal {
constructor(age) {
this.age = age
};
move() {};
}
// 哺乳動(dòng)物
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ǔn)秸{(diào)用
這種模式相當(dāng)有用,可以在很多庫(kù)中發(fā)現(xiàn)它的身影,比如 jQuery、Lodash 等。它讓你的代碼簡(jiǎn)潔優(yōu)雅。實(shí)現(xiàn)起來(lái)也非常簡(jiǎn)單,在類的方法最后返回 this 可以了。
Bad:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
}
setModel(model) {
this.model = model;
}
setColor(color) {
this.color = color;
}
save() {
console.log(this.make, this.model, this.color);
}
}
const car = new Car('Ford','F-150','red');
car.setColor('pink');
car.save();
Good:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
return this;
}
setModel(model) {
this.model = model;
return this;
}
setColor(color) {
this.color = color;
return this;
}
save() {
console.log(this.make, this.model, this.color);
return this;
}
}
const car = new Car('Ford','F-150','red')
.setColor('pink');
.save();
不要濫用繼承
很多時(shí)候繼承被濫用,導(dǎo)致可讀性很差,要搞清楚兩個(gè)類之間的關(guān)系,繼承表達(dá)的一個(gè)屬于關(guān)系,而不是包含關(guān)系,比如 Human->Animal vs. User->UserDetails
Bad:
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
// ...
}
// TaxData(稅收信息)并不是屬于 Employee(雇員),而是包含關(guān)系。
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super();
this.ssn = ssn;
this.salary = salary;
}
// ...
}
Good:
class EmployeeTaxData {
constructor(ssn, salary) {
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);
}
// ...
}
SOLID 是幾個(gè)單詞首字母組合而來(lái),分別表示 單一功能原則
、開(kāi)閉原則
、里氏替換原則
、接口隔離原則
以及依賴反轉(zhuǎn)原則
。
單一功能原則
如果一個(gè)類干的事情太多太雜,會(huì)導(dǎo)致后期很難維護(hù)。我們應(yīng)該厘清職責(zé),各司其職減少相互之間依賴。
Bad:
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}
Good:
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
// ...
}
}
class UserSetting {
constructor(user) {
this.user = user;
this.auth = new UserAuth(this.user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...
}
}
}
}
開(kāi)閉原則
“開(kāi)”指的就是類、模塊、函數(shù)都應(yīng)該具有可擴(kuò)展性,“閉”指的是它們不應(yīng)該被修改。也就是說(shuō)你可以新增功能但不能去修改源碼。
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) => {
// 傳遞 response 并 return
});
} else if (this.adapter.name === 'httpNodeAdapter') {
return makeHttpCall(url).then((response) => {
// 傳遞 response 并 return
});
}
}
}
function makeAjaxCall(url) {
// 處理 request 并 return promise
}
function makeHttpCall(url) {
// 處理 request 并 return promise
}
Good:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = 'ajaxAdapter';
}
request(url) {
// 處理 request 并 return promise
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = 'nodeAdapter';
}
request(url) {
// 處理 request 并 return promise
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then((response) => {
// 傳遞 response 并 return
});
}
}
里氏替換原則
名字很唬人,其實(shí)道理很簡(jiǎn)單,就是子類不要去重寫父類的方法。
Bad:
// 長(zhǎng)方形
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();
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 幾乎沒(méi)有接口的概念,所以這條原則很少被使用。官方定義是“客戶端不應(yīng)該依賴它不需要的接口”,也就是接口最小化,把接口解耦。
Bad:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.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() {}
}
});
依賴反轉(zhuǎn)原則
說(shuō)就兩點(diǎn):
總結(jié)下來(lái)就兩個(gè)字,解耦。
Bad:
// 庫(kù)存查詢
class InventoryRequester {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
// 庫(kù)存跟蹤
class InventoryTracker {
constructor(items) {
this.items = items;
// 這里依賴一個(gè)特殊的請(qǐng)求類,其實(shí)我們只是需要一個(gè)請(qǐng)求方法。
this.requester = new InventoryRequester();
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();
Good:
// 庫(kù)存跟蹤
class InventoryTracker {
constructor(items, requester) {
this.items = items;
this.requester = requester;
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
// HTTP 請(qǐng)求
class InventoryRequesterHTTP {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
// webSocket 請(qǐng)求
class InventoryRequesterWS {
constructor() {
this.REQ_METHODS = ['WS'];
}
requestItem(item) {
// ...
}
}
// 通過(guò)依賴注入的方式將請(qǐng)求模塊解耦,這樣我們就可以很輕易的替換成 webSocket 請(qǐng)求。
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterHTTP());
inventoryTracker.requestItems();
隨著項(xiàng)目變得越來(lái)越龐大,時(shí)間線拉長(zhǎng),有的老代碼可能半年都沒(méi)碰過(guò),如果此時(shí)上線,你有信心這部分代碼能正常工作嗎?測(cè)試的覆蓋率和你的信心是成正比的。
PS: 如果你發(fā)現(xiàn)你的代碼很難被測(cè)試,那么你應(yīng)該優(yōu)化你的代碼了。
單一化
Bad:
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles date boundaries', () => {
let date;
date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
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);
});
});
Good:
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles 30-day months', () => {
const date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
});
it('handles leap year', () => {
const date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
});
it('handles non-leap year', () => {
const date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});
不再使用回調(diào)
不會(huì)有人愿意去看嵌套回調(diào)的代碼,用 Promises 替代回調(diào)吧。
Bad:
import { get } from 'request';
import { writeFile } from 'fs';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {
if (requestErr) {
console.error(requestErr);
} else {
writeFile('article.html', response.body, (writeErr) => {
if (writeErr) {
console.error(writeErr);
} else {
console.log('File written');
}
});
}
});
Good:
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) => {
return writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});
Async/Await 比起 Promises 更簡(jiǎn)潔
Bad:
import { get } from 'request-promise';
import { writeFile } from 'fs-promise';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) => {
return writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});
Good:
import { get } from 'request-promise';
import { writeFile } from 'fs-promise';
async function getCleanCodeArticle() {
try {
const response = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
await writeFile('article.html', response);
console.log('File written');
} catch(err) {
console.error(err);
}
}
不要忽略拋異常
Bad:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Good:
try {
functionThatMightThrow();
} catch (error) {
// 這一種選擇,比起 console.log 更直觀
console.error(error);
// 也可以在界面上提醒用戶
notifyUserOfError(error);
// 也可以把異常傳回服務(wù)器
reportErrorToService(error);
// 其他的自定義方法
}
不要忘了在 Promises 拋異常
Bad:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
console.log(error);
});
Good:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
// 這一種選擇,比起 console.log 更直觀
console.error(error);
// 也可以在界面上提醒用戶
notifyUserOfError(error);
// 也可以把異常傳回服務(wù)器
reportErrorToService(error);
// 其他的自定義方法
});
代碼風(fēng)格是主觀的,爭(zhēng)論哪種好哪種不好是在浪費(fèi)生命。市面上有很多自動(dòng)處理代碼風(fēng)格的工具,選一個(gè)喜歡就行了,我們來(lái)討論幾個(gè)非自動(dòng)處理的部分。
常量大寫
Bad:
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}
Good:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}
先聲明后調(diào)用
就像我們看報(bào)紙文章一樣,從上到下看,所以為了方便閱讀把函數(shù)聲明寫在函數(shù)調(diào)用前面。
Bad:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getManagerReview() {
const manager = this.lookupManager();
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();
Good:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
getManagerReview() {
const manager = this.lookupManager();
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();
只有業(yè)務(wù)邏輯需要注釋
代碼注釋不是越多越好。
Bad:
function hashIt(data) {
// 這是初始值
let hash = 0;
// 數(shù)組的長(zhǎng)度
const length = data.length;
// 循環(huán)數(shù)組
for (let i = 0; i < length; i++) {
// 獲取字符代碼
const char = data.charCodeAt(i);
// 修改 hash
hash = ((hash << 5) - hash) + char;
// 轉(zhuǎn)換為32位整數(shù)
hash &= hash;
}
}
Good:
function hashIt(data) {
let hash = 0;
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
// 轉(zhuǎn)換為32位整數(shù)
hash &= hash;
}
}
刪掉注釋的代碼
git 存在的意義就是保存你的舊代碼,所以注釋的代碼趕緊刪掉吧。
Bad:
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Good:
doStuff();
不要記日記
記住你有 git!,git log 可以幫你干這事。
Bad:
/**
* 2016-12-20: 刪除了 xxx
* 2016-10-01: 改進(jìn)了 xxx
* 2016-02-03: 刪除了第12行的類型檢查
* 2015-03-14: 增加了一個(gè)合并的方法
*/
function combine(a, b) {
return a + b;
}
Good:
function combine(a, b) {
return a + b;
}
注釋不需要高亮
注釋高亮,并不能起到提示的作用,反而會(huì)干擾你閱讀代碼。
Bad:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
menu: 'foo',
nav: 'bar'
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
// ...
};
Good:
$scope.model = {
menu: 'foo',
nav: 'bar'
};
const actions = function() {
// ...
};
翻譯自 ryanmcdermott 的 《clean-code-javascript》,本文對(duì)原文進(jìn)行了一些修改。
當(dāng)前名稱:淺談JavaScript代碼簡(jiǎn)潔之道
當(dāng)前URL:http://www.rwnh.cn/article28/igpojp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、網(wǎng)站策劃、ChatGPT、微信公眾號(hào)、企業(yè)建站、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)