Right. Rather than checking if the array exists, his hook should just always be returning an empty array. The map would then do nothing and his component would never even try to render.
You're making the assumption that the person reading this is using modern JS tooling (not just writing vanilla JavaScript). What's proposed here, though verbose, is a good way to teach type checking in JS without a dependency.
Tooling is a crutch. We should be teaching more of the above, not less.
`if (window.stripe && stripe.handleCardPayment)`; just `stripe` may throw a ReferenceError if not loaded, while `window.stripe` will be `undefined` (appropriately falsy) if not loaded.
(This is assuming a browser environment, where `window` is roughly the global object; see also `globalThis`, especially for further reading on how unfortunately complicated the whole situation is.)
This is probably loaded from Stripe's servers so they can do their own updates without customers involvement. There is no compile check because the library is not included during compilation.
>This is probably loaded from Stripe's servers so they can do their own updates without customers involvement.
They can make whatever updates they want to the API and publish a new NPM version. Package management exists for a reason. Including scripts on a page like this for web development is simply anachronistic and wrong in 2021. Maybe for just throwing something into a blog site, whatever fine. But it's just not an acceptable practice for application development anymore.
Eh, there is pros and cons. When Stripe updates it's ask it's immediately deployed to _every_ user immediately. Yes it's possible with npm and semver but it's also possible people pin specific versions or get stuck on a certain minor version.
While I think that's fine for normal software Stripe lives in the world of financial regulation and compliance with laws around the world. They might not be able to even allow their users to use an older SDK (regulation changed, new regulatory steps, etc) in a graceful manner.
Sticking to live loaded sdk and the technical debt that comes with to not break clients and end users might just be the best option for something as risky as payments online.
The thought of a critical revenue impacting API changing dynamically without warning is absolutely terrifying to me.
The thing is, no other software in existence works like this. Payment systems are ubiquitous across an incredible array of devices outside the context of a web browser and they are perfectly fine with using normal software engineering techniques like package management and static linking. Dynamic script imports are fraught with hangups like http errors and slow user hardware causing race conditions. And you're then always tied to using some kind of loading function. Remember having to write entire applications within a jQuery onLoad? It's a vestigial pattern from the early days of the web. We're better than that on the front end now.
The Stripe SDK could be loaded from stripe.com, the user might be running extensions that disable Stripe. Nothing wrong with making sure that a critical operation can be run.
Not trolling: counting on compile time checks in Javascript is the opposite of what I think of as resilient.
Although I'm a huge fan of TypeScript and use it and a bundle in all my UIs, you cannot guarantee that everything was typed correctly or that things haven't escaped being typed.
Sometimes I wonder if the right thing is to actually guard every object from being undefined like this sample. It does seem crazy.
This is basically a problem for every language with metaprogramming/eval/LD_PRELOAD: you can't necessarily truly count on things being defined like you think they are. That also starts getting pretty paranoid but again the topic here is resilience so should you ignore that things can/actually do change under the hood when you don't expect?
Error-handling code is the buggiest code out there. You should strongly avoid unnecessary error-checking stuff, because how you handle it will commonly be worse than having just done nothing. As a related example, validating email addresses badly (forbidding valid email addresses) is probably worse than doing nothing (accepting invalid email addresses).
This is an excellent example of necessary error-checking stuff; user-agent feature detection (which includes the browser’s baseline functionality which is basically a completely uncontrolled third-party library, the worst situation by far) is another good example. But it’s extremely easy to take it too far. Any library stuff that gets bundled into your code, you should prefer compile-time to runtime type-checking. But anything outside, yeah, then you have to decide.
The code that you locally control and own you can have strong confidence in and type checking can be very valuable. Anything brought in at run time (say a third party lib loaded via a script tag or data fetched from an endpoint) you have no guarantees at all about what it will actually be. You can still use TS to your advantage here and type the third party code/data in such a way that will cause the compiler to encourage you to be more defensive, such as using the unknown type.
Just because you wrote user input code or an HTTP client does not mean that the results of it are trustworthy. So no I would not agree that even your own code is trustworthy.
If it's your own code that does no IO then yeah it's more ok to trust TypeScript.
The HTTP client itself is trustworthy to the extent that static code can be. The data it acquires through requests is not.
Same with user input code. You can trust your implementation to the extent of static type checking and analysis. You can't trust anything a user entered into your system. Those are examples of the externals that I stated you can't trust.
If you aren't personally in control of the backend, you have to assume that your backend is occasionally going to have bugs or breaking changes or unexpected information. If unexpected data is going to cause your app to crash or work wrong, IMO you absolutely need to have a plan for spotting those errors and failing gracefully.
It depends on how much you want to lie to yourself.
At the end of the day, Javascript is still a dynamic language where you might be loading dependencies from outside your build chain. It's up to you to decide how defensive/safe you want to be I guess. I don't think there's a wrong answer.
And this is the dirty truth about Typescript in modern frontend. The truth of the matter is, the true type of everything that hits the backend is type any, because it could truly be anything. You can run from it all you want, but compile time checking won’t save you from the JavaScript gods (who are merciless). Better get those run time null checks in.
I dare you to connect to a real time api and rely on your type checking, it ain’t gonna happen.
Well, have you ever built something real? You sincerely believe that at runtime an API call will always be what you think it will be because you set the type in Typescript?
I don’t want to be that guy, but there’s no way you built anything real.
‘hey guys, I totally set the type of the api response to String, so it’s always going to return a string, right right?’.
Wait till all your compile time type checks don’t mean jack shit on prod when multiple endpoints just do something unexpected. Now you have a UI that didn’t gracefully handle any of it, and have fun extolling the virtues of type checking to the business — ‘but it says right here, it’s a String!’.
Testing things are available before you use them is just basic progressive enhancement. The cost of doing it is so minimal, and the improvement to the user experience so great, that there's no reason not to.
I think this code is bad because it uses if/else (it should be if not abort) and it only tests/handles one error case and the log is not very valuable, would be much better to wrap the block in try catch and capture all exceptions (should be fairly obvious from exception that stripe.handleCardPayment doesn't exist)
27 comments
[ 2.7 ms ] story [ 66.8 ms ] thread[1] Second example here: https://www.smashingmagazine.com/2021/08/build-resilient-jav...
Tooling is a crutch. We should be teaching more of the above, not less.
`if (stripe && stripe.handleCardPayment)`
(I know the behaviour isn't identical, but checking the actual type is pointless in this case)
(This is assuming a browser environment, where `window` is roughly the global object; see also `globalThis`, especially for further reading on how unfortunately complicated the whole situation is.)
Imports and Webpack. I cannot imagine externally loading a payment SDK like that in this day and age.
They can make whatever updates they want to the API and publish a new NPM version. Package management exists for a reason. Including scripts on a page like this for web development is simply anachronistic and wrong in 2021. Maybe for just throwing something into a blog site, whatever fine. But it's just not an acceptable practice for application development anymore.
While I think that's fine for normal software Stripe lives in the world of financial regulation and compliance with laws around the world. They might not be able to even allow their users to use an older SDK (regulation changed, new regulatory steps, etc) in a graceful manner.
Sticking to live loaded sdk and the technical debt that comes with to not break clients and end users might just be the best option for something as risky as payments online.
Although I'm a huge fan of TypeScript and use it and a bundle in all my UIs, you cannot guarantee that everything was typed correctly or that things haven't escaped being typed.
Sometimes I wonder if the right thing is to actually guard every object from being undefined like this sample. It does seem crazy.
This is basically a problem for every language with metaprogramming/eval/LD_PRELOAD: you can't necessarily truly count on things being defined like you think they are. That also starts getting pretty paranoid but again the topic here is resilience so should you ignore that things can/actually do change under the hood when you don't expect?
This is an excellent example of necessary error-checking stuff; user-agent feature detection (which includes the browser’s baseline functionality which is basically a completely uncontrolled third-party library, the worst situation by far) is another good example. But it’s extremely easy to take it too far. Any library stuff that gets bundled into your code, you should prefer compile-time to runtime type-checking. But anything outside, yeah, then you have to decide.
If it's your own code that does no IO then yeah it's more ok to trust TypeScript.
Same with user input code. You can trust your implementation to the extent of static type checking and analysis. You can't trust anything a user entered into your system. Those are examples of the externals that I stated you can't trust.
At the end of the day, Javascript is still a dynamic language where you might be loading dependencies from outside your build chain. It's up to you to decide how defensive/safe you want to be I guess. I don't think there's a wrong answer.
I dare you to connect to a real time api and rely on your type checking, it ain’t gonna happen.
I don’t want to be that guy, but there’s no way you built anything real.
‘hey guys, I totally set the type of the api response to String, so it’s always going to return a string, right right?’.
Wait till all your compile time type checks don’t mean jack shit on prod when multiple endpoints just do something unexpected. Now you have a UI that didn’t gracefully handle any of it, and have fun extolling the virtues of type checking to the business — ‘but it says right here, it’s a String!’.
And what if it’s not? The problem with induction.