Alex Bespoyasov

Posted on

# Refactoring Tools: Fewer Errors with Declarative Style

Let's continue our series of short posts about code refactoring! In it, we discuss technics and tools that can help you improve your code and projects.

Today we will talk about declarative code style and how it can help us decrease the number of accidental mistakes and errors in our code.

## Imperative and Declarative Styles

To understand the difference between imperative and declarative styles, let's take a look at the example:

``````// 1.

const evenNumbers = []

for (const x of array) {
if (x % 2 === 0) {
evenNumbers.push(x)
}
}

// 2.

const isEven = x => x % 2 === 0
const evenNumbers = array.filter(isEven)
``````

The first code snippet is imperative—it describes how to solve the problem as a set of instructions:

• Create an empty `evenNumbers` array;
• Iterate over the given `array` variable;
• Check each number if it is even;
• If so, add it to `evenNumbers`.

On the other hand, the second snippet describes what needs to be done. It focuses on the filtering criteria, not the details of the filtering algorithm.

This is the difference between the imperative and declarative styles. Declarative code describes what to do, while imperative code describes how to do it.

## Mixed Concerns

In imperative code, we have to think about “the goal” and “how to achieve it” simultaneously Because of this, the imperative code often contains more lines and statistically is more likely to contain an error.

Let's look at another example. The `selectOperation` function below chooses a mathematical operation by the given key:

``````function selectOperation(kind) {
let operation = null;

switch (kind) {
case "log":
operation = (x, base) => Math.log(x) / Math.log(base);
case "root":
operation = (x, root) => x ** -root;
default:
operation = (x) => x;
}

return operation;
}
``````

The function seems okay, but every case block in it misses the `break` statement. As a result, the `operation` variable will always be equal to `(x) => x`.

Such an error is relatively easy to spot in a small function, but if there's a lot of code, it's much easier to miss.

Since in imperative code the number of lines and characters is smaller, it's easier for us to see such errors.

For missing `break` statements, we can always set up linter rules and tests to automatically search for such errors.
However, in my experience it isn't always enough because if there's a way in the code to make a mistake people will make the mistake.
So it's better to write code in such a way that there are fewer potential “pits of failure”.

## Improving Code

We can get rid of the problem of accidental errors by making the selection declarative:

``````const log = (x, base) => Math.log(x) / Math.log(base);
const pow = (x, power) => x ** power;
const id = (x) => x;

function selectOperation(kind) {
const operations = { root, pow, id };
return operations[kind] ?? operations.id;
}
``````

In the code above, we now delegate the “selection” to the language itself. We don't care how that choice is made, we only care about its result.

In this code, it's much more difficult to make an accidental mistake because there's less code and its structure is more robust.

I also like the last snippet for aesthetic reasons. Selecting from an object by key looks like a more natural solution to this problem, while code with `switch` seems noisy and verbose.

## More About Refactoring in My Book

In this post, we only discussed the reliability of the declarative code style.

We haven't mentioned its other benefits, like better readability and extensibility, more accurate modeling of the domain, and the ability to split the code and configs.

If you want to know more about these aspects and refactoring in general, I encourage you to check out my online book:

The book is free and available on GitHub. In it, I explain the topic in more detail and with more examples.

Hope you find it helpful! Enjoy the book 🙌