JavaScript作风指南

Original Repository: ryanmcdermott/clean-code-javascript
中文版GitHub地点

JavaScript作风指南

引见

作者依据Robert C. Martin《代码整齐之道》总结了适用于JavaScript的软件工程准绳《Clean Code JavaScript》

本文是对其的翻译。

没必要严格恪守本文的一切准绳,偶然少恪守一些效果能够会更好,详细应依据现实状况决议。这是依据《代码整齐之道》作者多年履历整顿的代码优化发起,但也仅仅只是一份发起。

软件工程已生长了50多年,至今仍在不停前进。如今,把这些准绳看成试金石,尝试将他们作为团队代码质量审核的规范之一吧。

末了你须要晓得的是,这些东西不会让你马上变成一个优异的工程师,历久推行他们也并不意味着你能够万事大吉不再出错。千里之行,始于足下。我们须要常常和偕行们举行代码评审,不停优化本身的代码。不要恐惧改良代码质量所需支付的勤奋,加油。

变量

运用有意义,可读性好的变量名

反例:

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

正例:

var yearMonthDay = moment().format('YYYY/MM/DD');

回到目次

运用ES6的const定义常量

反例中运用”var”定义的”常量”是可变的。

在声明一个常量时,该常量在全部顺序中都应当是不可变的。

反例:

var FIRST_US_PRESIDENT = "George Washington";

正例:

const FIRST_US_PRESIDENT = "George Washington";

回到目次

对功用相似的变量名采纳一致的定名作风

反例:

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

正例:

getUser();

回到目次

运用易于检索称号

我们须要浏览的代码远比本身写的要多,使代码具有优越的可读性且易于检索异常主要。浏览变量名艰涩难明的代码对读者来说是一种相称蹩脚的体验。
让你的变量名易于检索。

反例:

// 525600 是什么?
for (var i = 0; i < 525600; i++) {
  runCronJob();
}

正例:

// Declare them as capitalized `var` globals.
var MINUTES_IN_A_YEAR = 525600;
for (var i = 0; i < MINUTES_IN_A_YEAR; i++) {
  runCronJob();
}

回到目次

运用申明变量(即有意义的变量名)

反例:

const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);

正例:

const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
const match = cityStateRegex.match(cityStateRegex)
const city = match[1];
const state = match[2];
saveCityState(city, state);

回到目次

不要绕太多的弯子

显式优于隐式。

反例:

var locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
  doStuff();
  doSomeOtherStuff();
  ...
  ...
  ...
  // l是什么?
  dispatch(l);
});

正例:

var locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
  doStuff();
  doSomeOtherStuff();
  ...
  ...
  ...
  dispatch(location);
});

回到目次

防止反复的形貌

当类/对象名已有意义时,对其变量举行定名不须要再次反复。

反例:

var Car = {
  carMake: 'Honda',
  carModel: 'Accord',
  carColor: 'Blue'
};

function paintCar(car) {
  car.carColor = 'Red';
}

正例:

var Car = {
  make: 'Honda',
  model: 'Accord',
  color: 'Blue'
};

function paintCar(car) {
  car.color = 'Red';
}

回到目次

防止无意义的前提推断

反例:

function createMicrobrewery(name) {
  var breweryName;
  if (name) {
    breweryName = name;
  } else {
    breweryName = 'Hipster Brew Co.';
  }
}

正例:

function createMicrobrewery(name) {
  var breweryName = name || 'Hipster Brew Co.'
}

回到目次

函数

函数参数 (抱负状况下应不凌驾2个)

限定函数参数数目很有必要,这么做使得在测试函数时越发轻松。过量的参数将致使难以采纳有用的测试用例对函数的各个参数举行测试。

应防止三个以上参数的函数。一般状况下,参数凌驾两个意味着函数功用过于庞杂,这时刻须要从新优化你的函数。当确切须要多个参数时,大多状况下能够斟酌这些参数封装成一个对象。

JS定义对象异常轻易,当须要多个参数时,能够运用一个对象举行替代。

反例:

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

正例:

var menuConfig = {
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
}

function createMenu(menuConfig) {
  ...
}

回到目次

函数功用的单一性

这是软件功用中最主要的准绳之一。

