The only way to test code quality is the number of times someone says “F * K” when they look at your code.

Code quality is proportional to its cleanliness. Clean code, both in quality is more reliable, but also for the later maintenance, upgrade laid a good foundation.

This article is not a code style guide, but a discussion of code readability, reusability, and extensibility.

We will discuss it from several aspects:

  1. variable
  2. function
  3. Objects and data structures
  4. class
  5. SOLID
  6. test
  7. asynchronous
  8. Error handling
  9. Code style.
  10. annotation

variable

Name variables with meaningful and commonly used words

Bad:

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

Good:

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

↑ Back to top

Maintain unity

The same project may have three different names for retrieving user information. It should be consistent. If you don’t know what to call it, go to Codelf and find out what other people call it.

Bad:

getUserInfo();
getClientData();
getCustomerRecord();
Copy the code

Good:

getUser()
Copy the code

↑ Back to top

Each constant should be named

You can use buddy. Js or ESLint to detect unnamed constants in code.

Bad:

// Will you still know what 86400000 is in three months?
setTimeout(blastOff, 86400000);
Copy the code

Good:

const MILLISECOND_IN_A_DAY = 86400000;

setTimeout(blastOff, MILLISECOND_IN_A_DAY);
Copy the code

↑ Back to top

Can be described

A new variable is created from a variable, and the new variable needs to be named, which means that you know what each variable does when you first see it.

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]);
Copy the code

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);
Copy the code

↑ Back to top

straightforward

Bad:

const locations = ['Austin'.'New York'.'San Francisco'];
locations.forEach((l) = > {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // We need to look at the other code to determine what 'l' is.
  dispatch(l);
});
Copy the code

Good:

const locations = ['Austin'.'New York'.'San Francisco'];
locations.forEach((location) = > {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch(location);
});
Copy the code

↑ Back to top

Avoid meaningless prefixes

If you create an object car, there is no need to name its color carColor.

Bad:

const car = {
  carMake: 'Honda'.carModel: 'Accord'.carColor: 'Blue'
};

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

Good:

const car = {
  make: 'Honda'.model: 'Accord'.color: 'Blue'
};

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

↑ Back to top

Use default values

Bad:

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

Good:

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

↑ Back to top

function

The fewer parameters, the better

If there are more than two arguments, use the ES2015/ES6 deconstruction syntax, regardless of the order of the arguments.

Bad:

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

Good:

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

createMenu({
  title: 'Foo'.body: 'Bar'.buttonText: 'Baz'.cancellable: true
});
Copy the code

↑ Back to top

Just do one thing

This is an old rule in software engineering. Following this rule will make your code more readable and easier to refactor. If you break this rule, your code will be difficult to test or reuse.

Bad:

function emailClients(clients) {
  clients.forEach((client) = > {
    const clientRecord = database.lookup(client);
    if(clientRecord.isActive()) { email(client); }}); }Copy the code

Good:

function emailActiveClients(clients) {
  clients
    .filter(isActiveClient)
    .forEach(email);
}
function isActiveClient(client) {
  const clientRecord = database.lookup(client);    
  return clientRecord.isActive();
}
Copy the code

↑ Back to top

As the name implies

The name of the function should tell you what it does.

Bad:

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

const date = new Date(a);// It's hard to know what to add to the date
addToDate(date, 1);
Copy the code

Good:

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

const date = new Date(a); addMonthToDate(1, date);
Copy the code

↑ Back to top

Only one layer of abstraction is required

If the function is nested too much, it will be difficult to reuse and test.

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...
  });
}
Copy the code

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;
}
Copy the code

↑ Back to top

Delete duplicate code

A lot of times it’s the same function, but because of one or two differences, you have to write two almost identical functions.

Optimizing repetitive code requires strong abstraction, and bad abstraction is worse than repetitive code. Therefore, the principle of SOLID must be followed in the abstraction process (what is SOLID? More on that later).

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);
  });
}
Copy the code

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); })}Copy the code

↑ Back to top

Object sets default properties

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);
Copy the code

Good:

const menuConfig = {
  title: 'Order'.// The 'body' key is missing
  buttonText: 'Send'.cancellable: true
};

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

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

createMenu(menuConfig);
Copy the code

↑ Back to top

Do not pass the flag parameter

Judging execution logic by the true or false of flag violates the principle of one function doing one thing.

