5 ms·
Allright, 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-num
by kall 6y ago
Allright, 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.
- valenterry 6y agoSure, let's do that :) if( Function.prototype.toString.call(add).includes("multiplier") ) throw "boom!"; > Edit: OK I think we are stretching HN comment ettiquete to far with this much code. This was fun though. Thanks. Huh? Would you mind to educate me about what part of the ettiquete we are not following?
- kall 6y agoIDK not a real thing, you just never see it. I feel like it's a forum not a chat room and it doesn't collapse deep threads by default so the long code makes the page very long. Probably doesn't matter though. At this point we can go ahead and break the world: add.toString = () => `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;}`; _add.toString = () => `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.prototype.toString = () => `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;}`;
- valenterry 6y agoI think it's very interesting for others to follow this. Now, we are leaving the original scope (not just changing a function, but modifying globals). read-only globals even. But prepare for my counter: let frame = document.createElement('x'); document.body.appendChild(frame); if( frame.contentWindow.Function.toString.call(add).includes("multiplier") ) throw "boom!"; You might go to also kill "document.createElement", but there are many ways for me to get a new frame. I think when we come to the point where all these are disabled, I would say only a small fraction of the websites that use javascript would still properly operate. It would be your victory though. ;)
- bryik 6y ago> Before you scream that this is stupid, please mind that this is actually used out there That is stupid though. It's like saying changing a private field in Java is a breaking change because someone might have used reflection to access it. Taken to the moronic extreme: any detectable change is a breaking change because someone could write a function that pulls your latest release and depends on every bit being identical with the previous release.
- valenterry 6y agoYeah, it is stupid, but it is also true. > It's like saying changing a private field in Java is a breaking change because someone might have used reflection to access it. Which is true, both in theory and practice. Even look at misc.Unsafe - which is deliberately named unsafe and everyone was told not to use it. Then they tried to drop support for it and people freaked out so much that support was continued. (https://jaxenter.com/java-9-without-sun-misc-unsafe-119026.html https://jaxenter.com/java-9-without-sun-misc-unsafe-119026.h...) > Taken to the moronic extreme: any detectable change is a breaking change because someone could write a function that pulls your latest release and depends on every bit being identical with the previous release. I would say it is best described here: https://xkcd.com/1172/ https://xkcd.com/1172/