Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
We use TypeScript not based on preference, but because we want to make money (paularmstrong.dev)
34 points by paularmstrong on Sept 15, 2023 | hide | past | favorite | 70 comments


I see type safety as akin to the “Swiss cheese” model in aviation safety. It’s creating one more barrier against accidentally crashing your application.

The thing with type safety is that systems like typescript are so easy and cheap to deploy, relative to E2E or even unit tests; it’s a small investment that pays off in multiple ways over time (quality, documentation, developer onboarding).

I really struggle to understand the sometimes vehement arguments against it.

https://en.m.wikipedia.org/wiki/Swiss_cheese_model


Make it part of the Javascript standard and see me using it the second Firefox supports it. Are there any arguments against that?


The only real argument is an argument against waiting.

Most developers would need to wait until all browsers support it, and the vast majority of users use it, and you could easily be looking at a decade of lost opportunities while you wait for that.

That said, in a world of impending WASM, I don’t even know if TS on the browser makes sense. It seems like it won’t be long and we won’t need a JavaScript intermediary between TS and the browser at all.


I’m not a pro and don’t follow the development closely, but that said, I feel like I’ve been hearing about impending WASM for about 10 years


Nah, it’s well and truly in production now. There are new features needed to support some languages better, and closer integration with the DOM may arguably be desirable, but my understanding is that it’s at the point where heavy lifting can be done in wasm, with automatically generated JS stubs.

I might be wrong (not a front end specialist) but ISTM that a native TS implementation built today would more likely be a compiler to WASM than try to become a first class browser language.

And anyway, direct browser support for TS would take as long as WASM has taken to gain broad support.


This year is the year of WASM on the browser!


Welcome to the Python community, where type highlighting is a first class syntax feature, but actually checking it is optional (unlike the TypeScript compiler), and you get people complaining their favourite languages is polluted by ugly stuff they don’t ever use (presumably) hurting readability. You can’t please everybody.


Our team actually dropped FE testing entirely with Typescript.

We found that our E2E tests took way too long to write and didn’t really cover the visual defects we often hit.


We definitely skip all automated tests with the exception of integration testing by mounting the app and robo-interacting via jsdom.

I think our BE engineers are surprised by the lack of testing while at the same time rare runtime exceptions.

We have enough critical flows tested and with TS to prevent runtime errors, we have a decent degree of confidence.

If only we had a good way to test webauthn…


> If only we had a good way to test webauthn

Preach, we had to check the source of the official test suite to figure out how to test it.


They lost hundreds of thousands of dollars on an issue that was in production for several days. On the one and only feature generating revenue: showing ads.

No unit testing, no integration testing (as was admitted) and no production sanity test at all. I guess the accountant was asleep at the wheel as well.

You have bigger issues than strong types.


Exactly. Look at what he says:

> But how strong and reliable are your test environments to ensure that this all works correctly? How much effort, time, and money does it take to maintain full testing solutions?

Seriously? The entire company revenue depends on counting ad views and you don't have resources to make sure you're counting right? Oh, c'mon!

Strong typing has its place, but this was a bad argument in favor of it...


Very selective quoting, they clearly have integration testing.

The resolution of the post mortem was to both add 'an' (they were already set up for it) integration test, and strict typing.

The criticism of the test was that it would only cover that specific path, not others or potential future ones. Which is true.


You’re assuming this was the only location they showed ads.

It could have been one of the many components displaying ads. And this specific component might have accounted for a small percentage of those ads.

And presumably this wasn’t the only feature deployed so any noticeable change in revenues could have been attributed to many other things before a typo in the ID would have been discovered.


Then they need to incorporate all components that display ads in the ad counting integration tests!


Agreed, and as someone who likes Typescript and typed languages in general, I've noticed many of the loudest "OMG HOW can you develop without Typescript" also tend to be people who don't write tests.

It's like the types are one giant unit test for them.


Yes, this was my takeaway as well: no KPI relation ship tests whatsoever. This should have never passed testing. But that doesn't mean TS doesn't have advantages, just that this particular error could have - and should have - been caught by other means as well before hitting production.

So both of these would have been good, none of them was clearly bad. Notice that you don't necessarily have to choose between these two.


I very much enjoy the false dichotomy argument that the bigger issue is tests, rather than types. Thank you for brightening my day.


