Clean JavaScript: Write Clean JavaScript code translated from clean-code-javascript. This paper belongs to the author’s Web front-end introduction and engineering practice.

The Introduction:

Many developers have embraced the software engineering principles outlined in Robert C. Martin’s Clean Code, and this article is a summary of their practical application to JavaScript development. This article is not just a style guide, but a guide on how to write highly readable, highly available, and reconfigurable software systems based on JavaScript. While this article compares good and bad practices, it does not suggest that all guidelines should be mandatory. In fact, the concept of Clean Code can vary from team to team and developer to developer, and many of the points in this article are controversial. Software engineering has been going on for more than 50 years, and as we move forward, it’s hard to say that any principle is always true. The author hopes that these guidelines and considerations will serve as a litmus test for the quality of a team’s JavaScript code.

Last but not least, good code and good architecture come slowly, not too quickly. A journey of a thousand miles begins with a single step. There are bound to be many detours and mistakes on the way forward, but as long as we keep adjusting the direction, we will always return to the right road. We can’t be afraid of change, and we can’t take what other people say as the last word. No matter how many years old programmers make mistakes.

VariablesVariables:

Use pronounce variable names that make sense

Bad:

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

Good:

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

Use the same keywords for variables of the same type

Bad:

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

Good:

getUser();
Copy the code

Use searchable names

During development, we spend a lot more time reading code than writing it, so it’s important to make your code readable and searchable. Remember, don’t cheat yourself.

Bad:

// What does 525600 mean? for (var i = 0; i < 525600; i++) { runCronJob(); }Copy the code

Good:

Var MINUTES_IN_A_YEAR = 525600; for (var i = 0; i < MINUTES_IN_A_YEAR; i++) { runCronJob(); }Copy the code

Use descriptive temporary variables

Bad:

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

Good:

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

Avoid confusing temporary variables

Avoid short, meaningless variable names during traversal or mapping.

Bad:

var locations = ['Austin', 'New York', 'San Francisco']; locations.forEach((l) => { doStuff(); doSomeOtherStuff(); . . . // Wait, what is `l` for again? dispatch(l); });Copy the code

Good:

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

Avoid adding unnecessary content

If your class/instance name already expresses some information, there is no need to repeat the name in the class/instance attributes.

Bad:

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

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

Good:

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

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

Short-circuiting is better than conditional selection

Bad:

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

Good:

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

function

It is best to have no more than two function arguments

Limiting the number of arguments to a function is important because it makes it easier to test the function and avoid messing up your code when you need to run different Case tests. We should try to limit the number of arguments to less than or equal to two. If you have more than two arguments, it is recommended to use higher-order objects for proper encapsulation.

Bad:

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

Good:

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

function createMenu(menuConfig) {
  ...
}
Copy the code

Functions should follow the single responsibility principle

This is by far one of the most important principles in software engineering. If we assign too much responsibility to a single function, it will be difficult to use for composition, testing, and so on. If you keep functions single-responsibility, it’s easier to refactor and make your code more readable.

Bad:

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

Good:

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

Function names should reflect their functionality

Bad:

function dateAdd(date, month) { // ... } let date = new Date(); DateAdd (date, 1); dateAdd(date, 1);Copy the code

Good:

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

let date = new Date();
dateAddMonth(date, 1);
Copy the code

Functions should only be a layer of abstraction

This rule is similar to the single responsibility rule, but tends to focus on the level of abstraction of a function. If we add too many layers of abstraction to a single function, it will also make the function less readable and more difficult to refactor.

Bad:

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

Good:

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

Remove duplicate code

Do not allow duplicate code under any circumstances. Repetitive code refers to the intersection of multiple pieces of code that need to be modified to modify a single piece of logic. JavaScript itself is a weakly typed language, making it easier to write generic functions.

Bad:

function showDeveloperList(developers) {
  developers.forEach(developers => {
    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);
  });
}
Copy the code

Good:

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

Use the default argument to replace the or operation

Bad:

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

Good:

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

Assign sets the default value using object.assign

Bad:

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

Good:

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

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

createMenu(menuConfig);
Copy the code

Avoid using Flags in arguments

Some developers use Flags to control functions to execute different logic flows, but as we mentioned above, we should split the function into different parts and then call different functions on outer calls based on Flags.

Bad:

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

Good:

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

function createFile(name) {
  fs.create(name);
}
Copy the code

Avoid redundant side effects

A function is said to have side effects if it does more than accept input and return values. Typical side effects include writing files, modifying some global variables, modifying memory parameters, and so on. In programming we inevitably have side effects, such as writing to an external file in the example above. Instead of splitting the writing into several classes or functions, you should have all the file writing handled by a single service. The biggest advantage of this is that it avoids sharing state between different objects, which is the root of all evil.

Bad:

Var name = 'Ryan McDermott'; var name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { name = name.split(' '); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott'];Copy the code

Good:

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

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

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

Avoid contaminating global functions

One of the worst practices in JavaScript is to modify a global function to point it to another library or custom function, but this can be frustrating to an uninitiated user. If you want to add a diff function to JavaScript’s native Array to show the difference between two arrays. You can choose to mount functions to array. prototype, but this will likely conflict with other libraries that intend to use this location. We prefer to use ES6 classes and use inheritance to add new functionality.

Bad:

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

Good:

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

Prioritize functional programming over imperative programming

JavaScript is not a purely functional programming language like Haskell, but it does embrace the idea of practicing functional programming. Functional programming is more readable and easier 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
  }
];

var totalOutput = 0;

for (var 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
  }
];

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

