How to write elegant and durable JavaScript code

Preface

In our normal work development, most of them are public projects developed by adults. When we develop code coding, we consider the readability, reusability and extensibility of code.

Clean code is not only reliable in quality, but also lays a good foundation for later maintenance and upgrading.

We will discuss from the following aspects:

variable

1. Variable Naming

Generally, we define variables by using meaningful vocabulary commands, and by meeting people.

//bad code
const yyyymmdstr = moment().format('YYYY/MM/DD');
//better code
const currentDate = moment().format('YYYY/MM/DD');

2. Descriptible

Generating a new variable through a variable also requires naming the new variable, which means that each variable knows what it is when you see it at first glance.

//bad code
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]);

//better code
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);

3. Nomenclature of Formal Parameters

In the loop for, for Each, map, we need to name directly

//bad code
const locations = ['Austin', 'New York', 'San Francisco'];
locations.map((l) => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // You need to look at other code to determine what'l'does.
  dispatch(l);
});

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

4. Avoid meaningless prefixes

For example, if we only create an object, there is no need to add the attribute of each object to the object name.

//bad code
const car = {
  carMake: 'Honda',
  carModel: 'Accord',
  carColor: 'Blue'
};

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

//better code
const car = {
  make: 'Honda',
  model: 'Accord',
  color: 'Blue'
};

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

5. Default value

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

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

function

1, parameters

If there are many general parameters, ES6 should be used to deconstruct and transmit parameters.

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

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

//better code
createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
});

2. Simplification

It's better to do only one thing in a method and not deal with it too much, so that the code is very readable.

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

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

3. Object Setting Default Properties

//bad code
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);


//better code
const menuConfig = {
  title: 'Order',
  // 'body'key missing
  buttonText: 'Send',
  cancellable: true
};

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

  // Configuration becomes: {title:'Order', body:'Bar', buttonText:'Send', cancellable: true}
  // ...
}

createMenu(menuConfig);

4. Avoiding side effects

Function receives a value and returns a new value. In addition, we call it side effects, such as modifying global variables, IO operations on files, etc.

When functions do need side effects, such as IO operations on files, do not use multiple functions/classes for file operations, and only one function/class for file operations. That is to say, side effects need to be dealt with in only one place.

Three major sinkholes of side effects: random modification of variable data types, random sharing of states without data structures, and no handling of side effects in a unified place.

//bad code
// Global variables are referenced by a function
// Now that this variable has changed from a string to an array, unforeseen errors can occur if there are other function references.
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
  name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];


//better code
var name = 'Ryan McDermott';
var newName = splitIntoFirstAndLastName(name)

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

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

In JavaScript, basic 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, add items to the cart through the addItemToCart() method, and modify the cart array. At this point, the purchase() method is invoked to purchase, and the acquired shopping cart array is the latest data due to reference passing.

It looks all right, doesn't it?

If the network fails when the user clicks on the purchase, the purchase() method is repeatedly invoked, at the same time the user adds new goods, and the network is restored. So the purchase() method is wrong to get an array of shopping carts.

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

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

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

5. Global approach

In JavaScript, you should never pollute the whole world and create unpredictable bug s in the production environment. For example, you add a diff method to Array.prototype to determine the difference between the two arrays. Your colleague is going to do something similar, but his diff method is used to determine the difference between the first elements of two arrays. It's obvious that your approach will conflict, and we can extend Array with ES2015/ES6 grammar when we encounter this kind of problem.

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

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

6. Avoid type checking

JavaScript is typeless, meaning that you can pass any type of parameter. This degree of freedom can easily be confusing, and you will check the type unconsciously. Think about it carefully. Do you really need to check the type or do you have problems with your API design?

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

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

If you need to do static type checking, such as strings, integers, etc., recommend using TypeScript, otherwise your code will become stinky and long.

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

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

Judgment of Complex Conditions

When we write js code, we often encounter the situation of complex logic judgment. Generally, you can use if/else or switch to realize multiple conditional judgment. But there will be a problem. With the increase of logic complexity, if/else/switch in the code will become more and more bloated, more and more incomprehensible, so how to write more elegant judgment Broken logic

