TypeScript: when it helps & when it’s worse!
I love TS until the point I hate it and this post is about the latter part.
Community Expectations
Well … Yes! Like many other core libraries’ developers that would avoid TS for reasons I’ll explain in here, among others, we all want to publish on npm something that can provide a great DX all over the developers’ space: targeting people sticking with vanilla JS or people using TS as default, and it’s all doable behind the scene!
This post is about exploiting just one of (hopefully not) many things TS makes you believe your code is better, or safer, while it’s not, actually the opposite … and from a developer (me) that, during his certified PHP days was all about “strict” (or even all) error reporting around the code, trust me: this is not looking good at all, especially when TS enthusiasts don’t understand the underlying issue!
A Failing Use Case
You can try yourself this simple case over TypeScript Playground:
const wm = new WeakMap<Object,string[]>;
const key = {};
if (!wm.has(key))
wm.set(key, []);
wm.get(key).push('WUT?');
// ^^^^^^^^ oh, "the horror"!
You can play with strict VS non strict behavior and see that the error will always be the same:
Object is possibly 'undefined'.
And What Could Possibly Go Wrong …
First of all, from a single-threaded PL like JS is, nothing at all could possibly go wrong … unless:
- the guard is about using a potentially poisoned
get
method of aMap
or aWeakMap
randomly used in the code (spoiler: it’s not!) - the guard is about TS not understanding the context around that
WeakMap
orMap
in the wild … 🤔
Not sure you really needed to think about these options, but the answer is … 🥁🥁🥁 … the latter! 🥳
If a .get
can fail, so can any .has
or .set
accordingly, but most importantly, in there I wrote code I want to be sure about that if such .get
fails, I expect a “poisoned environment, GTFO or bad things will happen!” sort of instantly throwing message … right? … right?
I don’t want that code to silently fail and moveover because I trust I can find what I have been looking for (literally 2 LOC before, but the logic breaks further) … and I would never expect any error otherwise, accordingly to the logic I explicitly wrote!
Enter TS Misleading Safety Feeling
Most of the TS lovers still reading this, would just fix that issue with an “innocent” ?
, isn’t it?
// previous code ...
wm.get(key)?.push('WUT?');
… fair enough, now I am going to ask you: what did that change to your original intent in writing the code the way you meant to write it?
Here some clues:
- that code will silently fail in there, keep going and doing things after instead of throwing in case that
.get
method was poisoned by evil code around (that isWeakMap
orMap
.prototype.get
to be clear) - my code trusts no evil code is around (bad for libraries authors, still the most common case in the wild) so I just want the TS compiler to be happy … writing meaningless and potentially less predicable code in the making … (congrats? when that will become a habit? 🙃)
Again, fair enough if you are OK with the second clue, yet … have you read, and understood, the first one at all?
Basically what we are saying is:
it’s OK if TS makes me write code that is more prone to 3rd party evil attacks and bugs, as I cannot guarantee anymore my expectations would fail fast when needed, i.e. when a poisoned or broken
.get
happens!
… but wasn’t the whole premises of TS about writing better code?
Enter @ts-ignore
The moment I need to write that comment anywhere in my otherwise fully valid JS code, is the moment I start questioning what TS really brings on the table … to me it starts falling into one of these categories:
- I use TS to help me refactoring later on (likely the only use case I agree about)
- I use TS because it makes me write better code (honestly the worst use case I agree about)
- I use TS because everyone else uses it (fair, in terms of job-market thinking, yet … not fully satisfying?)
Wondering about me? … Well, I am more about:
- I use TS because the community asks for it and my IDE understands JSDoc TS so that occasionally I have hints instead of shenanigans created by TS itself I need to fix later
… and that’s pretty much it, yet happy when it works!
Personal Conclusions
TS is there to stay, or even take over the JS world, but it has still tons of things to improve and I feel like it’s still in its early stages, despite all the great things it improved over recent years.
My personal thinking though, is why anyone would chose to make a powerful scripting language less powerful, and more error prone, due intrinsic conflicts between being strictly typed and naturally being “just a scripting language” (if not the best one) like JS is … I feel like everyone claims successful stories around how better TS made their daily workflow but nobody is able to admit how many times that workflow is stubbornly stuck behind, or slower due, TS incapability to really understand the JS code expectation and logic underneath, like I’ve shown in this post.
Once again, I love TS and what it does for me on daily basis, when I try to make it work out of JSDoc TS awesome integration, but it is in these cases that I wonder if I’m wasting my time instead, using directly or inderectly a project that feels not fully there yet.
Curious to know about your feelings around this topic too though and please, no hate, just sharing, thanks 👋
Closing with this gem: folks, it’s not about how strict or not strict TS is, it’s about TS not understanding the code where strict checks make it just more obvious TS does not, in fact, understand the surrounding code!