• JavaScript best practices: function

In JavaScript function is a central working unit. It's very important to understand what it is and how it works due to the fact that almost every script uses one or more of them. So, to be more specific, function is a named sections of a program (a "subprogram") that performs a specific task. It consists of a function body which is a sequence of statements, and the values can be passed to a function, and it will return them. So in short, functions in JavaScript are function objects, because like objects they can have properties and methods, but unlike objects they can also be called (invoked) by code external to the function. The possibility to call a group of reusable code (which is a function) anywhere in your program rules out the need to write the same code over and over again. With the functions' help you are enabled to divide a big program into a number of small and manageable functions. A JavaScript function is defined with the function keyword, followed by a name, followed by parentheses (). Function names can contain letters, digits, underscores, and dollar signs (same rules as variables). The code to be executed, by the function, is placed inside curly brackets: {}. Now let's see how we can use these functions more productively.

Arguments

A very important thing to make testing of your function easier is to limit the amount of function parameters. When you have more than three it results in a combinatorial explosion and then you have to test a lot of different cases with each separate argument.

Ideal to have one or two arguments, and avoid (if possible) having three. Anything more than that needs to be consolidated. In most cases when have more than two arguments your function is trying to do too much. When it’s not a case, a higher-level object typically serve as an argument.

Due to the fact that JavaScript allows to make objects out of hand, not dealing with a lot of class boilerplate, then if you need a lot of arguments you can simply use an object.

You can use the ES2015/ES6 destructuring syntax to make absolutely clear what properties the function expects, which has several advantages. Among them is that it will be evident right away what properties are being used when somebody looks at the function signature. Also to prevent side effects destructuring clones the specified primitive values of the argument object passed into the function (objects and arrays that are destructured from the argument object are not cloned). And, of course, without destructuring it would be impossible for you to be worn by linters about the unused properties.

Bad:

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

Good:

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

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

Default objects need to be set with Object.assign

Bad:

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

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

createMenu(menuConfig);

Good:

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

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

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

createMenu(menuConfig);

Writing to global functions

Don't do that, don't write to global functions, because polluting globals is a bad practice in JavaScript. If you do that you could clash with another library and the user of your API would be none-the-wiser until they get an exception in production. It would be much better to just use ES2015/ES6 classes and simply extend the Array global. Because what if you wanted to extend JavaScript's native Array method to have a diff method that could show the difference between two arrays? You could write your new function to the Array.prototype, which could clash with another library that tried to do the same thing. What if that other library was just using diff to find the difference between the first and last elements of an array? Too much can go wrong.

Bad:

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

Good:

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

Type-checking (part 1)

The thing to have to do is to avoid type-checking. Your functions can take any type of argument because JavaScript is untyped. You might be drawn to do this in your functions, so there are many ways to avoid it. You can consider consistent APIs.

Bad:

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

Good:

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

Type-checking (part 2)

If you feel the need to type-check while working with basic primitive values like strings and integers, and you can't use polymorphism, you can consider TypeScript. It provides you with static typing on top of standard JavaScript syntax which is a great alternative. Manual type-checking of normal JavaScript requires too much of extra verbiage to do it well and the faux "type-safety" you get doesn't make up for the lost readability.

Bad:

function combine(val1, val2) {
  if (typeof val1 === 'number' && typeof val2 === 'number' ||
      typeof val1 === 'string' && typeof val2 === 'string') {
    return val1 + val2;
  }

  throw new Error('Must be of type String or Number');
}

Good:

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

Conclusion

As you can see, functions is an extremely important building block of JavaScript, and by keeping in mind even a few of the good function using practices while avoiding those bad ones, you can significantly improve your work with code and make it much easier.

Used materials

Clean Code concepts adapted for JavaScript

Welcome to CheckiO - games for coders where you can improve your codings skills.

The main idea behind these games is to give you the opportunity to learn by exchanging experience with the rest of the community. Every day we are trying to find interesting solutions for you to help you become a better coder.

Join the Game