DEV Community

loading...
Cover image for Tossing TypeScript

Tossing TypeScript

bytebodger profile image Adam Nathaniel Davis Updated on ・12 min read

I don't need TypeScript. There. I said it. Honestly, it feels pretty good to finally assert that on the record. And if we're all being honest with each other, you probably don't need it, either.

My loyal readers (both of them) know that I've been diving into TS fairly heavily over the last several months. Those loyal readers (both of them) also know that I've been running into a few... headaches. But I've finally reached a point where I just have to admit - to myself, and to anyone else who cares to listen - that the whole push toward TS just feels misguided.

This doesn't mean that I've written my last line of TS. My employer seems to be fairly dedicated to it. So, by extension, I guess I am too. But I can't claim, with a straight face, that TS provides any real benefits. In fact, I've found it to be an unnecessary burden.

If this sounds like the rantings of an angry and entrenched greybeard, I suppose that would be fair. But consider this: I hadn't written a single Hook until February and I was increasingly exasperated by all the Hooks/FP fanboys who wanted to shout down any use of class-based React components. But now, 100% of my development is in full-fledged functional programming using React Hooks. So my point is that - I'm stubborn, to be sure. But I'm not completely set in my ways.


Alt Text

TypeScript's Type "Safety" Is Illusory

I've started to wonder how much TS devs even think about runtime issues while they're writing code. I feel like there's this misplaced, near-religious faith bestowed upon TS's compiler. The irony here is that, if you have any experience writing in strongly-typed, compiled languages, you know that "it compiles" is a common JOKE amongst devs.

When I was doing Java and C#, we'd be on some kinda tight deadline. Some dev would push a branch at the 11th hour. And he'd say, "Well... it compiled." To which we'd respond, "Ship it!!!"

Obviously, we didn't just "ship it". The joke is that getting code to compile is the lowest possible standard. Saying that your code compiled is like saying that an athlete managed to remain upright during the entire match.

Umm... Yay?

But in TS, sooooo much effort is poured into getting that magical compiler to acquiesce. And after you've busted your tail making all the interfaces and partials and generics line up, what have you achieved? You've achieved... compilation. Which means that you haven't achieved much at all.

It'd be fair to wonder how TS is, in this regard, any different from, say, C#. After all, even C#, with it's strong typing and robust compiling is vulnerable to runtime issues. But here's why I think it's so much more troublesome in TS.

Most frontend applications have no real data store. Sure, you can chunk a few things into localStorage. And the occasional app leverages the in-browser capabilities of tools like IndexedDB. But for the most part, when you're writing that Next Great React App (or Angular, or Vue, or... whatever), you must constantly rely on a stream of data from outside sources - data that can only be properly assessed at runtime.

When I was writing a lot more C#, it was not uncommon for my apps to run almost entirely in a walled-garden environment where I could truly control the database formats, or the returns from our own internal APIs, or the outputs from our own proprietary DLLs. With this kind of certainty at my fingertips, I'd spend copious time defining all of the data types my app expected. And in those environments, it was often true that, if my code properly compiled, it probably was pretty close to being "ship-worthy".

But when you're cranking out that next Unicorn Single Page Application, most of your critical data probably comes from outside the app. So the comfort of knowing that something compiled is... little comfort at all. In fact, it can be borderline-useless.


Alt Text

Code Is Only As Good As Its Interfaces

No, I'm not talking about TS's definition of an "interface". I'm not even talking about the true-OOP concept of interfaces. I'm talking about an interface as:

Any point in your application where data is exchanged with another application. Or, any point in your application where data is exchanged with another portion of your own app.


Once your app grows beyond a dozen-or-so LoC, you're no longer writing a single app. You're writing dozens of them. And eventually, hundreds or even thousands of them. This happens because we break our code into many, many, many smaller, more easily-digestible bites. If you're an "OOP type", you call these "bites" classes, or methods, or packages. If you're more of an "FP type", you call these "bites" functions, or components, or modules. Regardless of terminology, the effect is the same.

