Web Dev Stories
AboutArticlesProjectsBookmarksContact

written on 07/08/2019

Function parameters in JavaScript — Clean Code

In my time as a web developer I have to deal a lot with JavaScript Code. I love JavaScript, no question asked. But sometimes I hate code written by people who are not embracing the language features. The time where most mistakes are made in my opinion are in function parameters. Functions with a lot of parameters are most often stale and cannot be changed in a lot of places. This is a real frustrating experience for other developers who have to shift a lot around. But let us tackle this problem in this blog article.

So let us imagine a function which is calling an REST API endpoint with a lot of parameters. This HTTP call will be a GET with a lot of filters. In our example I will take the API call to get twitter tweet timelines. You can find the documentation here: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline

So let us create our simple function. It will take the query parameters as function arguments:

1
const getStatusHomeTimeline = (
2
count,
3
since_id,
4
max_id,
5
trim_user,
6
exclude_replies,
7
include_entities
8
) => {
9
axios
10
.get("/1.1/statuses/home_timeline.json", {
11
params: {
12
count,
13
since_id,
14
max_id,
15
trim_user,
16
exclude_replies,
17
include_entities
18
}
19
})
20
.then(response => {
21
// ...
22
})
23
.catch(error => {
24
//...
25
});
26
};

The function is then called like this:

1
getStatusesHomeTimeline(5, 12345, 54321, true, true, false);

This seems quite reasonable. But now think about the client. This function is used 100 times in your code base (let us just assume this) and now Twitter is changing the API to include another parameter. You would need to change the function of your API-calling business logic to something like this:

1
const getStatusHomeTimeline = (
2
count,
3
since_id,
4
max_id,
5
trim_user,
6
exclude_replies,
7
include_entities,
8
new_parameter
9
) => {
10
// ...
11
};

The problem now is that you need to change every caller of the function to include the last argument. A solution for this would be to add a default parameter. This is supported since ES6. So let us refactor the function to use default parameters to the last parameter.

1
const getStatusHomeTimeline = (
2
count,
3
since_id,
4
max_id,
5
trim_user,
6
exclude_replies,
7
include_entities,
8
new_parameter = null
9
) => {
10
// ...
11
};

Problem solved :) You do not need to change all the callers of the function. Problem is that if a required parameter now gets included you need to append them to the function and change all function callers. Not really scalable. A easy solution is provided by the book Clean Code: If there are more than two or three parameters in a function, provide the parameters as one object. So let us transform the function to use an object instead of multiple parameters.

1
const getStatusesHomeTimeline = params => {
2
axios
3
.get("/1.1/statuses/home_timeline.json", {
4
params: {
5
count: params.count,
6
since_id: params.since_id,
7
max_id: params.max_id,
8
trim_user: params.trim_user,
9
exclude_replies: params.exclude_replies,
10
include_entities: params.include_entities,
11
new_parameter: params.new_parameter
12
}
13
})
14
.then(response => {
15
// ...
16
})
17
.catch(error => {
18
// ...
19
});
20
};

Still, this seems really verbose and also the default parameter is gone 🤷

There are two ECMAScript features which can help us here:

  • Object destructuring

  • Default parameters (hey we have used them before)

So, what we will do here now is quite simple: We will destruct the object given as parameter to the properties and also add a default parameter for the destructured property new_parameter :

1
const getStatusesHomeTimeline = ({
2
count,
3
since_id,
4
max_id,
5
trim_user,
6
exclude_replies,
7
include_entities,
8
new_parameter = null,
9
}) => {
10
axios
11
.get("/1.1/statuses/home_timeline.json", {
12
params: {
13
count,
14
since_id,
15
max_id,
16
trim_user,
17
exclude_replies,
18
include_entities,
19
new_parameter,
20
},
21
})
22
.then(response => {
23
// ...
24
})
25
.catch(error => {
26
// ...
27
});
28
};

Now we can still call the function with an object but also have the advantage of extensibility and default parameters.

Another good reason to use objects as function parameters is that you need to name the properties in the caller function. This is indirect documentation and helps a lot to read code for other developers. The main advantage here is the extensibility of functionality which is super important in modern software development because features will change and code will be moved around. So dynamic and extensible functionality is key.

Thanks for reading this. You rock 🤘 If you have any feedback or want to add something to this article just comment here. You can also follow me on twitter or visit my personal site to stay up-to-date with my blog articles and many more things.

You might also like

© Kevin Peters 2021

Imprint