nicenote/docs/fea/norms.md
2024-01-18 16:29:26 +08:00

34 KiB
Raw Permalink Blame History

nav group
title path
前端 /fea
title order path
书写规范 4 /norms

书写规范

使用语义化的命名

Bad:

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

Good:

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

对相同类型的变量使用相同的词汇表

Bad:

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

Good:

getUser();

静态变量用全大写定义好

Bad:

// 86400000 是啥意思?
setTimeout(blastOff, 86400000);

Good:

// 说明这个时间的定义原因
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;

setTimeout(blastOff, MILLISECONDS_PER_DAY);

函数

函数名应该说明它们的作用

Bad:

function addToDate(date, month) {
  // ...
}

const date = new Date();

// It's hard to tell from the function name what is added
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 syntaxTree = parse(tokens);
  syntaxTree.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 parse(tokens) {
  const syntaxTree = [];
  tokens.forEach((token) => {
    syntaxTree.push(/* ... */);
  });

  return syntaxTree;
}

删除重复的代码

尽最大努力避免重复代码。 重复代码是不好的,因为这意味着如果您需要更改某些逻辑,有不止一个地方可以更改某些内容。

想象一下,如果你经营一家餐厅并跟踪您的库存:所有的西红柿、洋葱、大蒜、香料等。如果有多个清单,那么当提供带有西红柿的菜肴时,所有清单都必须更新一遍。 但如果只有一个列表,只需要更新一个地方。

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 'manager':
        data.portfolio = employee.getMBAProjects();
        break;
      case 'developer':
        data.githubLink = employee.getGithubLink();
        break;
    }

    render(data);
  });
}

使用 Object.assign 设置默认对象

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',
  // User did not include 'body' key
  buttonText: 'Send',
  cancellable: true,
};

function createMenu(config) {
  let finalConfig = Object.assign(
    {
      title: 'Foo',
      body: 'Bar',
      buttonText: 'Baz',
      cancellable: true,
    },
    config,
  );
  return finalConfig;
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

不要使用标志作为函数参数

标志告诉用户这个函数做不止一件事。函数应该只做一件事。如果函数基于布尔值执行不同的代码逻辑,则应该将它们分割开来。

Bad:

function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

Good:

function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

避免副作用1

Bad:

let name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
  name = name.split(' ');
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

Good:

function splitIntoFirstAndLastName(name) {
  return name.split(' ');
}

const name = 'Ryan McDermott';
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

避免副作用2

Bad:

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

Good:

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

不要更改全局函数

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

多用函数式编程

JavaScript 不像 Haskell 那样是一种函数式语言,但它具有函数式风格。函数式语言更简洁,更容易测试。如果可以的话,尽量使用这种编程风格。

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

const totalOutput = programmerOutput.reduce(
  (totalLines, output) => totalLines + output.linesOfCode,
  0,
);

封装条件

Bad:

if (fsm.state === 'fetching' && isEmpty(listNode)) {
  // ...
}

Good:

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

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

避免条件

这似乎是一个不可能完成的任务。大多数人一听到这句话就会说:“如果没有 if 语句,我该怎么做呢?”答案是,您可以在许多情况下使用多态性来实现相同的任务。第二个问题通常是,"这很好,但我为什么要这么做?"答案是我们以前学到的一个干净的代码概念:一个函数应该只做一件事。当你有带有 if 语句的类和函数时,你是在告诉用户你的函数做不止一件事。记住,只做一件事。

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

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

避免类型检查 (part 1)

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

避免类型检查 (part 2)

如果你使用的是基本的原始值,比如字符串和整数,你不能使用多态性,但你仍然觉得有必要进行类型检查,你应该考虑使用 TypeScript。它是普通 JavaScript 的一个很好的替代方案,因为它提供了标准 JavaScript 语法之上的静态类型。手动类型检查常规 JavaScript 的问题是,要想做得好,需要太多额外的废话,以至于你得到的伪“类型安全”并不能弥补失去的可读性。保持 JavaScript 整洁,编写优秀的测试,并进行良好的代码审查。 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:

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

对象和数据结构

使用 getters/setters 语句

使用 getter 和 setter 访问对象上的数据可能比简单地查找对象上的属性更好。你可能会问:“为什么?“,下面列举了一些原因:

  • 当您想要做的不仅仅是获取一个对象属性时,您不必在代码库中查找和更改每个访问器。
  • 使得set执行集合时添加验证变得简单。
  • 易于添加日志和错误处理时,获取和设置。
  • 你可以延迟加载对象的属性,比如说从服务器获取。

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

使对象具有私有成员

这可以通过闭包(针对 ES5 及以下版本)来实现。

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

Classes

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

使用方法链

这种模式在 JavaScript 中非常有用,您可以在许多库中看到它,例如 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;
    // 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();

偏好组合而非继承

正如在 Gang of Four 的设计模式中所说的那样,您应该尽可能地选择组合而不是继承。有很多很好的理由使用继承,也有很多很好的理由使用组合。这条格言的主要观点是,如果你的头脑本能地倾向于继承,试着思考一下构图是否能更好地模拟你的问题。在某些情况下是可以的。

您可能会想,“我应该在什么时候使用继承?”这取决于您手头的问题,但这是继承比组合更有意义的一些不错的点:

  1. 你的继承代表的是“是-a”关系而不是“是-a”关系(Human->Animal vs. User->UserDetails)。 relationship (Human->Animal vs. User->UserDetails).
  2. 您可以重用基类中的代码(人类可以像所有动物一样移动)。
  3. 您希望通过更改基类来对派生类进行全局更改。(改变所有动物运动时的热量消耗)。

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

固态

单一责任原则

正如 Clean Code 中所述,“一个类的更改原因不应该超过一个”。如果你只能带一个行李箱上飞机,那就很容易把一门课程塞进很多功能中。这样做的问题是,你的类在概念上没有连贯性,这会给它带来很多改变的理由。将更改类的时间最小化是很重要的。这很重要,因为如果一个类中有太多的功能,而您修改了其中的一部分,就很难理解这将如何影响代码库中的其他依赖模块。

Bad:

class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}