As a body is comprised of billions of semi-autonomous actors (cells), an app is comprised of hundreds, or even thousands, of semi-autonomous programs. So the quality of your app isn't so much predicated on the brilliance of your individual lines of code. Instead, the app's usefulness and hardiness are generally determined by how well all those little "pieces" of your app manage to talk to each other. Screw up the interface between two parts of your app (or between one part of your app and some "outside" data source), and your spiffy little app will suddenly look shoddy and amateurish.

What does any of this have to do with TypeScript? (Or even, JavaScript?) Well, I'm going to drop a radical concept on you:

Type "certainty" has little value inside of a program. But type certainty is critical at the interfaces between programs.



Alt Text

Bad Handshakes

Let's consider the havoc that can be wreaked by sloppy interfaces. Let's imagine that you need to generate random IDs throughout your application. You might write a function that looks something like this:

const createId = (length = 32) => {
  let id = '';
  const alphanumeric = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9'];
  for (let i = 0; i < length; i++) {
    let randomNumber = Math.floor(Math.random() * 35);
    id += alphanumeric[randomNumber];
  }
  return id;
}
Enter fullscreen mode Exit fullscreen mode

On the surface, this isn't a particularly "bad" function. We can use it to generate IDs of any arbitrary length - but by default, it will generate IDs containing 32 characters. Assuming we don't need true cryptographic randomness, the IDs generated by this function should work just fine for our app. But there's a problem...

There's a default value set for length. That's helpful. Assuming we want IDs that are of a default length, it allows us to call the function like this:

console.log(createId());  // ET6TOMEBONUC06QX9EHLODSR9KN46KWC
Enter fullscreen mode Exit fullscreen mode

Or we can call it like this:

console.log(createId(7)); // MSGFXO6
Enter fullscreen mode Exit fullscreen mode

But what if we throw a 'monkey' into the works by doing this?

console.log(createId('monkey')); // [empty string]
Enter fullscreen mode Exit fullscreen mode

This... could cause some problems. Potentially big problems.

'monkey' doesn't actually break the function. It still "runs" just fine. But it doesn't produce an expected result. Rather than receiving some kind of randomly-generated ID, we just get... nothing. An empty string.

Given how critical it can be to have valid, unique IDs in most apps, the generation of "IDs" that are nothing more than empty strings could cause significant issues.

You see, the interface for createId() (i.e., the function signature) allows us to pass in nothing at all, or any value for length - even if that value is not a positive integer. But the logic inside createId() contains an implicit expectation that length will either be a positive integer, or it will be undefined (in which case, the default value of 32 will be used).

This is where I often hear people say something like, "This is my program and I know all the places where createId() will be called. And I know that I'll never pass in some stupid value like 'monkey'." And that might be true. But even if it is, that's no excuse for crappy code.

You shouldn't create forms that will "break" if the user provides bad data. And you shouldn't create functions (or methods, or components, or classes, or... whatever) that will "break" if another programmer invokes them with bad data. Period. If your function only works properly because you always call it in the "right" way, then it's a poorly-written function.

In my experience, "handshakes", that happen all over our apps, are a major source of bugs - sometimes, nasty bugs. Because a function is written with the assumption that a certain type of data will be passed in. But somewhere else, in the far reaches of the code, that function is called with an unexpected set of arguments.

This is why I contend that:

Type certainty is critical at the interfaces between programs.



Alt Text

Under the Hood

Once you get "under the hood" of the function - in other words, beyond the interface - the utility of "type certainty" quickly diminishes. As shown above, it's critical to know that the value of length is a positive integer.

So is it critical to know the data types of the variables inside the function? Not so much.

Ensuring the "safety" of the length variable is important because it emanates from outside the function. So, from the perspective of the function itself, it can never "know" exactly what's being passed into it. But once we're inside the function, it's easy to see (and control) the data types in play.

Inside createId(), we have the following variables:

id (string)
alphanumeric (Array<string>)
i (number)
randomNumber (number)
Enter fullscreen mode Exit fullscreen mode

Even if we converted this to TS, would it be worth our time to explicitly define all of these data types? Probably not. The TS compiler can easily infer the data types that are inherent in each variable, so it's unnecessarily verbose to explicitly spell them out. Additionally, any first-year dev can do the same just by reading the code.

More importantly, explicit data types inside this function will do almost nothing to minimize the creation of bugs. Because it's easy to grok all the data types at play, it's very unlikely that any flaws in the function's logic will be spawned by mismatched data types.

The only variable in the function that could really use some explicit "type safety" is the variable - length - that originated outside the function. That's the only variable that wasn't created explicitly inside this function. And that's the only variable that could create bugs that aren't readily apparent as we read this code.

This isn't meant to imply that there couldn't be other bugs lurking inside our function's code. But adding a pile of verbosity to define all the data types, for variables scoped inside this function, will do little to help us spot or fix such bugs. Because type-checking is not a magic bug-killing elixir. Type-checking is merely the first step in eradicating bugs.

This is why I contend that:

Type "certainty" has little value inside of a program.



Alt Text

Runtime FAIL

It may feel like I've just made a case in favor of TS. Even if you accept that type checking is most critical at interfaces, that's still a vital use of TS, right??

Well...

The real issue here is that TS fails at runtime. To be more accurate, TS doesn't even exist at runtime. When your app is actually doing its thing, it's nothing more than JS. So none of that warm, comforting type-checking means anything when your app is actually, you know... running.

This doesn't mean that TS is worthless. Far from it. TS excels when you're writing one part of your app that talks to another part of your app while exchanging your own trusted data. Where TS becomes borderline pointless is when your app needs to pass around data that was only defined at runtime.

When you're dealing with runtime data, if you want to create robust applications with minimal bugs, you still need to write all those pesky runtime checks on your data. If you start writing enough of those runtime checks, you may find yourself eventually wondering why you're even bothering with TS in the first place.

Let's imagine that our createId() function is tied to a user-facing application, whereby the user can request an ID of variable length. Let's also imagine that we've converted our function to TS. So our function signature would probably look something like this:

const createId = (length: number = 32): string => {
Enter fullscreen mode Exit fullscreen mode

Mmm, mmm! Look at that tasty TS type checking! It sure does protect us from all those nasty bugs, right??

Well...

If length ultimately emanates from a runtime source, then that comforting :number annotation doesn't actually do anything for us. Because, at runtime, the annotation doesn't even exist. So then we'd have to add some additional runtime checking, like so:

const createId = (length: number = 32): string => {
  if (isNaN(length)) length = 32;
Enter fullscreen mode Exit fullscreen mode

And that approach... works. But if that doesn't look kinda duplicative to you, then you've probably been writing TS code for too long.

In the function signature, it looks to the naked eye like we've defined length as type number and we've given it a default value of 32. But then, in the very first line of that same function, we're running a check to ensure that length is indeed a number. And if it's not, we're giving it a default value of 32.

Huh??

If you weren't already drunk on that sweet, sweet TS Kool-Aid, you'd be forgiven for wondering why we'd even bother defining a type number in the function signature at all. Of course, the answer is that, at runtime, there is no type declaration for length. So we end up checking its type twice. Once in the compiler, and once at runtime. Yuck.


Alt Text

Fuzzy Definitions

You may have noticed another problem with the data type definition above. We're annotating that length is of type number. But the definition of "number" is too broad - too fuzzy - to be of much use in our function.

We've already established that, for our function to properly generate IDs, length must be:

  1. A number
  2. Preferably, an integer
  3. Specifically, a positive integer


Any negative value for length is no more useful than passing in 'monkey'. 0 is similarly useless. Technically speaking, decimal/float values would work, as long as they're greater-than-or-equal-to 1, but they would imply a level of precision that is not accommodated in the logic. That's why it makes most sense to limit the input to positive integers.

This isn't a fault of TS. TS is built on top of JS. And JS's native types are... limited.

And even if TS had a custom type that allowed us to annotate that length must be a positive integer, we'd still be limited by the fact that those types are only available at compile time. In other words, we'd still find ourselves writing runtime validations for things that we thought we'd already defined in our code.


Alt Text

A Better Way

So is this just a "TypeScript Is Da Sux" post?? Not exactly.

First, I understand that there are many practical reasons why teams choose TS. And most of those reasons haven't even been addressed in this post. Many of them have little to do with the code itself. And that's fine. I get it.

For those teams, I'm certain that I've written absolutely nothing here that will change your commitment to TS - in any way.

Second, I've noticed amongst the "TS crowd" that there's this kinda mindless mantra about it. A persistent chant about all the supposed bugs they feel they've avoided in their glorious TS code. But the more I look at TS code - and the more I look at the way TS shops operate - the harder it is for me to see any quantifiable benefits. IMHO, the "benefits" are mostly in their heads.

For a certain type of dev, TS seems to provide some kind of comforting blanket. A mental safety net, if you will. It doesn't matter if you prove that the safety net is flawed and will break under minimal stress. Some people just get a "warm fuzzy" when they look in their code and they see all those comforting type definitions.

(And please don't go quoting any of that AirBnB-study nonsense. It was based on a wholesale refactoring of a codebase. Of course they eliminated a ton of bugs when they refactored to TS. That's the whole point of refactoring. They would've eliminated piles of bugs even if they refactored everything in plain ol' JS.)

Third, I'm not claiming that the answer is to simply throw out any notions of type "safety" or type validations. Far from it. In fact, I'm rather anal retentive about crafting fastidious validations - in all my functions - with nothing more than JS.

Back in March, I posted an article detailing how I do data validations - in regular JavaScript. (If you're interested, you can read it here: https://dev.to/bytebodger/javascript-type-checking-without-typescript-21aa)

My recent foray into TS has led me to revisit my JS type-checking library. And I'm happy to report that I've made some significant improvements to it. So significant, in fact, that I just don't see any reason at all to use TS in my personal development.

The next article I write will be a detailed illustration of how I use my new-and-improved JavaScript, runtime, type-checking library.

Stay tuned...

Discussion (31)

pic
Editor guide
Collapse
phantas0s profile image
Matthieu Cneude

Wow. I don't write JS (and I don't really want to), but I still read this article from top to bottom because... it's good.

  1. It's well written, precise, and detailed.
  2. I'm always happy when somebody comes back to the real meaning of interface. It's an important concept, but everybody is confused about it because of the interface construct in OOP.
  3. Everything else.

I think more and more that programming is a bit like writing: there are many ways to do it, some are better than others, and it's more a question of personal preferences that we dare to admit. Typing is no exception.

There is no study which proves that "strong type safety" (which, in essence, doesn't really mean anything) improve the quality of our software. It's a safety net, but a basic one. With C++, many believed that the compiler would solve everything, till it became, indeed, a joke.

Here's what I think: history is repeating fast in our industry. We forget what was discovered already. It's a mistake, and I think we should sometimes look a bit more in the past than always lurking on the new tool, new ways, new trend.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author • Edited

I think more and more that programming is a bit like writing: there are many ways to do it, some are better than others, and it's more a question of personal preferences that we dare to admit. Typing is no exception.

(Nodding along...)

I'm not "mad" that many people love TS. I'm not even particularly bothered by the fact that my employer is currently trending in this direction. But sometimes I wish that more devs/teams/companies would be honest about the fact that some architectural choices are made because they match their preferences.

If someone says, "A lot of our devs prefer TS and we've already made the choice to standardize on TS. Therefore, TS will be our default tool choice going forward." - You know what? I get that. I've got no problem with it. Those kinds of decisions get made every day in dev shops all over the world.

But it's a little annoying to me when someone tries to tell me that TS is empirically, measurably, demonstrably BETTER for the React frontend application I'm building that will depend upon a dozen different outside data sources. In those cases, TS isn't necessarily the best tool for the job. It's just... a tool. That could do the job. If you want it to.

Collapse
patarapolw profile image
Pacharapol Withayasakpunt • Edited

At least IDE integration, in case of TypeScript (and Kotlin), makes my development cycle faster, if even possible at all.

But sometimes strong-typed in Kotlin just hinder development... There are usually harder ways around, but that is no fun.

But I don't think it is ever proved to be better, though.

Collapse
bradtaniguchi profile image
Brad

I'll always use TS for one reason and one reason only.

I am a lazy programmer.

I'm a lazy dev, and I wish JS+VSCode gave me better intellisense on the variables/interfaces I'm using, but it doesn't once you start including multiple files, or external data. When it does work, it actually is just using TS definitions under the hood anyways.

Its true TS isn't this a fool-proof safety-net that produces a false sense of security as its based on a "flawed" language model that can explode at any time during runtime due to my interfaces being wrong and me assuming the wrong stuff, and me being too lazy to check.

Id rather have a wonky interface than no interface. I don't know how many bugs I've created with my assumptions, and faulty pretenses, but I at least built something without split screening the docs, checking for typos every 5 seconds and building out a full test-suite of all edge cases for every function for full test coverage.

I'm sure the best of us will build the "perfect" kind of code that wont ever break because its been battle tested so it wont ever explode at run-time and can handle every and all errors perfectly, without using TS as a crutch. If your that kind of developer with that kind of resources, yes go ahead and make the jump.

I'm just a "lazy programmer" trying to make a deadline, and TS helps me get the job done faster with better intellisense, even if its "faulty" and makes my code "worse". Id rather have a faulty crutch than nothing.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

I completely appreciate these sentiments. And though it may not be clear from the content of my article - I completely agree with these sentiments.

Obviously, I've personally come to believe that TS is not my favorite tool. And for me, it seems to cause more headaches than it's worth. But that's all... for me.

If TS is the right tool for you, then... by all means, use it! If it's the right tool for your team, or your company, or for the particular project on which you're currently working, then... use it! One of the common themes that runs through many of my articles is: Don't become dogmatic about any particular tool. Instead, always strive to use the right tool for the job.

Also, I'm acutely aware of the fact that my experience with "plain ol" JavaScript has somewhat skewed my opinion on this. If I were just starting out in web dev today, I might genuinely prefer TS. Hell, I might even prefer it because I too can be an incredibly lazy programmer.

But I've been doing this shit for long enough now that most of TS's "benefits" feel lost on me. There are all these additional hoops to jump through to accomplish what I was already doing in vanilla JS. But I am not everyone. Everyone is not me. And I'll gladly admit that, for a great many people, TS might absolutely be the right tool for the job.

Thanks for taking the time to comment!!

Collapse
jfbrennan profile image
Jordan Brennan

That’s an honest TS dev right there. Totally get it, just don’t have the same needs/priorities

Collapse
dvddpl profile image
Davide de Paolis

You shouldn't create forms that will "break" if the user provides bad data. And you shouldn't create functions (or methods, or components, or classes, or... whatever) that will "break" if another programmer invokes them with bad data. Period.

Thanks for writing this.
Awesome article

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

You're welcome! I'm glad you enjoyed it!

Collapse
somedood profile image
Basti Ortiz (Some Dood)

Excellent points made. As much as I am a fan of TypeScript, I agree that TypeScript tends to make me feel too "safe" with my code.

The reason why type annotations are necessary is because we want to avoid "defensive programming", where we use a bunch of if checks just to sanitize and validate input. TypeScript removes this habit by giving us the illusion of type safety.

However, this type safety falls apart in user-facing "interfaces", where runtime data comes from the "outside". In this case, I wholly agree that TypeScript isn't a robust solution. Instead, defensive programming is perhaps more ideal, never mind the type annotations.

But in secure environments where the data has already been sanitized and validated (by means of defensive programming beforehand), I can say that this is where TypeScript really shines.

My main takeaway from this article is that TypeScript lives in a "perfect" world. It's an incredible tool for controlled internal environments. Otherwise, in user-facing runtimes, type "safety" is a dangerous habit that leads us to assume too much about the "outside" world.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

Great feedback!

As much as I am a fan of TypeScript, I agree that TypeScript tends to make me feel too "safe" with my code.

The key to being a great coder is to acknowledge that "too safe" feeling. It doesn't mean that TS is garbage. It doesn't mean that you should abandon it. It still has many points in its favor. As long as you're fully cognizant of that "too safe" tendency, you'll at least be more likely to guard against it.

we want to avoid "defensive programming", where we use a bunch of if checks just to sanitize and validate input.

Totally agree. That's why my next post will be outlining my approach to that problem, which, I believe, vastly simplifies the problems associated with defensive coding. (And endless if (... checks.)

But in secure environments where the data has already been sanitized and validated (by means of defensive programming beforehand), I can say that this is where TypeScript really shines.

Absolutely!

My main takeaway from this article is that TypeScript lives in a "perfect" world. It's an incredible tool for controlled internal environments. Otherwise, in user-facing runtimes, type "safety" is a dangerous habit that leads us to assume too much about the "outside" world.

That's precisely one of my main points. TS certainly has value. In some cases, it can be an amazing tool. But I'm seriously questioning its usefulness in purely frontend applications that rely on reams of "outside" data to fulfill their purpose.

Collapse
patarapolw profile image
Pacharapol Withayasakpunt • Edited
  • Type safety is opinionated, and TypeScript team choose not to emit runtime type checks.
    • I think Hegel is a tried alternative. I don't know how well it works, though.
  • You should always consider safety beyond types. Validating users' (or even devs') inputs are way beyond that (e.g. empty string "", and empty arrays [], are possible even in dev environment).
Collapse
somedood profile image
Basti Ortiz (Some Dood)

Wow. Hegel looks very promising, but I'd imagine the build times to exponentially soar. I'd say this is an option worth considering particularly for the front-end (i.e. clients and front-facing APIs).

Thread Thread
patarapolw profile image
Pacharapol Withayasakpunt

You know that even tsc (or ts-node / ts-node-dev) can be slow to compile, right? Not as bad as Gradle, though.

Thread Thread
somedood profile image
Basti Ortiz (Some Dood) • Edited

Yup, I believe I am too familiar with that reality. 😂

I just figured that the additional runtime checks make compilation more computationally expensive, hence the longer build times than "just" TypeScript.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

Oh... that's cool. I hadn't even heard of Hegel before. I'm definitely going to look into that one.

Collapse
camerenisonfire profile image
Cameren Dolecheck

That was an excellent, thorough, and stylistic explanation. You're quickly becoming my favorite writer here on Dev.to.

My team is making a transition over to using TS. I think the biggest benefit has been less the hard assurances that things will work, because like you said they are not hard assurances much of the time, but the way TS helps us think of things in a different way. It can help slow down the coding process, in a positive manner. For reasons you said, I'm still not totally sold on the use of TS, but our last release at least felt better, partially because of TS.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

Thank you for the feedback!

I think you've hit on something good here. With TS, I've been looking for tangible benefits. But not all benefits are empirical. If the team is "all in" on TS, and if that commitment to its paradigms gives your team more confidence in their code and their deployments, then... what's not to like? Granted, that won't be enough for me to embrace it in, say, my personal projects. But that doesn't mean that it doesn't have real benefits for many devs/teams.

Collapse
jakkc profile image
Jakkc

Typescript devs thrive off gaslighting Javascript devs. Apparently you can't build web apps now unless you jump through a million unnecessary and arbitrary geeky typescript shaped hoops. It's stupid. I've been a professional for 8 years using exclusively JS. I have never ran into any of the problems Typescript fundamentalists claim we need to protect ourselves against. Perhaps JS became the most popular language in the world by virtue of it's dynamic typing system?

Collapse
bytebodger profile image
Adam Nathaniel Davis Author • Edited

I have long contended that dynamic typing isn't some bug to be washed out of the language. It's a feature.

Granted, if I were so inclined, I could spend some time making a solid case about why dynamic typing is evil. I could also make a solid case about why static typing is evil. In other words, both dynamic and static typing are neither good nor bad. They just... are.

I don't have any problem with dynamic typing languages or static typing languages. But I've never been a fan of choosing one - and then trying to make it into the other.

TS feels to me like someone said, "Oh, mannn... I really like this JS thing - but I gotta find a way to fix that darned dynamic typing!" I know a lot of people who wouldn't have any complaints about that scenario. But if I told them that I was working on a way to make C# dynamically typed, they'd look at me like I was an idiot.

Collapse
jakkc profile image
Jakkc

I completely agree with this. Today was my final straw with Typescript, I was working in a Vue TS app and writing a really basic action in a Vuex store. I wanted to reference the "rootState" parameter which is provided out of the box by Vuex. However I had to teach my compiler about this functionality in a third party library. This is the point where TS becomes complete and utter farce for me. I understand being wary of whats going in and out of your own functions, but if I have to teach the compiler how to handle a third party library then we have clearly strayed far away from the original selling point of TS. At this point it offers no tangible benefits whatsoever and becomes unnecessary overhead. Should I not trust my third party library to handle all its internals?

Thread Thread
jfbrennan profile image
Jordan Brennan

Been there! I was like, "Hey, wait a second TypeScript! This was not part of our agreement when I installed you. You never said you'd interrupt me and waste my time about things that are 1) actually perfectly fine, and 2) none of your business."

Collapse
ecyrbe profile image
ecyrbe • Edited

Hi Adam,

I know you wrote this article some time ago now, i just wanted to share with you what i use typescript for.

First i completely agree, typescript is not the answer to all runtime issues we face when using javascript.

Like you said it can make you too confortable about your code if you don't ckeck your external data, and then when feeding it live with bad data all this comfortable dream goes away!

But like a lot of programmers, i'm lazy, i don't want to do defensive programming in all my code. So what i do is :

  • use typescript as code completion utility.
  • use typescript to make type checking sound asuming i'm feeding correct data to my program.
  • use data validation from any external data input to not make this typescript dream go away (i use ajv or hapi joi to validate everything) :
    • user data (forms) validation
    • data transfert objects (dto) validations
    • api result data validation
    • localstorage validation
    • database data validation (when using mongo)
    • cache validation
    • config file validation
  • ban any type, prefer generics and when generics don't apply use unknown or Record<string,unknown> or unknown[].

All this comes at a cost that not everybody wants to pay. But for me and my team it works. We are now using typescript for 2 years and we are now really efficient with it and this workflow.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author • Edited

First, a sincere "Thanks!" for the feedback. It's appreciated. And, for the most part, I think I actually agree with everything you've written here.

I've been "slinging code" long enough now that I can (usually) assess, fairly quickly, the relative strengths-and-weaknesses of any given approach. Specifically, with regard to TS, I definitely don't disagree with the benefits you've outlined here.

In fact, another senior member of our team spoke up a month-or-so ago and said, "Guys... why are we going down the TS path???" And when he did, I pointed out code-completion as a major benefit. I use JetBrains/WebStorm, and I find their code-completion to be awesome - even for non-TS projects. But I will freely admit that the code completion is even better when I use it for TS projects.

That being said, part of my inspiration for writing this article was that, in the final analysis, I just didn't find these "benefits" to be worth the cost. To be absolutely crystal clear, I'm NOT saying that the benefits aren't there. And I'm not asking, at all, for you or anyone else to agree with my analysis. But for me, the benefit... just, isn't, there.

I will also outline one last point here: I've come to accept the fact that, a lot of TS's supposed-benefits are probably things that I've kinda figured out how to "fix" in my own vanilla-JS dev. And I'll freely admit that, in most teams, you can't assume that most of your devs are senior enough to have figured out those things on their own. So... on "most" teams, maybe TS is a far better option. I can freely acknowledge this. But for me.... it just feels like an unnecessary burden.

But... that's just a lotta opinion from me...

Collapse
blueharborbrandon profile image
BlueHarborBrandon

I see a lot of anti-TS arguments that amount to "it doesn't provide runtime guarantees". It's like suggesting that seatbelts are bad because they won't help if you get T-boned by a truck. The problem was not introduced the safety measures! The fact of the matter is that TypeScript moves some of the correctness-checking to compile-time instead of runtime. Maybe you wish it did more, but it is inarguably a step up from plain, untyped JavaScript. There are some mistakes that will be caught much faster with static types. If you don't see them, it's not because they're not there.

You might think it's a false sense of security, but that's user error. For external-facing code, if you're not confident that you'll get a value of a certain type, don't type it as such. Type it as any and then do your checks, which may allow TS to infer the proper types for the rest of the function. Or, next call a private/internal version that has explicit types.

So that just leaves the idea that it's too much effort for the benefit. That's primarily a matter of opinion... so I probably won't change your mind, but you'd have to do better if you cared to change my mind. (And if you don't care, why write a blog post and read/respond to its comments?) But let me back up my opinion with some ideas you might have overlooked:

  1. TypeScript can infer a lot. You could replace const createId = (length: number = 32): string => ... with const createId = (length = 32) => ... and it would understand the types just fine. Fortunately, this works well with the internal code you don't want to write types for.
  2. You said yourself that well-written apps are built of countless pieces. You also said types matter most at the interface. But if we connect those dots, we can conclude that the in-between makes up a significant part of the app! Combine that with #1: There's a lot of interface - where typing is agreed to be valuable - and a lot of internal code where typing takes little effort. Sounds like a win-win to me.
  3. You've looked at programming at rest, but what about change? If you learn that createId needs to take another parameter, anything that uses it needs to change, too. TypeScript will not let you forget to update the consuming code. Or, if you decide to start using numerical Ids, things might just cascade through type inference. But, if anything relies on that id being a string, TypeScript will let you know. TypeScript requires more work in the places where things can go wrong.
  4. There's an incredible amount of expressiveness in the type system, especially when complex structures are involved. It goes way beyond primitives, classes, and interfaces. I'd highly recommend browsing the advanced features TS offers. A favorite of mine is "Discriminated Unions".
blueharborbrandon profile image
BlueHarborBrandon

I mean that a type in TypeScript represents a set, like you learned about in math class. Like the set of all integers is a subset of the set of all real numbers. In TypeScript, the type string is a set that contains every string, but no numbers or functions or anything else. That's why you can pick out string literal types and union them together, but it's also why you can pseudo-extend a type by creating its intersection with another type.

blueharborbrandon profile image
BlueHarborBrandon • Edited

Conceptually, perhaps, but not in practice. That is, you may be able to identify sets within the type system in other languages, but it's fundamental to TypeScript.

In C#, for example, objects must claim exactly one class that must be tightly associated with the object in order to use it. Trying to say it's a type outside of the inheritance chain results in an error. Or, to put it differently, one value at runtime has a small, rigid set of types it fits - unlike the number 2, which belongs to an infinite number of sets. In short, values have types, rather than types having values.

But in TypeScript, types are modeled as sets essentially from the ground up. Just like in math, you can say "this set contains every value with these properties", or "this set contains precisely these values", or "this set is the union/intersection of these other sets". Understanding fundamental set theory helps you think about types in TS in a way that it doesn't with C# or many other languages.

Collapse
blueharborbrandon profile image
BlueHarborBrandon

I've never seen Reason before. How does it compare? In particular, I'm a big fan of how TS models types as sets of values... very mathematical and familiar.