The Trouble with TypeScript

Ryan Carniato - Feb 9 '20 - - Dev Community

Hi my name is Ryan, and this is my first post on dev.to. I regularly write for medium.com but I wanted to give dev.to a try. I'm a big enthusiast of reactive libraries and front-end JavaScript performance. I'm the author of Solid.js one of the top-performing libraries on JS Frameworks Benchmark. But today I want to write about something else.

I've been using TypeScript now for about a year. Hardly enough time to come to a conclusion about it, but I've wanted to write this article for months. Pushing it off each time hoping that it would finally click. I also decided that I might not be the best judge as being a library writer I was sort of thrust right over the deep end. So I wanted to give people of different experience levels and programming backgrounds I trusted an unbiased chance at it. So not only did I convert all my open-source libraries to TypeScript, but 6 months later I asked the developers at the startup I work at if they would like to use TypeScript on a rewrite of our core application. They had a varied interest in learning it, but they were all open to it. Now that several more months have passed, I finally feel that I'm at a point I can say something. So let's dig in.

TypeScript is an Art, Not a Science

I've been programming for about 25 years now. I've used dozens of typed languages over the years. But TypeScript was a first in that it was trying to put types on top of a dynamic language. This in itself seems like it would be an incredible feat. But then again dynamically typed languages did so a few decades ago. At one point getting rid of the types was actually seen as progress.

When you start with simple examples it all seems familiar enough. You add a few annotations and marvel at how it won't let you assign a string to a number. You make sure your functions have clear parameters and return types and you start feeling like you are getting it. And then you hit a place where you need to pass in different objects. Your first thought is loosening up the definition but then you see an example with generics and realize TypeScript uses generics way more liberally than you are used to with C++ or Java. Even cooler, their type can often be inferred which means you don't even need to annotate and everything magically works.

That is until you add a few extra levels on to, and you start coming across the inconsistencies, or the places where types can't be inferred. The other day I was helping my lead dev work through some typings on factory function that produces hooks that return CSS in JS generated classes that are a result of the style definition passed into the factory and props passed into the hook. He had something very basic and couldn't quite figure out why his types weren't working. So I sat down and started using generics to assign multiple values and creating wrappers to project types for return values. Someone how after a couple of tries I got it working for the most part. I admit I felt pretty good about myself, but the developer looked bewildered. You see he thought he was finally getting TypeScript and he had no idea what I had just done. So I spent the next half an hour explaining it. In the end, he got it, but he still didn't feel any better about it as he would have never thought about it that way. And truthfully I was in the same boat months earlier.

You've heard the saying that programming is art? Developers choose how to model their problems and have their own stylistic approach. Everyone's code is different. I remember when I was a young developer I'd try to find the most clever way to solve a problem and feel so proud before a senior developer tore a hole in it and asked why I just didn't do the simplest thing. Over time my code got more directed and less frivolous. TypeScript has so many tools to do seemingly similar things since JavaScript has so much potential, that you might easily take a tact that can't get you 100% of the way there. It's very difficult to know what the right way is unless you've experienced it. But since it is impossible to safely type all things in JavaScript you don't even know if what you are trying to do is possible or if you are just thinking about the issue wrong.

This leads to a very strange scenario that the more complex the issue even when you go for help, communicating the intent of the code is as important as the function. When talking about possible solutions it isn't unlike people looking at modern art trying to critique the intent and the emotion of a toilet paper roll nailed to a wall. You can spend hours perfecting an elegant solution to your types without shipping any new workable code. It makes you feel really good and clever when you get it right. It is metaprogramming to the highest degree. It gets even more awkward when you are trying to use a 3rd party library who is more concerned about spending several months getting it right than getting something out that works (while in meanwhile the current types are effectively broken).

As I alluded to previously, programming itself has these characteristics, but it's super strange when your tools do too. That level of uncertainty, that need to solve a puzzle with your tools completely on the side of the programming problem you are solving is the kind of thing that I can see developers liking given their personality as problem solvers, but when it comes down to things like efficiency and productivity it is excess. Every time I use TypeScript and I realize that I remember being that young and unexperienced programmer just doing a lot of unnecessary stuff.

TypeScript focuses on Ceremony

I often wonder how many people who rave about TypeScript have ever really used JavaScript. I used CoffeeScript for 5 years almost exclusively and only returned to ES6 for the last couple of years. I wouldn't recommend people move over to CoffeeScript today except perhaps to appreciate some of its qualities briefly. CoffeeScript in some ways is the absolute opposite of TypeScript exemplifying the other characteristics of JavaScript. Forget types. You don't even declare variables for the most part. If you read the way these people talk about JavaScript I can only imagine what they'd think of CoffeeScript.

Would it surprise you that CoffeeScript increased productivity over JavaScript for our team? This was a different time and I'm not sure it would do as much now. But let me paint the picture. Writing CoffeeScript is a lot like writing pseudocode. So after you plan out how you are going to approach your programming task, you tend to just throw stuff up. Need a new variable just start using it. Getting an idea up was incredibly fast. The syntax being terse was nice as something that would be 80 lines in JavaScript would be about 30 lines in CoffeeScript. Sure you'd run it realize it didn't quite work since you missed a null check. And you'd add a ? (optional chaining operator). Then you realize your logic was wrong in the 2nd loop. So you need to do a refactor.

