亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

淺談JavaScript 代碼簡潔之道

2019-11-19 12:18:09
字體:
來源:轉載
供稿:網友

測試代碼質量的唯一方式:別人看你代碼時說 f * k 的次數。

代碼質量與其整潔度成正比。干凈的代碼,既在質量上較為可靠,也為后期維護、升級奠定了良好基礎。

本文并不是代碼風格指南,而是關于代碼的可讀性、復用性、擴展性探討。

我們將從幾個方面展開討論:

  • 變量
  • 函數
  • 對象和數據結構
  • SOLID
  • 測試
  • 異步
  • 錯誤處理
  • 代碼風格
  • 注釋

變量

用有意義且常用的單詞命名變量

Bad:

const yyyymmdstr = moment().format('YYYY/MM/DD');

Good:

const currentDate = moment().format('YYYY/MM/DD');

保持統一

可能同一個項目對于獲取用戶信息,會有三個不一樣的命名。應該保持統一,如果你不知道該如何取名,可以去 codelf 搜索,看別人是怎么取名的。

Bad:

 getUserInfo(); getClientData(); getCustomerRecord();

Good:

getUser()

每個常量都該命名

可以用buddy.js 或者ESLint 檢測代碼中未命名的常量。

Bad:

// 三個月之后你還能知道 86400000 是什么嗎?setTimeout(blastOff, 86400000);

Good:

const MILLISECOND_IN_A_DAY = 86400000;setTimeout(blastOff, MILLISECOND_IN_A_DAY);

可描述

通過一個變量生成了一個新變量,也需要為這個新變量命名,也就是說每個變量當你看到他第一眼你就知道他是干什么的。

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);

直接了當

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);});

避免無意義的前綴

如果創建了一個對象 car,就沒有必要把它的顏色命名為 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';}

使用默認值

Bad:

function createMicrobrewery(name) { const breweryName = name || 'Hipster Brew Co.'; // ...}

Good:

function createMicrobrewery(name = 'Hipster Brew Co.') { // ...}

函數

參數越少越好

如果參數超過兩個,使用 ES2015/ES6 的解構語法,不用考慮參數的順序。

Bad:

function createMenu(title, body, buttonText, cancellable) { // ...}

Good:

function createMenu({ title, body, buttonText, cancellable }) { // ...}createMenu({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true});

只做一件事情

這是一條在軟件工程領域流傳久遠的規則。嚴格遵守這條規則會讓你的代碼可讀性更好,也更容易重構。如果違反這個規則,那么代碼會很難被測試或者重用。

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();}

顧名思義

看函數名就應該知道它是干啥的。

Bad:

function addToDate(date, month) { // ...}const date = new Date();// 很難知道是把什么加到日期中addToDate(date, 1);

Good:

function addMonthToDate(month, date) { // ...}const date = new Date();addMonthToDate(1, date);

只需要一層抽象層

如果函數嵌套過多會導致很難復用以及測試。

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;}

刪除重復代碼

很多時候雖然是同一個功能,但由于一兩個不同點,讓你不得不寫兩個幾乎相同的函數。

要想優化重復代碼需要有較強的抽象能力,錯誤的抽象還不如重復代碼。所以在抽象過程中必須要遵循 SOLID 原則(SOLID 是什么?稍后會詳細介紹)。

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); })}

對象設置默認屬性

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 參數

