7 ms·
Named parameters misses the point. Functions should do one thing and only one thing. This is why we have cos, sin, and tan and not a universal function for trig
by bjourne 5y ago
Named parameters misses the point. Functions should do one thing and only one thing. This is why we have cos, sin, and tan and not a universal function for trigonometry: trig(mode='cos', ...). Such functions often become cumbersome to use since they are essentially multiple functions baked into one.
- flohofwoe 5y agoA valid use case for functions with many arguments are setup/init/creation functions which sometimes take dozens of configuration options. It's definitely better to do such setup operations in a single 'atomic' function call than spreading this out over dozens of configuration functions where it isn't clear when and in what order those should be called, or a combinatorial explosion of atomic setup functions, one for each valid combination of setup options.
- treve 5y agoReligiously splitting functions with boolean arguments doesn't always result in more maintainable code. Instead of trigonometry functions, how would you refactor JS's fetch() with many of its behaviour-altering flags?
- bjourne 5y agoGood rules of thumbs are given in the "Deciding which to use" section of the article. For the fetch() function, I'd keep most parameters as is since they don't change the essense of the function. But "cache" and "redirect" do (following redirects can cause N http requests rather than just one and using the cache perhaps 0) so I'd refactor them as new functions. Imagine adding retry functionality to the fetch() function using parameters. I think you can see how this leads to feature creep.
- peoplefromibiza 5y ago> how would you refactor JS's fetch() as @flavius29663 said (https://news.ycombinator.com/item?id=28593669 https://news.ycombinator.com/item?id=28593669) you can use the builder pattern FetchBuilder() .withUrl(ur) .withMode("cors") .withCache(true) .withHeader('Content-Type', 'application/json') .accept('*/*') .post() .then(response => response.json()) .then(data => console.log(data));
- kevincox 5y agoI see the builder pattern as a way to manage lack of keyword arguments. I see very little difference between your example and the actual fetch API that takes an object as JS's version of keyword arguments. Languages with good support for named/keyword arguments have more features such as required arguments and preventing duplicate arguments. With builder patterns your only real option is to make the builder constructor have required arguments (or throw a runtime error upon finalizing the builder).
- peoplefromibiza 5y ago> . I see very little difference between your example and the actual fetch API that takes an object as JS's version true the only difference is in the tooling code completion for methods names works much better than autocompletion for objects' fields. And you can't mistype a method name, it would not run and give you back a - hopefully - meaningful error, while the same is not true for objects' fields.
- kevincox 5y ago> code completion for methods names works much better than autocompletion for objects' fields. That is true for vanilla JS, unknown parameters will be ignored and unset will be set to undefined. However for languages that support keyword arguments (or even TypeScript[1]) the tooling should be even better than for the keyword argument case. [1] https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMOcAUBvAhgGkQIwF8BKRDAKESsQgQGc4AbAUwDpG4BzNXA4gbnKFy5FOmwAuRAEY8EKQCYS-IA https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMO...
- zamadatix 5y agoI'm not following how moving the options out of the function parameters and into the call chain makes the actual function more maintainable. It's still doing the exact same thing with the exact same options it's just pulling them from elsewhere. If anything you now have more functions to maintain on top of the function that does many different things based on the calling info. // The original "misses the point" trig(mode="cos", type="hyperbolic") // The style fetch() uses today trig({mode: "cos", type: "hyperbolic"}) // The builder refactor trigBuilder().withMode("Cos").withType("hyperbolic").calculate()
- silvestrov 5y agofunction pizza(boolean pepperoni, boolean bacon, boolean mushroom, boolean artichoke) now becomes 16 distinct functions.
- junon 5y agopizza_with_pepperoni_and_bacon_and_mushroom() Wow glad I took out those boolean params!
- flavius29663 5y agopizza is very suitable for the builder pattern: CreatePizza() .WithBacon() .With(artichoke) .Build(); or any combination of the above: CreatePizza() .WithMussroom() .Build() Even better, you can add new ingredients without changing any of the existing signatures: CreatePizza() .WithProsciuto() .WithTomatoSauce() .Build()
- stnmtn 5y agoWhat is the difference of this and doing an object paramater, similar to JS? createPizza({bacon: true, artichoke: true}); This has the same benefit you describe of being able to add parameters without altering any old call sites createPizza({prosiuto: true, tomatoSauce: true});
- flavius29663 5y agoIn my example the could matter, or not. I see this all the times. In your example, can you make the order matter? In my example, after each selection, you can limit or expand the further options.
- CRConrad 5y agoNothing to stop you from doing the same within the code of the multi-parameterized single function, is there? if Shrimp in Fillings then begin Add(Shrimp); Add(ShrimpOil); // So yummy together Fillings.Remove(Jalapenos); // Don't go together end; if Jalapenos in Fillings then begin Add(Jalapenos); end; etc. No?
- wst_ 5y agoI do agree with you completely. But the problem here is as old as programming itself. It's not always easy to define function boundries. If your function returns pizza, as mentioned in other comment, adding some toppings won't change the boundaries. It still returns pizza, with peperoni or not. It doesn't change how you make the pizza. You're just adding more data to it. You may solve it with syntax, language data structures, etc. Whatever you like to make it more readable to the caller. But probably you'd want to pass toppings in arguments. On the other hand if you have a boolean param that changes how function works. That's questionable in my opinion. Say you want to return list of users from DB but omit interns, sometimes, and you need to call API (or query DB) to know if someone's intern. You could define `omitInterns` bool argument but it seems clunky to me. I may be mistaken, though. As said: defining boundries is not easy. This also touches other problem a bit. Should we strive to decrease `if` branching in our functions or not? I personally tend to branch very early on, so later I can follow straight path. That's not always possible, but if it is, it helps greatly. Makes code easier to follow.