Selection of package conditions

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

Avoid negative class conditions

Bad:

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

Good:

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

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

Avoid conditional selection

The first time many people hear this concept, how do you program without an if conditional selection statement? Here we recommend using polymorphism to achieve this goal, because embedding too many IF statements in a function or class can cause that function or class to break the single responsibility principle.

Bad:

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

Good:

class Airplane { //... } class Boeing777 extends Airplane { //... getCruisingAltitude() { return getMaxAltitude() - getPassengerCount(); } } class AirForceOne extends Airplane { //... getCruisingAltitude() { return getMaxAltitude(); } } class Cesna extends Airplane { //... getCruisingAltitude() { return getMaxAltitude() - getFuelExpenditure(); }}Copy the code

Avoid relying on type detection

Many times we rely on JavaScript input parameter types to enter different control flows, but given JavaScript’s nature as a weakly typed language, we should avoid this practice. The first approach is to use a more consistent interface.

Bad:

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

Good:

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

Avoid relying on type detection

If you need to manipulate basic data types like strings, values, lists, etc., you can’t rely on polymorphism to implement type detection. The recommendation is to use TypeScript, which adds static typing support to normal JavaScript.

Bad:

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

Good:

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

Avoid over-optimization

Modern browsers already have a lot of runtime optimizations, so many times it’s just a waste of time to follow the age-old optimizations. Refer to this for recommended optimization points.

Bad:

// On old browsers, each iteration would be costly because `len` would be
// recomputed. In modern browsers, this is optimized.
for (var i = 0, len = list.length; i < len; i++) {
  // ...
}
Copy the code

Good:

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

Remove deprecated code

Deprecated code is just like duplicate code, and there is no reason to keep it. Just in case, it’s not recommended to delete them from Git’s history entirely.

Bad:

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

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

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

Good:

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

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

Objects and data structures

Use getters and setters

In JavaScript, it is recommended to use getters or setters instead of reading or assigning values directly. However, JavaScript doesn’t have keywords like public or private, so it’s hard to enforce restrictions through interfaces. However, we strongly recommend using getters or setters for the following advantages: 1. If you’re going to get more than just the original value, using getters can avoid modifying every value. 2. Use set to add validation easily.

  1. Encapsulate the internal representation.

  2. Easy logging and error handling.

  3. The ability to override default functionality through inheritance.

  4. Lazy loading of attributes is supported.

Bad:

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

let bankAccount = new BankAccount();

// Buy shoes...
bankAccount.balance = bankAccount.balance - 100;
Copy the code

Good:

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

Add private attributes to an object

Private attributes can be added using closures:

Bad:

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

Good:

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

class

Single responsibility principle

As stated in Clean Code, you should not change the Code of a class for more than one reason, because you can cram too much functionality into a class. Minimizing the number of times you need to change a class is critical to code stability; too many changes can affect other modules in the code base that depend on that class.

Bad:

class UserSettings { constructor(user) { this.user = user; } changeSettings(settings) { if (this.verifyCredentials(user)) { // ... } } verifyCredentials(user) { // ... }}Copy the code

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

Open and closed principle

As Bertrand Meyer points out, entities such as classes, modules, and functions should be open to extension and resistant to modification. In other words, we recommend inheriting and extending a function or module rather than modifying the source code every time.

Bad:

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

Good:

class AjaxRequester { constructor() { this.HTTP_METHODS = ['POST', 'PUT', 'GET']; } get(url) { // ... } addHTTPMethod(method) { this.HTTP_METHODS.push(method); }}Copy the code

Richter’s substitution principle

The principle sounds tricky, but the concept is easy to understand. The formal description is that if S is a subtype of T, then instances of type T can be replaced by instances of type S without modifying any code. For images, the parent class and its children should be used interchangeably without causing exceptions, as in the square-Rectangle example below. Square is also a Rectangle:

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

Good:

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

Interface Isolation Principle

JavaScript itself does not contain support for interface syntax, so it cannot be as restrictive as other languages. However, given the lack of JavaScript’s own type system, it is important to follow the principle of interface isolation. Isps say that clients should not be forced to rely on interfaces they don’t need, and a typical example of this in JavaScript is objects that require a lot of configuration information. Users don’t need to care about every configuration item, and allowing them to do it dynamically saves a lot of time and makes the code more readable.

Bad:

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

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

Dependency inversion principle

This principle states two essential things:

  1. High-level modules should not depend on low-level modules. Both should