功用不单一的函数将致使难以重构、测试和明白。功用单一的函数易于重构,并使代码越发清洁。

反例:

function emailClients(clients) {
  clients.forEach(client => {
    let clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

正例:

function emailClients(clients) {
  clients.forEach(client => {
    emailClientIfNeeded(client);
  });
}

function emailClientIfNeeded(client) {
  if (isClientActive(client)) {
    email(client);
  }
}

function isClientActive(client) {
  let clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

回到目次

函数名应明白表明其功用

反例:

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

let date = new Date();

// 很难明白dateAdd(date, 1)是什么意义

正例:

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

let date = new Date();
dateAddMonth(date, 1);

回到目次

函数应当只做一层笼统

当函数的须要的笼统多于一层时一般意味着函数功用过于庞杂,需将其举行剖析以进步其可重用性和可测试性。

反例:

function parseBetterJSAlternative(code) {
  let REGEXES = [
    // ...
  ];

  let statements = code.split(' ');
  let tokens;
  REGEXES.forEach((REGEX) => {
    statements.forEach((statement) => {
      // ...
    })
  });

  let ast;
  tokens.forEach((token) => {
    // lex...
  });

  ast.forEach((node) => {
    // parse...
  })
}

正例:

function tokenize(code) {
  let REGEXES = [
    // ...
  ];

  let statements = code.split(' ');
  let tokens;
  REGEXES.forEach((REGEX) => {
    statements.forEach((statement) => {
      // ...
    })
  });

  return tokens;
}

function lexer(tokens) {
  let ast;
  tokens.forEach((token) => {
    // lex...
  });

  return ast;
}

function parseBetterJSAlternative(code) {
  let tokens = tokenize(code);
  let ast = lexer(tokens);
  ast.forEach((node) => {
    // parse...
  })
}

回到目次

移除反复的代码

永久、永久、永久不要在任何轮回下有反复的代码。

这类做法毫无意义且潜伏风险极大。反复的代码意味着逻辑变化时须要对不止一处举行修正。JS弱范例的特征使得函数具有更强的普适性。好好应用这一长处吧。

反例:

function showDeveloperList(developers) {
  developers.forEach(developer => {
    var expectedSalary = developer.calculateExpectedSalary();
    var experience = developer.getExperience();
    var githubLink = developer.getGithubLink();
    var data = {
      expectedSalary: expectedSalary,
      experience: experience,
      githubLink: githubLink
    };

    render(data);
  });
}

function showManagerList(managers) {
  managers.forEach(manager => {
    var expectedSalary = manager.calculateExpectedSalary();
    var experience = manager.getExperience();
    var portfolio = manager.getMBAProjects();
    var data = {
      expectedSalary: expectedSalary,
      experience: experience,
      portfolio: portfolio
    };

    render(data);
  });
}

正例:

function showList(employees) {
  employees.forEach(employee => {
    var expectedSalary = employee.calculateExpectedSalary();
    var experience = employee.getExperience();
    var portfolio;

    if (employee.type === 'manager') {
      portfolio = employee.getMBAProjects();
    } else {
      portfolio = employee.getGithubLink();
    }

    var data = {
      expectedSalary: expectedSalary,
      experience: experience,
      portfolio: portfolio
    };

    render(data);
  });
}

回到目次

采纳默许参数精简代码

反例:

function writeForumComment(subject, body) {
  subject = subject || 'No Subject';
  body = body || 'No text';
}

正例:

function writeForumComment(subject = 'No subject', body = 'No text') {
  ...
}

回到目次

运用Object.assign设置默许对象

反例:

var menuConfig = {
  title: null,
  body: 'Bar',
  buttonText: null,
  cancellable: true
}

function createMenu(config) {
  config.title = config.title || 'Foo'
  config.body = config.body || 'Bar'
  config.buttonText = config.buttonText || 'Baz'
  config.cancellable = config.cancellable === undefined ? config.cancellable : true;

}

createMenu(menuConfig);

正例:

var menuConfig = {
  title: 'Order',
  // User did not include 'body' key
  buttonText: 'Send',
  cancellable: true
}

function createMenu(config) {
  config = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);

  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

回到目次

不要运用标记(Flag)作为函数参数

这一般意味着函数的功用的单一性已被损坏。此时应斟酌对函数举行再次分别。

反例:

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

正例:

function createTempFile(name) {
  fs.create('./temp/' + name);
}

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

回到目次

防止副作用

当函数发生了除了“接收一个值并返回一个效果”以外的行动时,称该函数发生了副作用。比方写文件、修正全局变量或将你的钱全转给了一个陌生人等。

顺序在某些状况下确切须要副作用这一行动,如先前例子中的写文件。这时刻应当将这些功用集合在一起,不要用多个函数/类修正某个文件。用且只用一个service完成这一需求。

反例:

// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
var name = 'Ryan McDermott';

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

splitIntoFirstAndLastName();

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

正例:

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

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

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

回到目次

不要写全局函数

在JS中污染全局是一个异常不好的实践,这么做能够和其他库起争执,且挪用你的API的用户在现实环境中获得一个exception前对这一状况是一窍不通的。

设想以下例子:假如你想扩大JS中的Array,为其增添一个diff函数显现两个数组间的差别,此时应怎样去做?你能够将diff写入Array.prototype,但这么做会和其他有相似需求的库形成争执。假如另一个库对diff的需求为比较一个数组中扫尾元素间的差别呢?

运用ES6中的class对全局的Array做简朴的扩大显然是一个更棒的挑选。

反例:

Array.prototype.diff = function(comparisonArray) {
  var values = [];
  var hash = {};

  for (var i of comparisonArray) {
    hash[i] = true;
  }

  for (var i of this) {
    if (!hash[i]) {
      values.push(i);
    }
  }

  return values;
}

正例:

class SuperArray extends Array {
  constructor(...args) {
    super(...args);
  }

  diff(comparisonArray) {
    var values = [];
    var hash = {};

    for (var i of comparisonArray) {
      hash[i] = true;
    }

    for (var i of this) {
      if (!hash[i]) {
        values.push(i);
      }
    }

    return values;
  }
}

回到目次

采纳函数式编程

函数式的编程具有更清洁且便于测试的特征。只管的运用这类作风吧。

反例:

const programmerOutput = [
  {
    name: 'Uncle Bobby',
    linesOfCode: 500
  }, {
    name: 'Suzie Q',
    linesOfCode: 1500
  }, {
    name: 'Jimmy Gosling',
    linesOfCode: 150
  }, {
    name: 'Gracie Hopper',
    linesOfCode: 1000
  }
];

var totalOutput = 0;

for (var i = 0; i < programmerOutput.length; i++) {
  totalOutput += programmerOutput[i].linesOfCode;
}

正例:

const programmerOutput = [
  {
    name: 'Uncle Bobby',
    linesOfCode: 500
  }, {
    name: 'Suzie Q',
    linesOfCode: 1500
  }, {
    name: 'Jimmy Gosling',
    linesOfCode: 150
  }, {
    name: 'Gracie Hopper',
    linesOfCode: 1000
  }
];

var totalOutput = programmerOutput
  .map((programmer) => programmer.linesOfCode)
  .reduce((acc, linesOfCode) => acc + linesOfCode, 0);

回到目次

封装推断前提

反例:

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

正例:

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

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

回到目次

防止“否认状况”的推断

反例:

function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}

正例:

function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

回到目次

防止前提推断

这看起来好像不太能够。

大多人听到这的第一反应是:“怎么能够不必if完成其他功用呢?”很多状况下经由过程运用多态(polymorphism)能够到达一样的目标。

第二个问题在于采纳这类体式格局的缘由是什么。答案是我们之前提到过的:坚持函数功用的单一性。

反例:

class Airplane {
  //...
  getCruisingAltitude() {
    switch (this.type) {
      case '777':
        return getMaxAltitude() - getPassengerCount();
      case 'Air Force One':
        return getMaxAltitude();
      case 'Cessna':
        return getMaxAltitude() - getFuelExpenditure();
    }
  }
}

正例:

class Airplane {
  //...
}

class Boeing777 extends Airplane {
  //...
  getCruisingAltitude() {
    return getMaxAltitude() - getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  //...
  getCruisingAltitude() {
    return getMaxAltitude();
  }
}

class Cessna extends Airplane {
  //...
  getCruisingAltitude() {
    return getMaxAltitude() - getFuelExpenditure();
  }
}

回到目次

防止范例推断(part 1)

JS是弱范例言语,这意味着函数可接收恣意范例的参数。

偶然这会对你带来贫苦,你会对参数做一些范例推断。有很多要领能够防止这些状况。

反例:

function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.peddle(this.currentLocation, new Location('texas'));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location('texas'));
  }
}

正例:

function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location('texas'));
}

回到目次

防止范例推断(part 2)

假如需处置惩罚的数据为字符串,整型,数组等范例,没法运用多态并仍有必要对其举行范例检测时,能够斟酌运用TypeScript。

反例:

function combine(val1, val2) {
  if (typeof val1 == "number" && typeof val2 == "number" ||
      typeof val1 == "string" && typeof val2 == "string") {
    return val1 + val2;
  } else {
    throw new Error('Must be of type String or Number');
  }
}

正例:

function combine(val1, val2) {
  return val1 + val2;
}

回到目次

防止过分优化

当代的浏览器在运转时会对代码自动举行优化。偶然工资对代码举行优化多是在浪费时间。

这里能够找到很多真正须要优化的处所

反例:


// 这里运用变量len是由于在老式浏览器中,
// 直接运用正例中的体式格局会致使每次轮回均反复盘算list.length的值,
// 而在当代浏览器中会自动完成优化,这一行动是没有必要的
for (var i = 0, len = list.length; i < len; i++) {
  // ...
}

正例:

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

回到目次

删除无效的代码

不再被挪用的代码应实时删除。

反例:

function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}

var req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

正例:

function newRequestModule(url) {
  // ...
}

var req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

回到目次

对象和数据结构

运用getters和setters

JS没有接口或范例,因而完成这一形式是很难题的,由于我们并没有相似publicprivate的关键词。