> The any type was a mistake and never should have been included in TypeScript in the first place.

Disagree. `any` is important for incremental adoption, not having it would just make it harder to introduce type-safety to a codebase.


I think it would have been better if the "unknown" type was available since the start. A lot of developers learned to bail out with "any" when "unknown" is really what they wanted.


Agreed. The success of TypeScript can only be explained by its direct interoperability with JavaScript. But it is also the bane of its existence.


The fact that Typescript was (is?) a superset of Javascript is the reason it won over Dart. You can't be a Javascript superset without any.


Casting and `as unknown as <type>` are just as useful, probably more so because they specify what the type _should_ be.


IMO, it's a million times better than accepting something as any, if the latter is possible to avoid. Sadly, every Typescript codebase I've worked with is riddled with `any`, and developers seem skeptical of `as`. That doesn't make sense to me. Throwing your hands up and saying "whatever" is better than declaring what the type should be when Typescript can't infer it otherwise?


I don't really use JS or TS, so sorry if it should be obvious, but why is that so much better?

You just move the (incorrect) assumption from the logic to the type annotation? And you're still not going to get a compile-time error for it, because you're explicitly telling the compiler to make that assumption?


I didn't know about 'as' until right now, because our codebase doesn't use it (we create a lot of weird unions instead),and TS is like 5% of what we do (and we are very small).

Thank you, i'll try to see if i can use it when needed, it's a detail but can save me hours imho.


in my heart I feel where the author is coming from (a place of pain and experience).. however.. without an easy adoption path to TypeScript that the any type provided... we'd probably not have TypeScript today. TypeScript works because you can migrate to it incrementally. That's huge for many people.


Now feels like a good time to mention the talk "Ideology" by Gary Bernhardt. https://www.destroyallsoftware.com/talks/ideology

Tests and Types are useful for preventing errors, but the types of errors they can prevent differ. You probably need both!


Sure, but the point is, a halfway decent type system will eliminate whole classes of errors—consequently eliminating whole swaths of things you would ever need to write tests for.

What's more, though, is that a good type system will help you 'Make Impossible States Impossible'[0]—even more behavior you don't have to test because bad behavior is simply not representable in your program.

Tests are strictly more powerful in the small, because they can be arbitrarily complex, but I would say a (really good) type system is more powerful in the large, because you get way more leverage in terms of overall program correctness.

A lot of my personal projects are in Elm (yeah, yeah, bring on the hate)—the models are really well-defined, but the test suites usually only have a handful of tests of behavior I really want to verify. Haven't even played with the property checker yet, but again, that's something that is strictly more powerful than unit tests, but you can only get it* if you have a type system.

[0] https://www.youtube.com/watch?v=IcgmSRJHu_8

* Okay that's not really true: see clojure.spec in Clojure—but there are so few counterexamples that it's true for most practical purposes


> Warnings are pointless. They are nothing but noise and invisible ...

I absolutely abhor the the trend to accept that this is OK. Your code shouldn't generate warnings and the build system should treat them as errors.


Yes! The code you deploy should be compiled with stuff like -Werror or whatever will stop the deployment if a warning exist. It's fine for development but it encourages lazy practices. Any accepted warning should be explicitly declared in code.


If they should be treated as errors then how are the different than errors?

The only difference between a warning and an error is if it's ignorable to still proceed to execution. If it isn't it's an error, if it is, it's a warning.

If all warnings should stop builds then they are no longer warnings. And to be clear you can argue that point - that there shouldn't be such a thing as a warning. And that's a fair argument to make (regardless of if I agree or disagre), but saying "the build system should treat all warnings as errors" isn't really coherent.


Because to the compiler errors are non negotiable. It doesn't understand what you are trying to say.

Warning is the compiler saying that yes, he understands but you're probably doing it wrong.

Unused variable. Yeah, legal. But why?

Most code that generates warnings can be written in a way so it doesn't. For the rare extreme nonsense warning, deactivate that warning with a comment and justification why to the next reader.

Because if you don't treat warnings like errors they become just a background din.

> ... isn't really coherent.

It's supported by many compilers and isn't a new idea.


I would love to have types in JavaScript. But I also don't want to add a compile step for the small to medium things I use it for. I'd rather have something like "use strict, types". And if I made something really complex purely client side I'd rather spend the time to learn Rust and compile that to WASM, so I actually get something out of it.

> Because there was no real error, just a warning, this issue went into production

If you don't at least parse a new warning to figure out what causes it and decide why it's okay to ignore in this case, there's your problem. Types are fine but if this is the lesson you took from that, you learned nothing.


I love strong types, but I disagree with the final note here:

> The `any` type was a mistake and never should have been included in TypeScript in the first place. It is disappointing to see the number of published libraries that embrace `any` types, making it difficult to verify with certainty that code is correct.

`any` is a consequence of TypeScript being JavaScript under the hood. Is `unknown` a better type to use when you're not sure what something might be? Sure, the fact that TypeScript forces you to carefully pick apart what it actually is by guiding you through the appropriate type narrowing is great. But sometimes you actually just want to write some code and sort out the types later, especially when you have a lot of interfaces that came from JS or from an only partially converted TS codebase that would otherwise be full of `as unknown as T`.

I think the only mistake here was allowing the `noImplicitAny` tsconfig option to be off by default. When TypeScript can't figure out what a type is and that setting is off, it just decides that the type is `any`. But when that setting is on, it's an error to not explicitly state the type, even if you explicitly state it's `any`! Then any usage of `any` is explicit, auditable, lintable, etc. and then it's much less of a problem to use effectively.


If all code was Typescript, maybe the author would have a sound point.

Because of JavaScript (godblessit), there are many things that were written in a way that makes types difficult to implement. I work on Ember codebases and, while some of my colleagues who happen to work on Ember probably would disagree, there are things about the way it lazily resolves things makes it extremely difficult for everything to be typed properly without lots of recasting and bureaucracy that wouldn't have happened had the whole thing been written in Typescript from the very beginning. Typescript in Ember will always be a shitshow because of this design flaw that was encouraged by the era of JavaScript it was spawned from.

Nevertheless, we shouldn't replace things that are otherwise working just fine, hence it's better to just accept that some things are going to be "any" or "unknown" and deal with them properly.


And I used plain JavaScript for my latest project, not because 'I wanted to make money' (or lose money) but because that removes one barrier to contribution by outsiders. Requiring a compiled toolchain and a second language (some of the libraries the project relies on are in JavaScript) is a step up from just a browser and a text editor (and in the case of a GitLab hosted project: just a browser). JS is a terrible language, let there be absolutely no doubt about it. But TypeScript is not what browsers run.

It's nice that the author puts their main reason for using this particular tool up front: they want to make money. But wanting to make money is not necessarily correlated with the use of TypeScript, JavaScript or any other programming language. And it misses that 'wanting to make money' even if it does correlate with the use of TypeScript would still be a preference.

Finally: what's missing from the story about the typo is why there wasn't a system around all of this code that would notice that a particular KPI's relationship to another KPI came in wildly outside of the predicted range. Obviously: typed languages are great, but typos can happen in any language and such a defense-in-depth strategy against silly mistakes will benefit you regardless of the kind of language that you use.

Awaiting the 'we use Rust because we want to make money' blogpost that is sure to follow.


I've noticed in a few examples that the people who dislike TypeScript and use pure JavaScript generally are really good at keeping track of things in their head so they can go fast when they write. I'm not good at doing the housekeeping in my head, so I appreciate tools like TypeScript. I go slower, but it saves me time in the long run.

edit: right -> write


Also, quality doesn’t scale. These people can do whatever they like when they work alone but once you get more people to work with them their claim falls apart like an unsafe function call


They're also just blissfully unaware of all the bugs in their code.


Right, I always suspect there’s bit of Dunning-Kruger going on when I read about people who reject compile time type safety.


Yes, there's really no reason to believe someone when they say they get along perfectly fine without strict typing.

Every project I've converted from JS to TS, no matter the size, even the small, only a few hundred LOC projects, has had at least one major "how the hell did this ever work" type bug that the type checking discovered immediately and would have prevented from happening in the first place.

So I don't really do even small projects without TS anymore. Anything more than can fit in my text editor, without scrolling, with the editor window tiled so I can see the app and the debug log at the same time.


Yes, exactly my point. How do you know that your code doesn’t have subtle bugs when one of the systems that detects subtle bugs isn’t available?

I used server side JavaScript for a decade before Node existed, so I have lots of experience with this sort of thing.

