Tossing TypeScript
Tags: typescript, react, javascript, codequality
Published: 2020-07-27 14:05:41

I don't need TypeScript.  There.  I said it.  Honestly, it feels pretty good to finally assert that on the record.  And if we're all being honest with each other, _you_ probably don't need it, either.

My loyal readers (both of them) know that I've been diving into TS fairly heavily over the last several months.  Those loyal readers (both of them) also know that I've been running into a few... headaches.  But I've finally reached a point where I just have to admit - to myself, and to anyone else who cares to listen - that the whole push toward TS just feels _misguided_.  

This doesn't mean that I've written my last line of TS.  My employer seems to be fairly dedicated to it.  So, by extension, I guess I am too.  But I can't claim, with a straight face, that TS provides any real benefits.  In fact, I've found it to be an unnecessary _burden_.

If this sounds like the rantings of an angry and entrenched greybeard, I suppose that would be fair.  But consider this:  I hadn't written a single Hook until February and I was increasingly exasperated by all the Hooks/FP _fanboys_ who wanted to shout down any use of class-based React components.  But now, 100% of my development is in full-fledged functional programming using React Hooks.  So my point is that - I'm stubborn, to be sure.  But I'm not _completely_ set in my ways.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8rbm3fmp4g9oxobro8m4.png)
## TypeScript's Type "Safety" Is Illusory
I've started to wonder how much TS devs even think about _runtime issues_ while they're writing code.  I feel like there's this misplaced, near-religious faith bestowed upon TS's compiler.  The irony here is that, if you have any experience writing in strongly-typed, _compiled_ languages, you know that "it compiles" is a common **_JOKE_** amongst devs.

When I was doing Java and C#, we'd be on some kinda tight deadline.  Some dev would push a branch at the 11th hour.  And he'd say, "Well... it _compiled_."  To which we'd respond, "_Ship it!!!_"

Obviously, we _didn't_ just "ship it".  The joke is that getting code to _compile_ is the lowest possible standard.  Saying that your code compiled is like saying that an athlete managed to remain upright during the entire match.

Umm... _Yay?_

But in TS, sooooo much effort is poured into getting that magical compiler to acquiesce.  And after you've busted your tail making all the interfaces and partials and generics line up, what have you achieved?  You've achieved... compilation.  Which means that you haven't achieved much at all.

It'd be fair to wonder how TS is, in this regard, any different from, say, C#.  After all, even C#, with it's strong typing and robust compiling is vulnerable to _runtime_ issues.  But here's why I think it's so much more troublesome in TS.

Most _frontend_ applications **have no real data store**.  Sure, you can chunk a few things into `localStorage`.  And the occasional app leverages the in-browser capabilities of tools like IndexedDB.  But for the most part, when you're writing that Next Great React App (or Angular, or Vue, or... whatever), you must constantly rely on a stream of data from outside sources - data that can only be properly assessed _at runtime_.

When I was writing a lot more C#, it was not uncommon for my apps to run almost entirely in a walled-garden environment where I could truly control the database formats, or the returns from our own _internal_ APIs, or the outputs from our own proprietary DLLs.  With this kind of certainty at my fingertips, I'd spend copious time defining _all_ of the data types my app expected.  And in those environments, it was often true that, if my code properly compiled, it probably _was_ pretty close to being "ship-worthy".  

But when you're cranking out that next Unicorn Single Page Application, most of your critical data probably comes from _outside_ the app.  So the comfort of knowing that something _compiled_ is... little comfort at all.  In fact, it can be borderline-useless.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6ref28s9l4mv4zbeadv4.jpg)
## Code Is Only As Good As Its _Interfaces_
No, I'm not talking about TS's definition of an "interface".  I'm not even talking about the true-OOP concept of interfaces.  I'm talking about an interface as:
<br/>

> _Any_ point in your application where data is exchanged with another application.  **_Or_**, any point in your application where data is exchanged _with another portion of your own app_.

