国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript 代碼簡(jiǎn)潔之道

鄒立鵬 / 642人閱讀

摘要:代碼質(zhì)量與其整潔度成正比。它讓你的代碼簡(jiǎn)潔優(yōu)雅。是幾個(gè)單詞首字母組合而來,分別表示單一功能原則開閉原則里氏替換原則接口隔離原則以及依賴反轉(zhuǎn)原則。開閉原則開指的就是類模塊函數(shù)都應(yīng)該具有可擴(kuò)展性,閉指的是它們不應(yīng)該被修改。

測(cè)試代碼質(zhì)量的唯一方式:別人看你代碼時(shí)說 f * k 的次數(shù)。

代碼質(zhì)量與其整潔度成正比。干凈的代碼,既在質(zhì)量上較為可靠,也為后期維護(hù)、升級(jí)奠定了良好基礎(chǔ)。

本文并不是代碼風(fēng)格指南,而是關(guān)于代碼的可讀性、復(fù)用性、擴(kuò)展性探討。

我們將從幾個(gè)方面展開討論:

變量

函數(shù)

對(duì)象和數(shù)據(jù)結(jié)構(gòu)

SOLID

測(cè)試

異步

錯(cuò)誤處理

代碼風(fēng)格

注釋

變量 用有意義且常用的單詞命名變量 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);
可描述

通過一個(gè)變量生成了一個(gè)新變量,也需要為這個(gè)新變量命名,也就是說每個(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 l = ["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);
});
避免無意義的前綴

如果創(chuàng)建了一個(gè)對(duì)象 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";
}
使用默認(rèn)值 Bad:
function createMicrobrewery(name) {
  const breweryName = name || "Hipster Brew Co.";
  // ...
}
Good:
function createMicrobrewery(name = "Hipster Brew Co.") {
  // ...
}
函數(shù) 參數(shù)越少越好

如果參數(shù)超過兩個(gè),使用 ES2015/ES6 的解構(gòu)語法,不用考慮參數(shù)的順序。(注:不要超過3個(gè)參數(shù),如果確實(shí)需要3個(gè)以上的參數(shù),用對(duì)象包起來)

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)該知道它是干啥的。(注:其實(shí)就是語義化命名,代碼是給人看的)

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ù)嵌套過多會(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ù)代碼。所以在抽象過程中必須要遵循 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ù)

通過 flag 的 true 或 false,來判斷執(zhí)行邏輯,違反了一個(gè)函數(shù)干一件事的原則。(這個(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ù)/類來處理。也就是說副作用需要在唯一的地方處理。

副作用的三大天坑:隨意修改可變數(shù)據(jù)類型、隨意分享沒有數(shù)據(jù)結(jié)構(gòu)的狀態(tài)、沒有在統(tǒng)一地方處理副作用。

(注:這就是純函數(shù)的作用,同樣的輸入,返回的一定是同樣的輸入,這樣對(duì)于結(jié)果是可預(yù)料的,不會(huì)出現(xiàn)意料之外甚至很難修復(fù)的問題)

Bad:
// 全局變量被一個(gè)函數(shù)引用
// 現(xiàn)在這個(gè)變量從字符串變成了數(shù)組,如果有其他的函數(shù)引用,會(huì)發(fā)生無法預(yù)見的錯(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 中,基本類型通過賦值傳遞,對(duì)象和數(shù)組通過引用傳遞。以引用傳遞為例:

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

看起來沒問題對(duì)不對(duì)?

如果當(dāng)用戶點(diǎn)擊購買時(shí),網(wǎng)絡(luò)出現(xiàn)故障, purchase() 方法一直在重復(fù)調(diào)用,與此同時(shí)用戶又添加了新的商品,這時(shí)網(wǎng)絡(luò)又恢復(fù)了。那么 purchase() 方法獲取到 購物車數(shù)組 就是錯(cuò)誤的。

為了避免這種問題,我們需要在每次新增商品時(shí),克隆 購物車數(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 方法來判斷兩個(gè)數(shù)組的不同。而你同事也打算做類似的事情,不過他的 diff 方法是用來判斷兩個(gè)數(shù)組首位元素的不同。很明顯你們方法會(huì)產(chǎn)生沖突,遇到這類問題我們可以用 ES2015/ES6 的語法來對(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)
封裝條件語句 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:絕大多數(shù)場(chǎng)景可以用多態(tài)替代。

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

A:為了讓代碼更簡(jiǎn)潔易讀,如果你的函數(shù)中出現(xiàn)了條件判斷,那么說明你的函數(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();
  }
}

// 利用對(duì)象使用分支判斷
var Airplane = {
    "777": function() {
        return this.getMaxAltitude() - this.getPassengerCount();
    },
    "Air Force One": function() {
        return this.getMaxAltitude();
    },
    "Cessna": function() {
        return this.getMaxAltitude() - this.getFuelExpenditure();
    },
}
避免類型檢查(第一部分)

JavaScript 是無類型的,意味著你可以傳任意類型參數(shù),這種自由度很容易讓人困擾,不自覺的就會(huì)去檢查類型。仔細(xì)想想是你真的需要檢查類型還是你的 API 設(shè)計(jì)有問題?(注:持保留意見)

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

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;
}
不要過度優(yōu)化