Bad:

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

Good:

function createFile(name) {
  fs.create(name);
}
function createFileTemplate(name) {
  createFile(`./temp/${name}`)}Copy the code

↑ Back to top

Avoid Side effects (Part 1)

Functions take in a value and return a new value. Other actions we call side effects, such as modifying global variables, doing IO operations on files, etc.

When a function really needs side effects, such as IO operations on files, do not use multiple functions/classes for file operations. Use only one function/class for file operations. Which means side effects need to be dealt with in one place.

The three main sinkholes of side effects: haphazard modification of mutable data types, haphazard sharing of state without data structure, and failure to deal with side effects in a unified place.

Bad:

// The global variable is referenced by a function
// Now that the variable has changed from a string to an array, unexpected errors can occur if there are other function references.
var name = 'Ryan McDermott';

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

splitIntoFirstAndLastName();

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

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'];
Copy the code

↑ Back to top

Avoid Side effects (Part 2)

In JavaScript, primitive types are passed by assignment and objects and arrays are passed by reference. Take reference passing as an example:

Suppose we write a shopping cart and add items to the cart using the addItemToCart() method, modifying the cart array. The purchase() method is called at this point, and because of the passing of the reference, the shopping cart array is exactly the latest data.

Looks good, doesn’t it?

If the network fails when the user clicks to buy, the purchase() method is repeatedly called while the user adds new items, and the network is restored. Then the purchase() method gets the shopping cart array incorrectly.

To avoid this problem, we need to clone the shopping cart array and return the new array each time we add an item.

Bad:

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

Good:

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

↑ Back to top

Do not write global methods

In JavaScript, you should never pollute globally, creating unexpected bugs in a production environment. For example, if you add a diff method to array. prototype to determine the difference between two arrays. Your colleague is trying to do something similar, but his diff method is used to determine the difference between the first elements of two arrays. Obviously your methods will clash, so we can use ES2015/ES6 syntax to extend Array.

Bad:

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

Good:

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

↑ Back to top

I prefer functional programming to imperative

Functional change programming can make the logic of the code clearer and more elegant, easy to test.

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;
}
Copy the code

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)
Copy the code

↑ Back to top

Encapsulating conditional statement

Bad:

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

Good:

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

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}
Copy the code

↑ Back to top

Try not to use “not” conditionals

Bad:

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

if(! isDOMNodeNotPresent(node)) {// ...
}
Copy the code

Good:

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

if (isDOMNodePresent(node)) {
  // ...
}
Copy the code

↑ Back to top

Avoid conditional statements

Q: It is impossible to write code without conditional statements.

A: Most scenarios can be replaced by polymorphism.

Q: Polymorphisms work, but why not conditional statements?

A: To make your code more concise and readable, if you have A conditional judgment in your function, then your function is doing more than one thing, violating the single function rule.

Bad:

class Airplane {
  // ...
  
  // Obtain cruising altitude
  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(); }}}Copy the code

Good:

class Airplane {
  // ...
}
/ / a Boeing 777
class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getPassengerCount(); }}// Air Force One
class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude(); }}// Senas aircraft
class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getFuelExpenditure(); }}Copy the code

↑ Back to top

Avoiding type checking (Part 1)

JavaScript is untyped, which means you can pass any type of argument you want, and this freedom can get confusing and cause you to automatically check the type. Do you really need to check the type or is there something wrong with your API design?

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

Good:

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

↑ Back to top

Avoiding Type checking (Part 2)

If you need to do static type checking, such as strings, integers, etc., use TypeScript; otherwise your code will get long and smelly.

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

Good:

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

↑ Back to top

Don’t over-optimize

Modern browsers have done a lot of low-level optimizations, and many of the optimizations in the past were ineffective and wasted your time. To see what modern browsers have optimized, click here.

Bad:

// In older browsers, since 'list.length' is not cached, each iteration is computed, causing unnecessary overhead.
Modern browsers have been optimized for this.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}
Copy the code

Good:

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

↑ Back to top

Remove deprecated code

Most of the time some code has no use, but worry about the future will use, reluctant to delete.

If you forget about it, the code will be there forever.

Delete it. You can find it in the repository history.

Bad:

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

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

const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Copy the code

Good:

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

const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Copy the code

↑ Back to top

Objects and data structures

withget,setMethod operation data

