Clean JavaScript code

directory

  1. An overview of the
  2. variable
  3. function
  4. Objects and data structures
  5. class
  6. test
  7. concurrent
  8. Error handling
  9. format
  10. annotation

An overview of the

The same software engineering principles described by Robert C. Martin in The Code Clean Way apply to JavaScript. This is not a style reference. It provides guidance on how to write readable, reusable, and reconfigurable software in JavaScript.

Not every principle has to be strictly followed and is rarely even agreed upon. They are for reference only, but it is important to know that these principles have been the collective experience of the authors of Clean Code for years.

We’re just over 50 years old in software engineering, and we’re still learning a lot. When software architecture is as old as the architecture itself, we should follow stricter rules. Now, use these criteria to evaluate the quality of the JavaScript code you and your team write.

One more thing: knowing these things won’t immediately make you a better software developer, and using them for years at work won’t protect you from mistakes. Each piece of code starts from the initial sketch to the final shape, like shaping wet clay. Finally, when we review with our peers, we iron out the imperfections. Don’t beat yourself up because your first draft needs improvement, only the code needs to beat!

variable

Use variable names that make precise sense

Bad:

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

Good:

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

Siep back to the top

ES6 constants are used when the value of the variable does not change

In bad cases, variables can be changed. If you declare a constant, it stays the same throughout the program.

Bad:

var FIRST_US_PRESIDENT = "George Washington";
Copy the code

Good:

const FIRST_US_PRESIDENT = "George Washington";
Copy the code

Siep back to the top

Use the same vocabulary for variables of the same type

Bad:

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

Good:

getUser();
Copy the code

Siep back to the top

Use searchable names

The code we read is always worse than the code we write. It is important to write code that is readable and easy to retrieve. Using variable names that have no clear meaning in a program can be difficult to understand and do a disservice to the reader. So, make the name searchable.

Bad:

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

Good:

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

Siep back to the top

Use explanatory variables

Bad:

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

Good:

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

Siep back to the top

Avoid suggested

Explicit is better than implicit.

Bad:

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

Good:

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

Siep back to the top

Don’t add unnecessary context

If your class/object names already indicate what they are, don’t repeat them in the variable names.

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

Siep back to the top

Short-circuit syntax is clearer than conditional syntax

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

Siep back to the top

function

Function parameters (theoretically less than or equal to 2)

Limiting the number of arguments to a function is extremely important; it makes it easier to test the function. More than three parameters can lead to combinatorial bloat, so that you have to test a lot of different cases against different parameters.

Ideally, no parameters. Having one or two parameters is fine, but three should be avoided. Anything more than that should be considered for consolidation. In general, if you have more than 2 arguments, your function will try to do too many things. If not, you can use a higher-order object as a parameter most of the time.

Since JavaScript allows you to create objects at run time without having to pre-define templates, you can use one object when you need a lot of parameters.

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

Siep back to the top

A function only does one thing

This is by far the most important principle in software engineering. If a function does more than one thing, it’s harder to compose, test, and guess. When you make functions do only one thing, they are easy to refactor, and the code reads much clearer. You just need to follow this one tip of this guide to get ahead of many other developers.

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

Siep back to the top

The function name should say what it does

Bad:

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

Good:

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

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

Siep back to the top

Functions should be abstracted at only one level

If you have multiple levels of abstraction, your functions are usually doing too much, and should be split up to make them easy to reuse and test.

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

Siep back to the top

Delete duplicate code

Under no circumstances should there be duplicate code. For no reason at all, it’s probably the worst thing that can stop you from becoming a professional developer. Repetitive code means you have to change more than one area of code to change some logic. JavaScript is weakly typed, so it’s easy to write general-purpose functions. Take advantage of that!

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

Siep back to the top

Use default arguments instead of short circuit expressions

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

Siep back to the top

Assign Sets the default Object with 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: '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); // now config equals: {title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true} } createMenu(menuConfig);Copy the code

Siep back to the top

Do not use tags as function arguments

The tag tells your user that this function does more than one thing. But the function should only do one thing. If you have a function that has different branches based on a Boolean argument, split the function.

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

Siep back to the top

Avoid side effects

If a function does not take one input value and return another, it can have side effects. These side effects could be writing files, changing global variables, or accidentally transferring all your money to a stranger.

Now you really need to have side effects in the program. As mentioned earlier, you may need to write files. Now all you need to do is figure out where to focus on getting it done. Don’t use several functions or classes to write to a particular file. Use one, one service to do it.

