Categories
JavaScript Best Practices

JavaScript Clean Code — Function Arguments

Spread the love

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

In this article, we’ll look at more characteristics of good functions, including reducing the use of switch statements, reducing the number of parameters in our functions, and getting arguments in an easy to understand way.

Switch Statements

switch statements are always long even if they have only a few cases. Therefore, they should be used in a way that’s never repeated by separating into a function that can be invoked wherever is needed.

An example of a bad switch statement would be something like the following code:

const calculatePay = (employeeType) => {
  switch (employeeType) {
    case 'PART_TIME':
      return calculatePartTimePay();
    case 'FULL_TIME':
      return calculateFullTimePay();
    case 'COMMISSIONED':
      return calculateContracorPay();
    default:
      return 0;
  }
}

The code above is long and it does more than one thing. It’s better to just skip the switch statement and then call them wherever they’re needed.

Also, we have to change this function every time we change the type.

Another problem is that we’ll have many other functions with this structure, which means the code will get longer and have more functions with the same problems as this one.

To deal with this, we can return the correct object for the type of employee that we want. For example, we can write instead:

const getEmployeeObjectByType = (employeeType) => {
  switch (employeeType) {
    case PART_TIME:
      return new PartTimeEmployee();
    case FULL_TIME:
      return new FullTimeEmployee();
    case CONTRACTOR:
      return new ComissionedEmployee();
    default:
      return undefined;
  }
}

And then we can just call this function to get the right type of employee and then do what we want with it.

It’s much better than having multiple functions with the same switch statement cases doing different things.

Descriptive Names

Like any other identifier in our code, the names in functions should be descriptive like everything else.

They should be named with names that tell what they mean. Information like the meaning, intent, and action should be conveyed from the code.

The type may also be helpful in some cases if the type of something isn’t immediately clear from looking at the code since JavaScript is a dynamically typed language.

Longer names are OK is they’re needed to convey all the information that we need to know. They can still be easy to read if they follow JavaScript’s convention, which is to use camel case for variables and non-constructor functions, upper case for constants, and upper camel case for constructor functions.

Function Arguments

Functions should have as few parameters as possible. This is because we have to look at all of them and when we call them, we have to make sure that they’re all passed in.

Therefore, if we don’t need parameters, then we shouldn’t include them. Zero is better than one and one is better than 2.

More than 5 is probably too much unless there’s some special reason for it.

Passing in arguments is hard since we have to gather all the variables and values and pass them in. Likewise, it makes testing harder for the same reason.

When we test a function with lots of arguments, we’ve to test all the possible combinations of arguments to get full test coverage. This makes testing a function that takes lots of arguments many times harder than ones that take fewer arguments.

Also, with the rest operator being a standard feature in JavaScript, it’s time to abandon the arguments object if we need to define a function with lots of arguments. It’s an array-like object so it has some property of arrays, like indexes and the length property.

We can also loop through it with a for or a while loop. However, it doesn’t have any array methods that are part of a normal array.

Like other array-like objects, it can be spread with the spread operator.

They just create confusion for lots of people since many people aren’t familiar with the arguments object.

The arguments object also doesn’t bind to arrow functions, so we can’t use it with them.

With the rest operator, we get an array of arguments returned from it, so we can do everything that we can do with arrays.

So instead of writing:

function add() {
  return [...arguments].reduce((a, b) => a + b, 0);
}

We should write:

function add(...args) {
  return args.reduce((a, b) => a + b, 0);
}

To make a function that adds some numbers together. Or better yet:

const add = (...args) => {
  return args.reduce((a, b) => a + b, 0);
}

since we aren’t using this in our code.

As we can see, the 2 later examples with the rest operator are much clearer than the first one. The first one has no parameters in the signature and yet we’re getting the arguments passed in from the arguments object.

The other 2 shows that our add function actually takes arguments and we’re actually doing something with them.

Conclusion

Functions might look easy to define on the surface, but to define functions that are easy to read and change, we have to do it with care. We should avoid having too many switch statements.

If we do need them, we should abstract them so that one switch statement can be used in multiple places. They’re long and should be minimized.

Also, the names of things inside a function should be descriptive, and we should never take too many arguments in our functions. If we do, use the rest operator instead of the arguments object to get the arguments passed into our functions.

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.