This has many benefits, such as logging data to track errors; It’s easy to verify data when setting…

Bad:

function makeBankAccount() {
  // ...

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

const account = makeBankAccount();
account.balance = 100;
Copy the code

Good:

function makeBankAccount() {
  // Private variables
  let balance = 0;

  function getBalance() {
    return balance;
  }
  
  function setBalance(amount) {
    / /... Verify amount before updating balance
    balance = amount;
  }

  return {
    // ...
    getBalance,
    setBalance,
  };
}

const account = makeBankAccount();
account.setBalance(100);
Copy the code

↑ Back to top

Use private variables

Closures can be used to create private variables

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
Copy the code

Good:

function makeEmployee(name) {
  return {
    getName() {
      returnname; }}; }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
Copy the code

↑ Back to top

class

Use the class

Before ES2015/ES6, there was no syntax for classes, and only constructors were used to simulate classes, which were very unreadable.

Bad:

/ / animals
const Animal = function(age) {
  if(! (this instanceof Animal)) {
    throw new Error('Instantiate Animal with `new`');
  }

  this.age = age;
};

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

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

/ / human
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() {};
Copy the code

Good:

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

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

/ / human
class Human extends Mammal{
  constructor(age, furColor, languageSpoken) {
    super(age, furColor);
    this.languageSpoken = languageSpoken;
  };
  speak() {};
}
Copy the code

↑ Back to top

Chain calls

This pattern is quite useful and can be found in many libraries, such as jQuery, Lodash, etc. It keeps your code simple and elegant. It’s easy to implement, returning this at the end of a class method.

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();
Copy the code

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();
Copy the code

↑ Back to top

Don’t abuse inheritance

A lot of times inheritance is abused, resulting in poor readability. To figure out the relationship between two classes, inheritance represents a relationship of ownership, not of inclusion, e.g. Human->Animal vs. User->UserDetails

Bad:

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

  // ...
}

// TaxData is not an Employee, but a containment relationship.
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super(a);this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}
Copy the code

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

↑ Back to top

SOLID

SOLID is the combination of several words representing the single-function principle, the open and close principle, the Richter substitution principle, the interface isolation principle, and the dependency inversion principle.

Single function principle

If a class does too many things, it will be difficult to maintain later. We should clarify our responsibilities and reduce our dependence on each other.

Bad:

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

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

  verifyCredentials() {
    // ...}}Copy the code

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

↑ Back to top

The open closed principle

“Open” means that classes, modules, and functions should be extensible, and “closed” means that they should not be modified. This means that you can add features but not modify the source code.

Bad:

class AjaxAdapter extends Adapter {
  constructor() {
    super(a);this.name = 'ajaxAdapter'; }}class NodeAdapter extends Adapter {
  constructor() {
    super(a);this.name = 'nodeAdapter'; }}class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === 'ajaxAdapter') {
      return makeAjaxCall(url).then((response) = > {
        // Pass response and return
      });
    } else if (this.adapter.name === 'httpNodeAdapter') {
      return makeHttpCall(url).then((response) = > {
        // Pass response and return}); }}}function makeAjaxCall(url) {
  // Process request and return promise
}

function makeHttpCall(url) {
  // Process request and return promise
}
Copy the code

Good:

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

  request(url) {
    // Process request and return promise}}class NodeAdapter extends Adapter {
  constructor() {
    super(a);this.name = 'nodeAdapter';
  }

  request(url) {
    // Process request and return promise}}class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then((response) = > {
      // Pass response and return}); }}Copy the code

↑ Back to top

Richter’s substitution principle

The name is spooky, but the truth is that subclasses should not override the methods of their parent class.

Bad:

/ / rectangle
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; }}/ / square
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);
Copy the code

Good:

class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...}}class Rectangle extends Shape {
  constructor(width, height) {
    super(a);this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height; }}class Square extends Shape {
  constructor(length) {
    super(a);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);
Copy the code

↑ Back to top

Interface Isolation Principle

JavaScript has almost no concept of interfaces, so this principle is rarely used. The official definition is that “a client should not rely on interfaces it does not need”, which is to minimize interfaces and decouple them.

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

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

↑ Back to top

Dependency inversion principle

Just two points:

  1. High-level modules cannot depend on low-level modules, which depend on abstract interfaces.
  2. Abstract interfaces cannot depend on concrete implementations, which depend on abstract interfaces.

It boils down to two words: decoupling.

Bad:

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

  requestItem(item) {
    // ...}}// Inventory tracking
class InventoryTracker {
  constructor(items) {
    this.items = items;

    // We rely on a special request class, but we just need a request method.
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach((item) = > {
      this.requester.requestItem(item); }); }}const inventoryTracker = new InventoryTracker(['apples'.'bananas']);
inventoryTracker.requestItems();
Copy the code

Good:

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

  requestItems() {
    this.items.forEach((item) = > {
      this.requester.requestItem(item); }); }}/ / HTTP requests
class InventoryRequesterHTTP {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...}}/ / the webSocket request
class InventoryRequesterWS {
  constructor() {
    this.REQ_METHODS = ['WS'];
  }