然则,运用getters和setters猎取对象的数据远比直接运用点操纵符具有上风。为何呢?

  1. 当须要对猎取的对象属性实行分外操纵时。

  2. 实行set时能够增添划定规矩对要变量的合法性举行推断。

  3. 封装了内部逻辑。

  4. 在存取时能够轻易的增添日记和毛病处置惩罚。

  5. 继续该类时能够重载默许行动。

  6. 从服务器猎取数据时能够举行懒加载。

反例:

class BankAccount {
  constructor() {
       this.balance = 1000;
  }
}

let bankAccount = new BankAccount();

// Buy shoes...
bankAccount.balance = bankAccount.balance - 100;

正例:

class BankAccount {
  constructor() {
       this.balance = 1000;
  }

  // It doesn't have to be prefixed with `get` or `set` to be a getter/setter
  withdraw(amount) {
      if (verifyAmountCanBeDeducted(amount)) {
        this.balance -= amount;
      }
  }
}

let bankAccount = new BankAccount();

// Buy shoes...
bankAccount.withdraw(100);

回到目次

让对象具有私有成员

能够经由过程闭包完成

反例:


var Employee = function(name) {
  this.name = name;
}

Employee.prototype.getName = function() {
  return this.name;
}

var employee = new Employee('John Doe');
console.log('Employee name: ' + employee.getName()); // Employee name: John Doe
delete employee.name;
console.log('Employee name: ' + employee.getName()); // Employee name: undefined

正例:

var Employee = (function() {
  function Employee(name) {
    this.getName = function() {
      return name;
    };
  }

  return Employee;
}());

var employee = new Employee('John Doe');
console.log('Employee name: ' + employee.getName()); // Employee name: John Doe
delete employee.name;
console.log('Employee name: ' + employee.getName()); // Employee name: John Doe

回到目次

单一职责准绳 (SRP)

如《代码整齐之道》一书中所述,“修正一个类的来由不应当凌驾一个”。

将多个功用塞进一个类的主意很诱人,但这将致使你的类没法到达观点上的内聚,并常常不能不举行修正。

最小化对一个类须要修正的次数是异常有必要的。假如一个类具有太多太杂的功用,当你对个中一小部分举行修正时,将很难设想到这一修够对代码库中依靠该类的其他模块会带来什么样的影响。

反例:

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

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

  verifyCredentials(user) {
    // ...
  }
}

