DEV Community

András Tóth
András Tóth

Posted on • Updated on

Toxic flexibility - a case for TypeScript

Which option would you take?

  • Spend medium amount of time in rare exotic edge cases but also in the majority of the cases (vanilla JS)
  • Spend a lot less time in the majority of cases but a lot more development time in rare and exotic cases (TypeScript)

I will give you my answer at the end of this article, but now let's see a needlessly complicated JS example:

let thing = {
  sushi: '🍣',
  doThangStuff() {
    return thang.stuff.a;
  },
  thisWillBeBound() {
    console.log('Sushi:', this.sushi);
  },
};

let thang = {
  stuff: {
    a: 123,
    b: thing,
  },
  callThing() {
    thing.thisWillBeBound = thing.thisWillBeBound.bind(Object.assign(this, { sushi: 'sushi' }));
    thing.thisWillBeBound();
  },
  doMagic() {
    console.log(this.aPropertyAddedLater + ['55']);
  },
};

thang.aPropertyAddedLater = 123;
thang.doMagic();

thing.thisWillBeBound(); // Sushi: 🍣
thang.callThing(); // Sushi: sushi
thing.thisWillBeBound(); // Sushi: sushi

function renameFunction(fn, name) {
 Object.defineProperty(fn, 'name', { value: name });
}

renameFunction(thang.doMagic, 'doUnicornStuff');
console.log(thang.doMagic.name); // doUnicornStuff
Enter fullscreen mode Exit fullscreen mode

I know it's a lot, you don't need to fully understand it, but the point is: everything here is valid JS code. The flexibility of the language is allowing all this and more.

This is really-really useful when you are writing a throwaway script (originally intended design of the language) and you do not want to write pages of code. This is however counterproductive when you have hundreds of files in a large project.

Who would write this convoluted code?

Well, renameFunction was already coming from a project I took over, so some of it is in the wild already, but in most cases nobody would write it. That's one thing.

The other one is that tooling (your IDE, the engine running code, the code linters, code coverage tools etc.) must work with the language as it is with all its options.

So yes, nobody writes code like this, but no, the tooling cannot trust the language not to be like this.

Toxic flexibility

Let me define it:

Toxic flexibility of a programming language is when allowing a large number of exotic edge cases hurt the coding experience of the majority of the cases.

...rendering it impossible to have useful tooling built around the language.

Simple example, before ES5 you could even overwrite undefined 😱. (This option got removed and no-one cried about backward compatibility).

On one end you have complete trust in your code resulting from strict, statically computable rules, yielding that at any given line in your code you are sure what are the possible values.

And on the other end: toxic flexibility. You only have probability arising from coding conventions, developer taste, usage stats...

I.e. the IDE can only say: "I think most likely you wish to access one of these possible properties of this object, function or primitive, I don't know, don't get angry at me if it's wrong!".

Fun fact: One of my former colleagues in 2017 decided he wanted to do JS to stay relevant after doing mostly MSSQL and C#. Whenever WebStorm code completion feature suggested a property he would press Enter and get on with it. Later he called us because he was confused "Why is everything damn undefined?". We giggled and told him, "Man, that list is gibberish, don't even look at it!". At that time I already knew the pain for 2 straight years.

The reason behind the creation of TypeScript

No, it was not enforcing the dated Object oriented principle on this beautifully flexible and functional language (irony). It was created for increasing trust in the code the tooling was working with. Let me quote Anders Hjelsberg, co-creator of TypeScript:

All the programmer productivity features we have like VSCode's IntelliSense, code definition and code navigation require the IDE to be able to reason about the code that you're working on.

A type system is one way you can reason about your code. It's the ability to check your code before you run and deploy it. Without types in a language that's almost impossible.

So by adding type information and understanding how that information changes from line-to-line gave us better tooling, living documentation and quicker development times.

