Two different styles of functions: does either have an advantage?

  softwareengineering

In the past few months that I’ve been learning Javascript, I’ve wondered which is the better usage of functions:

Functions that perform actions based on their arguments with no return value:

const someName = 'John';

function sayHello(name) {
  console.log(`Hello, my name is ${name}!`);
}

sayHello(someName);

Or functions that return a value based on their arguments, which can then be used to perform an action:

const someName = 'John';

function hello(name) {
  return `Hello, my name is ${name}!`;
}

console.log(hello(someName));

Maybe a more practical scenario… (Edited)

const myImage = document.getElementById('myImage'); // get image
const numberField = document.getElementById('myField').value; // get input field data

function calculateBrightness(input) { // option 1: return calculated brightness percentage
  return `brightness(${input / 2}%)`;
}

function setBrightness(image, value) { // option 2: set image brightness to calculated percentage
  image.style.filter = `brightness(${input / 2}%)`;
}

// option 1
let brightness = calculateBrightness(numberField);
myImage.style.filter = brightness;

// option 2
setBrightness(myImage, numberField);

My style is usually in line with the latter examples. To my understanding, the latter functions are also considered “pure” functions since they do not mutate any variables (which are a core principle in functional programming).

My main questions are:

  1. Does either style have any strict advantages, or is it all down to personal preference/use scenario?
  2. Should I be using either style over the other?
  3. Is there a name for the way in which the former examples are structured?

EDIT: Reorganized the second scenario as per Adam B’s suggestion. Earlier answers may be referring to the old code.

The first variant works with side effects: calling the function 10 times will produce 10 lines in the console.

The second version is without side-effects: you can call it 10 times and 10 times you can do something or nothing with its result.

The latter is sometimes called a functional style. Now about preferences, the question is too broad. It can be a matter of taste in many cases. But the choice might be constrained by requirements (e.g. some OOP methods are expected to modify an object, or a logging function is expected to immediately log somthing into a file), and more general project directives.

My personal advice would be to prefer the second form when possible since it facilitates reuse, and side effects are error prone.

2

To my understanding, the latter functions are also considered “pure” functions since they do not mutate any variables (which are a core principle in functional programming).

You’re absolutely right.

Does either style have any strict advantages, or is it all down to personal preference/use scenario?

The advantage of a pure function—it’s easier to test, easier to understand, less risky to invoke.

This being said, all four examples you posted are very simplistic. There is no even need for a function there. The first series can be rewritten like this:

console.log(`Hello, my name is ${someName}!`);

And the second one could become:

const numberField = document.getElementById('myField').value;
document.getElementById('myImage').style.filter = `brightness(${numberField / 2})`;

Such more compact view makes it much easier to understand what’s happening here. Functions are good to isolate parts of the code which are easier to understand by looking at the name of the function rather as at its body. For instance, a function such as Math.round(x) is clear, and I would rather avoid looking at the actual implementation every time I want to round a number. Same for findNextPrimeNumber(n), or file.saveChanges().

Your case is different. When I see a function called hello, I have no idea what is it doing. I have to look at its implementation, in order to know it. It’s a bit less true for calculateBrightness, but still, I’m wondering what exactly is happening there, and still need to go and see the implementation.

1

Your question is similar to “which is better, strings or numbers”. Neither is better, they serve different purposes. Other languages go so far as to use separate syntax for the two, in order to help categorize what should be happening.

Functions that return values are easier to understand, as their purpose should be implicit in their names, and regardless of how complex the calculation, the result is a single data structure (even in languages that have multiple return, the result can be viewed as a single structure).

Functions that do something, are necessary, but inherently more prone to being difficult to understand as they can’t necessarily be looked at as making a single change.

Instead of trying to always use one or the other, one should instead strive to use them appropriately. Note that the builder pattern is a common use case for a function which does both.

Your first method is easier to use but very inflexible. There’s only one thing it can do with the string. The second method is totally flexible, the caller can do with the string whatever they like, but it’s more work to use.

If a method is used a lot, people tend to start with one way, and when it turns out to be inconvenient, they refactor.

May I suggest a third option that would make it easier to read 6 months from now:

const image = document.getElementById('myImage'); // get image
const numberField = document.getElementById('myField').value; // get input field data

function calculateBrightness(input) {
  return `brightness(${input / 2}%)`; // return calculated data
}

function setBrightness(image, value) {
  image.style.filter =value; // set image brightness to calculated data
}

let brightness = calculateBrightness(numberField);
setBrightness(image, brightness);

Although this seems like it adds complexity, it actually decreases the mental effort required to understand the code in several ways. The naming of both functions clarifies what they do.

Furthermore, the names hint at whether your functions are changing the objects passed to them. You wouldn’t expect any side effects from a function named calculate, but the set function hints that the image will be changed.

The logic of the calling code is also improved because each step is quite clear in what it is doing. You’ve made it so each function better adheres to the single responsibility principle.

If possible, I would also move the set brightness code to the image class if able. Like so.

image.setBrightness(value);

3

LEAVE A COMMENT