clean-code-javascript - 图1

变量

  • 使用有意义且能发音的单词

Bad:
  1. const yyyymmdstr = moment().format("YYYY/MM/DD");

Good:
  1. const currentDate = moment().format("YYYY/MM/DD");
  • 使用可以搜索的单词

Bad:
  1. // What the heck is 86400000 for?
  2. setTimeout(blastOff, 86400000);

Good:
  1. // Declare them as capitalized named constants.
  2. const MILLISECONDS_IN_A_DAY = 86400000;
  3. setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
  • 使用解释说明的单词

Bad:
  1. const address = "One Infinite Loop, Cupertino 95014";
  2. const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
  3. saveCityZipCode(
  4. address.match(cityZipCodeRegex)[1],
  5. address.match(cityZipCodeRegex)[2]
  6. );

Good:
  1. const address = "One Infinite Loop, Cupertino 95014";
  2. const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
  3. const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
  4. saveCityZipCode(city, zipCode);
  • 不要增加多余的上下文

Bad:
  1. const Car = {
  2. carMake: "Honda",
  3. carModel: "Accord",
  4. carColor: "Blue"
  5. };
  6. function paintCar(car) {
  7. car.carColor = "Red";
  8. }

Good:
  1. const Car = {
  2. make: "Honda",
  3. model: "Accord",
  4. color: "Blue"
  5. };
  6. function paintCar(car) {
  7. car.color = "Red";
  8. }
  • 使用默认参数

Bad:
  1. function createMicrobrewery(name) {
  2. const breweryName = name || "Hipster Brew Co.";
  3. // ...
  4. }

Good:
  1. function createMicrobrewery(name = "Hipster Brew Co.") {
  2. // ...
  3. }

函数

  • 参数(最好不超过两个)

一个或两个参数是理想的情况,如果可能的话应该避免三个。
应该整合除此之外的任何东西。
通常,如果你有两个以上的参数,那么你的函数试图做太多(秉承一个函数做一件事)。

Bad:
  1. function createMenu(title, body, buttonText, cancellable) {
  2. // ...
  3. }

Good:
  1. function createMenu({ title, body, buttonText, cancellable }) {
  2. // ...
  3. }
  4. createMenu({
  5. title: "Foo",
  6. body: "Bar",
  7. buttonText: "Baz",
  8. cancellable: true
  9. });
  • 一个函数应该只做一件事

Bad:

  1. function emailClients(clients) {
  2. clients.forEach(client => {
  3. const clientRecord = database.lookup(client);
  4. if (clientRecord.isActive()) {
  5. email(client);
  6. }
  7. });
  8. }

Good:

  1. function emailActiveClients(clients) {
  2. clients.filter(isActiveClient).forEach(email);
  3. }
  4. function isActiveClient(client) {
  5. const clientRecord = database.lookup(client);
  6. return clientRecord.isActive();
  7. }
  • 函数的名字就是她做的事

Bad:

  1. function emailClients(clients) {
  2. clients.forEach(client => {
  3. const clientRecord = database.lookup(client);
  4. if (clientRecord.isActive()) {
  5. email(client);
  6. }
  7. });
  8. }

Good:

  1. function emailActiveClients(clients) {
  2. clients.filter(isActiveClient).forEach(email);
  3. }
  4. function isActiveClient(client) {
  5. const clientRecord = database.lookup(client);
  6. return clientRecord.isActive();
  7. }
  • 删除重复的代码(提升代码的复用性)

Bad:

  1. function showDeveloperList(developers) {
  2. developers.forEach(developer => {
  3. const expectedSalary = developer.calculateExpectedSalary();
  4. const experience = developer.getExperience();
  5. const githubLink = developer.getGithubLink();
  6. const data = {
  7. expectedSalary,
  8. experience,
  9. githubLink
  10. };
  11. render(data);
  12. });
  13. }
  14. function showManagerList(managers) {
  15. managers.forEach(manager => {
  16. const expectedSalary = manager.calculateExpectedSalary();
  17. const experience = manager.getExperience();
  18. const portfolio = manager.getMBAProjects();
  19. const data = {
  20. expectedSalary,
  21. experience,
  22. portfolio
  23. };
  24. render(data);
  25. });
  26. }

