The TypeScript Tax
medium.com
medium.com
I strongly disagree. You can start using typescript without thinking for a second about what parts of it will be beneficial.
Initially you can just run javascript through the typescript compiler and let type inference find some bugs. You can start adding types in places where you want autocomplete to work better. You can ratchet up the strictness if you start to find bugs that stricter type checking would have caught. If you've read any typescript discussions on HN you already know this.
On the flip side, obvious once you think about it, the option is always there to ratchet down strictness if for your codebase the requirements turn out to be too heavy, i.e. slowing feature development down more than the bug reduction warrants. But you don't have to think deeply about it in advance. Just write your code, and every so often take a step back and look at how well things are working. Then adjust based on actual benefits and costs, not some premature estimate of them.
Have you been on a team that adequately managed a list of thousands of warnings, always finding and fixing just the ones that are "important"?
In fact, out of the dozen or so TS codebases I've worked on, I've rarely seen one completely free of type errors. And yet no team has ever doubted the value of the type system. It's a very pragmatic approach which works very well for many teams in my experience.
We opted to keep it strict (no implicit `any`) through the entire transition but only apply it to ts files, so we are indeed completely free of type errors now. We only have 50 cases of (explicit) `any` in 85K+ LOC and they're in type def files.
Let me be the first person you've met to doubt the value of the type system. I do like the in-editor support, but you pay for it with about 25% more code.
I'm certainly not advocating undoing it, but I personally wouldn't do it again (though the team has mixed opinions).
Just curious: What's your test coverage like?
"TypeScript is a typed superset of JavaScript..."
"Use existing JavaScript code, incorporate popular JavaScript libraries..."
You should be able to use --allowJs and have everything compile just fine, otherwise log a bug.
This is a fundamental part of error handling and DX that, unfortunately, is left out 10 out of 10 in tooling, applications and libraries alike, in special opensource projects.
If you agree with the premise that static typing is a waste of time, you'll probably agree with the rest, but then you're likely not using TS in the first place. If you don't agree, the article is just wrong.
That's because his stupid chart is comparing a bunch of different things to TS instead of just comparing it to JS. In that case he's putting it up against TDD + code reviews, for reasons that I entirely do not understand. His numbers are so wildly incommensurate that they don't even make sense for any sort of informal water-cooler analysis.
[EDIT] if... if I'm reading his reasoning for that 0.1 score correctly, he'd still give it a -3.9 ROI if it caught 100-friggin-percent of bugs that would otherwise reach production, unaided by any other tools or practices. WTF.
[EDIT EDIT] for reference, that means he rates the overhead of noting one's types at 3x as bad as the benefit of magically catching 100% of bugs would be good. This is what I mean by these individual values being ZOMGWTF incommensurate.
Not to mention that TypeScript makes it easier to do both. Code reviews are easier when you can read the types instead of trying to infer them by reading the code, writing tests is easier when you have type-safe test data.
I wish.
[edit] let me demonstrate: the most basic problem is that your numbers in various categories do not measure up to one another in any way that makes sense. -3 for writing typings, +0.1 for catching 10-20% of bugs that would reach production. Either you're having way more trouble writing types than you should or you have so few bugs that catching 10-20% of them isn't very valuable. Plus, read the thread. You just ignore all kinds of benefits of TypeScript (mostly benefits of having any, even half-assed, static type system on top of JS, period). If you like something else, propose it and compare TS to that. You're saying it's 1/10 better than vanilla JS with TDD + code review but 30x worse in terms of that benefit than JS when it comes to time spent adding types. 30. Times. That's just one example, the whole chart's a wreck in a similar fashion. Plus I bet if you ask on this thread folks will be happy to suggest + entries you simply omitted. See the ones suggesting (correctly) that static types make both TDD and code review, which you like, a hell of a lot faster.
In the worst case if some little thing you want to do won't work well in TS or is too expensive to type properly, write that piece in JS. Yes, that's terrible. [pause; significant look; raised eyebrows]. But it's not worse than JS. Obviously. And you can do that in TS. It's part of what makes it such an incredibly easy sell.
As for the bug reducing benefit, yes it's up to 20% if you do nothing but TypeScript. As explained in the article, 80% of bugs can't be addressed by static types at all, so you can't skip the other measures safely. The zero point includes the other measures, which easily cover 90%+ reductions, combined.
Since other measures catch type errors, too, we apply the TypeScript reduction after discounting the other errors caught. Even with the maximum benefit of the doubt, assuming TypeScript catches 20% of the remainder, it's still a small percent of a small percent. You get exponentially diminishing returns with each bug prevention measure you apply, and TypeScript can't catch the lion's share of bugs, so it's the only one that makes sense to leave out as a quality control measure.
But the majority of my bugs aren't "public", because I see that things aren't working when I run the code and don't commit it. With the combo of IDE just-in-time help/suggestions/reminders/enforcement, a lot of mistakes that I will catch at some point later by the "other means" you compare to, before I commit and push (which is the majority of my bugs) are prevented or immediately pointed out. Stopping them before they start instead of having them cause write-run-debug cycles, which will stop them before they are committed, is a huge advantage to me that won't show up at all in the "public" bug analysis.
"Immediately tells me things that I will eventually discover later by other means after wasting time" is a big, fuzzy category in which most of the value I get from several typed languages seems to hide from many analyses, even those that mention IDE features.
> and you can't leave those out safely
Who's advocating for the replacement of code review, specs, and tests with TypeScript? TypeScript makes these things better, it doesn't replace them.
[1] https://twitter.com/Hillelogram/status/1084991487702691840
I shipped an app about 90 days ago based on Typescript.
It's basically a document manager which supports annotations, comments and keeping your documents in the cloud.
Typescript was a MASSIVE win. If you're on a simple code base doing a refactor is easy. Anything significant and it's a nightmare.
If you change an object method JS won't complain.
With Typescript and IntelliJ or VS Code it's just a simple refactoring and your code just works.
I would have probably given up on the project had it not been for TS.
> TypeScript is only capable of addressing a theoretical maximum of 15% of “public bugs”, where public means that the bugs survived past the implementation phase and got committed to the public repository, according to Zheng Gao and Earl T. Barr from University College London, and Christian Bird from Microsoft Research.
The devil's in the details for studies like this and people ignore this to make it look like their opinion is backed up by science when it isn't. Measuring what percent help type safety gives in general is an impossibly vague concept while also very difficult to test, especially when making comparisons between numbers.
From the second study for example right in the abstract:
> Evaluating static type systems against public bugs, which have survived testing and review, is conservative: it understates their effectiveness at detecting bugs during private development, not to mention their other benefits such as facilitating code search/completion and serving as documentation. Despite this uneven playing field, our central finding is that both static type systems find an important percentage of public bugs: both Flow 0.30 and TypeScript 2.0 successfully detect 15%!
15% is a minimum from that study then and it isn't counting bugs that were caught from manual testing or otherwise before making a commit, or how much time was saved.
Quoting the study in this way is incredibly misleading in my opinion.
For me personally as a TDD person, I just don't care about testing types. I test values. The type-checker just doesn't do much that I actually care about (better in-editor support is nice though). I know it's not a fashionable answer these days, but for me it's true. I don't see the pay-off in doing both, and types alone are not enough.
The study then finds that about 80% of the bugs they found were not simply ts-undetectable, but not type errors at all. Instead, they're things like the wrong URLs being used (sting errors), wrong branching logic, wrong predicate logic, etc.
That means the maximum effect must be less than 20%, but the authors couldn't detect 20% even knowing exactly what the bug and fix was, down to the line numbers and exact code used to fix the bug.
Even being extremely generous and assuming they could fix 20% of remaining bugs, it's too far down the ladder of exponentially diminishing returns to make much difference at that stage.
Assuming you wrote the article, why did you cite that study then?
> That means the maximum effect must be less than 20%
But what effect are you trying to argue? Maximum of what? I feel this is so vague it's not useful.
In my opinion (I'm not going to try to back this up with a study that doesn't measure exactly this), types make an enormous difference while you're in the middle of development before you commit anything: they're capturing cases where you forget a variable could be undefined, when you mix up items and lists, when you want to aggressively refactor, when you forget to pass required parameters etc. Type annotations are rarely required and for the few minutes I take to write them I easily make that time back even in the short term. Only looking at the bugs that get committed (which is super hard to measure) is missing out on a big aspect of the benefits.
You also need to consider that code reviews and writing TDD tests are time consuming to do as well.
I do consider TDD and code reviews are costly but you can't skip them with TypeScript because at least 80% of bugs are not detectable with TypeScript.
I give up. You're repeatedly using this figure in this thread in a misleading way to make it sound like what you're saying is more legitimate that just an opinion.
The abstract of the study you cite explicitly mentions this is a conservative estimate of effectiveness and ignores bugs detected in private:
> Evaluating static type systems against public bugs, which have survived testing and review, is conservative: it understates their effectiveness at detecting bugs during private development, not to mention their other benefits such as facilitating code search/completion and serving as documentation. Despite this uneven playing field, our central finding is that both static type systems find an important percentage of public bugs: both Flow 0.30 and TypeScript 2.0 successfully detect 15%!
Would it make a difference if TypeScript was used before the code was committed? How does the subject domain of the project impact this figure? How do other testing and review approaches impact this number? Are public bugs of certain kinds more likely to be reported for certain projects?
It's highly misleading to quote this figure in such a simplistic manner without caveats.
Note I'm not pro or anti myself, I've only just begun playing with it, but would like to hear from all kinds of people.
I'm a huge fan of strongly-typed code, but I like to add the types much later, once all of the pieces are in place and are not in constant change, and mostly as a way to not have to write unit tests. If I type from the start I find I lock in to sub-optimal data structures and get "attached" to them. I already typed them, I don't want to break things!
Also, I live by the "What Would Rich Hickey Do?" school of thought... so when he mandates types in Clojure, I'll switch to TypeScript by default ;)
You could use the allowJs typescript flag and mostly use JS files at first; perhaps only typing a few models.
But TS is at this moment really eating JS jobs, and that kinda hurts. I just don't understand the proponents, besides the job opportunity, give me one single reason to write in TS? If you want to go for a strictly typed language TS is definitely the worse choice. TS simply doesn't fit in the JS ecosystem. Look at (a part of) the dependency part of this package.json, from the beginning of a large project. Don't you agree something is going very wrong in the JS world?
"ts-jest": "^23.1.3",
"ts-loader": "^4.5.0",
"ts-node": "^7.0.1",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.14.0",
"tslint-react": "^3.6.0",
"typescript": "^3.0.1",
"typings-for-css-modules-loader": "^1.7.0",
"@sentry/browser": "^4.0.0-beta.12",
"@sentry/node": "4.0.0-beta.12",
"@sentry/types": "^4.0.0-beta.12",
"@types/autoprefixer": "^6.7.3",
"@types/classnames": "^2.2.6",
"@types/clean-webpack-plugin": "^0.1.2",
"@types/compression": "0.0.36",
"@types/cookie-parser": "^1.4.1",
"@types/cors": "^2.8.4",
"@types/cucumber": "^4.0.4",
"@types/enzyme": "^3.1.13",
"@types/enzyme-adapter-react-16": "^1.0.3",
"@types/enzyme-to-json": "^1.5.2",
"@types/express": "^4.16.0",
"@types/helmet": "0.0.38",
"@types/html-webpack-plugin": "^3.2.0",
"@types/jest": "^23.3.1",
"@types/js-cookie": "^2.1.0",
"@types/memwatch-next": "^0.3.1",
"@types/node": "^10.5.7",
"@types/node-sass": "^3.10.32",
"@types/optimize-css-assets-webpack-plugin": "^1.3.3",
"@types/react": "^16.4.11",
"@types/react-dom": "^16.0.7",
"@types/react-helmet": "^5.0.7",
"@types/react-intl": "^2.3.10",
"@types/react-loadable": "^5.4.1",
"@types/react-redux": "^6.0.6",
"@types/react-router-dom": "^4.3.0",
"@types/redux-mock-store": "^1.0.0",
"@types/serve-favicon": "^2.2.30",
"@types/webdriverio": "^4.10.3",
"@types/webpack-merge": "^4.1.3",
"@types/webpack-node-externals": "^1.6.3",
"webpack": "4.19.0",
"webpack-assets-manifest": "^3.0.2",
"webpack-cli": "3.1.0",
"webpack-dev-middleware": "^3.1.3",
"webpack-dev-server": "^3.1.5",
"webpack-hot-middleware": "^2.22.3",
"webpack-merge": "^4.1.4",
"webpack-node-externals": "^1.7.2",
"stylelint": "^9.5.0",
"stylelint-config-prettier": "^4.0.0",
"stylelint-config-recess-order": "^2.0.0",
"stylelint-config-recommended-scss": "^3.2.0",
"stylelint-scss": "^3.3.0",The absolute worst case with TS is that I'm as bad off as I would be in JS (use "any", or "as", or provide a dummy anything-goes d.ts for a lib, or whatever). I haven't found that I actually need to do those things very often, even being fairly lazy and having a very low tolerance for time lost to managing my tools.
You and the author are both overly concerned about writing code and completely ignoring maintainability. If you write your code once and never touch it again, sure, use plain JS. If you're going to come back in six months month and refactor it or hand it off to a co-worker and expect him to write some code TypeScript is a godsend.
JS developers moving to TS should at least do a few weeks of Assembly and C programming to first understand the origin of types. I've spoken too many front-end devs who don't have a clue what a type actually is and at the same time promote TS. It's a godsend we don't need them in scripting languages. Use tooling to catch issues, that's the future, TS is just a whim.
Yeah, obviously. Think about it -- when you write the code you still think about the "types", you're just not writing them down.
Typescript's virtues are completely related to developer productivity. Documenting your code with types, better autocompletion, catching silly mistakes before needing to run it in a browser, getting new people up to speed, etc.
For example, if you know that a function takes a generic list and returns an integer, then the list’s length is pretty much the only non-trivial computation it can perform.
This is patently wrong. You _are_ thinking about types. Every object, function, variable you write has a type you store in your head. The question is about how easy it is to remember those types in 6 months, or how hard it would be for a new developer to figure them out.
> JS has always had a test driven approach
Yes, and you should keep that test-driven approach with TypeScript. I'm not sure why anyone would assert otherwise.
const len = x => x.length;
Another problem is that it focuses on bugs that make it out to production. Maybe for some projects production bugs are so costly that pre-production bugs are relatively negligible, but for most projects you can't treat pre-production bugs as 0 cost.
1. I get type feedback in my browser with inference. 2. I get near real-time feedback from TDD on file save. 3. I get real-time lint feedback, too.
The net result is I get several multiples better bug coverage than TypeScript alone can provide at about the same speed -- while writing idiomatic JS.
If writing your unit tests contributes less overhead than writing TypeScript types you're doing something horribly wrong.
But when I actually had to use it in a project, I instantly fell in love with it. Not only does it force me to think about the proper scope and type of a function or variable, it also helps refactoring and reading undocumented code.
Yes, sometimes it can be frustrating (meh: no typings for a legacy project, build-toolchains have to handle another layer of abstraction, etc.). But this really is peanuts compared to the hoops we had go through before.
TS has been the best thing that has happened to JS-development in the last decade.
But I'd say it simply has a different scope: The Closure Compiler takes a subset of Javascript and creates highly optimized code. It optimizes the execution, not the development workflow.
Typescript forces you to think differently about the implementation itself. While the CC also imposes constraints on types and checks them during compilation, in the end I really just wrote Javascript (and sometimes pretty messy one).
In the end, I don't think either technology can be a substitute for code quality. But for me, with Typescript its certainly easier to produce a better coding style.
I thought that was literally the reason TypeScript came to be. Microsoft were having lots trouble wrangling Bing web app and internal MS devs wanted a tool to help catch the bugs!
I would say, if you are embarking on a large scale app, you most definitely want to include TypeScript from the start. It will pay dividends in the end.
My usual "time to 'oh thank you typescript'" is roughly 2 days from the beginning of the project.
This is 20% for bugs, and 80% for sanity in long term maintenance. Refactoring is so much better / safer when you have a compilation process and at a minimum, types.
I just wish I could use Typescript without npm.
Uh... I'm pretty sure I've used both in TypeScript projects.
I will admit I have to pass type hints via generics to my compose / pipe calls more often than I would like, but it definitely still works.
Would I use Typescript for my own production use? Likely not. My perception is that it is yet another layer of complexity added to front-end development that, ultimately, does not need to be there.
Another very useful part is that refactoring becomes much easier, as you can simply follow the errors when you break an interface other parts of your code are relying on.
Typescript forces me to be more disciplined when coding, and that is clearly a good thing in my opinion. There are probably cases where you don't want that, e.g. rapid prototyping, but otherwise it's very useful.
This seems to imply that for whatever reason, TS may not be suitable in larger projects, but both Vue.js and Visual Studio Code are full TypeScript projects.
I don’t think you can write off the cost of learning all of those different tools in the non-Typescript environment. Typescript at least centralizes everything and I trust Microsoft to do good language development because of C#’s legacy alone.
> ... but conscientious developers will be interested in doing things right. They’ll spend hours Googling for examples, trying to learn how to type things that TypeScript simply can’t type properly.
> ... if you only need to hire one or two developers, using TypeScript may make your opening more attractive to almost half the candidate pool.
On the other hand, it seems that the author worked on a few medium-size projects and then moved on:
> All projects for which impact was judged were >50k LOC with several collaborators working over several months.
So I wonder if he was able to evaluate the impacts from a longer term perspective, which shall include refactoring experience, iteration speed, developer morale, etc.
Going beyond just adding annotations is not what the references study attempted to do.
That said, Sucrase has a number of issues still. Nothing that can't be resolved, but enough to prevent me from using it in a few projects.
The biggest issue with typescript, and the reason I'll probably never reach for it for another large project despite having used it for a 50k line+ project, is that it's still, at its core, javascript. We've experienced a tremendous amount of pain because of this.
- We've needed libraries which don't provide typescript typings. Like a good little typescript user, we have strict mode enabled, which means we have to build our own typings when we encounter this. We've had library maintainers reject our typings we provide back to the community. We've had library maintainers say they have no desire to provide any support to typescript users because "its a fad" and "ts is just js, you don't need types, live with it" (not joking, not exaggerating).
- All of the good high-level JS frameworks and architectural libraries are built for, well, JS. So, you want to adapt them for TS, but the JS idioms are so core to their design that you throw away any benefit TS gives you. Express is a good example; you might have middleware which parses a bearer token and attaches it to the request. When the request hits the next middleware or request handler, its just a normal request. There's no way to assert at compile time that "this middleware came before, the request is now a new type with this extra field". Express, Apollo, the list goes on.
- Further, the community around creating Typescript-native libraries is exceedingly small and, as far as I can tell, dying. I'm talking libraries that are made for typescript and make use of the true end-to-end compile-time verification typescript gives you. TypeStack is probably the leading organization (on GitHub) which builds (amazing!) projects like this, and their velocity is slowing down. TypeDI hasn't seen an update in nine months. Vesper was an incredible web framework that hasn't been touched in a while. There's nothing available for MongoDB if you use that. Basically, you're stuck with JS native projects 90% of the time, and about 10% of those don't have typings available.
- We've had NPM libraries redefine the Error global. This isn't caught at compile-time, obviously, and totally borked the error responses our API sends.
- It's a constant uphill battle with the `as` keyword. I wish there was a way to easily ban it globally. You can essentially assert Anything As Anything, and if its not caught in review you'll end up with incredibly hard to trace behavior because "the compiler says this field should be there, why am I getting undefined"... well, its because sixteen layers down someone asserted a type without verifying its accuracy at runtime, and in 5% of situations its incorrect, and it missed review.
- It's a constant uphill battle with lodash. We have developers on some teams that swear by it, and will scatter `_.get`s all over the codebase, then be startled when things break in unexpected ways. Typescript added the `!` operator, which is basically just an escape hatch, and it took us a long time to get everyone educated that this is, without a doubt, the dumbest thing Microsoft has added to the language because it doesn't actually do anything at runtime, it just suppresses a potentially useful compiler error.
In summary, don't pick Typescript for the backend. It's an obvious improvement over JS, but you'll still be left wishing you hadn't.
But I also don't know what the better option is for a high-velocity organization. Go and Rust are too unproductive. Ruby and Python aren't performant and aren't strongly typed. JVM languages are a pain. Elixir shows a lot of promise but the lack of typing concerns me.
Why did you submit the types directly to them rather than to DefinitelyTyped? It's great when libraries maintain their own typings, but TypeScript handles the case where they don't really well.
>Express is a good example; you might have middleware which parses a bearer token and attaches it to the request. When the request hits the next middleware or request handler, its just a normal request. There's no way to assert at compile time that "this middleware came before, the request is now a new type with this extra field". Express, Apollo, the list goes on.
Yeah, this can be a pain. In situations like this, I've defined types like `type RequestWithAuthField = Request & {auth: AuthField};` and then in handlers that I knew came after the auth middleware, I'd immediately cast the request parameter to that type. It is an escape hatch and I'd prefer to not have to do that though.
>Further, the community around creating Typescript-native libraries is exceedingly small and, as far as I can tell, dying.
Besides that they're sure to avoid TypeScript-unfriendly APIs (unlike Express), I don't think there's much difference between a library with good type definitions and a TypeScript-native library. (Maybe the library could have bugs that it being authored in TypeScript would have avoided.)
I don't fully disagree with your points. There's a lot awkward about TS on the backend, but at the same time there's not much quite like it.
I find this less than ideal, of course, but still not worse than writing the same in plain JS, since at least this way the next person to come along (or you, a few months later) has clear guidance re: wtf is going on with those weird extra fields on the Request object. It's less effort—for reader and writer—and is more maintainable than expressing the same information as effectively in docs and comments.
if(x<1) throw new Error("This should never happen: x=" + x);
If it can happen it will happen. So instead of support getting a call that some customers balance is negative, with defensive programming you not only prevented the bad state from spreading, you can also see why it happened. And fix it asap. Meanwhile if you trusted the static analysis good luck and have fun debugging something after-the-fact that "couldn't" happen.I discovered that in contrast to Flow, TypeScript has horrible errors. Often I ran into errors that would not give much more information than "error" -- no line information, no file information, nothing. One of the causes of such opaque errors was empty Flow-style typecasts, so they were pervasive throughout a large project.
In contrast, Flow has beautiful error messages.
I don't know Eric Elliot, but this sentence captures my perception of most "influencers" perfectly.
After skimming through, It's obvious that reading this article would be counterproductive for anyone with any sort of interest in this topic.