If you have ever tried a strongly-typed language with a good IDE (like Visual Studio and C#) you know how it can give you for every line the suggestions just you need, you only need one letter from it basically.

Working for the majority of cases

As I wrote above: the tooling of JS cannot really help you, since you have to run the code to actually know what it is really doing. In that case the very rare edge cases of the flexibility of the language forbid any smarter static analysis of the code.

By adding code with proper types you are reducing the number of cases you can have in a given line of code. Inferring the rest from primitive types (we are mostly dealing with phoneNumbers and emails and scores, strings and numbers...) and solid coding TypeScript compiler does the heavy lifting for you.

To give you my answer to the question at the start: I prefer code completion and static refactoring help in every line I write over the flexibility of vanilla JS.

The "silence" of coding tools makes me check the functions I call, searching for signs of what they'd return, write me tests I would not need and makes me nervous with simple everyday tasks, like renaming a function.

These are everyday scenarios and it is time lost. With TypeScript these are usually covered by the tooling so I need to think more about what my code actually should do over making sure it is really getting and emitting the correct data.

Using TypeScript has also drawbacks of course. Writing exotic, very dynamic code and typing it properly will take a lot more time than in vanilla JS.

But I would rather pay this price than not having the tooling that works for me.

Conclusion

Strict typing might be just one particular solution to lower toxic flexibility to the point that meaningful help can be provided by our tooling. If you have a disdain for it - or with TypeScript in concretion - you can still embark on your own journey on figuring out novel methods to know more about the code before you are running it.

Discussion (4)

Collapse
latobibor profile image
András Tóth Author

I would like to thank my readers ask for your help if you find anything wrong with this article. English is not my native language and it's even worse that I learned computer science in Hungarian. 😱 Which I finished in 2009 anyways. So my jargon might be rusty and my phrases might be off therefore I really appreciate the kind suggestion to improve this text.

But you get the gist I hope:
Lax rules and lots of options => weak code completion/Intellisense => shit developer experience.
Strict, computable rules => great code completion => rich developer experience.

Collapse
psiho profile image
Mirko Vukušić

Did my first 2 projects in TS. This article covers one of the better aspects of using TS vs JS. After 2 projects, I can say i still have like love-hate relationship with TS. Sometimes it feels like blessing, sometimes (complicated cases) gets me frustrated. Eficciency... I find myself doing less debugging, a lot less, code just works from the first try more often. But I find myself programming "arround" TS a lot more. Sometimes TS feels "stupid", and insists variable type is not known, while in fact it is (TS just doesnt get it) and Im forced to write more code. Sometimes it just saves me from a ton of little errors. Sometimes I spend hours to write a typesafe version of the function that would take me minutes to do in plane JS. Code is not as readabke as before in my opinion.

But bottom line, autocomplete just works and I couldn't live without it anymore. Also, I hope it's going to get better because in my opinion, TS code needs to be written different, apps architecture needs to be setup different, right from the start. JS app cannot just be converted to TS by adding types to existing code. And that's what I ws thinking before. So 3rd project will be better, I hope. :)

Collapse
latobibor profile image
András Tóth Author • Edited

I agree with you fully; lot of it is coming from JS itself and the fact TS is a superset. It's like the great sand worm in Dune: you have to know your way around and then you can ride it. It is by no means perfect.

You are not saved from learning some TS hacks like let timerId: ReturnType<typeof setTimeout> = ..., when you have to deal with a mixed environment (node and browser).

Typescript must be also be written a certain way to avoid conflicts - I am planning to write about that as well (mainly how (not) to use any and as using instead generics).

However when the puzzles are solved the working type system is super powerful and gives me a lot of confidence when I have to do renamings, usage checks and other every day things, not to mention how superful powerful is when you have just npm install-ed a library written in TypeScript and you get all the properties, parameters and return types immediately. Forces you to have a simpler API which I believe in the long run is a good thing.

Collapse
henryong92 profile image
henry_ong_92

I cannot agree more with the full article you wrote here.
I've started working with JS 4 years ago as my first work related programming language.
Everything was so messy and there were so much ambiguities and spaghetti code!

Everyday feels so anxiety inducing where everyone was battling with the code rather than focusing on what's more important which a scalable code structure would take away those worries.

After working on TS this year made me realised how overly confident and arrogant pure JS developers can be when they've done little to no large scale production level code. I was guilty of being like that too in the past.

TS enforces so much of clean code fundamentals using OOP as a tool. I'm more than happy to have picked it up. 😊