React/TypeScript (4 Part Series)
My recent journey into the Land of the TypeScripts has led me to some odd realizations about what it does - and what it doesn't do. Specifically, I've become aware of some stunning similarities between the "language" of TypeScript and the linting tool we know as JSDoc.
Throughout this article I'm going to use a handful of key assumptions. I freely admit that these assumptions may not always apply to your projects. And given the way that your team codes, you might feel that there's little in common between TS and JSDoc. But the following "ground rules" are what lead me to question the utility of one over the other:
If we are keeping our functions concise, the biggest obstacle to writing bug-free code is ensuring clean inputs to our functions. Similarly, we strive to ensure a consistent output from our functions. With both TS and JSDoc, it's at least possible to annotate the specific type of every variable.
But in my experience, this is rare. If we have a LoC like this:
const foo = 'bar';- we rarely bother to specifically annotate that
foois of type
string. It's obvious to any first-year dev that
foois of type
string. And there's rarely much utility in codifying this for the compiler.
Because functions (and their associated code files) can often be organized in such a way that they're nowhere near the place where they're being invoked, it can be easy to accidentally pass the "wrong" type of data. "Bad" data often leads to "bad" results. So even in TS-heavy (or JSDoc-heavy) codebases, most of the type hinting we're trying to do tends to be focused on function signatures.
I'll be the first to admit that I've never been a huge fan of JSDoc. Why? Well, every JSDoc annotation is a comment. And, for some time now, I've generally tried to avoid almost all comments in my code. I'm a firm believer that, rather than writing comments that explain your code, it's far better to write self-explanatory code. (That's a subject for a whole other article...)
Worthless for runtime errors
Although the path to plain ol' JS isn't entirely identical for TS vs. JSDoc, they both share one critical trait (or, if you will, fault): They're both powerless to address RUNTIME errors. In fact, in some respects, I actually prefer the JSDoc approach because every junior dev understands that comments serve no functional purpose. And every JSDoc annotation is a comment. Conversely, it's easy, after writing many hours of TS code, to nearly forget that all that glorious TS magic doesn't even exist at the point that your application is, you know... running.
TS & JSDoc are both as loose - or as strict - as you'd like them to be. Granted, with TS it's easy to set global configs in such a way that it's more... conspicuous to bypass the compiler. But at the end of the day, neither one is doing a single thing to change the way that JS operates. They're only changing the way that you (and the other members of your team) grok the code as you're writing it.
One of the things that always turned me off about JSDoc was the gaggle of extra LoC that littered every one of my files. Sure, those lines of "code" didn't contain any logic. They were all... comments. But I hated taking a nice, clean, concise function that accepts, say, four inputs and returns one output, and chunking seven extra lines of comments above the function just to explain what those inputs/outputs should be. Then I started writing TS - and I find that I'm routinely writing many extra LoC, and many extra functions/types/interfaces, and many extra code files, just to achieve what I was previously doing with vanilla JS. Suddenly, the extra "load" of JSDoc's comments don't seem like such a burden.
It's pretty difficult to construct a "serious" type model when you're confined to the types defined in the underlying language. With TS or JSDoc, I can annotate that the
firstNameargument is type
string. Umm... great? I mean, sure, it's nice to signify that
firstNamecan't be, say, a
Array. But what if someone passes an empty string for
firstName? Is that a "valid" input? Probably not. It's still, technically, a
string. But neither TS nor JSDoc are adequately equipped to handle these common "gotchas". To guard against this, we still have to fall back on runtime checks, typically performed at the top of the function.
Less coding tools than IDE tools
If you don't believe this, try coding up some tasty TS code... in Notepad++, or Sublime, or any other "basic" text editor. Then tell me how fun that experience is. Granted, advanced IDEs are the tools in which nearly all modern code is written. So I'm not painting this as a "knock" against against either TS or JSDoc. But most of the "magic" that we get from TS occurs when our IDE puts one of those warning squigglies under our code. And you can receive the exact same warnings... with JSDoc.
Whenever you're using any tool designed to better annotate your code, it can be insanely annoying when the IDE/compiler somehow gets it "wrong". I gave an example of this in my previous article talking about TS object handling. But the fact is that I've run into the same problem before with JSDoc. I go to great lengths to fastidiously "define" all of my data types. And then, at some random place in the function, I get another of those squiggly underlines. Sometimes this can happen when I know that the code will run perfectly and even after I've gone to great pains to explain to the IDE/compiler how everything should be handled. But sometimes these tools still don't "get it". This is a big reason why I rarely use JSDoc unless the project already uses it as a standard. And it's why I'm already growing somewhat exasperated with TS.
To be clear, I'm not trying to claim that TS is equivalent to JSDoc. I'm sure that a few TS fanboys have already fired off some defensive comments below - before they even got to this point in the article.
When I started this little TS journey about a month ago, I dove in with the (misguided) belief that TS and JSDoc had nothing in common. In fact, before I started writing TS, I had never even attempted to compare both of them in the same paradigm.
But the more TS code I write, the more I find myself thinking that TS is "basically" JSDoc, except it's even more verbose, it gets more bogged down in edge-cases (partials, generics, etc), and its "benefits" are even harder to suss out when weighed against the extra overhead of time and cognitive load.
If this sounds like a long rant of "TypeScript bashing", it's not. As far as I can tell (at the time being), the following are the biggest points in TS's favor:
It's easier to set global rules in TS that will more easily be enforced across your entire team and throughout your entire codebase.
Assuming that global rules are set, it's easier to spot when any dev is trying to subvert those rules. An
anyhas a tendency to stand out like a sore thumb during a pull request. Conversely, the way to "subvert" JSDoc rules is to simply... avoid writing the rules altogether. And it's not nearly such a red flag when some dev has simply failed to write something than it is to catch when he's littered all his code with
Obviously, there are more "edge cases" that have been accounted for in TS. Some complex, abstract TS types can be an absolute eyesore to mentally parse. But at least you can annotate them. In JSDoc, it can feel borderline impossible to thoroughly annotate those same use cases.
My anecdotal assessment is that TS is a growing, thriving, evolving community. Although I could argue that TS is as much of a "language" as JSDoc, I'll freely admit that JSDoc is fairly... stagnant. I can't really say that it's "growing" in any meaningful way. I suppose its skeptics could even claim that it's dying - although it certainly won't be truly going away any time soon.
There are many devs out there who are passionate about working on TS projects. But even for longtime, hardcore "JS guys", I don't know if there are too many of them getting all giddy just because their next project will be using JSDoc. This is not an inconsequential observation. I fully understand that sometimes, if you're building and maintaining a large team of skilled developers, it can be important to utilize the "hot" technologies - even if the "old, stodgy" tech does nearly the same thing and offers nearly the same benefits.
So what say you, TS fanboys? Have I written enough to royally piss you off? Are you circling your legions of TS acolytes to come burn my house down??
Seriously, though... I don't really care too deeply about TS versus JSDoc. I'm not trying to trash TS. I'm not trying to sing JSDoc's praises. (As I tried to make clear in multiple places above, I actually don't care much for JSDoc either.) This is not some creed imploring everyone to dump their TS codebases and convert them all to JS + JSDoc.
If you already have a huge TS codebase, of course you're gonna stick with TS. If your team is already littered with hardcore TS fans, of course you're gonna stick with TS. In fact, even if you're starting from scratch on a "green fields" project, you might look at the strengths/weaknesses of both and decide that TS is the path for you. And that's fine.
I just sometimes get a kick out of observing the way that Technology X is held up as "The New Hotness". And then, when I get in and actually start playing around with that technology, I find myself thinking, "Hmm, this New Hotness feels an awful lot like... the Old Hotness."