What I've witnessed with TypeScript is that 30 line CoffeeScript file is now 150 lines. I can't see the whole thing in my IDE window anymore without scrolling. At about the same time the CoffeeScript developer is starting the refactor the TypeScript developer has just reconciled all the types and is about to run their code for the first time. Type annotation doesn't take much time unless you need to look up Types you don't know (seriously for the browser MDN is such a lifesaver here), but the tendency here is to ensure everything matches that it all works out the first time you run it. Sure the TypeScript developer never has that run where the browser spits out Cannot read 'name' of undefined but by the time they are realizing their logic is wrong in the 2nd loop our first developer is already testing the refactor.

Many JavaScript developers are very used to just throwing stuff against a wall and see if it sticks sort of development. They rapidly test ideas without the code being perfect. This just wasn't a luxury afforded compiled languages. If you are going to wait a couple of minutes you better make sure your code works before you hit build. To me, it isn't that different from the difference between waterfall and agile methodologies. We know that some larger companies can still have some issues being as agile and I feel the argument for TypeScript is sort of similar. Now don't get me wrong. The CoffeeScript probably produced more bugs, but trying something can often reveal when your assumptions are wrong quicker. Waste less time perfecting something you aren't going to use anyway.

TypeScript is Noisy

As in it has a higher noise to signal ratio. Less of the code you are looking at is functional. I've already talked about more code being required but this goes beyond initial development. I know this is perhaps more opinion based but when Dan Abramov (React Core Team) recently tweeted that when he looks at someone else's code the Types actually get in the way of him seeing the code, I realized I wasn't alone. Type information can be noise when you are just trying to see the flow. Truthfully this is less of an issue compared to the last as it doesn't change how you approach coding. But it is something. We can filter out the annotations pretty readily but simply function declarations going from one line to 5 lines starts you on a path where you are always looking at less.

TypeScript is a Subset of JavaScript

I can't impress this one enough. Technically is a superset from a feature support perspective. However, people use it so they have compile-time type checking so once that becomes a requirement for you there are just things you can't do with TypeScript. I hit this right away when writing Solid.js. It uses JSX in a completely different way than React, it has a lot of functional patterns like currying, and functions that support paths and dynamic arguments. Not to mention at its core it is incredibly tuned for performance so I was unwilling to change what the underlying JavaScript compiled to. I kid you not, within 3 weeks I ran into over a dozen unsolved TypeScript issues with open tickets and reported 2 more myself. I have received a lot of help from the TypeScript community and have no ill will towards the people working on it and supporting it. But when for the solutions that are solvable the best options are: change your API or add another function call to get the compiler to work the way you want, I was understandably very uncooperative.

Ultimately I settled on not having custom bindings with $ prefixes in the JSX attributes, using JSX namespaces, and introducing intrinsic elements with special characters (all things supported by the JSX spec). I introduced another syntax against my better judgement to avoid paths. I just think is vital to understand that there are a ton of patterns you'd do with JavaScript that cannot be made type-safe and many more that would require an expert to determine if it is.

Obviously, as a lower-level library writer, I hit these right away, but I've even seen these affect application developers. They've had to change the way they would approach an interopt layer since it wasn't as TypeScript friendly. Similarly hitting weird TypeScript only idiosyncrasies when using 3rd party libraries. Pretend you haven't sold your soul to TypeScript and read this guide for Material UI. Why would I ever sign up for this?

Conclusion

If you treat TypeScript as a language in its own right, with albeit a smaller feature set than JavaScript, you will do just fine. If you treat TypeScript as JavaScript with types you will be disappointed. The thing is despite how terrible of an experience I or those around with me have had we are sticking with it. As a library writer it makes a lot of sense since there are many people that want it. It hasn't meant any compromise thus far that I wasn't willing to make so I'm committed to supporting it. I know somewhere in my head by doing so I'm limiting my creativity. Some of my more interesting ideas don't work with TypeScript so taking this position might compromise my motivation to look into them. But Solid.js, as it is today, is already very impressive.

On my team, it was split. The backend developers did not have a hard time with TypeScript and their solution scaled with their knowledge as they've found better ways to structure their project. However, on the frontend, it has been nearly a disaster. TypeScript has basically dictated other technology choices. Where we've been like use TypeScript or use this library. So far we've sided with TypeScript because of the promise of what it brings. In hindsight, I would have never introduced it there but I feel like we are starting to get over the hump so the time invested is worth seeing it through. It's just ironic that many of the advertised benefits I believe are actually detrimental.

TypeScript doesn't improve productivity or readability. It doesn't particularly improve on modern JavaScript feature set. If anything it restricts what you can do. But it isn't all negative. It pushes developers to document code. It sets a contract when dealing with 3rd party APIs. However, the biggest win I think is it makes developers more comfortable. It inspires developer confidence which is something we can all get behind even if the language itself might be the worst mess of compromise I've witnessed in my 25 years of programming.

TypeScript might not be the language we need, but it is the language we deserve for now.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .