13 ms·
How do you feel about adding arguments to a "single object as named arguments that is destructured in function declaration"? I can also always come up with some
by kall 6y ago
How do you feel about adding arguments to a "single object as named arguments that is destructured in function declaration"? I can also always come up with something that breaks, but seems pretty safe.
add({a:2, b:4})
function add({a, b}) {
return a + b
}
function _add({a, b, multiplier = 1}) {
return a+b*multiplier
}
challenge: come up with a breaking use that does not involve something having a property named "multiplier"
- valenterry 6y agoFirst of all, I _can_ demonstrate code that will work when using "add" but explodes when using the changed version - even with your restriction of not having a "multiplier" property. However, let's keep the tension for while. The fact that having a working function using "multiplier" is already a bit broken - it shouldn't work from the beginning, but javascript is designed so that it does. Hence you have to give me this restriction, because otherwise it is obvious how your example can be "broken". Before I expose my (very simple) solution to still break your example even without using "multiplier", I would like to ask you to try to come up with another example first, where you don't require restrictions. I think it's a good exercise. :) If no one comes up with one, I'll show it in, say, a day from now.
- benburton 6y agoconst add = ({ a, b }) => a + b; const _add = ({ a, b }) => b + a;
- valenterry 6y agoThat one is rather simple to break. console.log(add({a:"1", b:2})) //12 console.log(_add({a:"1", b:2})) //21 So to make the code break with the change to _add, I can just do: if(add({a:"1", b:2}) != 12) boom()
- benburton 6y agoAh of course!
- kall 6y agoYeah ok, it‘s a saturday in lockdown, why not. I have a hunch that the only way I can do this safely is with a bunch of safety checks. I have to think about wether your breaking has something to do with the intricacies of function arguments in js or if you consider addition of a property to any type of "options" object that is passed around always breaking. For day to day, I feel this pattern is good enough, as typescript works nicely with it.
- kall 6y agoAllright, I didn't really come up with anything clever. I may have been to sloppy in the original function/restriction. Obviously once you start passing non-numbers, all bets are off. E.g. a simple add({a: "hello", b: "world"}) breaks. Add may well have been intended as a string concatination function so yeah, fair enough. A tricky thing I could see without ever explicitly defining "multiplier" (e.g. on the object prototype) is passing a Proxy that e.g. has a fallback for all missing properties. Detecting a proxy is only kind of possible (?) but we can copy all the original target properties from it, which should make it safe. So here goes, my safe solution for modifying function signature in a non breaking way: function add({ ...args }) { const { a, b, ...rest } = args; if (typeof a !== "number" || typeof b !== "number") { throw "all arguments must be numbers"; } if (Object.keys(rest).length > 0) { throw "You may only pass arguments a and b"; } return a + b; } function _add({ ...args }) { const { a, b, multiplier = 1, ...rest } = args; if ( typeof a !== "number" || typeof b !== "number" || typeof multiplier !== "number" ) { throw "all arguments must be numbers"; } if (Object.keys(rest).length > 0) { throw "You may only pass arguments a, b and multiplier"; } return a + b * multiplier; } Most of these issues (not the proxy one) should be solved by typescript.
- valenterry 6y agoOkay, you definitely deserve praise for that this prevents problems in a real world scenario. Now I feel a bit bad for having made you write so much code. In the evil world, I can break your code like that: try { add(1, 2, 3, 4) } catch (e) { if(e !== "You may only pass arguments a and b") throw "boom"; } However, you can of course make your exception string generic. Then I'll have no choice to use one of my jokers: calling "add.toString()" and inspect your function in detail. Before you scream that this is stupid, please mind that this is actually used out there (looking for example at you, angular).
- kall 6y agoOh you wanna have a go? Let's have a go function add({ ...args }) { const { a, b, ...rest } = args; if (typeof a !== "number" || typeof b !== "number") { throw "no"; } if (Object.keys(rest).length > 0) { throw "no"; } return a + b; } function _add({ ...args }) { const { a, b, multiplier = 1, ...rest } = args; if ( typeof a !== "number" || typeof b !== "number" || typeof multiplier !== "number" ) { throw "no"; } if (Object.keys(rest).length > 0) { throw "no"; } return a + b * multiplier; } add.toString = () => "nice try"; _add.toString = () => "nice try"; Edit: OK I think we are stretching HN comment ettiquete to far with this much code. This was fun though. Thanks.