Categories
JavaScript Best Practices

JavaScript Clean Code — Function Parameters and Side Effects

Spread the love

Functions are an important part of JavaScript programs. They’re used for dividing code up into reusable chunks. Therefore in order to have clean JavaScript code, we need to have easy to understand functions.

In this article, we’ll look at more properties of good functions, including flag arguments, dyadic and triadic functions, and side effects.

Flag Arguments

Boolean parameters should be used sparingly. It makes the function signature more complex and it tells us that the function does more than one thing (has multiple paths).

Dyadic Functions

Dyadic functions are harder to understand than functions that take fewer arguments. However, sometimes they make sense. For example, if we have an object that holds the Cartesian coordinate, then it should take 2 arguments.

For example, we can have a class with a constructor that takes 2 arguments as follows:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

const point = new Point(1, 2);

It’s pretty much impossible to define it any other way.

However, we do have to be aware that it takes more time and brainpower than functions that take fewer arguments.

Triadic Function

Functions with 3 arguments take a lot of time and brainpower to understand that functions that take 2 arguments.

There’re many more combinations of arguments to think about that if there’re 2 or fewer arguments.

Combining Arguments into Objects

If a function takes many arguments, we should consider combining them into objects.

This is especially true if they’re related. For example, the following function takes many parameters:

const describeFruit = (color, name, size, price, numSeeds, type) => {
  return `${fruitName} is ${fruitColor}. It's ${fruitSize}. It costs ${price}. It has ${numSeeds}. The type if ${type}`;
}

6 parameters is probably too many. We can clean this up by passing in an object instead:

const describeFruit = (fruit) => {
  return `${fruit.name} is ${fruit.color}. It's ${fruit.size}. It costs ${fruit.price}. It has ${fruit.numSeeds}. The type if ${fruit.type}`;
}

As we can see, it’s much cleaner. We don’t have to worry about passing in many arguments.

It also fits better on the screen since it’s shorter.

5 parameters are probably the maximum that should be in a function.

Verbs and Keywords

It’s a good idea to include verbs and keywords into our function names since they do something, which means an action word in the name is justified.

Also, we need to know what things we’re applying the action to. This means that we have to add some keywords to do that.

For example, a good function definition that meets this rule would be something like:

const copyArray = (array) => [...array];

The copyArray name let us know that our function makes a copy of an array.

It also lets us know what we’re passing into the function, and that’s clearly an array.

No Side Effects

Side effects are code in a function that makes changes to things that are outside the function.

This isn’t good because it’s making hidden changes to things that are outside the function.

We should avoid this as much as possible since it does something unexpected and also it takes testing harder because, in addition to taking in arguments, doing things and returning a result, it also makes changes to something outside the function that we have to account for.

This means that we have to test things outside of what the function returns.

For example, if we have:

let numFruits = 1;
const addFruit = () => {
  numFruits++;
}

const removeFruit = () => {
  numFruits--;
}

Then we have 2 functions with side effects because they’re both changing the numFruits variable which is outside each function.

A better way to write these functions is to write them as pure functions. Pure functions are functions that returns the same thing given the same arguments are passed in. Also, it has no side effects.

Pure functions are easier to test because of that and the behavior of them is also predictable.

We can rewrite the code above by writing them as follows:

let numFruits = 1;
const addFruit = (numberOfFruits) => numberOfFruits + 1;
const removeFruit = (numberOfFruits) => numberOfFruits - 1;

numFruits = addFruit(numFruits);
numFruits = removeFruit(numFruits);

We now have 2 functions that take in one numFruits parameter and return a number that’s one bigger or smaller respectively.

Then we can use them to change the numFruits variable that we have outside the functions.

As we can see, they do nothing to the numFruits but rather it returns the numberOfFruits parameter plus 1 or minus 1 respectively.

If we write tests for them, then we can test them easily by passing in the input and checking if the output is what we want. This is much better than committing side effects to a variable that might be available to the test code.

Conclusion

Flag arguments should be minimized. They tell us that the function can do more than one thing and it’s another parameter in the function signature.

Functions that take fewer arguments are better than the one that takes more. If it takes lots of arguments, consider combining them into a single object.

Finally, side effects should be avoided if possible. Functions with side effects do hidden things and are hard to test. Pure functions are much more testable and more predictable since they don’t commit side effects.

Leave a Reply

Your email address will not be published.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.