Lesson Tuesday

We've learned about three ways to create loops with JavaScript. In this lesson, we will apply these looping concepts to talk about the importance of refactoring code - as well as a key coding concept known as DRY, which means Don't Repeat Yourself.

Let's start by creating a little example. We are looking at a small part of a codebase where we have to double the values in an array. Then, a little later, we need to triple the values in that same array. It may seem to be a silly example but we are a junior developer for SillyGamez and we're making a game about a duck migration. The game is actually an educational one for children to learn about how migrations work so maybe it's not so silly after all.

Sometimes the duck gets a power-up that doubles its stats and sometimes the duck gets a power-up that triples its stats. Players have to get those ducks to their migratory spot for the winter. Sure, the example is a bit contrived - but the point is that it's feasible to need to sometimes double and sometimes triple the values in the array.

Let's take a look at the starting code:

const duckStats = [2, 3, 1, 6];

let doublePowerDuck = [];
for (let i = 0; i < duckStats.length; i++) {
  doublePowerDuck.push(duckStats[i] * 2);
}

let triplePowerDuck = [];
for (let i = 0; i < duckStats.length; i++) {
  triplePowerDuck.push(duckStats[i] * 3);
}

This code will do what it's intended to do - create an array with doubled duck stats or tripled duck stats. However, the code isn't very concise. The loops look very similar - and if we needed to make changes in one loop, we'd have to do so in the other loop as well. And what if sometimes duck stats get quadrupled or quintupled? That's even more repeated code - more places where we can run into bugs, more things we can forget to change if needed, more verbosity that will confuse other developers working on the project.

So let's refactor. This will be a two-part refactor. In the first part of the refactor, we'll clean up the code to use a loop that better suits this code. Then, in the second part, we'll DRY up our code with a function.

First, because we want to create a new array with modified elements, a for loop probably isn't the best tool for the job. Sure, it works, but it's not an elegant solution. We have a better tool for tranforming arrays: Array.prototype.map().

So here's our first refactor: use Array.prototype.map() instead.

const duckStats = [2, 3, 1, 6];

const doublePowerDuck = duckStats.map(function(element) {
  return element * 2;
});

const triplePowerDuck = duckStats.map(function(element) {
  return element * 3;
});

This code does the same thing and it's already cleaner than it was before. There are fewer places for a bug to hide (and we no longer have to worry about the risk of off-by-one errors).

However, our code still has a lot of repetition. We could copy and paste one of these Array.prototype.map() methods - and then create quadruplePowerDuck and quintuplePowerDuck variables - but that would add even more repetition. We see this kind of repetition quite often in the code of Introduction to Programming students. And while it's perfectly fine to just get code working, once we actually have working code, we should look for opportunities to make it better.

So now it's time to DRY things up. This means eliminating repetition. As we've already mentioned, this will make our code more concise and easier to understand. It will also make it less prone to bugs and easier to change in the future - instead of needing to change a piece of code related to duck stats in many places, we'll only need to do so in one place.

We'll DRY it up by creating a function to hold our repeated code:

const duckStats = [2, 3, 1, 6];

function duckStatModifier(statArray, multiplier) {
  const duckStatArray = statArray.map(function(element) {
    return element * multiplier;
  });
  return duckStatArray;
}

const doublePowerDuck = duckStatModifier(duckStats, 2);
const triplePowerDuck = duckStatModifier(duckStats, 3);

Now we have a named function called duckStatModifier. It takes two arguments: the first is the duck stat array to be modified while the second is the stat multiplier. We no longer repeat ourselves other than to invoke the function when needed (duckStatModifier(duckStats, 2)).

A key thing to consider here that can trip up newcomers - the return statement in Array.prototype.map() is only for determining the transformed array - it does not break us out of our named function. We still need a separate return statement to return from the duckStatModifier function itself.

This may not seem like much of a savings in code - but imagine that we kept creating separate loops for modifying a duck's stats. Now imagine we want to make a very small adjustment to our algorithm for determining duck stats (an algorithm is just a formula and our multiplier, however simple, is a formula for determining duck stats).

Let's make a small adjustment to our algorithm - we just want to add one to each result. We could update our function very easily:

function duckStatModifier(statArray, multiplier) {
  const duckStatArray = statArray.map(function(element) {
    return element * multiplier + 1;
  });
  return duckStatArray;
}

All we had to do was add +1 in one place. In our first example at the beginning of this lesson, we would've had to change the code in two places - and we would've created a bug in our code if we forgot to change one of them. Now imagine that same code repeated a dozen times in our codebase - each place we forget to add +1 creates a new bug. That's one of the many advantages of keeping our code DRY.

By the way, we can make another little refactor to our function above. There's no need to save the results of Array.prototype.map() in a variable. We can just return it from the duckStatModifier function like this:

function duckStatModifier(statArray, multiplier) {
  return statArray.map(function(element) {
    return element * multiplier + 1;
  });
}

This code can look more confusing for people new to looping and coding in general. However, we just made one small change. There's no need to create a variable and then return that variable from the function. After all, the return value of duckStatModifier is going to get saved in another variable. Instead, we can just directly return the result of Array.prototype.map() from the duckStatModifier lesson.

In this lesson, we've refactored and DRYed up a snippet of code. We chose a better looping tool for the job at hand and then refactored our loops into a reusable function. Creating functions is often a great way to DRY and refactor code, so if you see repeated code that's not in a function, see if you can wrap that code in a function instead and reuse it.

At this point in your coding development, you may only have time to focus on getting code working. The ability to refactor and DRY code comes with practice. Over time, as you get better at getting code "just working," you'll also develop a knack for cleaning up that code. The key phrase here is over time - and that time frame will extend well beyond your time at Epicodus. So while you should look for opportunities to clean up and refactor your code now, don't give yourself a hard time if you're mainly focused on getting code working - or if sometimes the code doesn't work at all - which is a common experience not just for beginners but also experienced developers as well.

Terminology


  • DRY: An acronym meaning "Don't Repeat Yourself." Where possible, you should avoid repeated code. Repeated code causes bugs, makes it harder to refactor and change code, and also makes it harder for others to read and understand your code.

Lesson 3 of 10
Last updated July 2, 2020