Skip to content

Refactoring

As you tinker with a sketch you’ll find yourself building up lines of code, variables and functions. The risk is your understanding of the code collapses when you’re no longer sure what does what (or why).

DRY

Don’t Repeat Yourself (DRY) is a principle where you try to avoid writing code that does the same thing, or pretty much the same thing. If you ever find yourself copy and pasting code, duplicating it in a sketch, it’s a sign you might be breaking the DRY principle.

Two common ways of repeating yourself are:

  1. ‘Magic numbers’ sprinkled through your code. Numbers used for tuning this and that, but you’re re-using the same number in different places.
  2. Lines of code that perform an action or do a computation which are repeated because you want them to apply in several different places or on different inputs

Keep in mind that DRY is not just an ideal to strive for. It can make the task of understanding your sketch and iterating it further difficult — especially if you’re a beginning. So when you start losing track yourself, consider ‘editing’ your code and looking for opportunities to DRY.

A simple case

Maybe you’re computing a colour value based on a scalar (ie. 0..1 number) and we want it restricted to a narrower part of the hue range. In our case, the range is 30-60 degrees.

An input of 0 should give a colour of hsl(30, 50%, 40%), through to an input of 1 which should yield hsl(60, 50%, 40%).

const use = () => {
const { relative } = state;
// Compute an absolute hue value from 30-60
const hue = (relative*30) + 30;
// Create a CSS-style string also specifying 50% saturation and 40% lightness
const hsl = `hsl(${hue}, 50%, 40%)`
// Use the colour...
document.body.style.backgroundColor = hsl;
}

If you’re repeating this snippet in different places of your code, it will be painful if you change your mind of the hue range, or that all colours should have 50% lightness instead of 40%.

Consider too if you now want to do something almost the same but for a different element. Now you’ve got two sets of almost identical code to maintain:

const use = () => {
const { a, b } = state;
const hueOfA = (a*30) + 30;
someElement.style.backgroundColor = `hsl(${hueOfA}, 50%, 40%)`;
const hueOfB = (b*30) + 30;
someOtherElement.style.backgroundColor = `hsl(${hueOfB}, 50%, 40%)`;
}

The first strategy is to parameterise the range:

1
const use = () => {
2
const { relative } = state;
3
const hueOffset = 30; // Start of hue range
4
const hueRange = 30; // How far hue range goes from offset
5
const hue = (relative*hueRange) + hueOffset;
6
document.body.style.backgroundColor = `hsl(${hue}, 50%, 40%)`;
7
}

Now we have the benefit of variables, which, when well-named, can improve code readability, especially the math on line 5.

The next step, assuming the values are constant throughout the sketch would be to put them in settings:

1
const settings = Object.freeze({
2
hueOffset: 30 // Start of hue range
3
hueRange: 30; // How far range goes from offset
4
})
5
6
const use = () => {
7
const { relative } = state;
8
const { hueOffset, hueRange } = settings;
9
const hue = (relative*hueRange) + hueOffset;
10
document.body.style.backgroundColor = `hsl(${hue}, 50%, 40%)`;
11
}

Now we have one place, settings, to make tweaks to the overall behaviour of the sketch, rather than ‘magic numbers’ sprinkled - or even repeated - throughout the code. use() pulls in those values from settings and even if lines 8-10 were repeated, we still had the benefit of central settings.

The next step is to refactor it into a function. Maybe you realise all the code cares about is the input scalar and an output colour string.

/**
* Returns a colour string for input scalar
* @param {number} value
**/
const generateColour = (value) => {
const { hueOffset, hueRange } = settings;
const hue = (value*hueRange) + hueOffset;
return `hsl(${hue}, 50%, 40%)`
}

Assuming you were doing this in several places in your code, now you can delete this and have one place for it. Give generateColour() a number 0..1, and it gives you back a colour string, ready for use:

const use = () => {
const { relative } = state;
document.body.style.backgroundColor = generateColour(relative);
}

Another benefit we gain with generateColour() is it becomes a central place for doing checks or processing of a value before it gets converted to a hue. For example, maybe we want to ensure the input is clamped to the 0..1 scale:

const generateColour = (value) => {
value = clamp(value);
...
}

There’s many other ways this function could be.

If you wanted saturation and lightness to be dynamic, these too could be parameters for the function. Or perhaps every time you generated a colour you were also assigning it to the background colour of an element. Maybe it would make sense to include that functionality:

/**
* Sets the background colour of an element according to
* a relative value
* @param {number} value
* @param {HTMLElement} element
**/
const setBackground = (value, element) => {
const { hueOffset, hueRange } = settings;
const hue = (value*hueRange) + hueOffset;
element.style.backgroundColour = `hsl(${hue}, 50%, 40%)`;
}

Now it’s also trivial to set the background colour for more elements using different relative values:

const use = () => {
const { someOtherThing } = settings;
const { relative } = state;
setBackground(relative, document.body);
setBackground(1-relative, someOtherThing); // Set to the inverse
}

If, however, sometimes you set the colour to the background and sometimes used it in a different way, maybe a better strategy is to make a composition of functions, each with narrower duties.

/**
* Returns a colour string for input scalar
* @param {number} value
**/
const generateColour = (value) => {
const { hueOffset, hueRange } = settings;
const hue = (value*hueRange) + hueOffset;
return `hsl(${hue}, 50%, 40%)`
}
/**
* Sets the background colour of an element according to
* a relative value
* @param {number} value
* @param {HTMLElement} element
**/
const setBackground = (value, element) => {
const hsl = generateColour(value);
element.style.backgroundColour = hsl;
}

That way we can call generateColour() when we just want a colour string, or call setBackground() when we want to assign a colour based on a scalar. Because setBackground() uses generateColour(), we maintain the benefits of DRY. You can also see that setBackground() becomes very readable since generateColour() has an obvious name.

Hopefully the refactored versions we ended up with are more readable than the version we started out with. There’s not even a need to add comments, because the function names and variables do the talking. This not the case if we go back to the original, sans comments:

const use = () => {
const { relative } = state;
const hue = (relative*30) + 30;
document.body.style.backgroundColor = `hsl(${hue}, 50%, 40%)`;
}

…at the very least the refactored versions will be more useful, reliable and easier to extend.

This is all to say there are many different ways of slicing up a problem, and what is correct in a given case will really depend on what the needs are of the sketch. Don’t make functions just for the sake of it, but as soon as you find yourself repeating things, ask whether it would be better to make a function.