Good:

  1. function showEmployeeList(employees) {
  2. employees.forEach(employee => {
  3. const expectedSalary = employee.calculateExpectedSalary();
  4. const experience = employee.getExperience();
  5. const data = {
  6. expectedSalary,
  7. experience
  8. };
  9. switch (employee.type) {
  10. case "manager":
  11. data.portfolio = employee.getMBAProjects();
  12. break;
  13. case "developer":
  14. data.githubLink = employee.getGithubLink();
  15. break;
  16. }
  17. render(data);
  18. });
  19. }
  • 使用Object.assign来设置对象的默认值

Bad:

  1. const menuConfig = {
  2. title: null,
  3. body: "Bar",
  4. buttonText: null,
  5. cancellable: true
  6. };
  7. function createMenu(config) {
  8. config.title = config.title || "Foo";
  9. config.body = config.body || "Bar";
  10. config.buttonText = config.buttonText || "Baz";
  11. config.cancellable =
  12. config.cancellable !== undefined ? config.cancellable : true;
  13. }
  14. createMenu(menuConfig);

Good:

  1. const menuConfig = {
  2. title: "Order",
  3. // User did not include 'body' key
  4. buttonText: "Send",
  5. cancellable: true
  6. };
  7. function createMenu(config) {
  8. config = Object.assign(
  9. {
  10. title: "Foo",
  11. body: "Bar",
  12. buttonText: "Baz",
  13. cancellable: true
  14. },
  15. config
  16. );
  17. // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  18. // ...
  19. }
  20. createMenu(menuConfig);
  • 封装条件

Bad:

  1. if (fsm.state === "fetching" && isEmpty(listNode)) {
  2. // ...
  3. }

Good:

function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}
  • 不需要过度优化

Bad:

// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
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");

对象和数据结构

  • 使用 getter 和 setter

Bad:

function makeBankAccount() {
  // ...

  return {
    balance: 0
    // ...
  };
}

const account = makeBankAccount();
account.balance = 100;

Good:

function makeBankAccount() {
  // this one is private
  let balance = 0;

  // a "getter", made public via the returned object below
  function getBalance() {
    return balance;
  }

  // a "setter", made public via the returned object below
  function setBalance(amount) {
    // ... validate before updating the balance
    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 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

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

class虽然看起来比原型链更简洁,但是不懂原型链何来 class

  • 使用链式方法

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;
    // NOTE: Returning this for chaining
    return this;
  }

  setModel(model) {
    this.model = model;
    // NOTE: Returning this for chaining
    return this;
  }

  setColor(color) {
    this.color = color;
    // NOTE: Returning this for chaining
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // NOTE: Returning this for chaining
    return this;
  }
}

const car = new Car("Ford", "F-150", "red").setColor("pink").save();
  • 更多时候我们想用的是组合而不是继承

Bad:

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // ...
}

// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

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);
  }
  // ...
}
  • 接口隔离原则

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

测试

  • 每个测试的单一原则

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

错误捕获

  • try…catch

Bad:

try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

Good:

try {
  functionThatMightThrow();
} catch (error) {
  // One option (more noisy than console.log):
  console.error(error);
  // Another option:
  notifyUserOfError(error);
  // Another option:
  reportErrorToService(error);
  // OR do all three!
}
  • promise也一样

Bad:

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    console.log(error);
  });

Good:

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    // One option (more noisy than console.log):
    console.error(error);
    // Another option:
    notifyUserOfError(error);
    // Another option:
    reportErrorToService(error);
    // OR do all three!
  });