1,if/else

Click List Button Event

/**
 * Button Click Event
 * @param {number} status Activity Status: 1. In the process of opening a group, 2. Failure in opening a group, 3. Sale of goods, 4. Success in opening a group, 5. System Cancellation
 */
const onButtonClick = (status)=>{
  if(status == 1){
    sendLog('processing')
    jumpTo('IndexPage')
  }else if(status == 2){
    sendLog('fail')
    jumpTo('FailPage')
  }else if(status == 3){
    sendLog('fail')
    jumpTo('FailPage')
  }else if(status == 4){
    sendLog('success')
    jumpTo('SuccessPage')
  }else if(status == 5){
    sendLog('cancel')
    jumpTo('CancelPage')
  }else {
    sendLog('other')
    jumpTo('Index')
  }
}

As we can see from the above, we can do different things through different states. The code looks very ugly. You can easily propose a rewriting scheme for this code. switch comes out.

2,switch/case

/**
 * Button Click Event
 * @param {number} status Activity Status: 1. In the process of opening a group, 2. Failure in opening a group, 3. Sale of goods, 4. Success in opening a group, 5. System Cancellation
 */
const onButtonClick = (status)=>{
  switch (status){
    case 1:
      sendLog('processing')
      jumpTo('IndexPage')
      break
    case 2:
    case 3:
      sendLog('fail')
      jumpTo('FailPage')
      break  
    case 4:
      sendLog('success')
      jumpTo('SuccessPage')
      break
    case 5:
      sendLog('cancel')
      jumpTo('CancelPage')
      break
    default:
      sendLog('other')
      jumpTo('Index')
      break
  }
}

This looks much clearer than if/else. Careful students also find some tricks. When case 2 and case 3 are the same logic, they can omit executing statements and break s, and case 2 automatically executes case 3 logic.

3. Store in Object

The judgement condition is regarded as the attribute name of the object and the processing logic is regarded as the attribute value of the object. When the button is clicked, the logical judgement is made by searching the attribute of the object, which is especially suitable for the case of unary condition judgement.

const actions = {
  '1': ['processing','IndexPage'],
  '2': ['fail','FailPage'],
  '3': ['fail','FailPage'],
  '4': ['success','SuccessPage'],
  '5': ['cancel','CancelPage'],
  'default': ['other','Index'],
}
/**
 * Button Click Event
 * @param {number} status Activity Status: 1. In the process of opening a group, 2. Failure in opening a group, 3. Sale of goods, 4. Success in opening a group, 5. System Cancellation
 */
const onButtonClick = (status)=>{
  let action = actions[status] || actions['default'],
      logName = action[0],
      pageName = action[1]
  sendLog(logName)
  jumpTo(pageName)
}

4. Store in Map

const actions = new Map([
  [1, ['processing','IndexPage']],
  [2, ['fail','FailPage']],
  [3, ['fail','FailPage']],
  [4, ['success','SuccessPage']],
  [5, ['cancel','CancelPage']],
  ['default', ['other','Index']]
])
/**
 * Button Click Event
 * @param {number} status Activity Status: 1. In the process of opening a group, 2. Failure in opening a group, 3. Sale of goods, 4. Success in opening a group, 5. System Cancellation
 */
const onButtonClick = (status)=>{
  let action = actions.get(status) || actions.get('default')
  sendLog(action[0])
  jumpTo(action[1])
}

Isn't it better to use the Map object in es6 in this way? What's the difference between a Map object and an Object object?

  1. An object usually has its own prototype, so an object always has a "prototype" key.
  2. The key of an object can only be a string or Symbols, but the key of a Map can be any value.
  3. You can easily get the number of key-value pairs of a Map by using the size attribute, while the number of key-value pairs of objects can only be confirmed manually.

Code style

Constant capitalization

//bad code
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 {}

//better code
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 {}

Declare before call

//bad code
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();

//better code
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();

Keywords: Javascript Attribute brew Database

Added by sargus on Fri, 20 Sep 2019 10:03:54 +0300