Good:

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

开放封闭原则

正如 Bertrand Meyer 所说,“软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。”这意味着什么呢?这个原则基本上是说,您应该允许用户添加新功能,而不改变现有的代码。

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) => {
        // transform response and return
      });
    } else if (this.adapter.name === 'nodeAdapter') {
      return makeHttpCall(url).then((response) => {
        // transform response and return
      });
    }
  }
}

function makeAjaxCall(url) {
  // request and return promise
}

function makeHttpCall(url) {
  // request and return promise
}

Good:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'ajaxAdapter';
  }

  request(url) {
    // request and return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'nodeAdapter';
  }

  request(url) {
    // request and return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then((response) => {
      // transform response and return
    });
  }
}

里氏替换原则

对于一个非常简单的概念来说,这是一个可怕的术语。它被正式定义为“如果 S 是 T 的子类型,那么 T 类型的对象可以被 S 类型的对象替换(也就是说S 类型的对象可以替换 T 类型的对象),而不改变该程序的任何理想属性(正确性,执行的任务,等等)。”这是一个更可怕的定义。

对此最好的解释是,如果你有一个父类和一个子类,那么基类和子类可以互换使用,而不会得到错误的结果。这可能仍然令人困惑,所以让我们看看经典的正方形-矩形示例。从数学上讲正方形是一个矩形但是如果您通过继承使用“is-a”关系来建模它您很快就会遇到麻烦。

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(); // BAD: Returns 25 for Square. Should be 20.
    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 没有接口,所以这个原则不像其他原则那么严格。然而,即使在 JavaScript 缺乏类型系统的情况下,它也很重要和相关。

ISP 声明:“不应该强迫客户依赖他们不使用的接口。”由于 duck 类型,接口在 JavaScript 中是隐式契约

在 JavaScript 中演示这一原则的一个很好的例子是需要大型设置对象的类。不需要客户设置大量的选项是有益的,因为大多数时候他们不需要所有的设置。使它们可选有助于防止出现“臃肿”。

Bad:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.settings.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. 抽象不应该依赖于细节。 细节应该取决于抽象。