<br/>Once your app grows beyond a dozen-or-so LoC, you're no longer writing a _single_ app.  You're writing _dozens_ of them.  And eventually, _hundreds_ or even _thousands_ of them.  This happens because we break our code into many, _many_, **_many_** smaller, more easily-digestible bites.  If you're an "OOP type", you call these "bites" _classes_, or _methods_, or _packages_.  If you're more of an "FP type", you call these "bites" _functions_, or _components_, or _modules_.  Regardless of terminology, the effect is the same.

As a body is comprised of billions of semi-autonomous actors (cells), an app is comprised of hundreds, or even thousands, of semi-autonomous programs.  So the quality of your app isn't so much predicated on the brilliance of your individual lines of code.  Instead, the app's usefulness and hardiness are generally determined by how well all those little "pieces" of your app manage to _talk_ to each other.  Screw up the _interface_ between two parts of your app (or between one part of your app and some "outside" data source), and your spiffy little app will suddenly look shoddy and amateurish.

What does any of this have to do with TypeScript?  (Or even, JavaScript?)  Well, I'm going to drop a radical concept on you:
<br/>

> Type "certainty" has little value _inside_ of a program.  But type certainty is critical _at the interfaces between programs_.

<br/><br/>![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7w26oforu8p2dp7qw3x0.jpg)
## Bad Handshakes
Let's consider the havoc that can be wreaked by sloppy interfaces.  Let's imagine that you need to generate random IDs throughout your application.  You might write a function that looks something like this:

```javascript
const createId = (length = 32) => {
  let id = '';
  const alphanumeric = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9'];
  for (let i = 0; i < length; i++) {
    let randomNumber = Math.floor(Math.random() * 35);
    id += alphanumeric[randomNumber];
  }
  return id;
}
```

On the surface, this isn't a particularly "bad" function.  We can use it to generate IDs of any arbitrary length - but by default, it will generate IDs containing 32 characters.  Assuming we don't need true _cryptographic_ randomness, the IDs generated by this function should work just fine for our app.  But there's a problem...

There's a default value set for `length`.  That's helpful.  Assuming we want IDs that are of a default length, it allows us to call the function like this:

```javascript
console.log(createId());  // ET6TOMEBONUC06QX9EHLODSR9KN46KWC
```

Or we can call it like this:

```javascript
console.log(createId(7)); // MSGFXO6
```

But what if we throw a 'monkey' into the works by doing this?

```javascript
console.log(createId('monkey')); // [empty string]
```

This... could cause some problems.  Potentially _big_ problems.

`'monkey'` doesn't actually _break_ the function.  It still "runs" just fine.  But it doesn't produce an _expected_ result.  Rather than receiving some kind of randomly-generated ID, we just get... nothing.  An empty string.

Given how critical it can be to have valid, unique IDs in most apps, the generation of "IDs" that are nothing more than empty strings could cause significant issues.

You see, the _interface_ for `createId()` (i.e., the function signature) allows us to pass in nothing at all, or _any_ value for `length` - even if that value is not a positive integer.  But the logic _inside_ `createId()` contains an implicit expectation that `length` will either be a positive integer, or it will be `undefined` (in which case, the default value of `32` will be used).

This is where I often hear people say something like, "This is _my_ program and I _know_ all the places where `createId()` will be called.  And I _know_ that I'll never pass in some stupid value like `'monkey'`."  And that _might_ be true.  But even if it is, that's no excuse for crappy code.  

You shouldn't create forms that will "break" if the user provides bad data.  And you shouldn't create functions (or methods, or components, or classes, or... whatever) that will "break" if another programmer invokes them with bad data.  Period.  If your function only works properly because you always call it in the "right" way, then it's a poorly-written function.

In my experience, "handshakes", that happen _all over our apps_, are a major source of bugs - sometimes, _nasty_ bugs.  Because a function is written with the assumption that a certain _type_ of data will be passed in.  But somewhere else, in the far reaches of the code, that function is called with an unexpected set of arguments.

This is why I contend that: 
<br/>

> Type certainty is critical _at the interfaces between programs_.

<br/><br/>![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dxidgfpd4uaor3hpaftc.jpg)
## Under the Hood
Once you get "under the hood" of the function - in other words, beyond the _interface_ - the utility of "type certainty" quickly diminishes.  As shown above, it's critical to know that the value of `length` is a positive integer.

So is it critical to know the data types of the variables _inside_ the function?  Not so much.

Ensuring the "safety" of the `length` variable is important because it emanates from _outside_ the function.  So, from the perspective of the function itself, it can never "know" exactly what's being passed into it.  But once we're _inside_ the function, it's easy to see (and control) the data types in play.

Inside `createId()`, we have the following variables:

```javascript
id (string)
alphanumeric (Array<string>)
i (number)
randomNumber (number)
```

Even if we converted this to TS, would it be worth our time to explicitly define all of these data types?  Probably not.  The TS compiler can easily infer the data types that are inherent in each variable, so it's unnecessarily verbose to explicitly spell them out.  Additionally, any first-year dev can do the same just by reading the code.

More importantly, explicit data types _inside_ this function will do almost nothing to minimize the creation of bugs.  Because it's easy to _grok_ all the data types at play, it's very unlikely that any flaws in the function's logic will be spawned by mismatched data types.  

The only variable in the function that could really use some explicit "type safety" is the variable - `length` - that originated _outside_ the function.  That's the only variable that wasn't created explicitly inside this function.  And that's the only variable that could create bugs that aren't readily apparent as we read this code.

This isn't meant to imply that there couldn't be other bugs lurking inside our function's code.  But adding a pile of verbosity to define all the data types, for variables scoped _inside_ this function, will do little to help us spot or fix such bugs.  Because type-checking is not a magic bug-killing elixir.  Type-checking is merely _the first step_ in eradicating bugs.

This is why I contend that: 
<br/>

> Type "certainty" has little value _inside_ of a program.

<br/><br/>![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w96ztwqlod1vrjtg6icg.jpg)
## Runtime FAIL
It may feel like I've just made a case _in favor of_ TS.  Even if you accept that type checking is most critical at interfaces, that's still a vital use of TS, _right_??

Well...

The real issue here is that TS fails _at runtime_.  To be more accurate, TS doesn't even _exist_ at runtime.  When your app is actually doing its thing, it's nothing more than JS.  So none of that warm, comforting type-checking means anything when your app is actually, you know... _running_.

This doesn't mean that TS is worthless.  Far from it.  TS excels when you're writing one part _of your app_ that talks to another part _of your app_ while exchanging _your own trusted data_.  Where TS becomes borderline pointless is when your app needs to pass around data that was only defined _at runtime_.  

When you're dealing with _runtime_ data, if you want to create robust applications with minimal bugs, you still need to write all those pesky _runtime_ checks on your data.  If you start writing enough of those runtime checks, you may find yourself eventually wondering why you're even bothering with TS in the first place.

Let's imagine that our `createId()` function is tied to a _user-facing_ application, whereby the user can request an ID of variable `length`.  Let's also imagine that we've converted our function to TS.  So our function signature would probably look something like this:

```typescript
const createId = (length: number = 32): string => {
```

Mmm, mmm!  Look at that tasty TS _type checking_!  It sure does protect us from all those nasty bugs, right??

Well...

If `length` ultimately emanates from a _runtime_ source, then that comforting `:number` annotation doesn't actually _do_ anything for us.  Because, at runtime, the annotation doesn't even _exist_.  So then we'd have to add some additional _runtime_ checking, like so:

```typescript
const createId = (length: number = 32): string => {
  if (isNaN(length)) length = 32;
```

And that approach... works.  But if that doesn't look kinda duplicative to you, then you've probably been writing TS code for too long.  

In the function signature, it looks to the naked eye like we've defined `length` as type `number` and we've given it a default value of `32`.  But then, in the very first line of that same function, we're running a check to ensure that `length` is indeed _a number_.  And if it's not, we're giving it a default value of `32`.  

Huh??

If you weren't already drunk on that sweet, sweet TS Kool-Aid, you'd be forgiven for wondering why we'd even bother defining a type `number` in the function signature at all.  Of course, the answer is that, _at runtime_, there is no type declaration for `length`.  So we end up checking its type _twice_.  Once in the compiler, and once at runtime.  _Yuck._
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/o9sroupogzdh92g7w2qa.jpg)
## Fuzzy Definitions
You may have noticed another problem with the data type definition above.  We're annotating that `length` is of type `number`.  But the definition of "number" is too broad - too _fuzzy_ - to be of much use in our function.

We've already established that, for our function to properly generate IDs, `length` must be:
<br/>

1. A number
1. _Preferably_, an integer
1. Specifically, a _positive_ integer

<br/>Any negative value for `length` is no more useful than passing in `'monkey'`.  `0` is similarly useless.  Technically speaking, decimal/float values would work, as long as they're greater-than-or-equal-to `1`, but they would imply a level of precision that is not accommodated in the logic.  That's why it makes most sense to limit the input to positive _integers_.  

This isn't a fault of TS.  TS is built on top of JS.  And JS's native types are... limited.  

And even if TS had a custom type that allowed us to annotate that `length` must be a positive integer, we'd still be limited by the fact that those types are only available _at compile time_.  In other words, we'd still find ourselves writing runtime validations for things that we thought we'd already defined in our code.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9cclarm2or28qvstvtn7.jpg)
## A Better Way
So is this just a "TypeScript Is Da Sux" post??  Not exactly. 

First, I understand that there are many practical reasons why teams choose TS.  And most of those reasons haven't even been addressed in this post.  Many of them have little to do with the code itself.  And that's fine.  _I get it._

For those teams, I'm certain that I've written absolutely _nothing_ here that will change your commitment to TS - in any way. 

Second, I've noticed amongst the "TS crowd" that there's this kinda mindless mantra about it.  A persistent chant about all the supposed bugs they feel they've avoided in their glorious TS code.  But the more I look at TS code - and the more I look at the way TS shops operate - the harder it is for me to see any _quantifiable_ benefits.  IMHO, the "benefits" are mostly _in their heads_.

For a certain type of dev, TS seems to provide some kind of comforting blanket.  A mental safety net, if you will.  It doesn't matter if you prove that the safety net is flawed and will break under minimal stress.  Some people just get a "warm fuzzy" when they look in their code and they see all those comforting type definitions.

(And please don't go quoting any of that AirBnB-study nonsense.  It was based on a wholesale _refactoring_ of a codebase.  _Of course_ they eliminated a ton of bugs when they refactored to TS. _That's the whole point of refactoring._  They would've eliminated piles of bugs even if they refactored everything _in plain ol' JS_.)

Third, I'm not claiming that the answer is to simply throw out any notions of type "safety" or type validations.  Far from it.  In fact, I'm rather anal retentive about crafting fastidious validations - in _all_ my functions - _with nothing more than JS_.

Back in March, I posted an article detailing how I do data validations - in regular JavaScript.  (If you're interested, you can read it here: https://dev.to/bytebodger/javascript-type-checking-without-typescript-21aa)

My recent foray into TS has led me to revisit my JS type-checking library.  And I'm happy to report that I've made some significant improvements to it.  So significant, in fact, that I just don't see any reason at all to use TS in my personal development.  

The next article I write will be a detailed illustration of how I use my new-and-improved _JavaScript_, runtime, type-checking library.

Stay tuned...