And don’t even get me started on refactoring.


This. I get that there are some devs that dislike verbosity and love flexibility, but I'm not one of them, and in larger teams, there are more than one like me, so we are going to make mistakes.

Any tool that prevent me from shooting myself in the foot, is appreciated.


> generally are really good at keeping track of things in their head

Inherent in that is their teammates will not be able to go as fast as them.


Well it helps that the iteration loop on the FE is generally really fast, so type errors are really quick to debug. You rarely even have to run a test suite, just load the page on a local node instance, get an error, print the object, refresh, takes 10 seconds.

For BE code it can take 10 minutes to hours to deploy with debug info, so type errors are a huge PITA and strong types are critical.


The problem is not debugging the type error, the problem - as in this article - is not detecting it in the first place.


I dont get this defense of using JS.

Ok, maybe a dev is much faster writing JS because they keep stuff in their head better. But I’m assuming they also use third party libs, or functions that other devs in their team have written that they just need to call.

Typescript makes it easy for the IDE to show you what exactly you need to pass to call the teammate’s function (and this was the originally motivation behind typescript…improving MS dev tooling) without having to dig into the code or read the documentation.

And type inference means you’re rarely actually writing out the types. So I find it hard to believe that anyone writing a relatively complicated piece of code would be faster using JS over TS.

Small scripts and the like, sure. But complete complex applications? I doubt it.


This will be... a "fun" comment section ;)

I will put my conclusion first: as an industry, there is so much room for real research and understanding in what does and doesn't work in reducing defects. However, this itself is a pretty difficult problem because of the difficultly in tracking, quantifying, or even defining what a "bug" is.

Until we have that data/research, any conversations about type systems vs testing vs process vs monitoring and saying which is "better" I think very rarely changes opinion and is more likely just to reinforce opinions held by different groups.

That isn't to say this article isn't useful, it is a good real-world perspective of things that do happen. I think the problem is that something about most of our brains (either in the writing or in the reading) causes a huge portion of us to immediately look the issue with such absolutes that most of the learning/discussion that could be had are drowned out through disagreement about the basics.

So in an attempt to try and hopefully start a discussion that goes beyond the argument about the value of types, I would love to learn about any research/products/ideas/success stories of how we as an industry can make software more resilient?

In my (limited) research over the years, it seems like a lot of the research is often limited to a single company (MS did a lot of measurement in the past) or looks more at the people aspect and less at the tools/technology.

I would think that LLMs might make some analysis, like measuring the impact of bugs, more tractable as I think an LLM could probably do an okay job of assessing the real impact of a bug (assuming there is a post mortem doc) better than just having a label like "major" that determines impact. That, combined with some okay understanding of the code that fixed a bug, could maybe help give a more complete understanding of how different tech/process/team structure impacts defect rates?


Perhaps this means I'd have a lot of adapting to do if I were to work at a larger organization, but my first thought was "did they test the code they wrote"? I'm not talking about automation or integration testing or any of that. I mean did you try using the thing you just built. It seems to me like it would be immediately obvious there was a problem. I could never ship something as critical as this without seeing with my own eyes that it functions as expected.


But the advertising tracking system is not the thing they just built. That’s the problem.

They included a tag that included information that was to be passed to that other system, to indicate that this tweet was billable.

Presumably the tag was optional, the tag was just misspelled, so everything appeared to work just fine.

It’s the downstream systems that cared about this tag and they were presumably not even running on the dev machine on which the offending code was written.

The only thing that could have prevented this was some kind of schema that would have rejected an unknown tag at build time. Hence, typescript.


I see your point but the article says this:

> This property was then used when logging visible Tweets that it was in fact displayed. It was a relational ID that told the backend systems exactly which Tweet from which advertiser was being displayed.

It was supposed to be storing these IDs. Did nobody think to test that the thing was, in fact, storing IDs? Especially for something that puts hundreds of thousands of dollars on the line? Even if you're actually passing that value off to some 3rd party system somewhere, you can verify that this slice of your problem (the React component that receives a prop) is doing its job properly. Even if that means stubbing it to log locally. Something?


The problem is that in a large system like Twitter, the backend code that stored the ID is very distant from the front end code that generated the tag.

I’m not defending this or anything, but you said that this should have been manually tested, and I’m just observing that manually testing a system as complex as Twitter, in any kind of comprehensive way, is not at all trivial.


