> In the many years I've done front-end with Javascript, type-related bugs were very, very rare.
Wow, really? My experience has been the complete opposite.
Most of the bugs that slip into production have been dumb type errors. Things like accessing a property on an null/undefined value, or on an object that doesn't have that property and unfortunately JS just returns `undefined` instead of complaining (these silent ones can live longer before being spotted).
It's because of these kind of bugs that i've found many JS codebases tend to become brittle to the slightest change, and very difficult to refactor into a good shape. And why i'm seeking statically typed languages like TypeScript on that front at the moment.
But that's really a symptom of a "big ball of mud" design (non-design, really), where everything has dependencies scattered all over the system, and to change anything you have to consider everything.
Type checking can help you with a few kinds of problems you'll have trying to make changes in those kinds of systems, but you still have piles of problems and it doesn't do anything to help improve the underlying issue.
Static types can help you with some small problems, but that doesn't mean much when the problems you have are big.
> But that's really a symptom of a "big ball of mud" design (non-design, really)
Yep, that's exactly the problem :)
The thing is that, without ease of refactoring, it's very difficult to change that big ball of mud into something prettier. In fact, i'd say that without ease of refactoring it's kind of inevitable to fall into that big ball of mud "design" in the long run.
(Note: static type systems are not the only tools to help with refactoring. Good test coverage, style guidelines, a well documented / simple architecture, are of course very helpful too.)
Exactly. Static types ( I've used flow not typescript, but similar ) and good unit tests allow ridiculously easy and merciless refactoring of enormous swaths of code in an extremely short period of time with incredibly high confidence.
Integration tests ( not to be confused with functional tests / i.e.: selenium ) that test multiple classes in concert via input / expected output give the most bang for the buck, because you know your code works as a whole.
Static types are especially useful in big ball of mud design because you can refactor it (like change the shape of a canonical object) and mechanically fix all write/read sites without running the code.
Not sure how that's a small problem unless you never refactor code.
It's more likely that OP finds such errors just as frequently but because they are relatively straightforward to fix he doesn't weigh them as much as edge case debugging.
I've heard this same argument from a coworker working on a different project recently working on React/ES6 stack - I check their TFS history and find 3 such fixes in last 2 weeks (and they have reviews before checking in to TFS).
There's a lot of type bugs in my code as I type it, and while I'm writing the unit tests, but I've been keeping track for the last year or so when they happen in QA or Prod and they are quite rare for us (as part of an evaluation of if we should move to Flow/TS).
But there was a heavy correlation between type bugs and the kind of code being written. Our transactional frontend apps? Most bugs are browser css issues, weird environment specific edge cases, misunderstandings of the backend, memory leaks, etc.
In apps that are data heavy though, or backend JS apps, its a whole other story. Type bugs all day every day (though a part of it is because we don't do many and they aren't high profile here, they're very light on tests).
So we moved the later to using tests. While it helps a lot, we did end up with a different kind of issues: broken type definition issues and bugs in the type system. Turns out a very large portion of DefinitelyTyped definitions (especially those of the more obscure libs) are broken and you can't trust them, but you lose a lot of time fixing runtime bugs the type system forced you to make. And while they never hit production, there's more bugs than expected in TS itself, and it takes a while to figure out if I'm doing something wrong before I go look at the bug tracker (the issues I hit have almost always been reported already, but I don't expect them). Fortunately that last category rarely cause issues in production.
If you're only writing good tests, there's very few tests you wont write: eg, if I test that my function returns the string "Foo", I don't need to test that it returns a string.
And even in sound (er) type systems like Haskell or Elm, I'd (probably. There are ways...) have to write that test.
In JS with Flow or TS though, there's very few (good) unit tests you don't need to write anyway. If your tests are filled with "expect(foo()).toEqual(expect.any(string))", the team needs to be coached on writing proper unit tests regardless of types.
Though the topic that most teams don't know how to write effective unit tests and that type systems help those teams is a totally separate one.
The short answer is: it depends. With TS you can choose how much type "soundness" you want. The --strict option is very useful[1], but even having it enabled you can still choose to explicitly type something as `any`, and bypass the typechecker if you want.
Something to note though: the example given in the article is not showing any problem in TS. That's how its structural type system works. Both Animal and Bird classes don't have any properties, so the type of their instances is the same as `{}`. If you change the Bird class to something like:
class Bird extends Animal {
song: string
}
Then the line that tries to push a `new Animal` into an Array<Bird> will not type-check, because the types are invalid :)
Unfortunately I feel that this type of example expresses what not to do with type systems -- favoring composition over inheritance goes a long way to not finding yourself in a brittle type hierarchy and especially in a JavaScript environment where composition is so easily used (and with typescript, still type checked)
Wow, really? My experience has been the complete opposite.
Most of the bugs that slip into production have been dumb type errors. Things like accessing a property on an null/undefined value, or on an object that doesn't have that property and unfortunately JS just returns `undefined` instead of complaining (these silent ones can live longer before being spotted).
It's because of these kind of bugs that i've found many JS codebases tend to become brittle to the slightest change, and very difficult to refactor into a good shape. And why i'm seeking statically typed languages like TypeScript on that front at the moment.