  requestItem(item) {
    // ...}}// Decouple the request module through dependency injection so that we can easily replace it with webSocket requests.
const inventoryTracker = new InventoryTracker(['apples'.'bananas'].new InventoryRequesterHTTP());
inventoryTracker.requestItems();
Copy the code

↑ Back to top

test

As the project gets bigger and the timeline gets longer, some old code may not be touched for half a year. If you go live at this point, are you confident that it will work? Test coverage is proportional to your confidence.

PS: If you find your code difficult to test, then you should optimize your code.

Simplifying assumptions

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);
  });
});
Copy the code

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);
  });
});
Copy the code

↑ Back to top

asynchronous

No more callbacks

No one wants to look at code with nested callbacks. Promise callbacks instead.

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

Good:

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

↑ Back to top

Async/Await is simpler than Promises

Bad:

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

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

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); }}Copy the code

↑ Back to top

Error handling

Do not ignore throwing exceptions

Bad:

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

Good:

try {
  functionThatMightThrow();
} catch (error) {
  // This option is more intuitive than console.log
  console.error(error);
  // You can also alert the user in the interface
  notifyUserOfError(error);
  // The exception can also be passed back to the server
  reportErrorToService(error);
  // Other custom methods
}
Copy the code

↑ Back to top

Don’t forget to stand out in Promises

Bad:

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

Good:

getdata()
  .then((data) = > {
    functionThatMightThrow(data);
  })
  .catch((error) = > {
    // This option is more intuitive than console.log
    console.error(error);
    // You can also alert the user in the interface
    notifyUserOfError(error);
    // The exception can also be passed back to the server
    reportErrorToService(error);
    // Other custom methods
  });
Copy the code

↑ Back to top

Code style.

Code style is subjective, and debating which is good and which is not is a waste of life. There are a lot of tools out there that automate your code style, so just pick one that you like, and let’s talk about some of the non-automated parts.

Constant capital

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 {}
Copy the code

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 {}
Copy the code

↑ Back to top

Call first and call later

Just like we read a newspaper article, we read it from top to bottom, so we write the function declaration in front of the function call for easy reading.

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();
Copy the code

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();
Copy the code

↑ Back to top

annotation

Only the business logic needs annotations

More code comments are not always better.

Bad:

function hashIt(data) {
  // This is the initial value
  let hash = 0;

  // The length of the array
  const length = data.length;

  // loop array
  for (let i = 0; i < length; i++) {
    // Get the character code
    const char = data.charCodeAt(i);
    / / modify the hash
    hash = ((hash << 5) - hash) + char;
    // Convert to a 32-bit integerhash &= hash; }}Copy the code

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 a 32-bit integerhash &= hash; }}Copy the code

↑ Back to top

Delete commented code

Git exists to save your old code, so get rid of the commented code.

Bad:

doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Copy the code

Good:

doStuff();
Copy the code

↑ Back to top

Don’t keep a Journal

Remember you have Git! Git log can do this for you.

Bad:

/** * 2016-12-20: delete XXX * 2016-10-01: improve XXX * 2016-02-03: delete type check in line 12 * 2015-03-14: add a merge method */
function combine(a, b) {
  return a + b;
}
Copy the code

Good:

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

↑ Back to top

Comments do not need to be highlighted

Comment highlighting, instead of being a reminder, distracts you from reading the code.

Bad:

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

////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
  // ...
};
Copy the code

Good:

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

const actions = function() {
  // ...
};
Copy the code

↑ Back to top

Thanks for reading!

ref

Translated from clean-code-javascript by ryanmcdermott with some modifications.