I get you and that makes sense, and we're discussing things at a scale that I don't have experience with. I just wouldn't have been comfortable shipping this code without observing it functioning in some way. If your production environment is so complex that you can't properly test things locally then you should certainly have a staging environment that makes that kind of testing possible. I would never be satisfied doing zero manual testing of the code I ship.


That's one of the main reasons why automated tests exist: to make sure a change in one area is not breaking anything else in the system.

I'm not against strong typing. Just saying this is a bad argument in favor of it.


But the comment I was replying to said,

> I'm not talking about automation or integration testing or any of that


Here is a question for the TypeScript experts.

I have been reading about Zod which gives runtime type validation.

Is this a good fit to TypeScript and are we likely to see such functionality added to TypeScript?


Zod is really useful when you have complex data coming in and you're not sure what the structure may be at runtime. Zod helps you prove really quickly that the data coming in is of the appropriate shape.

Now, it's already possible to take certain kinds of objects and narrow their types using plain TypeScript and have all kinds of really solid guarantees about how you're checking the data (and the checks are always going to be as fast as the types will allow, which is great). But Zod is important for being able to take unstructured data and ensure it's compatible with your desired types, in a flexible way, at a cost to runtime speed.

I don't think the Zod stuff is going to find its way into the TypeScript language / standard library. There's very little actual codegen being done in TypeScript (sugar around enums notwithstanding), most of the work to convert it to JS is just checking the types and then stripping them. What you might see instead are improvements to type narrowing.


Zod is great. It has some weaknesses - you can't just reuse types from external libraries; it can be verbose.

There are some libraries that provide a cleaner experience (e.g. typescript-is/typia). But they rely either on code generation or extending the TypeScript compiler while Zod just makes do with some clever typing.

TypeScript itself will never integrate something like this as it goes against their design goals[1].

[1] https://github.com/microsoft/TypeScript/wiki/TypeScript-Desi...


zod and yup both give runtime types and they're great. They can also export typescript types. But as far as I understand it, typescript is a layer "on top of" javascript that does not exist at runtime. It all gets stripped and you're left with valid javascript. So no – I doubt it. They exist in different worlds.


zod or yup gets you quite a bit of the way there in practice - when you would reach for a Typescript type, making it in zod instead is more verbose but gives that runtime layer.

But for those who actually want full-stack non-stripped runtime type reflection based on Typescript syntax alone... https://deepkit.io/ - https://deepkit.io/blog/introducing-deepkit-framework - is a really promising and cool project.

It patches the typescript compiler (which pointedly considers runtime type information out of scope) with its own type compiler that emits a bespoke bytecode that is executed in a bespoke VM to communicate runtime type information to both server and client as needed. https://docs.deepkit.io/english/runtime-types.html

And from that baseline, there are very cool things you can do like an ORM entirely based on type annotations https://docs.deepkit.io/english/database.html or strongly-typed RPCs https://docs.deepkit.io/english/rpc.html .

It's very much in the alpha stage, but it's really well thought out - there's a tremendous degree of care the developer is taking towards code cleanliness and developer experience. I'm torn between wishing this project to have a fully funded team and take the world by storm, vs. "letting them cook" so to speak and seeing the developer experience unfold organically. Either way, it's a breath of fresh air into the Typescript ecosystem!


I've found libraries like Zod useful when interacting with external data sources like a database. Slonik[1] uses Zod to define the types expected from a SQL query and then performs runtime validation on the data to ensure that the query is yielding the expected type.

I don't think it's necessary to use Zod/runtime validation everywhere, but it's a nice tool to have on hand.

[1]https://github.com/gajus/slonik


They do different things. Static types can be used to make sure the runtime checks have been done.

I haven't heard about Typescript having any plans to generate runtime checks.


TypeScript management feels very strongly about remaining fully compilable to JavaScript, with no runtime component. So likely not as a native feature, no.


You can infer types from Zod and also generate Zod objects from typescript. Personally I prefer the latter.


the world should throw that entire stack away and start over


It's pretty unreal that the only real choice for the web is still JS and a language that transpiles to it.


we already have new frameworks and now runtimes every week, I really don't want that to carry over to the language level too


promotedContemtId is a hilarious typo. Nice post.


Does he not have autocomplete on his editor?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: