There is one major downside of untyped language. Yeah, you guessed it – the lack of types. It’s not a problem for a single person project, where you are the only author and nothing will surprise you. It was the same for Kakunin, except there was the two of us. 😉 We both knew each line of code in our library. We knew the type of each parameter. We knew what could be passed to each method and what type the result will be. The problem became obvious when the team started growing. More and more people started to add custom code, create pull requests and ask a single most popular question – why it doesn’t work if I pass string here? We had to deal with it immediately!
Why do we need types?
Let’s imagine a simple function.
It works, yet we don’t know which result is the correct one?
Is this function for strings concatenation or maybe misnamed method for sum calculation? It’s hard to tell.
Without types, we need either good documentation or knowledge about the code. What methods should be implemented by vehicle creation strategy? What fields we have in params for create method? VehicleFactory requires an array of creation strategies, yet once again – could a new developer pass anything there? Can you see where are we going with this?
Types make our code easier to understand for others. We don’t need to care so much about documenting every detail of code, just to make sure someone won’t pass a mistyped variable or forgets to implement a required method. What’s more, we can skip some of the type checks, even unit tests that were used specifically for testing a types correctness. And so we had to make a tough choice…
TypeScript or Flow?
TypeScript is fully based on structural typing. It means that two different classes are equal as long as their structure is the same. For example:
It will return true, as long as all properties are public (not cool!). It’s a little bit trickier with private fields and inheritance.
Two classes with private fields are not the same, even if they have the same structure (cool!). But if one of them is extending other, then once again they are the same (again: not cool!). On the other hand, we have Flow. It actually works pretty similar to TypeScript, with one important difference – classes are a nominal type. It means, two classes are the same only if they are exactly the same instance (cool!).
On the paper, Flow looks better than TypeScript (at least from a technical point of view), but there is one more thing we had to consider before we would give it a go – typing for external libraries. There’s no doubt, that TypeScript has a bigger community than Flow. So naturally, there are a lot more contributors to TS typings than to Flow ones. And this is something that matters! At TSH we decided that we could live without nominal typing (even though I am a big fan of it), but we cannot without good support for most popular libraries.
Because of that, we ended up with TypeScript for most of our open source and commercial projects.
And this is the same code in TypeScript:
It looks pretty much the same. We moved away from objects to classes, we added types where necessary and the rest of them are taken care by TypeScript itself – yeah, TypeScript is pretty good in resolving types on its own. The only problem we had was a large code base (we’re talking 19000 lines of code, people! 😱) but this could be mitigated by splitting it into multiple smaller chunks. We started with things related to forms, then moved to generators etc.
In exchange, we’ve got a code that is clear for everyone. No more documentation for methods parameters. No more silly mistakes with a passing number instead of a string. All of this was possible because of clean architecture and test coverage (we have both units, but also functional tests). A fun fact: we do test Kakunin with Kakunin. Yes, for reals!
Do we recommend it to you? Of course! All of ours node.js open-source libraries are developed using TypeScript and I’m pretty sure it will be the same for our upcoming projects.