現(xiàn)代瀏覽器已經(jīng)在底層做了很多優(yōu)化,過去的很多優(yōu)化方案都是無效的,會(huì)浪費(fèi)你的時(shí)間,想知道現(xiàn)代瀏覽器優(yōu)化了哪些內(nèi)容,請(qǐng)點(diǎn)這里。(注:持保留意見,低版本的瀏覽器沒有做該優(yōu)化,雖然性能提升不大,但這是一個(gè)好的編碼習(xí)慣)

Bad:
// 在老的瀏覽器中,由于 `list.length` 沒有做緩存,每次迭代都會(huì)去計(jì)算,造成不必要開銷。
// 現(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)沒有用了,但擔(dān)心以后會(huì)用,舍不得刪。

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

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

(持保留意見,因?yàn)楸A舨糠肿⑨尩闹匾a是以防出現(xiàn)問題可以直接線上恢復(fù)代碼而不用上緊急版本)

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");
對(duì)象和數(shù)據(jù)結(jié)構(gòu) 用 get、set 方法操作數(shù)據(jù)

這樣做可以帶來很多好處,比如在操作數(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);
使用私有變量

可以用閉包來創(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 之前,沒有類的語法,只能用構(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)有用,可以在很多庫中發(fā)現(xiàn)它的身影,比如 jQuery、Lodash 等。它讓你的代碼簡(jiǎn)潔優(yōu)雅。實(shí)現(xiàn)起來也非常簡(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

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

單一功能原則 The Single Responsibility Principle

如果一個(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()) {
      // ...
    }
  }
}
開閉原則 The Open Closed Principle

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

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
    });
  }
}
里氏替換原則 Liskov Substitution Principle

名字很唬人,其實(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);
接口隔離原則 The Interface Segregation Principle

JavaScript 幾乎沒有接口的概念,所以這條原則很少被使用。官方定義是“客戶端不應(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() {}
  }
});
依賴倒置原則 The Dependency Inversion Principle

說就兩點(diǎn):

高層次模塊不能依賴低層次模塊,它們依賴于抽象接口。

抽象接口不能依賴具體實(shí)現(xiàn),具體實(shí)現(xiàn)依賴抽象接口。

總結(jié)下來就兩個(gè)字,解耦。

Bad:
// 庫存查詢
class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

// 庫存跟蹤
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:
// 庫存跟蹤
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) {
    // ...
  }
}

// 通過依賴注入的方式將請(qǐng)求模塊解耦,這樣我們就可以很輕易的替換成 webSocket 請(qǐng)求。
const inventoryTracker = new InventoryTracker(["apples", "bananas"], new InventoryRequesterHTTP());
inventoryTracker.requestItems();
測(cè)試

隨著項(xiàng)目變得越來越龐大,時(shí)間線拉長(zhǎng),有的老代碼可能半年都沒碰過,如果此時(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);
  }
}
錯(cuò)誤處理 不要忽略拋異常 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)格

代碼風(fēng)格是主觀的,爭(zhēng)論哪種好哪種不好是在浪費(fèi)生命。市面上有很多自動(dòng)處理代碼風(fēng)格的工具,選一個(gè)喜歡就行了,我們來討論幾個(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();

javascript

不要記日記
記住你有 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ì)干擾你閱讀代碼。(注:在聯(lián)調(diào)或臨時(shí)修改代碼調(diào)試的時(shí)候可以用此方法引起自己的注意,保證在提交代碼的時(shí)候可以注意到此處,不會(huì)造成調(diào)試代碼的提交)

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

文末推薦一篇很好的講述前端代碼規(guī)范的文章,包含前端各種代碼的規(guī)范,我覺得可以根據(jù)自己公司項(xiàng)目的實(shí)際情況借鑒一二。前端代碼規(guī)范

