DEV Community

Cover image for Is your code really typesafe? Evolving any might ruin it
Kei95
Kei95

Posted on

Is your code really typesafe? Evolving any might ruin it

Summary

  • When you declare a variable like the following, the variable is "evolving any"
    • let variable without type annotation and initial value
    • const variable with mutable initial values such as an array but no type annotation
  • "Evolving any" is a variable that can be assigned to any value at any time though it has a value assigned to it already

Overview

If you have experience working on a project using TypeScript, you might have seen a pattern like below.

function foo(arg: "string" | "number") {
  let dynamicVariable;

  // depends on the given arg, change the value of "dynamicVariable"
  if(arg === "string") {
    dynamicVariable = "Yes I'm string";
  } else {
    dynamicVariable = 100;
  }

  return dynamicVariable;
}
Enter fullscreen mode Exit fullscreen mode

It seems to be just a regular function that returns either string or number. However, there's a little pitfall on dynamicVariable which has something to do with its implicit behavior.

Let's say there's another developer happened to work on this function and they assigned a new value to it. Then the code looks as follows;

function foo(arg: "string" | "number") {
  let dynamicVariable;

  // depends on the given arg, change the value of "dynamicVariable"
  if(arg === "string") {
    dynamicVariable = "Yes I'm string";
  } 

  if(arg === "number") {
    dynamicVariable = 100;
  }

  // adding an arbitrary value - you can assign it without a problem
  dynamicVariable = true;

  return dynamicVariable;
}
Enter fullscreen mode Exit fullscreen mode

Yes, there's nothing wrong with it. We know that because TypeScript doesn't cry on this code. But this dynamicVariable being able to be assigned to anything is quite concerning as someone who doesn't have a context that this is expected to return string | number could totally ruin this function's intention by assigning some unintended value as I did in the example above.

"Evolving any"

The behavior in the previous example is called Evolving any. You have two ways to declare an evolving any variable;

  1. When you declare a variable with let or var that has neither type nor initial value
  2. const variable without type annotation with mutable initial value such as array
//You can make this variable to any type at any point within the scope
let evolvingAnyLet;

//You can put any element in this array at any point within the scope. However, you cannot directly mutate this variable
const evolvingAnyConst = [];
Enter fullscreen mode Exit fullscreen mode

A variable with evolving any acts like a variable with type any. You can assign any value to it at any point and TypeScript updates the inference of the variable as you assign a new value to it. So it's something a bit more type-safe than any but less than typed variables.

Why is Evolving any bad?

Though TypeScript allows us to have some freedom with the dynamic types with evolving any, this can confuse your team or future-yourself. As I mentioned in the example, it's quite easy for a developer without the original context to misinterpret the intention of the variable or the scope which results in assigning some arbitrary value to it which eventually destroys the initial intention of the block of the code. There are several points why I think it's an anti-pattern.

1. It's hard to spot when there's a bug

A bug introduced by evolving any tends to be hard to debug since TypeScript, our beloved friend doesn't give us any clue of the error when it occurs as it's "type-safe". Imagine you have long chains of functions that contain multiple evolving any values...

2. Evolving any is an implicit behavior

Since this behavior is implicit, we cannot get help from IDE (e.g. VSCode)or TypeScript. Any implicit behavior like this can bring an unspoken nuance to your codebase which can be missed quite easily. The "original intention" is one of the examples.

3. Less readability

It's simply hard to read. You cannot trust the value until you hit the bottom line of the scope and you have to read from bottom to top to understand what it actually does. Any code that requires you to read back and forth typically has room for improvement and evolving any indeed is one of them.

What to do to avoid evolving any

Simple. Just assign either type or initial value to the variable when it's declared. If you do so, you won't need to deal with any of these surprising behaviors so it's such a huge win for a small effort. It's important to be clear on the intention when you declare a variable as every single line of code you write is a potential legacy of your project. The less clue you leave the higher chance you'd be cursed by the developers who look into the project later.

Conclusion

Once you understand what it is, it's not hard to avoid but I've seen quite a few developers (even seniors too!) making this mistake and ending up leaving cryptic-nuanced code. I'd strongly recommend cleaning up any evolving any when you see one. This little check would truly increase type-safety of your codebase. Making TypeScript silent isn't our goal, writing a code that prevents surprises is.

Top comments (0)