Recently in the pub after work, some colleagues and I were discussing a recent painful migration from Javscript to Typescript in an area of the SR Cloud codebase we’d (clearly) not migrated yet. The “is Typescript worth the effort?” question came up triggering this blog post.
The following is a mixture of some colleagues and my opinions.
Why did we make the shift towards Typescript in the first place?
Specifically: code that is written in a language that permits the declaration of types gives the programmer very useful information about how the program is supposed to work.
Additionally, and perhaps more importantly, we can now get compile-time assurance that our application works with data that is in the correct shape/type, and therefore avoid a whole class of runtime bugs.
What went well?
Refactoring is simpler/less risky as the Typescript compiler now points out all the impacted areas of code when the data structures/function signatures change.
The Typescript compiler APIs have been extremely useful for building custom tooling around frontend module import dependencies to decide what tests should be triggered on a production release.
What could have gone better?
A lot of the feedback I’ve had from engineers revolves around “the type error messages are hard to understand”. Having read many Typescript release notes I’m aware the team at Microsoft have improved things over the years but it can take some significant effort to a) understand complex types b) figure out exactly why the compiler is unhappy and finally c) decide how to fix things.
Some types can be quite verbose and difficult to wrap your head around. I think union types are really powerful but they can become a bit of a nightmare to deal with as you try and convince the compiler that some variable really is the type that you think it is. See previous point.
“Complete” type safety is hard to achieve in a developer friendly way… we’ve tried really hard to avoid both explicit and implicit
anys in our codebase, and casting too (eg
as ImportantData) and also the safe navigation
.! operator, to avoid us accidentally forcing the compiler to treat some data as a type that it isn’t. One of the consequences is that often we end up with application logic that just keeps the compiler happy but is logically entirely unecessary. I think the Typescript team have been reducing the common scenarios where this is required over time, so I expect things to improve further.
More specifically the teams I work with have had two large speedbumps in its journey with Typescript. The first was the adoption (and subsequent ongoing removal) of fp-ts which added extra levels of complexity to an already large and complicated codebase with incomplete and not-always-correct types. The motivation for fp-ts adoption was superior type safety heavily inspired by Haskell and friends. For both new and existing members of the team this additional overhead on top of the existing challenges with Typescript made it a lot harder to work on the product. The second was Redux Saga’s use of the
yield keyword and Typescript’s inability (fixed in v3.6 apparently) to know what data type had been generated.
Despite good ecosystem support, 3rd party types defined via https://github.com/DefinitelyTyped/DefinitelyTyped don’t necessarily match up to the reality of the libraries they are supposed to provide types for, and versioning consistency between the
@types/ modules and the actual modules is a mess.
even with my limited experience with JS I don’t want even try to go back to that old days, and will take TS over JS every single day. But TS is not perfect, it has it flaw with complex types, that are hard to understand and when defined incorrectly gives a lot of headache and require a “type debugging” session.
I’m not sure… I don’t think anyone I’ve spoken to is keen to drop or move away from Typescript but the list of “what could be better” is a lot longer than I expected!
What do you all think?