Let me ask you: What do you think this code does?<p><pre><code> resizeImage(imagePath, 300, 200, true, true, 1)
</code></pre>
It resizes image... but what exactly does it do? For the most part, we cannot tell without looking up the function definition.<p>Let's say you are reviewing a PR and it includes this change:<p><pre><code> -resizeImage(imagePath, 300, 200, true, true, 1)
+resizeImage(imagePath, 300, 200, false, true, 1)
</code></pre>
Can you confidently say what is the impact of this change? For the most part... no. You need to know what each positional argument does.<p>Let's say you know that the interface is:<p><pre><code> function resizeImage(
imagePath: string,
width: number,
height: number,
upscale: boolean,
crop: boolean,
quality: number,
): Promise<Buffer>
</code></pre>
But now a PR introduces a change to the parameter order (e.g. to make it consistent with other functions):<p><pre><code> function resizeImage(
imagePath: string,
width: number,
height: number,
+ crop: boolean,
upscale: boolean,
- crop: boolean,
quality: number,
): Promise<Buffer>
</code></pre>
How do you review this change? Sure, reviewing the interface diff is easy, but what about the dozens or hundreds of diffs that update function invocation?<p><pre><code> -resizeImage(imagePath, 300, 200, true, false, 1)
+resizeImage(imagePath, 300, 200, false, true, 1)
resizeImage(imagePath, 300, 200, false, false, 1)
-resizeImage(imagePath, 300, 200, false, true, 1)
+resizeImage(imagePath, 300, 200, false, false, 1)
-resizeImage(imagePath, 300, 200, true, false, 1)
+resizeImage(imagePath, 300, 200, false, true, 1)
</code></pre>
Hopefully the problem is self-explanatory: Positional arguments create a breading ground for hard and even impossible bugs to catch/debug/fix, esp. when code needs to be refactored. Fear not though as there is a better way.<p>Let's start from the start, but this time use a single-object parameter:<p><pre><code> resizeImage({
imagePath,
width: 300,
height: 200,
upscale: true,
crop: false,
quality: 1,
})
</code></pre>
Can you tell what is the intention behind this code? Yes, you can get a good sense, even if you are not familiar with the implementation.<p>Can you easily refactor the interface? Yes, linter will warn you if contract is not satisfied.<p>We end up with positional arguments because they feel the most natural to start with. However, as functions grow in scope, what started as a simple function with 1-2 arguments becomes an unreadable mess.<p>This is where [`max-params`](https://eslint.org/docs/latest/rules/max-params) comes to the rescue. Simply adding an ESLint rule that restricts having functions with more than X parameters ensures that your code remains legible and easy to refactor as your codebase scales.