Toxic flexibility - a case for TypeScript

András Tóth - Jul 31 '21 - - Dev Community

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .