Published on

Why Your Functions Shouldn't Have Side Effects

3 min read - 589 words
Authors
  • avatar
    Name
    Cédric RIBALTA
    Twitter
post image

Introduction

In the world of software development, writing clean and maintainable code is an ongoing challenge.

Among the many concepts that Robert Martin, aka Uncle Bob, discusses in his book Clean Code, the idea that a function should not have side effects is particularly crucial.

Shocked person

If you want to improve the quality of your code and avoid hard-to-debug bugs, understanding this principle is essential.

What is a Side Effect?

Simply put, a side effect is a change in state that occurs outside the direct scope of a function.

This could be the modification of a global variable, the alteration of an object passed as a parameter, or even writing to a file or the console.

In short, a side effect occurs whenever a function affects parts of the program that are not directly related to its main task.

Let's take an example to better understand.

Example of a function with side effects:

let total = 0

function addToTotal(amount: number) {
  total += amount
  console.log(`Total: ${total}`)
}

Why Avoid Side Effects?

  1. Predictability and Simplicity:
    A function without side effects is simple to understand.
    You know that for a given input, you will always get the same result without worrying about the impact on other parts of the program.
    This makes your code predictable and reduces surprises.

  2. Easier Debugging:
    Imagine a bug where a global variable has an incorrect value.
    If this variable is manipulated by multiple functions with side effects, finding the origin of the bug can be a real headache.
    By avoiding these effects, you save yourself hours of debugging.

  3. Simplified Unit Testing:
    Testing a function with side effects is complicated.
    If the function modifies the global state or produces external effects (such as writing to a file), the tests must account for these impacts, which complicates the coverage of the tests.
    In contrast, a function without side effects is much easier to test in isolation.

How to Avoid Side Effects?

The principle is simple: your functions should be pure.

A pure function is a function that, for a given set of inputs, always returns the same result without modifying the global state of the program.

Here an example of a pure function:

function add(a: number, b: number): number {
  return a + b
}

Managing Objects Properly

One of the frequent sources of side effects occurs when objects are passed as parameters.

If a function modifies an object passed as an argument, it introduces a subtle side effect. It's important to return a new instance with a modified value instead of changing the original object.

Here's how to avoid this:

Bad example:

function incrementAge(person: { age: number }) {
  person.age++
}

Good example:

function incrementAge(person: { age: number }): { age: number } {
  return { age: person.age + 1 }
}

Exceptions

Of course, there are cases where side effects are unavoidable, such as writing to a database or calling an API. But even in these cases, it's crucial to structure your code to isolate these operations.

A good way to achieve this is to use higher-level functions to handle side effects while keeping the other functions as pure as possible.

Conclusion

Uncle Bob teaches us in Clean Code that the absence of side effects in a function makes the code cleaner, easier to understand, test, and maintain.

By applying this rule, you reduce the risks of introducing subtle bugs and make life easier for those who will take over your code later — or even for yourself in a few months.

Ultimately, writing functions without side effects is an important step towards more robust and durable code.