  2. on abstractions.

  3. Abstractions should not depend upon details. Details should depend onabstractions.

This can be hard to understand at first, but if you’ve worked with Angular.js,

you’ve seen an implementation of this principle in the form of Dependency

Injection (DI). While they are not identical concepts, DIP keeps high-level

modules from knowing the details of its low-level modules and setting them up.

It can accomplish this through DI. A huge benefit of this is that it reduces

the coupling between modules. Coupling is a very bad development pattern because

it makes your code hard to refactor.

As stated previously, JavaScript doesn’t have interfaces so the abstractions

that are depended upon are implicit contracts. That is to say, the methods

and properties that an object/class exposes to another object/class. In the

example below, the implicit contract is that any Request module for an

InventoryTracker will have a requestItems method.

Bad:

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

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.
let inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
inventoryTracker.requestItems();
Copy the code

ES6 classes are preferred over ES5 basic function definitions

The traditional ES5 class implementation syntax is not very readable for class inheritance, construction, and method definition. If you are considering implementing inheritance in a class, it is recommended to give ES6’s class syntax sugar priority. If you just need to build simple objects, consider using ES5’s basic function definitions to construct class objects.

Bad:

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

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

Use method chaining

Against the advice of Clean Code, this is one place where we will have to differ. It has been argued that method chaining is unclean and violates the Law of Demeter. Maybe it’s true, but this pattern is very useful in JavaScript and you see it in many libraries such as jQuery and Lodash. It allows your code to be expressive, and less verbose. For that reason, I say, use method chaining and take a look at how clean your code will be. In your class functions, simply return this at the end of every function, and you can chain further class methods onto it.

Bad:

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

Good:

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

Prefer composition over inheritance

As stated famously in the Gang of Four, you should prefer composition over inheritance where you can. There are lots of good reasons to use inheritance and lots of good reasons to use composition. The main point for this maxim is that if your mind instinctively goes for inheritance, try to think if composition could model your problem better. In some cases it can.

You might be wondering then, “when should I use inheritance?” It

depends on your problem at hand, but this is a decent list of when inheritance

makes more sense than composition:

  1. Your inheritance represents an “is-a” relationship and not a “has-a”

  2. (Animal->Human vs. User->UserDetails).

  3. You can reuse code from the base classes (Humans can move like all animals).

  4. You want to make global changes to derived classes by changing a base class.(Change the caloric expenditure of all animals when they move).

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

  // ...
}
Copy the code

Good:

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

  // ...
}
Copy the code

test

Testing is an inevitable part of code deployment, and if you don’t add any tests, you can’t be sure that something unexpected will happen before every deployment. Different teams have different requirements for test coverage, but maintaining 100% coverage allows your team to maintain control and trust in the code. There are many excellent testing tools and test coverage tools available, and the recommendation is to add test cases for each new feature or module. If you prefer test-driven development, make sure that test coverage is as good as expected before you add new features or refactor your current code.

Each test case has a single goal

Bad:

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

Good:

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

concurrent

Use promises instead of callbacks

Callbacks are unclear and can lead to deep code nesting, the so-called callback hell. Promises are a built-in global type in ES6.

Bad:

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

Good:

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

Async/Await is clearer

Promises themselves are a good alternative to callbacks, while async and await in ES7 are a much cleaner solution that prevents you from writing lots of chains of then calls.

Bad:

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

Good:

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

formatting

As with many of the recommendations in this article, formatting itself is a very subjective principle. Instead of arguing over the details of formatting, use tools to automate formatting.

Upper case consistency of variables with similar meaning

JavaScript itself is untyped, so capitalizing variable names can also convey a lot of useful information. This rule is somewhat subjective and suggests that teams use their own internal specifications to maintain consistency in the case of variables with the same meaning.

Bad:

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

Good:

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

Functions are defined as close to where they are called as possible

Try to place two functions that call each other as close as possible vertically in the source file, and place the caller above the called. We tend to read code from top to bottom, and this layout improves the readability of the entire code.

Bad:

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

Good:

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

annotation

Only the business logic is annotated

Good code is written by name, and comments are more descriptive of the business logic.

Bad:

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

Good:

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

Avoid keeping annotated code

Bad:

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

Good:

doStuff();
Copy the code

Don’t use journal notes

Always use a version control tool instead of adding journal-style comments to your code, and use git logs to view the history.

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

Good:

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

Avoid additional code markup comments

The recommendation is to let function and variable names express their functionality and avoid adding too many additional comments.

Bad:

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

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

Good:

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

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

Avoid adding legal notices to source files

Bad:

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

Good:

function calculateBill() {
  // ...
}
Copy the code

read

  • Zhihu column: the road to a bear’s full stack

  • Zhihu column: when the front self-improvement

  • Zhihu column: LotuC programming path

  • 2016- My Technical Path: Programming Knowledge Architecture

  • 2016- My front-end road: instrumentalization and engineering

  • Bear Weekly Series: A Week of recommended Foreign technical materials (12.1)