正例:

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

  verifyCredentials() {
    // ...
  }
}


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

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

回到目次

开/闭准绳 (OCP)

“代码实体(类,模块,函数等)应当易于扩大,难于修正。”

这一准绳指的是我们应许可用户轻易的扩大我们代码模块的功用,而不须要翻开js文件源码手动对其举行修正。

反例:

class AjaxRequester {
  constructor() {
    // What if we wanted another HTTP Method, like DELETE? We would have to
    // open this file up and modify this and put it in manually.
    this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
  }

  get(url) {
    // ...
  }

}

正例:

class AjaxRequester {
  constructor() {
    this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
  }

  get(url) {
    // ...
  }

  addHTTPMethod(method) {
    this.HTTP_METHODS.push(method);
  }
}

回到目次

利斯科夫替代准绳 (LSP)

“子类对象应当能够替代其超类对象被运用”。

也就是说,假如有一个父类和一个子类,当采纳子类替代父类时不应当发生毛病的效果。

反例:

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  constructor() {
    super();
  }

  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderLargeRectangles(rectangles) {
  rectangles.forEach((rectangle) => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    let area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20.
    rectangle.render(area);
  })
}

let rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

正例:

class Shape {
  constructor() {}

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor() {
    super();
    this.width = 0;
    this.height = 0;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor() {
    super();
    this.length = 0;
  }

  setLength(length) {
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach((shape) => {
    switch (shape.constructor.name) {
      case 'Square':
        shape.setLength(5);
      case 'Rectangle':
        shape.setWidth(4);
        shape.setHeight(5);
    }

    let area = shape.getArea();
    shape.render(area);
  })
}

let shapes = [new Rectangle(), new Rectangle(), new Square()];
renderLargeShapes(shapes);

回到目次

接口断绝准绳 (ISP)

“客户端不应当依靠它不须要的接口;一个类对另一个类的依靠应当建立在最小的接口上。”

在JS中,当一个类须要很多参数设置才天生一个对象时,或许大多时刻不须要设置这么多的参数。此时削减对设置参数数目标需求是有益的。

反例:

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

  setup() {
    this.rootNode = this.settings.rootNode;
    this.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

let $ = new DOMTraverser({
  rootNode: document.getElementsByTagName('body'),
  animationModule: function() {} // Most of the time, we won't need to animate when traversing.
  // ...
});

正例:

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

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }

  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }

  traverse() {
    // ...
  }
}

let $ = new DOMTraverser({
  rootNode: document.getElementsByTagName('body'),
  options: {
    animationModule: function() {}
  }
});

回到目次

依靠反转准绳 (DIP)

该准绳有两个中心点:

  1. 高层模块不应当依靠于低层模块。他们都应当依靠于笼统接口。

  2. 笼统接口应当离开详细完成,详细完成应当依靠于笼统接口。

反例:

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

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

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

let inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();

正例:

class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach((item) => {
      this.requester.requestItem(item);
    });
  }
}

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

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

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

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

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

回到目次

运用ES6的classes而不是ES5的Function

典范的ES5的类(function)在继续、组织和要领定义方面可读性较差。

当须要继续时,优先选用classes。

然则,当在须要更大更庞杂的对象时,最好优先挑选更小的function而非classes。

反例:

var Animal = function(age) {
    if (!(this instanceof Animal)) {
        throw new Error("Instantiate Animal with `new`");
    }

    this.age = age;
};

Animal.prototype.move = function() {};

var Mammal = function(age, furColor) {
    if (!(this instanceof Mammal)) {
        throw new Error("Instantiate Mammal with `new`");
    }

    Animal.call(this, age);
    this.furColor = furColor;
};

Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function() {};

var Human = function(age, furColor, languageSpoken) {
    if (!(this instanceof Human)) {
        throw new Error("Instantiate Human with `new`");
    }

    Mammal.call(this, age, furColor);
    this.languageSpoken = languageSpoken;
};

Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function() {};

正例:

class Animal {
    constructor(age) {
        this.age = age;
    }

    move() {}
}

class Mammal extends Animal {
    constructor(age, furColor) {
        super(age);
        this.furColor = furColor;
    }

    liveBirth() {}
}

class Human extends Mammal {
    constructor(age, furColor, languageSpoken) {
        super(age, furColor);
        this.languageSpoken = languageSpoken;
    }

    speak() {}
}

回到目次

运用要领链

这里我们的明白与《代码整齐之道》的发起有些差别。

有争论说要领链不够清洁且违反了德米特轨则,或许这是对的,但这类要领在JS及很多库(如JQuery)中显得异常有用。

因而,我以为在JS中运用要领链是异常适宜的。在class的函数中返回this,能够轻易的将类须要实行的多个要领链接起来。

反例:

class Car {
  constructor() {
    this.make = 'Honda';
    this.model = 'Accord';
    this.color = 'white';
  }

  setMake(make) {
    this.name = name;
  }

  setModel(model) {
    this.model = model;
  }

  setColor(color) {
    this.color = color;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}

let car = new Car();
car.setColor('pink');
car.setMake('Ford');
car.setModel('F-150')
car.save();

正例:

class Car {
  constructor() {
    this.make = 'Honda';
    this.model = 'Accord';
    this.color = 'white';
  }

  setMake(make) {
    this.name = name;
    // NOTE: 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);
  }
}

let car = new Car()
  .setColor('pink')
  .setMake('Ford')
  .setModel('F-150')
  .save();

回到目次

优先运用组合形式而非继续

在有名的设想形式一书中提到,应多运用组合形式而非继续。

这么做有很多长处,在想要运用继续前,多想一想可否经由过程组合形式满足需求吧。

那末,在什么时刻继续具有更大的上风呢?这取决于你的详细需求,但大多状况下,能够恪守以下三点:

  1. 继续关联表现为”是一个”而非”有一个”(如动物->人 和 用户->用户细节)

  2. 能够复用基类的代码(“Human”能够看成是”All animal”的一种)

  3. 愿望当基类转变时一切派生类都受到影响(如修正”all animals”挪动时的卡路里消耗量)

反例:

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

  // ...
}

正例:

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

  }

  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}

class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

回到目次

测试

一些好的掩盖东西.

一些好的JS测试框架

单一的测试每一个观点

反例:

const assert = require('assert');

describe('MakeMomentJSGreatAgain', function() {
  it('handles date boundaries', function() {
    let date;

    date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    date.shouldEqual('1/31/2015');

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

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

正例:

const assert = require('assert');

describe('MakeMomentJSGreatAgain', function() {
  it('handles 30-day months', function() {
    let date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    date.shouldEqual('1/31/2015');
  });

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

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

回到目次

并发

用Promises替代回调

回调不够整齐并会形成大批的嵌套。ES6内嵌了Promises,运用它吧。

反例:

require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', function(err, response) {
  if (err) {
    console.error(err);
  }
  else {
    require('fs').writeFile('article.html', response.body, function(err) {
      if (err) {
        console.error(err);
      } else {
        console.log('File written');
      }
    })
  }
})

正例:

require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then(function(response) {
    return require('fs-promise').writeFile('article.html', response);
  })
  .then(function() {
    console.log('File written');
  })
  .catch(function(err) {
    console.error(err);
  })

回到目次

Async/Await是较Promises更好的挑选

Promises是较回调而言更好的一种挑选,但ES7中的async和await更赛过Promises。

在能运用ES7特征的状况下能够只管运用他们替代Promises。

反例:

require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then(function(response) {
    return require('fs-promise').writeFile('article.html', response);
  })
  .then(function() {
    console.log('File written');
  })
  .catch(function(err) {
    console.error(err);
  })

正例:

async function getCleanCodeArticle() {
  try {
    var request = await require('request-promise')
    var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
    var fileHandle = await require('fs-promise');

    await fileHandle.writeFile('article.html', response);
    console.log('File written');
  } catch(err) {
    console.log(err);
  }
}

回到目次

毛病处置惩罚

毛病抛出是个好东西!这使得你能够胜利定位运转状况中的顺序发生毛病的位置。

别忘了捕捉毛病

对捕捉的毛病不做任何处置惩罚是没有意义的。

代码中try/catch的意味着你以为这里能够涌现一些毛病,你应当对这些能够的毛病存在响应的处置惩罚计划。

反例:

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

正例:

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

不要疏忽被谢绝的promises

来由同try/catch.

反例:

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

正例:

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

回到目次

花样化

花样化是一件主观的事。犹如这里的很多划定规矩一样,这里并没有肯定/马上须要恪守的划定规矩。能够在这里完成花样的自动化。

大小写一致

JS是弱范例言语,合理的采纳大小写能够通知你关于变量/函数等的很多音讯。

这些划定规矩是主观定义的,团队能够依据喜好举行挑选。重点在于不管挑选何种作风,都须要注重坚持一致性。

反例:

var DAYS_IN_WEEK = 7;
var daysInMonth = 30;

var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
var Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restore_database() {}

class animal {}
class Alpaca {}

正例:

var DAYS_IN_WEEK = 7;
var DAYS_IN_MONTH = 30;

var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
var artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restoreDatabase() {}

class Animal {}
class Alpaca {}

回到目次

挪用函数的函数和被调函数应放在较近的位置

当函数间存在互相挪用的状况时,应将二者置于较近的位置。

抱负状况下,应将挪用其他函数的函数写在被挪用函数的上方。

反例:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  lookupMananger() {
    return db.lookup(this.employee, 'manager');
  }

  getPeerReviews() {
    let peers = this.lookupPeers();
    // ...
  }

  perfReview() {
      getPeerReviews();
      getManagerReview();
      getSelfReview();
  }

  getManagerReview() {
    let manager = this.lookupManager();
  }

  getSelfReview() {
    // ...
  }
}

let review = new PerformanceReview(user);
review.perfReview();

正例:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  perfReview() {
      getPeerReviews();
      getManagerReview();
      getSelfReview();
  }

  getPeerReviews() {
    let peers = this.lookupPeers();
    // ...
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  getManagerReview() {
    let manager = this.lookupManager();
  }

  lookupMananger() {
    return db.lookup(this.employee, 'manager');
  }

  getSelfReview() {
    // ...
  }
}

let review = new PerformanceReview(employee);
review.perfReview();

回到目次

解释

只对存在肯定营业逻辑复制性的代码举行解释

解释并非必需的,好的代码是能够让人一览无余,不必过量无谓的解释。

反例:

function hashIt(data) {
  // The hash
  var hash = 0;

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

  // Loop through every character in data
  for (var i = 0; i < length; i++) {
    // Get character code.
    var char = data.charCodeAt(i);
    // Make the hash
    hash = ((hash << 5) - hash) + char;
    // Convert to 32-bit integer
    hash = hash & hash;
  }
}

正例:


function hashIt(data) {
  var hash = 0;
  var length = data.length;

  for (var i = 0; i < length; i++) {
    var char = data.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;

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

回到目次

不要在代码库中遗留被解释掉的代码

版本掌握的存在是有缘由的。让旧代码存在于你的history里吧。

反例:

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

正例:

doStuff();

回到目次

不须要版本更新范例解释

记着,我们能够运用版本掌握。废代码、被解释的代码及用解释纪录代码中的版本更新申明都是没有必要的。

须要时能够运用git log猎取汗青版本。

反例:

/**
 * 2016-12-20: Removed monads, didn't understand them (RM)
 * 2016-10-01: Improved using special monads (JP)
 * 2016-02-03: Removed type-checking (LI)
 * 2015-03-14: Added combine with type-checking (JR)
 */
function combine(a, b) {
  return a + b;
}

正例:

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

回到目次

防止位置标记

这些东西一般只能代码贫苦,采纳恰当的缩进就能够了。

反例:

////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
let $scope.model = {
  menu: 'foo',
  nav: 'bar'
};

////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
let actions = function() {
  // ...
}

正例:

let $scope.model = {
  menu: 'foo',
  nav: 'bar'
};

let actions = function() {
  // ...
}

回到目次

防止在源文件中写入执法批评

将你的LICENSE文件置于源码目次树的根目次。

反例:

/*
The MIT License (MIT)

Copyright (c) 2016 Ryan McDermott

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
*/

function calculateBill() {
  // ...
}

正例:

function calculateBill() {
  // ...
}

回到目次

    原文作者:Pines_Cheng
    原文地址: https://segmentfault.com/a/1190000008227094
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