DEV Community

George Rodier
George Rodier

Posted on

A Quick Functional Refactoring Tip

Originally posted on my personal site, georgerodier.com.

There's a ton of benefits to making your code more functional. However, there is a learning curve in getting started with functional programming. Luckily, with JavaScript, you can slowly adapt some functional practices and don't need to go full functional all at once.

One of the best things you can do is slowly start to refactor certain parts of your application until you can gain a better understanding. In a previous post, I talked about how refactoring for loops into array methods is a great first step that can teach you a ton about functional programming. However, that's not the only easy thing you can do to improve your codebase. The following video from Eric Normand provides a great tip for making your existing code more functional.

The tip is great and easier than you would think to implement.

var myAge = 31;
var myName = 'George';

function nameAndDecadeString() {
  const decade = Math.floor(myAge / 10) * 10;
  const nameAndDecadeString =
    myAge === decade
      ? `${myName} is ${myAge} years old`
      : `${myName} is older than ${decade} and less than ${decade + 10}`;

  return nameAndDecadeString;
}

console.log(nameAndDecadeString());
// George is older than 30 and less than 40

In this example of a hypothetical existing codebase, myAge and myName are global mutable variables. They are both defined outside the scope of the nameAndDecadeString function where they are referenced. The function doesn't mutate these variables and is only reading them. This is still problematic because the function will return a different result depending on when you read those values. They could be updated at some point by a user interaction thus changing what the function may return. To test this, we would need to set up mocks or globals that would make the tests brittle.

All that is needed to make nameAndDecadeString more functional is to take the global variables (myAge and myName) and make them arguments to the function.

var myAge = 31;
var myName = 'George';

function nameAndDecadeString(name, age) {
  const decade = Math.floor(age / 10) * 10;
  const nameAndDecadeString =
    age === decade
      ? `${name} is ${age} years old`
      : `${name} is older than ${decade} and less than ${decade + 10}`;

  return nameAndDecadeString;
}

console.log(nameAndDecadeString(myName, myAge));
// George is older than 30 and less than 40

Now that the parameters have been changed, the nameAndDecadeString function can read in the global variables as arguments. The global variables could still be modified unexpectedly, but the code within the scope of nameAndDecadeString is no longer relying on them. The overall functionality hasn't changed, but the code is now more testable and more functional.

One of the principles of functional programming is writing pure functions. Pure functions are any functions that will always produce the same outputs for the same inputs. Previously, nameAndDecadeString didn't take any inputs, but might produce different outputs every time its called depending on the values of myAge and myName at any given time. Now that they are passed in as arguments, the function will produce the same output every time the same age and name are passed into the function. It no longer relies on global state.

The goal now is to slowly refactor your codebase following this approach. Your codebase won't be fully functional, but it is a good first step to having more reliable and testable code.

Top comments (0)