翻譯自 ryanmcdermott 的 clean-code-javascript,本文對(duì)原文進(jìn)行了一些修改。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/53542.html

相關(guān)文章

  • JavaScript簡(jiǎn)潔之道

    摘要:考慮到函數(shù)表示某種行為,函數(shù)名稱應(yīng)該是動(dòng)詞或短語,用以說明其背后的意圖以及參數(shù)的意圖。不好的方式好的方式使用條件簡(jiǎn)寫。這可能微不足道,但值得一提。 為了保證可讀性,本文采用的音譯而非直意。 簡(jiǎn)介 如果你關(guān)注代碼本身和代碼的編寫方式,而不是只關(guān)心它是否能工作,那么你寫代碼是有一定的水準(zhǔn)。專業(yè)開發(fā)人員將為未來的自己和其他人編寫代碼,而不僅僅只編寫當(dāng)前能工作就行的代碼。 在此基礎(chǔ)上,簡(jiǎn)潔代碼...

    wudengzan 評(píng)論0 收藏0
  • PHP代碼簡(jiǎn)潔之道——變量部分

    摘要:將代碼寫的簡(jiǎn)潔并且易讀易懂是每一位優(yōu)秀的所應(yīng)該具備的基本功。前幾天在上看到這個(gè)項(xiàng)目,感覺很有收獲,于是在這里記錄一下。 將代碼寫的簡(jiǎn)潔并且易讀易懂是每一位優(yōu)秀的coder所應(yīng)該具備的基本功。 前幾天在github上看到clean-code-php這個(gè)項(xiàng)目,感覺很有收獲,于是在這里記錄一下。 使用有意義并且可讀的變量名稱 Bad: $ymdstr = $moment->format(y-...

    mgckid 評(píng)論0 收藏0
  • PHP 代碼規(guī)范簡(jiǎn)潔之道

    摘要:統(tǒng)一的編碼規(guī)范編碼規(guī)范往簡(jiǎn)單說其實(shí)就是三個(gè)方面換行空格變量命名放在里面,還有一些附加的地方,比如關(guān)鍵字大小寫,語法糖的使用與等的問題。這些都是規(guī)范代碼的重要手段。推廣給你的隊(duì)友團(tuán)隊(duì)項(xiàng)目中,隊(duì)友的配合對(duì)整個(gè)代碼的規(guī)范起著決定性的作用。 1. 統(tǒng)一的編碼規(guī)范 編碼規(guī)范往簡(jiǎn)單說其實(shí)就是三個(gè)方面: 換行 空格 變量命名 放在 PHP 里面,還有一些附加的地方,比如關(guān)鍵字大小寫,語法糖的使用...

    BearyChat 評(píng)論0 收藏0
  • PHP代碼簡(jiǎn)潔之道——類和對(duì)象部分

    摘要:使用和在中,通過為屬性或方法設(shè)置和關(guān)鍵字可以實(shí)現(xiàn)對(duì)屬性或方法的可見性控制。你的繼承表達(dá)了一個(gè)對(duì)等比如人類是動(dòng)物的關(guān)系,不是包含的關(guān)系比如用戶具有用戶詳情你能從基類中復(fù)用代碼你想通過修改全局類來對(duì)所有派生類進(jìn)行修改。 使用getter和setter 在 PHP 中,通過為屬性或方法設(shè)置 public, protected 和 private 關(guān)鍵字可以實(shí)現(xiàn)對(duì)屬性或方法的可見性控制。不過,...

    cyixlq 評(píng)論0 收藏0
  • 編碼之道(一):程序員的“圣經(jīng)“

    摘要:與此類似,理所當(dāng)然的,我們程序員也會(huì)有自己的圣經(jīng)。這便是程序員的圣經(jīng)三個(gè)原則我認(rèn)為做為一個(gè)程序員,最神圣的就是三個(gè)原則,它幾乎能完整無誤的定義做為一個(gè)程序員應(yīng)該如何去編碼。 ...

    Elle 評(píng)論0 收藏0
  • 2017年前端工程師應(yīng)該學(xué)習(xí)什么

    摘要:的黑客與設(shè)計(jì)剖析設(shè)計(jì)之美的秘密,英文原版在這里,還有一套免費(fèi)教程。的代碼整潔之道程序員的職業(yè)素養(yǎng),英文原版的學(xué)習(xí)如何為他人寫文檔我們與同事或者其他人溝通的很大一部分都是通過文字來的。 作者:Artem Sapegin 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58aaa33bfc5b7f63e8c23f68...

    airborne007 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<