Best-Practice: Embrace Named Arguments
tl;dr: When designing functions with multiple parameters, use named arguments via an object instead of relying on positional arguments.
Consider this function call:
const order = placeOrder("Latte", 2, "Extra Hot");
It’s easy to assume that the first argument specifies the drink type and the second indicates quantity. But what about the third argument? Is it the drink's temperature, or could it represent something else?
Now, look at this example:
const area = calculateDimensions(5, 10, 15, 20);
What do the values 5
, 10
, 15
, and 20
signify? Are they the dimensions of a rectangle? Or do they represent coordinates in a certain format? Without context, it can be challenging to interpret.
This ambiguity is a frequent issue with positional arguments. While you might guess the meanings based on the function name or implementation, this isn’t the most efficient approach. Plus, when reviewing code outside your IDE, you lose the benefit of inline hints, making it harder to grasp the function's purpose.
The Solution: Named Arguments
Instead of relying on positional parameters, you can utilize an object with clearly defined properties. Here’s how you can refactor the previous example:
type OrderInfo = {
drink: string;
quantity: number;
temperature?: string;
};
function placeOrder({ drink, quantity, temperature = "Hot" }: OrderInfo) {
// ...
}
While this method involves some additional structure, it greatly enhances the clarity of what each parameter represents.
Advantages of Named Arguments
-
Clarity
Named arguments remove any confusion. A function call like this:placeOrder({ drink: "Latte", quantity: 2, temperature: "Extra Hot" });
Clearly defines what each property means.
-
Ease of Discovery
If you encounter a function you haven't used before, you can quickly check its expected properties by looking at its definition. There’s no need to sift through documentation or hover in your IDE for hints. -
Handling Optional Parameters
In functions with numerous parameters, optional arguments can complicate things. With positional arguments, optional parameters must be placed at the end, which can lead to confusing calls. For example:function updateSettings(a: string, b?: number, c?: boolean) { // ... } updateSettings("Sample", undefined, true);
This can obscure the purpose of the call. With named arguments, you can easily skip over properties you don’t wish to define:
updateSettings({ a: "Sample", c: true });
Are There Any Downsides?
While the benefits of named arguments are significant, there are a few minor drawbacks. One is the additional repetition involved in defining the type of the object and destructuring it in the function signature. Another consideration is performance, as passing an object entails creating a new instance each time the function is called. However, in most scenarios, this performance hit is negligible. If you find yourself in a performance-critical context, you can always revert to positional arguments as needed.
Conclusion
Based on my experience, I highly recommend adopting named arguments for functions with multiple parameters. The clarity, ease of use, and improved readability they provide far outweigh the minor downsides. Say farewell to confusion and embrace a more intuitive way of writing functions!