Code readability is a big thing. Developers spend a lot of our time reading code: other peoples code, our own code, code we have never seen before, etc. Writing code in a way that is as readable as possible can help save everyone on your team a lot of time. Readability can sometimes be a bit of a trade off with performance, but I try to stay on the readable side as often as possible.
One pattern I have to like a lot for Javascript is the RORO pattern, or Receive an Object, Return and Object. The gist of the pattern is as follows: functions should always accept an object as their parameters and they should always return an object as their result. We will then destructure the arguments and the return to have more expressive way of knowing what goes in and out of a function. Receiving an Object as standard for all the functions I write is something I started doing after learning Python and getting experience with kwargs, which I greatly enjoyed since it made it so easy to know what was going into a function. It's great to be able to label the arguments you are passing into a function, and the RORO pattern in Javascript gives us some similar functionalities.
Consider the following function call
const item = await getItemFromCollection(54391, 'shop');
This is the type of function I am trying to avoid writing. This function has a descriptive enough name, we can assume that it is going to get an item from a collection. However, what does the rest of the information going in mean? We could try to make some educated guesses about some of it; 54391 looks most like an id, and shop is probably the name of a collection. We can't be positive about either of those though, and to fully understand the interface for this we would have to go find the function declaration in our codebase.
async function getItemFromCollection(id, collectionName) {
Ok, so we had to go through the code base and we found the function, but that took a bit of time. If we write this function using the RORO pattern, we can eliminate the need to do this.
async function getItemFromCollection({ id, collectionName }) {
// do something
};
const item = await getItemFromCollection({
id: 54391,
collectionName: 'shop',
});
By immediately destructuring the parameters object, we have for the most part the same syntax when declaring our function. However, later when we are calling our function we now have a way to passed named arguements. Just by reading the function call we know exactly what the different arguements are supposed to be, there is no more having to go and look up the function at it's declaration location to have context. The context of the arguements are now a part of the call itself, which I find to be a lot more readable and having to cut down on searching through a codebase/documentation.
One of the big reasons I started adopting this pattern is that I have a lot of disdain for boolean parameters getting passed in. Consider:
someFunctionCall(false);
I don't like seeing functions like this at all. Booleans provide so little information outside of what they are, and I have seen people try to rectify this in all sorts of ways.
const variableNameDescribingBooleansPurpose = false;
someFunctionCall(variableNameDescribingBooleansPurpose);
someFunctionCall(/* comment describing the booleans purpose*/ false)
The best solution is to pass in an object and destructure immediately. Tie the description of what is being passed in directly to the function call itself and there is no more need to do anything extra to make your code readable; it will just be readable.
someFunctionCall({ booleanPurpose: false });
Now, this pattern does have two parts. So far we have touched on the Receive an Object part, but that still leaves Return an Object. This works similarly to what we have talked about already; our functions are going to return objects out and we are going to immediately destructure to get the data we want. What is nice about this about this is that it gives our functions an ability that some languages have but that Javascript doesn't natively support: multiple items in a single return.
async function runProcess({ processName }) {
// run process on your server
// maybe you decide to cache the results of this process running
return { result, wasCached };
};
// destructuring off the result we can now have multiple items return from our function
const { result, wasCached } = await runProcess({ processName });
if (wasCached) {
// run another process
}
I personally get a lot less utility of Return and Object than I do Receive an Object. Multiple return items are pretty nice, but I find that most functions I write only concern themselves with a single thing. In that case, it is a little extra labor to have to destructure the return value when you can just store it in a variable like normal. It does come in handy every once in a while, but I don't use it all the time.
I find the most useful part of all of this to be the named parameters. I've just found functions to be so much easier to read; especially when dealing with multi-parameter functions or functions that accept a boolean. Multiple return items is nice as well, but comes in play in more niche situations. Ultimately, this pattern is about making our Javascript functions more flexible. I started to adopt this style of programming after learning Python and Go and seeing some of the nice things those languages do for their functions. Drawing inspiration from other sources is a great way to learn and improve!