The key is to avoid the pitfalls of perception, such as sharing state between unstructured objects, using mutable data types that can be arbitrarily modified, and not dealing centrally with side effects that occur. If you can do that, you’ll be happier than most other programmers.

Bad:

// The following functions use global variables. // If there is another function that uses name, it may now fail because name is an array. 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

Siep back to the top

Do not write global functions

Global contamination is a bad thing in JavaScript because it can collide with another library, and users of your API won’t know about it — until they run into an exception in production. Consider an example: If you want to extend JavaScript’s native Array to have a diff method that shows the difference between two pieces of data, what do you do? You could add a new function to Array.prototype, but it might clash with other libraries that want to do the same thing. What happens if the diff that library implements is just like the difference and similarity between the first element and the last element in the array? That’s why it’s best to use ES6’s class syntax to derive a class from the global Array to do this.

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

Siep back to the top

Like functional programming over imperative programming

If Haskell is IPA then JavaScript is O’Douls. That is, unlike Haskell, JavaScript is not a functional programming language, but it still feels a bit functional. Functional languages are cleaner and easier to test, so you’d better enjoy this style of programming.

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

Siep back to the top

Encapsulation condition

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

Siep back to the top

Avoid negative conditions

Bad:

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

Good:

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

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

Siep back to the top

Avoid conditions

It seemed an impossible task. When most people first hear this, they say, “What do I do without the if statement?” The answer is that in most cases you can use polymorphism to accomplish the same task. The second question is usually, “That’s great, but why would I do that?” The answer lies in the neat concept we’ve seen before: a function should only do one thing. If your classes and functions have if statements, it means that your function is doing more. Remember, just do one thing.

Bad:

class Airplane { //... getCruisingAltitude() { switch (this.type) { case '777': return getMaxAltitude() - getPassengerCount(); case 'Air Force One': return getMaxAltitude(); case 'Cessna': 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 Cessna extends Airplane { //... getCruisingAltitude() { return getMaxAltitude() - getFuelExpenditure(); }}Copy the code

Siep back to the top

Avoiding type checking (Part 1)

JavaScript is untyped, which means functions can take arguments of any type. Sometimes this freedom is torture, and you can’t help but use type checking in your functions. There are many ways to avoid type checking. The first thing to consider is API consistency.

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

Siep back to the top

Avoiding type checking (Part 2)

Consider TypeScript if you feel the need to use type checking when dealing with primitive types such as strings, integers, and arrays that can’t be polymorphic. This is a perfect substitute for plain JavaScript, providing static typing on top of standard JavaScript syntax. The problem with normal JavaScript checking types by hand is that you write a lot of crap, and artificial “type safety” doesn’t make up for lost readability. Keep your JavaScript clean, write good tests, and maintain good code reviews. Otherwise let TypeScript (which, as I said, is a great alternative) do everything.

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

Siep back to the top

Don’t over-optimize

Browsers now do a lot of stealth optimization at runtime. Many times your optimizations are a waste of time. Here’s a great resource to see which optimizations are lacking. Keep them as targets until they can be fixed.

Bad:

// In older browsers, the cost of each loop is higher because len is recalculated each time. // Now in the browser, this has been 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

Siep back to the top

Remove unused code

Unused code is just as bad as duplicate code. It makes no sense to keep useless code in your code base. If you don’t need a piece of code, get rid of it! If you need it later, you can still find it in the historical version of the code base.

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

Siep back to the top

Objects and data structures

Use getters and setters

JavaScript has no interface or type, and no keywords like public and private, so it’s hard to apply design patterns. In fact, using getters and setters on objects to access data is far better than looking up object properties directly. “Why?” You might ask. Well, here’s why:

  1. You want to do more when you get an object property without having to go through all the access code and modify it one by one.

  2. In thesetAdditional data verification can be performed.
  3. Encapsulate internal presentation.

  4. Easy to add logging and error handling when fetching or setting.

  5. Inheriting the current class, you can override the default functionality.