通過 flag 的 true 或 false,來判斷執行邏輯,違反了一個函數干一件事的原則。

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}`)}

避免副作用(第一部分)

函數接收一個值返回一個新值,除此之外的行為我們都稱之為副作用,比如修改全局變量、對文件進行 IO 操作等。

當函數確實需要副作用時,比如對文件進行 IO 操作時,請不要用多個函數/類進行文件操作,有且僅用一個函數/類來處理。也就是說副作用需要在唯一的地方處理。

副作用的三大天坑:隨意修改可變數據類型、隨意分享沒有數據結構的狀態、沒有在統一地方處理副作用。

Bad:

// 全局變量被一個函數引用// 現在這個變量從字符串變成了數組,如果有其他的函數引用,會發生無法預見的錯誤。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 中,基本類型通過賦值傳遞,對象和數組通過引用傳遞。以引用傳遞為例:

假如我們寫一個購物車,通過 addItemToCart() 方法添加商品到購物車,修改 購物車數組。此時調用 purchase() 方法購買,由于引用傳遞,獲取的 購物車數組 正好是最新的數據。

看起來沒問題對不對?

如果當用戶點擊購買時,網絡出現故障, purchase() 方法一直在重復調用,與此同時用戶又添加了新的商品,這時網絡又恢復了。那么 purchase() 方法獲取到 購物車數組 就是錯誤的。

為了避免這種問題,我們需要在每次新增商品時,克隆 購物車數組 并返回新的數組。

Bad:

const addItemToCart = (cart, item) => { cart.push({ item, date: Date.now() });};

Good:

const addItemToCart = (cart, item) => { return [...cart, {item, date: Date.now()}]};

不要寫全局方法

在 JavaScript 中,永遠不要污染全局,會在生產環境中產生難以預料的 bug。舉個例子,比如你在 Array.prototype 上新增一個 diff 方法來判斷兩個數組的不同。而你同事也打算做類似的事情,不過他的 diff 方法是用來判斷兩個數組首位元素的不同。很明顯你們方法會產生沖突,遇到這類問題我們可以用 ES2015/ES6 的語法來對 Array 進行擴展。

Bad:

Array.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem));};

Good:

class SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem));  }}

比起命令式我更喜歡函數式編程

函數式變編程可以讓代碼的邏輯更清晰更優雅,方便測試。

Bad:

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)

封裝條件語句

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)) { // ...}

避免使用條件語句

Q:不用條件語句寫代碼是不可能的。

A:絕大多數場景可以用多態替代。

Q:用多態可行,但為什么就不能用條件語句了呢?

A:為了讓代碼更簡潔易讀,如果你的函數中出現了條件判斷,那么說明你的函數不止干了一件事情,違反了函數單一原則。

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 { // ...}// 波音777class Boeing777 extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getPassengerCount(); }}// 空軍一號class AirForceOne extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude(); }}// 賽納斯飛機class Cessna extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getFuelExpenditure(); }}

避免類型檢查(第一部分)

JavaScript 是無類型的,意味著你可以傳任意類型參數,這種自由度很容易讓人困擾,不自覺的就會去檢查類型。仔細想想是你真的需要檢查類型還是你的 API 設計有問題?

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'));}

避免類型檢查(第二部分)

如果你需要做靜態類型檢查,比如字符串、整數等,推薦使用 TypeScript,不然你的代碼會變得又臭又長。

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;}

不要過度優化

現代瀏覽器已經在底層做了很多優化,過去的很多優化方案都是無效的,會浪費你的時間,想知道現代瀏覽器優化了哪些內容,請點這里。

Bad:

// 在老的瀏覽器中,由于 `list.length` 沒有做緩存,每次迭代都會去計算,造成不必要開銷。// 現代瀏覽器已對此做了優化。for (let i = 0, len = list.length; i < len; i++) { // ...}

Good:

for (let i = 0; i < list.length; i++) { // ...}

刪除棄用代碼

很多時候有些代碼已經沒有用了,但擔心以后會用,舍不得刪。

如果你忘了這件事,這些代碼就永遠存在那里了。

放心刪吧,你可以在代碼庫歷史版本中找他它。

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 方法操作數據

這樣做可以帶來很多好處,比如在操作數據時打日志,方便跟蹤錯誤;在 set 的時候很容易對數據進行校驗…

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 前,對 amount 進行校驗 balance = amount; } return { // ... getBalance, setBalance, };}const account = makeBankAccount();account.setBalance(100);

使用私有變量

可以用閉包來創建私有變量

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 Doedelete 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 Doedelete employee.name;console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe


使用 class

在 ES2015/ES6 之前,沒有類的語法,只能用構造函數的方式模擬類,可讀性非常差。

Bad:

// 動物const Animal = function(age) { if (!(this instanceof Animal)) { throw new Error('Instantiate Animal with `new`'); } this.age = age;};Animal.prototype.move = function move() {};// 哺乳動物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:

// 動物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() {};}

鏈式調用

這種模式相當有用,可以在很多庫中發現它的身影,比如 jQuery、Lodash 等。它讓你的代碼簡潔優雅。實現起來也非常簡單,在類的方法最后返回 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();

不要濫用繼承

很多時候繼承被濫用,導致可讀性很差,要搞清楚兩個類之間的關系,繼承表達的一個屬于關系,而不是包含關系,比如 Human->Animal vs. User->UserDetails

Bad:

class Employee { constructor(name, email) { this.name = name; this.email = email; } // ...}// TaxData(稅收信息)并不是屬于 Employee(雇員),而是包含關系。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

SOLID 是幾個單詞首字母組合而來,分別表示 單一功能原則、開閉原則里氏替換原則、接口隔離原則以及依賴反轉原則。

單一功能原則

如果一個類干的事情太多太雜,會導致后期很難維護。我們應該厘清職責,各司其職減少相互之間依賴。

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()) {  // ... } }}}

開閉原則

“開”指的就是類、模塊、函數都應該具有可擴展性,“閉”指的是它們不應該被修改。也就是說你可以新增功能但不能去修改源碼。

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 }); }}

里氏替換原則

名字很唬人,其實道理很簡單,就是子類不要去重寫父類的方法。

Bad:

// 長方形class Rectangle { constructor() { this.width = 0; this.height = 0; } setColor(color) { // ... } render(area) { // ... } setWidth(width) { this.width = width; } setHeight(height) { this.height = height; } getArea() { return this.width * this.height; }}// 正方形class Square extends Rectangle { setWidth(width) { this.width = width; this.height = width; } setHeight(height) { this.width = height; this.height = height; }}function renderLargeRectangles(rectangles) { rectangles.forEach((rectangle) => { rectangle.setWidth(4); rectangle.setHeight(5); const area = rectangle.getArea();  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 幾乎沒有接口的概念,所以這條原則很少被使用。官方定義是“客戶端不應該依賴它不需要的接口”,也就是接口最小化,把接口解耦。

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() {} }});

依賴反轉原則

說就兩點:

  1. 高層次模塊不能依賴低層次模塊,它們依賴于抽象接口。
  2. 抽象接口不能依賴具體實現,具體實現依賴抽象接口。

總結下來就兩個字,解耦。

Bad:

// 庫存查詢class InventoryRequester { constructor() { this.REQ_METHODS = ['HTTP']; } requestItem(item) { // ... }}// 庫存跟蹤class InventoryTracker { constructor(items) { this.items = items; // 這里依賴一個特殊的請求類,其實我們只是需要一個請求方法。 this.requester = new InventoryRequester(); } requestItems() { this.items.forEach((item) => {  this.requester.requestItem(item); }); }}const inventoryTracker = new InventoryTracker(['apples', 'bananas']);inventoryTracker.requestItems();

Good:

// 庫存跟蹤class InventoryTracker { constructor(items, requester) { this.items = items; this.requester = requester; } requestItems() { this.items.forEach((item) => {  this.requester.requestItem(item); }); }}// HTTP 請求class InventoryRequesterHTTP { constructor() { this.REQ_METHODS = ['HTTP']; } requestItem(item) { // ... }}// webSocket 請求class InventoryRequesterWS { constructor() { this.REQ_METHODS = ['WS']; } requestItem(item) { // ... }}// 通過依賴注入的方式將請求模塊解耦,這樣我們就可以很輕易的替換成 webSocket 請求。const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterHTTP());inventoryTracker.requestItems();

測試

隨著項目變得越來越龐大,時間線拉長,有的老代碼可能半年都沒碰過,如果此時上線,你有信心這部分代碼能正常工作嗎?測試的覆蓋率和你的信心是成正比的。

PS: 如果你發現你的代碼很難被測試,那么你應該優化你的代碼了。

單一化

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); });});

異步

不再使用回調

不會有人愿意去看嵌套回調的代碼,用 Promises 替代回調吧。

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 更簡潔

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); // 也可以把異常傳回服務器 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); // 也可以把異常傳回服務器 reportErrorToService(error); // 其他的自定義方法 });

代碼風格

代碼風格是主觀的,爭論哪種好哪種不好是在浪費生命。市面上有很多自動處理代碼風格的工具,選一個喜歡就行了,我們來討論幾個非自動處理的部分。

常量大寫

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 {}

先聲明后調用

就像我們看報紙文章一樣,從上到下看,所以為了方便閱讀把函數聲明寫在函數調用前面。

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();

注釋

只有業務邏輯需要注釋

代碼注釋不是越多越好。

Bad:

function hashIt(data) { // 這是初始值 let hash = 0; // 數組的長度 const length = data.length; // 循環數組 for (let i = 0; i < length; i++) {  // 獲取字符代碼  const char = data.charCodeAt(i);  // 修改 hash  hash = ((hash << 5) - hash) + char;  // 轉換為32位整數  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;  // 轉換為32位整數  hash &= hash; }}

刪掉注釋的代碼

git 存在的意義就是保存你的舊代碼,所以注釋的代碼趕緊刪掉吧。

Bad:

doStuff();// doOtherStuff();// doSomeMoreStuff();// doSoMuchStuff();

Good:

doStuff();

不要記日記

記住你有 git!,git log 可以幫你干這事。

Bad:

/** * 2016-12-20: 刪除了 xxx * 2016-10-01: 改進了 xxx * 2016-02-03: 刪除了第12行的類型檢查 * 2015-03-14: 增加了一個合并的方法 */function combine(a, b) { return a + b;}

Good:

function combine(a, b) { return a + b;}

注釋不需要高亮

注釋高亮,并不能起到提示的作用,反而會干擾你閱讀代碼。

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》,本文對原文進行了一些修改。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲国产精品一区二区三区| 国产成人自拍视频在线观看| 久久这里只有精品视频首页| 国产亚洲精品美女久久久| 久久香蕉国产线看观看av| 日本久久久久久久| 狠狠色噜噜狠狠狠狠97| 亚洲人成在线观| 国产v综合v亚洲欧美久久| 国产www精品| 亚洲国产免费av| 久久精品国产精品| 久久精品国产亚洲精品2020| 日韩精品极品视频免费观看| 日韩视频在线观看免费| 久久躁日日躁aaaaxxxx| 在线观看久久久久久| 欧美一区三区三区高中清蜜桃| 久久久欧美一区二区| 国产精品九九久久久久久久| 日韩av片免费在线观看| 久久黄色av网站| 久久久久久久久久久久久久久久久久av| 午夜精品久久久久久久久久久久久| 91夜夜揉人人捏人人添红杏| 日韩一区视频在线| 欧洲日韩成人av| 欧美极度另类性三渗透| 久久久精品美女| 97免费视频在线播放| 国产主播喷水一区二区| 欧美巨猛xxxx猛交黑人97人| 国产亚洲视频在线观看| 中文字幕久久亚洲| 精品久久久久久亚洲精品| 久久精品欧美视频| 欧美日韩第一页| 国产精品成人一区二区三区吃奶| 亚洲激情视频在线| 欧美在线视频一区| 精品亚洲一区二区三区| 中文字幕日韩精品在线| 浅井舞香一区二区| 欧洲午夜精品久久久| 亚洲精品午夜精品| 91精品国产91久久久久久吃药| 精品一区二区三区电影| 日韩精品丝袜在线| 国产精品大陆在线观看| 亚洲国内精品视频| 亚洲最大福利视频网| 久久精品亚洲国产| 爽爽爽爽爽爽爽成人免费观看| 日韩欧美视频一区二区三区| 精品无人区太爽高潮在线播放| 午夜精品理论片| 日韩资源在线观看| 亚洲国产成人一区| 亚洲已满18点击进入在线看片| 亚洲黄色www网站| 欧美性猛交xxxx久久久| 久久777国产线看观看精品| 91欧美激情另类亚洲| 在线观看国产成人av片| 国产精品美女久久久久久免费| 久精品免费视频| 亚洲天堂av在线免费| 欧美日韩一二三四五区| 91精品国产成人| 国产亚洲精品高潮| 91影视免费在线观看| 亚洲男人天堂久| 国产精品亚洲综合天堂夜夜| 国产精品久久一区| 岛国av一区二区三区| 久久精品99久久久久久久久| 日韩精品在线免费观看| 国产精品亚洲视频在线观看| 一本久久综合亚洲鲁鲁| 日韩av免费在线观看| 亚洲石原莉奈一区二区在线观看| 日韩av综合中文字幕| 久久亚洲一区二区三区四区五区高| 久久人人97超碰精品888| 国产精品日韩精品| 国产专区欧美专区| 最近日韩中文字幕中文| 亚洲自拍高清视频网站| 日韩在线精品一区| 在线播放日韩欧美| 欧美精品xxx| 久久综合免费视频影院| 亚洲第一精品夜夜躁人人爽| 国产69精品久久久久久| 国产欧美一区二区三区久久人妖| 欧美精品在线视频观看| 欧美性xxxx极品高清hd直播| 欧美一区二粉嫩精品国产一线天| 久久中文字幕一区| 欧美日韩亚洲视频| 欧美性生活大片免费观看网址| 成人免费视频网址| 深夜福利国产精品| 日韩电影第一页| 国产精品永久免费| 久久成人亚洲精品| 日韩av观看网址| 国产精品久久久久免费a∨| 韩国国内大量揄拍精品视频| 欧美成人免费小视频| 久久精品视频99| 久久精品国产精品亚洲| 日韩精品中文字幕在线播放| 欧美日韩国产精品一区二区不卡中文| 久久躁日日躁aaaaxxxx| 97在线免费视频| 4444欧美成人kkkk| 欧美日韩激情视频| 日韩美女激情视频| 国产成人av网址| 日本久久久久久| 欧美在线视频一区二区| 国产精品∨欧美精品v日韩精品| 国产精品福利久久久| 最新国产精品拍自在线播放| 中文字幕欧美日韩| 一本大道香蕉久在线播放29| 啊v视频在线一区二区三区| 国产日韩欧美视频在线| 欧美日韩第一视频| 2019国产精品自在线拍国产不卡| 亚洲精品一二区| 国产美女久久精品香蕉69| 久久久久久久国产| 91美女福利视频高清| 亚洲欧洲自拍偷拍| 欧美在线欧美在线| 亚洲国产91精品在线观看| 欧美又大粗又爽又黄大片视频| 欧美日韩亚洲精品一区二区三区| 欧美孕妇性xx| 社区色欧美激情 | 日韩中文在线中文网在线观看| 欧美精品www在线观看| 日韩精品视频免费在线观看| 国产成人综合久久| 韩国v欧美v日本v亚洲| 精品久久久国产精品999| 午夜精品一区二区三区在线视频| 亚洲美女又黄又爽在线观看| 国产精品第100页| 国产精品99久久久久久人| 国产精品网站入口| 成人在线观看视频网站| 日韩精品在线免费播放| 日本a级片电影一区二区| 性夜试看影院91社区| 亚洲日韩欧美视频| 在线中文字幕日韩| 亚洲无av在线中文字幕| 亚洲日本成人女熟在线观看| 蜜臀久久99精品久久久久久宅男| 久久噜噜噜精品国产亚洲综合| 欧美高清激情视频|