摘要:概述在代碼整潔之道中提到的軟件工程原則,同樣適用于。我們?cè)谲浖こ谭矫娴募夹g(shù)發(fā)展剛剛超過(guò)年,我們?nèi)匀辉趯W(xué)習(xí)很多東西。不好好一個(gè)函數(shù)只做一件事目前這是軟件工程中最重要的原則。
概述
Robert C. Martin 在《代碼整潔之道》 中提到的軟件工程原則,同樣適用于 JavaScript。這不是一個(gè)風(fēng)格參考。它指導(dǎo)如何用 JavaScript 編寫可讀、可復(fù)用、可重構(gòu)的軟件。
并不是每一個(gè)原則都必須嚴(yán)格遵循,甚至很少得到大家的認(rèn)同。它們僅用于參考,不過(guò)要知道這些原則都是_《代碼整潔之道》_的作者們累積多年的集體經(jīng)驗(yàn)。
我們?cè)谲浖こ谭矫娴募夹g(shù)發(fā)展剛剛超過(guò) 50 年,我們?nèi)匀辉趯W(xué)習(xí)很多東西。當(dāng)軟件架構(gòu)和架構(gòu)本身一樣古老的時(shí)候,我們應(yīng)該遵循更為嚴(yán)格規(guī)則?,F(xiàn)在,對(duì)于你和你的團(tuán)隊(duì)編寫的 JavaScript 代碼,不妨依據(jù)這些準(zhǔn)則來(lái)進(jìn)行質(zhì)量評(píng)估。
還有一件事:知道這些不會(huì)馬上讓你成為更好的軟件開(kāi)發(fā)者,在工作中常年使用這些準(zhǔn)則不能讓你避免錯(cuò)誤。每一段代碼都從最初的草圖開(kāi)始到最終成型,就像為濕粘土塑形一樣。最后,當(dāng)我們與同行一起審查的時(shí)候,再把不完美的地方消除掉。不要因?yàn)槌醺逍枰纳贫穸ㄗ约?,需要要否定的只是那些代碼!
變量 使用有準(zhǔn)確意義的變量名不好:
var yyyymmdstr = moment().format("YYYY/MM/DD");
好:
var yearMonthDay = moment().format("YYYY/MM/DD");在變量的值不會(huì)改變時(shí)使用 ES6 的常量
在不好的示例中,變量可以被改變。如果你申明一個(gè)常量,它會(huì)在整個(gè)程序中始終保持不變。
不好:
var FIRST_US_PRESIDENT = "George Washington";
好:
const FIRST_US_PRESIDENT = "George Washington";對(duì)同一類型的變量使用相同的詞匯
不好:
getUserInfo(); getClientData(); getCustomerRecord();
好:
getUser();使用可檢索的名稱
我們閱讀的代碼永遠(yuǎn)比寫的折。寫可讀性強(qiáng)、易于檢索的的代碼非常重要。在程序中使用_無(wú)_明確意義的變量名會(huì)難以理解,對(duì)讀者造成傷害。所以,把名稱定義成可檢索的。
不好:
// 見(jiàn)鬼,525600 是個(gè)啥? for (var i = 0; i < 525600; i++) { runCronJob(); }
好:
// 用 `var` 申明為大寫的全局變量 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);避免暗示
顯式優(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); });不要添加沒(méi)必要的上下文
如果你的類名稱/對(duì)象名稱已經(jīng)說(shuō)明了它們是什么,不要在(屬性)變量名里重復(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"; }短路語(yǔ)法比條件語(yǔ)句更清晰
不好:
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ù) (理論上少于等于2個(gè))
限制函數(shù)參數(shù)的數(shù)量極為重要,它會(huì)讓你更容易測(cè)試函數(shù)。超過(guò)3個(gè)參數(shù)會(huì)導(dǎo)致組合膨脹,以致于你必須根據(jù)不同的參數(shù)對(duì)大量不同的情況進(jìn)行測(cè)試。
理想情況下是沒(méi)有參數(shù)。有一個(gè)或者兩個(gè)參數(shù)也還好,三個(gè)就應(yīng)該避免了。多于那個(gè)數(shù)量就應(yīng)該考慮合并。通常情況下,如果你有多于2個(gè)參數(shù),你的函數(shù)會(huì)嘗試做太多事情。如果不是這樣,大多數(shù)時(shí)候可以使用一個(gè)高階對(duì)象作為參數(shù)使用。
既然 JavaScript 允許我們?cè)谶\(yùn)行時(shí)隨意創(chuàng)建對(duì)象,而不需要預(yù)先定義樣板,那么你在需要很多參數(shù)的時(shí)候就可以使用一個(gè)對(duì)象來(lái)處理。
不好:
function createMenu(title, body, buttonText, cancellable) { ... }
好:
var menuConfig = { title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true } function createMenu(menuConfig) { ... }一個(gè)函數(shù)只做一件事
目前這是軟件工程中最重要的原則。如果函數(shù)做了較多的事情,它就難以組合、測(cè)試和推測(cè)。當(dāng)你讓函數(shù)只做一件事情的時(shí)候,它們就很容易重構(gòu),而且代碼讀起來(lái)也會(huì)清晰得多。你只需要遵循本指南的這一條,就能領(lǐng)先于其他很多開(kāi)發(fā)者。
不好:
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ù)名稱要說(shuō)明它做的事
不好:
function dateAdd(date, month) { // ... } let date = new Date(); // 很難從函數(shù)名了解到加了什么 dateAdd(date, 1);
好:
function dateAddMonth(date, month) { // ... } let date = new Date(); dateAddMonth(date, 1);函數(shù)應(yīng)該只抽象一個(gè)層次
如果你有多個(gè)層次的抽象,那么你的函數(shù)通常做了太多事情,此時(shí)應(yīng)該拆分函數(shù)使其易于復(fù)用和易于測(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ù)代碼
任何情況下,都不要有重復(fù)的代碼。沒(méi)有任何原因,它很可能是阻礙你成為專業(yè)開(kāi)發(fā)者的最糟糕的一件事。重復(fù)代碼意味著你要修改某些邏輯的時(shí)候要修改不止一個(gè)地方的代碼。JavaScript 是弱類型語(yǔ)句,所以它很容易寫通用性強(qiáng)的函數(shù)。記得利用這一點(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ù)代替短路表達(dá)式
不好:
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); // 現(xiàn)在 config 等于: {title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true} // ... } createMenu(menuConfig);不要把標(biāo)記用作函數(shù)參數(shù)
標(biāo)記告訴你的用戶這個(gè)函數(shù)做的事情不止一件。但是函數(shù)應(yīng)該只做一件事。如果你的函數(shù)中會(huì)根據(jù)某個(gè)布爾參數(shù)產(chǎn)生不同的分支,那就拆分這個(gè)函數(shù)。
不好:
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); }避免副作用
如果一個(gè)函數(shù)不是獲取一個(gè)輸入的值并返回其它值,它就有可能產(chǎn)生副作用。這些副作用可能是寫入文件、修改一些全局變量,或者意外地把你所有錢轉(zhuǎn)給一個(gè)陌生人。
現(xiàn)在你確實(shí)需要在程序中有副作用。像前面提到的那樣,你可能需要寫入文件?,F(xiàn)在你需要做的事情是搞清楚在哪里集中完成這件事情。不要使用幾個(gè)函數(shù)或類來(lái)完成寫入某個(gè)特定文件的工作。采用一個(gè),就一個(gè)服務(wù)來(lái)完成。
關(guān)鍵點(diǎn)是避免覺(jué)的陷阱,比如在沒(méi)有結(jié)構(gòu)的對(duì)象間共享狀態(tài),使用可以被任意修改的易變的數(shù)據(jù)類型,沒(méi)有集中處理發(fā)生的副作用等。如果你能做到,你就能比其他大多數(shù)程序員更愉快。
不好:
// 下面的函數(shù)使用了全局變量。 // 如果有另一個(gè)函數(shù)在使用 name,現(xiàn)在可能會(huì)因?yàn)?name 變成了數(shù)組而不能正常運(yùn)行。 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"];不要寫入全局函數(shù)
JavaScript 中全局污染是一件糟糕的事情,因?yàn)樗赡芎土硗鈳?kù)發(fā)生沖突,然而使用你 API 的用戶卻不會(huì)知道——直到他們?cè)谏a(chǎn)中遇到一個(gè)異常。來(lái)思考一個(gè)例子:你想擴(kuò)展 JavaScript 的原生 Array,使之擁有一個(gè) diff 方法,用來(lái)展示兩數(shù)據(jù)之前的區(qū)別,這時(shí)你會(huì)怎么做?你可以給 Array.prototype 添加一個(gè)新的函數(shù),但它可能會(huì)與其它想做同樣事情的庫(kù)發(fā)生沖突。如果那個(gè)庫(kù)實(shí)現(xiàn)的 diff 只是比如數(shù)組中第一個(gè)元素和最后一個(gè)元素的異同會(huì)發(fā)生什么事情呢?這就是為什么最好是使用 ES6 的類語(yǔ)法從全局的 Array 派生一個(gè)類來(lái)做這件事。
不好:
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ù)式編程
如果 Haskell 是 IPA 那么 JavaScript 就是 O"Douls。就是說(shuō),與 Haskell 不同,JavaScript 不是函數(shù)式編程語(yǔ)言,不過(guò)它仍然有一點(diǎn)函數(shù)式的意味。函數(shù)式語(yǔ)言更整潔也更容易測(cè)試,所以你最好能喜歡上這種編程風(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)) { // ... }避免條件
這似乎是個(gè)不可能完成的任務(wù)。大多數(shù)人第一次聽(tīng)到這個(gè)的時(shí)候會(huì)說(shuō),“沒(méi)有 if 語(yǔ)句我該怎么辦?”回答是在多數(shù)情況下都可以使用多態(tài)來(lái)實(shí)現(xiàn)相同的任務(wù)。第二個(gè)問(wèn)題通常是,“那太好了,不過(guò)我為什么要這么做呢?”答案在于我們之前了解過(guò)整潔的概念:一個(gè)函數(shù)應(yīng)該只做一件事情。如果你的類和函數(shù)有 if 語(yǔ)句,就意味著你的函數(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(); } }避免類型檢查(第1部分)
JavaScript 是無(wú)類型的,也就是說(shuō)函數(shù)可以獲取任意類型的參數(shù)。有時(shí)候你會(huì)覺(jué)得這種自由是種折磨,因而會(huì)不由自主地在函數(shù)中使用類型檢查。有很多種方法可以避免類型檢查。首先要考慮的就是 API 的一致性。
不好:
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")); }避免類型檢查(第2部分)
如果你在處理基本類型的數(shù)據(jù),比如字符串,整數(shù)和數(shù)組,又不能使用多態(tài),這時(shí)你會(huì)覺(jué)得需要使用類型檢查,那么可以考慮 TypeScript。這是普通 JavaScript 的完美替代品,它在標(biāo)準(zhǔn)的 JavaScript 語(yǔ)法之上提供了靜態(tài)類型。普通 JavaScript 手工檢查類型的問(wèn)題在于這樣會(huì)寫很多廢話,而人為的“類型安全”并不能彌補(bǔ)損失的可讀性。讓你的 JavaScript 保持整潔,寫很好的測(cè)試,并保持良好的代碼審查。否則讓 TypeScript (我說(shuō)過(guò),這是很好的替代品)來(lái)做所有事情。
不好:
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í)悄悄地做了很多優(yōu)化工作。很多時(shí)候你的優(yōu)化都是在浪費(fèi)時(shí)間。這里有很好的資源 可以看看哪些優(yōu)化比較缺乏。把它們作為目標(biāo),直到他們能固定下來(lái)的時(shí)候。
不好:
// 在舊瀏覽器中,每次循環(huán)的成本都比較高,因?yàn)槊看味紩?huì)重算 `len`。 // 現(xiàn)在瀏覽器中,這已經(jīng)被優(yōu)化了。 for (var i = 0, len = list.length; i < len; i++) { // ... }
好:
for (var i = 0; i < list.length; i++) { // ... }刪除不用的代碼
不用的代碼和重復(fù)的代碼一樣糟糕。在代碼庫(kù)中保留無(wú)用的代碼是毫無(wú)道理的事情。如果某段代碼用不到,那就刪掉它!如果你以后需要它,仍然可以從代碼庫(kù)的歷史版本中找出來(lá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");對(duì)象和數(shù)據(jù)結(jié)構(gòu) 使用 getter 和 setter
JavaScript 沒(méi)有接口或者類型,也沒(méi)有像 public 和 private 這樣的關(guān)鍵字,所以很難應(yīng)用設(shè)計(jì)模式。實(shí)事上,在對(duì)象上使用 getter 和 setter 訪問(wèn)數(shù)據(jù)遠(yuǎn)好于直接查找對(duì)象屬性?!盀槭裁??”你可能會(huì)這樣問(wèn)。那好,下面列出了原因:
你想在獲取對(duì)象屬性的時(shí)候做更多的事,不必在代碼中尋找所有訪問(wèn)的代碼來(lái)逐個(gè)修改。
在進(jìn)行 set 的時(shí)候可以進(jìn)行額外的數(shù)據(jù)檢驗(yàn)。
封裝內(nèi)部表現(xiàn)。
在獲取或設(shè)置的時(shí)候易于添加日志和錯(cuò)誤處理。
繼承當(dāng)前類,可以重寫默認(rèn)功能。
可以對(duì)對(duì)象屬性進(jìn)行懶加載,比如說(shuō)從服務(wù)器獲取屬性的數(shù)據(jù)。
不好:
class BankAccount { constructor() { this.balance = 1000; } } let bankAccount = new BankAccount(); // 買鞋... 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(); // 買鞋... bankAccount.withdraw(100);讓對(duì)象擁有私有成員
這可以通過(guò)閉包實(shí)現(xiàn)(ES5以之前的版本)。
不好:
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類 單一職責(zé)原則 (SRP)
正如《代碼整潔之道》所說(shuō),“不應(yīng)該有超過(guò)一個(gè)原因來(lái)改變類”。往一個(gè)類里塞進(jìn)許多功能是件誘人的事情,就像在坐飛機(jī)的時(shí)候只帶一個(gè)手提箱一樣。這帶來(lái)的問(wèn)題是,你的類不會(huì)在概念上有凝聚力,會(huì)有很多因素造成對(duì)它的改變。讓你的類需要改變的次數(shù)最少是件非常重要的事情。這是因?yàn)槿绻粋€(gè)類里塞入了太多功能,你只修改它的一部分,可能會(huì)讓人難以理解它為何會(huì)影響代碼庫(kù)中其它相關(guān)模塊。
不好:
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)
正如 Bertrand Meyer 所說(shuō),“軟件實(shí)體(類、模塊、函數(shù)等)應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉。”這是什么意思呢?這個(gè)原則基本上規(guī)定了你應(yīng)該允許用戶擴(kuò)展你的模塊,但不需要打開(kāi) .js 源代碼文件來(lái)進(jìn)行編輯。
不好:
class AjaxRequester { constructor() { // 如果我們需要另一個(gè) HTTP 方法,比如 DELETE,該怎么辦? // 我們必須打開(kāi)這個(gè)文件然后手工把它加進(jìn)去 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)
這是一個(gè)嚇人的術(shù)語(yǔ),但描述的卻是個(gè)簡(jiǎn)單的概念。它的正式定義為“如果 S 是 T 的子類,那所有 T 類型的對(duì)象都可以替換為 S 類型的對(duì)象(即 S 類型的對(duì)象可以替代 T 類型的對(duì)象),這個(gè)替換不會(huì)改變程序的任何性質(zhì)(正確性、任務(wù)執(zhí)行等)?!边@確實(shí)是個(gè)嚇人的定義。
對(duì)此最好的解釋是,如果你有父類和子類,那么父類和子類可以交替使用而不會(huì)造成不正確的結(jié)果。這可能仍然讓人感到疑惑,那么讓我們看看經(jīng)典的正方形和矩形的例子。在數(shù)學(xué)上,正方形也是矩形,但是如果你在模型中通過(guò)繼承使用 “is-a” 關(guān)系,你很快就會(huì)陷入困境。
不好:
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(); // 不好:這里對(duì)正方形會(huì)返回 25,但應(yīng)該是 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)
JavaScript 中沒(méi)有接口,所以實(shí)行這個(gè)原則不能像其它語(yǔ)言那樣嚴(yán)格。然而即使對(duì) JavaScript 的弱類型系統(tǒng)來(lái)說(shuō),它仍然是重要的相關(guān)。
ISP 指出,“客戶不應(yīng)該依賴于那些他們不使用的接口?!?由于 Duck Typing 理論,接口在 JavaScript 中是個(gè)隱性契約。
在 JavaScript 中有一個(gè)很好的例子來(lái)演示這個(gè)原則,即一個(gè)擁有巨大設(shè)置對(duì)象的類。比較好的做法是不要求客戶設(shè)置大量的選項(xiàng),因?yàn)槎鄶?shù)時(shí)候他們不需要所有設(shè)置。讓這些選項(xiàng)成為可選的有助于防止“胖接口”。
不好:
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() {} // 多數(shù)時(shí)候我們不需要?jiǎng)赢?huà) // ... });
好:
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() {} } });依賴倒置原則(DIP)
這個(gè)原則說(shuō)明了兩個(gè)基本問(wèn)題:
1. 上層模塊不應(yīng)該依賴下層模塊,兩者都應(yīng)該依賴抽象。
2. 抽象不應(yīng)該依賴于具體實(shí)現(xiàn),具體實(shí)現(xiàn)應(yīng)該依賴于抽象。
這一開(kāi)始可能很難理解,但是如果你使用 Angular.js,你已經(jīng)看到了對(duì)這個(gè)原則的一種實(shí)現(xiàn)形式:依賴注入(DI)。雖然它們不是完全相同的概念,DIP 阻止上層模塊去了解下層模塊的細(xì)節(jié)并設(shè)置它們。它可以通過(guò) DI 來(lái)實(shí)現(xiàn)。這帶來(lái)的巨大好處降低了模塊間的耦合。耦合是種非常不好的開(kāi)發(fā)模式,因?yàn)樗尨a難以重構(gòu)。
前提已經(jīng)提到,JavaScript 沒(méi)有接口,因此抽象依賴于隱性契約。也就是說(shuō),一個(gè)對(duì)象/類會(huì)把方法和屬性暴露給另一個(gè)對(duì)象/類。在下面的例子中,隱性契約是任何用于 InventoryTracker 的 Request 模塊都應(yīng)該擁有 requestItems 方法。
不好:
class InventoryTracker { constructor(items) { this.items = items; // 不好:我們創(chuàng)建了一個(gè)依賴于特定請(qǐng)求的實(shí)現(xiàn)。 // 我們應(yīng)該只依賴請(qǐng)求方法:`request` 的 requestItems 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) { // ... } } // 通過(guò)構(gòu)建外部依賴并注入它們,我們很容易把請(qǐng)求模塊替換成 // 一個(gè)使用 WebSocket 的新模塊。 let inventoryTracker = new InventoryTracker(["apples", "bananas"], new InventoryRequesterV2()); inventoryTracker.requestItems();多用 ES6 類語(yǔ)法,少用 ES5 構(gòu)造函數(shù)語(yǔ)法
在經(jīng)典的 ES5 的類定義中,很難找到易讀的繼承、構(gòu)造、方法定義等。如果你需要繼承(你會(huì)發(fā)現(xiàn)做不到),那就應(yīng)該使用類語(yǔ)法。不過(guò),應(yīng)該盡可能使用小函數(shù)而不是類,直到你需要更大更復(fù)雜的對(duì)象。
不好:
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() {} }使用方法鏈
在這里我的意見(jiàn)與《代碼整潔之道》的觀點(diǎn)不同。有人認(rèn)為方法鏈不整潔,而且違反了得墨忒耳定律。也許他們是對(duì)的,但這個(gè)模式在 JavaScript 中非常有用,你可以很多庫(kù)中看到,比如 jQuery 和 Lodash。它讓代碼變得既簡(jiǎn)潔又有表現(xiàn)力。在類中,只需要在每個(gè)函數(shù)結(jié)束前返回 this,就實(shí)現(xiàn)了鏈?zhǔn)秸{(diào)用的類方法。
不好:
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: 返回 this 以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用 return this; } setModel(model) { this.model = model; // NOTE: 返回 this 以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用 return this; } setColor(color) { this.color = color; // NOTE: 返回 this 以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用 return this; } save() { console.log(this.make, this.model, this.color); } } let car = new Car() .setColor("pink") .setMake("Ford") .setModel("F-150") .save();多用組合,少用繼承
大家都知道 GoF 的設(shè)計(jì)模式,其中提到應(yīng)該多用組合而不是繼承。對(duì)于繼承和組合,都有大量的理由在支撐,但這個(gè)準(zhǔn)則的要點(diǎn)在于,你的想法本能地會(huì)想到繼承,但這時(shí)候不防多思考一下用組合是否能更好的處理問(wèn)題——某些時(shí)候,的確能。
你可能會(huì)考慮:“我什么時(shí)候該用繼承?”這取決于你遇到的問(wèn)題。這里有一個(gè)不錯(cuò)的清單說(shuō)明了什么時(shí)候用繼承比用組合更合適:
你的繼承是一個(gè)“is-a”關(guān)系,而不是“has-a”關(guān)系(Animal->Human 對(duì)比 User->UserDetails)。
可以從基礎(chǔ)復(fù)用代碼 (人可以像所有動(dòng)物一樣移動(dòng))。
你想通過(guò)修改基礎(chǔ)來(lái)實(shí)現(xiàn)對(duì)所有子類的全局性更改。(改變動(dòng)物移動(dòng)時(shí)的熱量消耗)。
不好:
class Employee { constructor(name, email) { this.name = name; this.email = email; } // ... } // 這樣不好,因?yàn)?Employees "擁有" 稅務(wù)數(shù)據(jù)。EmployeeTaxData 不是屬于 Employee 的一個(gè)類型 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è)試
測(cè)試比生產(chǎn)更重要。如果你不進(jìn)行測(cè)試,或者測(cè)試的量不夠,那你就不能肯定你寫的代碼不會(huì)造成破壞。測(cè)試數(shù)量依靠你的開(kāi)發(fā)團(tuán)隊(duì)來(lái)決定,但 100% 覆蓋率(所有語(yǔ)句和分支)能讓你擁有巨大的信心,也能使程序員們安心。也就是說(shuō),你需要一個(gè)不錯(cuò)的測(cè)試框架,還需要一個(gè)好的覆蓋檢查工具.
沒(méi)有什么理由可以讓你不寫測(cè)試。這里有 大量不錯(cuò)的 JS 測(cè)試框架,可以去找個(gè)你們團(tuán)隊(duì)喜歡的來(lái)用。如果你找一個(gè)適合在你的團(tuán)隊(duì)中使用的工作,就把為每個(gè)新產(chǎn)生的特性/方法添加測(cè)試作為目標(biāo)。如果你喜歡測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)的方法,非常好,但要注意在讓你的測(cè)試覆蓋所有特性,或者重構(gòu)過(guò)的代碼。
每次測(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); }); });Concurrency 使用 Promise 而不是回調(diào)
回調(diào)并不整潔,它會(huì)導(dǎo)致過(guò)多的嵌套。ES6 的 Promise 是個(gè)內(nèi)置的全局類型。使用它!
不好:
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 比 Promise 還整潔
與回調(diào)相當(dāng),Promise 已經(jīng)相當(dāng)整潔了,但 ES7 帶來(lái)了更整潔的解決方案 —— async 和 await。你要做的事情就是在一個(gè)函數(shù)前加上 async 關(guān)鍵字,然后寫下命令形式的邏輯,而不再需要 then 鏈?,F(xiàn)在可以使用這個(gè) ES7 特性帶來(lái)的便利!
不好:
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ò)誤是件好事!這表示運(yùn)行時(shí)已經(jīng)成功檢測(cè)到程序出錯(cuò)了,它停止當(dāng)前調(diào)用框上的函數(shù)執(zhí)行,并中止進(jìn)程(在 Node 中),最后在控制臺(tái)通知你,并輸出棧跟蹤信息。
不要忽略捕捉到的錯(cuò)誤捕捉到錯(cuò)誤卻什么也不錯(cuò),你就失去了糾正錯(cuò)誤的機(jī)會(huì)。多數(shù)情況下把錯(cuò)誤記錄到控制臺(tái)(console.log)也不比忽略它好多少,因?yàn)樵谏倭康目刂婆_(tái)信息中很難發(fā)現(xiàn)這一條。如果嘗試在 try/catch 中封裝代碼,就意味著你知道這里可能發(fā)生錯(cuò),你應(yīng)該在錯(cuò)誤發(fā)生的時(shí)候有應(yīng)對(duì)的計(jì)劃、或者處理辦法。
不好:
try { functionThatMightThrow(); } catch (error) { console.log(error); }
好:
try { functionThatMightThrow(); } catch (error) { // 選擇之一(比 console.log 更鬧心): console.error(error); // 另一個(gè)選擇: notifyUserOfError(error); // 另一個(gè)選擇: reportErrorToService(error); // 或者所有上述三種選擇! }不要忽視被拒絕的Promise
這一條與不要忽略從 try/catch 捕捉到的錯(cuò)誤有相同的原因。
不好:
getdata() .then(data => { functionThatMightThrow(data); }) .catch(error => { console.log(error); });
好:
getdata() .then(data => { functionThatMightThrow(data); }) .catch(error => { // 選擇之一(比 console.log 更鬧心): console.error(error); // 另一個(gè)選擇: notifyUserOfError(error); // 另一個(gè)選擇: reportErrorToService(error); // 或者所有上述三種選擇! });格式
格式是個(gè)很主觀的東西,像這里提到的許多規(guī)則一,你不必完全遵循。要點(diǎn)不在于爭(zhēng)論格式。大量工具 可以自動(dòng)處理優(yōu)化格式。用一個(gè)!讓工程師爭(zhēng)論格式問(wèn)題簡(jiǎn)直就是在浪費(fèi)時(shí)間和金錢。
對(duì)于那些不能自動(dòng)處理的格式(可以自動(dòng)處理的包括縮進(jìn)、Tab或空格、雙引號(hào)或單引用等),就看看這里的指導(dǎo)。
使用一致的大小寫JavaScript 是無(wú)類型的,所以大小寫可以幫助你了解變量、函數(shù)等。這些規(guī)則具有較強(qiáng)的主觀性,所以你的團(tuán)隊(duì)?wèi)?yīng)該選擇需要的。重點(diǎn)不在于你選擇了什么,而在于要始終保持一致。
不好:
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 {}函數(shù)調(diào)用者和被調(diào)用者應(yīng)該盡可能放在一起
如果一個(gè)函數(shù)調(diào)用另一個(gè)函數(shù),那應(yīng)該讓他們?cè)谠次募械奈恢梅浅=咏?。理想情況下應(yīng)該把調(diào)用者放在被調(diào)用者的正上方,這會(huì)讓你的代碼更易讀,因?yàn)槲覀兌剂?xí)慣從上往下讀代碼,就像讀報(bào)紙那樣。
不好:
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();Comments 只注釋業(yè)務(wù)邏輯復(fù)雜的內(nèi)容
注釋是用來(lái)解釋代碼的,而不是必須的。好的代碼應(yīng)該 _自注釋_。
不好:
function hashIt(data) { // Hash 碼 var hash = 0; // 字符串長(zhǎng)度 var length = data.length; // 遍歷數(shù)據(jù)中所有字符 for (var i = 0; i < length; i++) { // 獲取字符編碼 var char = data.charCodeAt(i); // 生成 Hash hash = ((hash << 5) - hash) + char; // 轉(zhuǎn)換為32位整數(shù) 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; // 轉(zhuǎn)換為32位整數(shù) hash = hash & hash; } }不要把注釋掉的代碼留在代碼庫(kù)中
版本控制存在的原因就是保存你的歷史代碼。
不好:
doStuff(); // doOtherStuff(); // doSomeMoreStuff(); // doSoMuchStuff();
好:
doStuff();不需要日志式的注釋
記住,使用版本控制!沒(méi)用的代碼、注釋掉的代碼,尤其是日志式的注釋。用 git log 來(lái)獲取歷史信息!
不好:
/** * 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)記
位置標(biāo)記通常只會(huì)添加垃圾信息。通過(guò)對(duì)函數(shù)或變量名以及適當(dāng)?shù)目s進(jìn)就能為代碼帶來(lái)良好的可視化結(jié)構(gòu)。
不好:
//////////////////////////////////////////////////////////////////////////////// // 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() { // ... }避免在源文件中添加版權(quán)注釋
這是代碼文件樹(shù)頂層的 LICENSE 文件應(yīng)該干的事情。
不好:
/* 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() { // ... }
本文轉(zhuǎn)載自:眾成翻譯
譯者:邊城
鏈接:http://www.zcfy.cc/article/2273
原文:https://github.com/ryanmcdermott/clean-code-javascript/blob/master/README.md
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/81134.html
摘要:代碼整潔之道整潔的代碼不僅僅是讓人看起來(lái)舒服,更重要的是遵循一些規(guī)范能夠讓你的代碼更容易維護(hù),同時(shí)降低幾率。另外這不是強(qiáng)制的代碼規(guī)范,就像原文中說(shuō)的,。里式替換原則父類和子類應(yīng)該可以被交換使用而不會(huì)出錯(cuò)。注釋好的代碼是自解釋的。 JavaScript代碼整潔之道 整潔的代碼不僅僅是讓人看起來(lái)舒服,更重要的是遵循一些規(guī)范能夠讓你的代碼更容易維護(hù),同時(shí)降低bug幾率。 原文clean-c...
摘要:在代碼整潔之道,提出一種軟件質(zhì)量,可持續(xù)開(kāi)發(fā)不僅在于項(xiàng)目架構(gòu)設(shè)計(jì),還與代碼質(zhì)量密切相關(guān),代碼的整潔度和質(zhì)量成正比,一份整潔的代碼在質(zhì)量上是可靠的,為團(tuán)隊(duì)開(kāi)發(fā),后期維護(hù),重構(gòu)奠定了良好的基礎(chǔ)。 現(xiàn)在的軟件系統(tǒng)開(kāi)發(fā)難度主要在于其復(fù)雜度和規(guī)模,客戶需求也不再像Winston Royce瀑布模型期望那樣在系統(tǒng)編碼前完成所有的設(shè)計(jì)滿足用戶軟件需求。在這個(gè)信息爆炸技術(shù)日新月異的時(shí)代,需求總是在不停...
摘要:我們這里再介紹一下,朱重八家族的名字,都很有特點(diǎn)。取這樣的名字不是因?yàn)橹旒沂歉銛?shù)學(xué)的,而是因?yàn)樵谠?,老百姓如果不能上學(xué)和當(dāng)官就沒(méi)有名字,只能以父母年齡相加或者出生的日期命名。所以說(shuō)命名不僅僅是一種科學(xué),更是一種藝術(shù)。 在小朱元璋出生一個(gè)月后,父母為他取了一個(gè)名字(元時(shí)慣例):朱重八,這個(gè)名字也可以叫做朱八八。我們這里再介紹一下,朱重八家族的名字,都很有特點(diǎn)。朱重八高祖名字:朱百六;朱...
摘要:我們這里再介紹一下,朱重八家族的名字,都很有特點(diǎn)。取這樣的名字不是因?yàn)橹旒沂歉銛?shù)學(xué)的,而是因?yàn)樵谠?,老百姓如果不能上學(xué)和當(dāng)官就沒(méi)有名字,只能以父母年齡相加或者出生的日期命名。所以說(shuō)命名不僅僅是一種科學(xué),更是一種藝術(shù)。 在小朱元璋出生一個(gè)月后,父母為他取了一個(gè)名字(元時(shí)慣例):朱重八,這個(gè)名字也可以叫做朱八八。我們這里再介紹一下,朱重八家族的名字,都很有特點(diǎn)。朱重八高祖名字:朱百六;朱...
閱讀 2413·2021-11-23 09:51
閱讀 1224·2021-11-22 13:54
閱讀 3434·2021-09-24 10:31
閱讀 1107·2021-08-16 10:46
閱讀 3634·2019-08-30 15:54
閱讀 719·2019-08-30 15:54
閱讀 2899·2019-08-29 17:17
閱讀 3174·2019-08-29 15:08