  6. Lazy loading of object properties can be done, such as fetching property data from the server.

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

Siep back to the top

Let objects have private members

This can be done with closures (ES5 in previous versions).

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

Siep back to the top

class

Single Responsibility Principle (SRP)

As the Code Neatness Guide says, “There shouldn’t be more than one reason to change a class.” It’s tempting to cram a lot of functionality into a class, like taking a suitcase with you on an airplane. The problem with this is that your class will not be conceptually cohesive, and there will be many factors that cause it to change. It’s important to keep your classes to a minimum. This is because if you cram too much functionality into a class and you change only part of it, it may be hard to understand why it affects other related modules in the code base.

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

Siep back to the top

Open Package Principle (OCP)

As Bertrand Meyer says, “Software entities (classes, modules, functions, etc.) should be open to extension and closed to modification.” What does that mean? This principle basically states that you should allow users to extend your modules without opening.js source files to edit them.

Bad:

Class AjaxRequester {constructor() {// What if we need another HTTP method, such as DELETE? // We must open this file and add it manually.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

Siep back to the top

Richter’s Substitution Principle (LSP)

It’s a scary term, but it’s a simple concept. It is formally defined as “If S is a subclass of T, then all objects of type T can be replaced by objects of type S (i.e., objects of type S can be replaced by objects of type T), and this substitution does not change anything about the program (correctness, task execution, etc.).” That’s a scary definition.

The best explanation for this is that if you have a parent class and a child class, then the parent class and child class can be used interchangeably without causing incorrect results. This may still be confusing, so let’s look at the classic square and rectangle example. In mathematics, a square is also a rectangle, but if you use the “IS-A” relationship through inheritance in your model, you quickly run into trouble.

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(); Rectangle. Render (area); // Rectangle. }) } 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

Siep back to the top

Interface Isolation Principle (ISP)

There are no interfaces in JavaScript, so this principle cannot be applied as strictly as in other languages. However, even for JavaScript’s weak typing system, it is still an important correlation.

Isps point out that “customers should not rely on interfaces they do not use.” Interfaces are an implicit contract in JavaScript because of Duck Typing theory.

A good example of this principle can be found in JavaScript with a class that has a huge setup object. It’s better not to ask customers to set a lot of options, because most of the time they don’t need all of them. Making these options optional helps prevent “fat interfaces.”

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 don't need animation. });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

Siep back to the top

Dependency Inversion Principle (DIP)

This principle says two basic things:

1. Upper modules should not depend on lower modules; both should depend on abstractions.

2. Abstraction should not depend on concrete implementation. Concrete implementation should depend on abstraction.

This may be hard to understand at first, but if you use angular.js, you can already see one implementation of this principle: dependency injection (DI). Although they are not exactly the same concept, the DIP prevents the upper module from knowing the details of the lower module and setting them. It can be done through DI. This has the great benefit of reducing the coupling between modules. Coupling is a very bad development pattern because it makes code difficult to refactor.

As mentioned in the premise, JavaScript has no interface, so abstraction depends on an implicit contract. That is, one object/class exposes methods and properties to another object/class. In the example below, the implicit contract is that any Request module for InventoryTracker should have the requestItems method.

Bad:

class InventoryTracker { constructor(items) { this.items = items; // Bad: we created an implementation that depends on a particular request. Requester = new InventoryRequester(); // We should only rely on the request method: 'request' requestItems 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 building external dependencies and injecting them, we can easily replace the request module with // a new module that uses WebSocket. let inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); inventoryTracker.requestItems();Copy the code

Siep back to the top

Use more ES6 class syntax and less ES5 constructor syntax

In classic ES5 class definitions, it’s hard to find readable inheritance, constructs, method definitions, and so on. If you need inheritance (and you’ll find you can’t), use class syntax instead. However, you should use small functions instead of classes whenever possible, until you need larger and more complex 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

Siep back to the top

Method chain

My opinion differs here from that of The Code Clean Way. Some people think the method chain is untidy and violates Demeter’s law. Maybe they’re right, but this pattern is very useful in JavaScript, and you can see it in many libraries, such as jQuery and Lodash. It makes code both concise and expressive. In a class, you just need to return this at the end of each function to implement the chained invocation of the class method.

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: return this to implement the chain call return this; } setModel(model) { this.model = model; // NOTE: return this to implement the chain call return this; } setColor(color) { this.color = color; // NOTE: return this to implement the chain call 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

Siep back to the top

Use composition more than inheritance

You know the GoF design pattern, which says you should use composition rather than inheritance. There are plenty of reasons to support both inheritance and composition, but the point of the rule is that your mind instinctively thinks of inheritance, but it’s worth asking yourself if you can handle things better with composition — and sometimes it can.

You might be thinking, “When should I use inheritance?” It depends on the problem you have. Here’s a good list of when inheritance is better than composition:

  1. Your inheritance is an “IS-A” relationship, not a “has-A” relationship (Animal->Human vs User->UserDetails).

  2. Code can be reused from the base (humans can move like all animals).

  3. You want to make global changes to all subclasses by modifying the base. (changes the amount of energy the animal uses as it moves).

Bad:

class Employee { constructor(name, email) { this.name = name; this.email = email; } / /... } // This is bad because Employees "own" the tax data. 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

Siep back to the top

test

Testing is more important than production. If you don’t test, or if you don’t test enough, you can’t be sure that your code won’t break. It’s up to your development team to determine the number of tests, but 100% coverage (all statements and branches) gives you great confidence and peace of mind. That said, you need a good testing framework and a good coverage checker.

There is no reason not to write tests. There are plenty of good JS testing frameworks out there, so find one your team likes to use. If you’re looking for a job that’s appropriate for your team, aim to add tests for each new feature/method that comes along. If you like the test-driven development (TDD) approach, great, but be careful when making your tests cover all features, or when refactoring code.

Test one concept at a time

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

Siep back to the top

Concurrency

Use promises instead of callbacks

Callbacks are not neat and lead to excessive nesting. ES6 promises are a built-in global type. Use it!

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

Siep back to the top

Async /await is cleaner than Promise

Promises are already pretty neat, like callbacks, but ES7 comes with cleaner solutions — async and await. All you have to do is prefix a function with the async keyword and write the logic in command form, without the then chain. Now you can use the convenience of this ES7 feature!

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.error(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

Siep back to the top

Error handling

Throwing errors is a good thing! This means that the runtime has successfully detected an error in the program, stopped the function on the current call box, aborted the process (in Node), and finally notified you on the console and printed out the stack trace.

Do not ignore errors caught

When there is nothing wrong with catching it, you lose the opportunity to correct it. In most cases, logging the error to the console (console.log) is no better than ignoring it, because it is hard to spot in the small amount of console information. If you try to wrap code in a try/catch, it means that you know that something could go wrong, and you should have a plan for what to do when it does.

Bad:

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

Good:

try { functionThatMightThrow(); } catch (error) {// One of the options (more disturbing than console.log) : console.error(error); // Another option: notifyUserOfError(error); // Alternative: ReporToService (error); // Or all three of the above! }Copy the code

Don’t ignore rejected promises

This is for the same reason as not ignoring errors caught from try/catch.

Bad:

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

Good:

getdata() .then(data => { functionThatMightThrow(data); }). Catch (error => {// one of the options (more disturbing than console.log) : console.error(error); // Another option: notifyUserOfError(error); // Alternative: ReporToService (error); // Or all three of the above! });Copy the code

Siep back to the top

format

Formatting is a very subjective thing, and like many of the rules mentioned here, you don’t have to follow them all. The point is not the format of the argument. A number of tools can handle optimized formats automatically. With a! It’s a waste of time and money for engineers to argue about formatting.

For formatting that can’t be handled automatically (including indentation, tabs or Spaces, double quotes or single quotes, etc.), take a look at this guide.

Use consistent case

JavaScript is untyped, so case can help you understand variables, functions, and so on. These rules are highly subjective, so your team should choose what it needs. It’s not about what you choose, it’s about being consistent.

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

Siep back to the top

Function callers and callees should be placed together whenever possible

If one function calls another, they should be placed very close together in the source file. Ideally, place the caller directly above the caller. This will make your code easier to read because we all read code from the top down, like a newspaper.

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

Siep back to the top

Comments are used to explain code, not required. Good code should comment itself.

Bad:

Function hashIt(data) {var Hash = 0; // String length var length = data.length; For (var I = 0; i < length; Var char = data.charcodeat (I); // Generate Hash Hash = ((Hash << 5) -hash) + char; // Convert to a 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 a 32-bit integer hash = hash & hash; }}Copy the code

Siep back to the top

Don’t leave commented out code in the code base

Version control exists because it saves your historical code.

Bad:

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

Good:

doStuff();
Copy the code

Siep back to the top

Remember, use version control! Useless code, commented out code, especially journaling comments. Git log to get history information!

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

Siep back to the top

Avoid location tags

Location tags usually just add spam. Good visual structure can be achieved by indenting function or variable names appropriately.

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

Siep back to the top

This is what the LICENSE file at the top of the code file tree should do.

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