一开始这可能很难理解,但是如果您使用过 AngularJS您就会看到该原则以依赖注入 (DI) 的形式实现。 虽然它们不是相同的概念,但 DIP 使高级模块无法了解其低级模块的详细信息并进行设置。 它可以通过 DI 实现这一点。 这样做的一个巨大好处是它减少了模块之间的耦合。 耦合是一种非常糟糕的开发模式,因为它使您的代码难以重构。

如前所述JavaScript 没有接口,因此依赖的抽象是隐式契约。 也就是说,一个对象/类向另一个对象/类公开的方法和属性。 在下面的示例中,隐式约定是 anInventoryTracker 的任何请求模块都将具有 requestItems 方法。

Bad:

class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

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

class InventoryTracker {
  constructor(items) {
    this.items = items;

    // BAD: We have created a dependency on a specific request implementation.
    // We should just have requestItems depend on a request method: `request`
    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);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

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

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ['WS'];
  }

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

// By constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses WebSockets.
const inventoryTracker = new InventoryTracker(
  ['apples', 'bananas'],
  new InventoryRequesterV2(),
);
inventoryTracker.requestItems();

测试

每次测试单一概念

Bad:

import assert from 'assert';

describe('MomentJS', () => {
  it('handles date boundaries', () => {
    let date;

    date = new MomentJS('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);

    date = new MomentJS('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);

    date = new MomentJS('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

Good:

import assert from 'assert';

describe('MomentJS', () => {
  it('handles 30-day months', () => {
    const date = new MomentJS('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);
  });

  it('handles leap year', () => {
    const date = new MomentJS('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);
  });

  it('handles non-leap year', () => {
    const date = new MomentJS('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

并发性

尽量使用 promise

Callbacks aren't clean, and they cause excessive amounts of nesting. With ES2015/ES6, Promises are a built-in global type. Use them!

Bad:

import { get } from 'request';
import { writeFile } from 'fs';

get(
  'https://en.wikipedia.org/wiki/Robert_Cecil_Martin',
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      writeFile('article.html', body, (writeErr) => {
        if (writeErr) {
          console.error(writeErr);
        } else {
          console.log('File written');
        }
      });
    }
  },
);

Good:

import { get } from 'request-promise';
import { writeFile } from 'fs-extra';

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then((body) => {
    return writeFile('article.html', body);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

Async/Await 更优于 Promise

Promise 是回调的一个非常干净的替代方案,但 ES2017/ES8 带来了 asyncawait ,它们提供了一个更干净的解决方案。 您所需要的只是一个以 async 关键字为前缀的函数,然后您就可以在没有 then 函数链的情况下命令式地编写逻辑。 如果您今天可以利用 ES2017/ES8 功能,请使用它!

Bad:

import { get } from 'request-promise';
import { writeFile } from 'fs-extra';

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then((body) => {
    return writeFile('article.html', body);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

Good:

import { get } from 'request-promise';
import { writeFile } from 'fs-extra';

async function getCleanCodeArticle() {
  try {
    const body = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
    await writeFile('article.html', body);
    console.log('File written');
  } catch (err) {
    console.error(err);
  }
}

getCleanCodeArticle();

错误警告捕捉

不要忘记捕捉报错

对捕获的错误不采取任何措施并不能让您永远修复或应对所述错误。 将错误记录到控制台 (console.log) 并没有好到哪里去,因为它经常会迷失在打印到控制台的大量内容中。 如果你在 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!
}

模式

一致化命名

JavaScript 是无类型的,所以大写可以告诉你很多关于变量、函数等的信息。这些规则是主观的,所以你的团队可以选择他们想要的任何规则。关键是,无论你们选择什么,保持一致就好。

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) {
  // The hash
  let hash = 0;

  // Length of string
  const length = data.length;

  // Loop through every character in data
  for (let i = 0; i < length; i++) {
    // Get character code.
    const char = data.charCodeAt(i);
    // Make the hash
    hash = (hash << 5) - hash + char;
    // Convert to 32-bit integer
    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;

    // Convert to 32-bit integer
    hash &= hash;
  }
}

不要在你的代码库中留下注释掉的代码

Bad:

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

Good:

doStuff();

不要写日志评论

记住,使用版本控制!不需要死代码、注释代码,尤其是日志注释。使用 git log 来获取历史记录

Bad:

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

Good:

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