Why I Chose to Use Flow for Static Type Checking My JavaScript

May 11, 2017 0 Comments

Why I Chose to Use Flow for Static Type Checking My JavaScript

 

 


To begin, let’s talk a bit about the problems that can arise with how JavaScript handles types.

Static vs Dynamic Typing

In simplest terms, a statically-typed language has types that are known at compile time (before the program runs) and dynamically-typed languages have types that are not known until runtime.

So, in a statically-typed language, there are errors that will be identified very early—you will most likely see them displayed in your editor or when you try to build your program. They are hard to miss. But things are different in a dynamically-typed language like JavaScript. Consider the following code:

 

var myObject = { prop1: 'Some text here'
}
console.log(myObject.prop1()); 

What this code does is declare an object and assign it a property with the value "Some text here", which is a string. Then we try to call that property as if it’s a function, producing the following error:

TypeError: myObject.prop1 is not a function



This sort of error can be very easy to miss in JavaScript. There is no compile error to warn the developer and if that bit of code is only fired after some sequence of events—say after a few different button clicks—then it could very well sneak into the code base.

The benefit of having a static type checker then, is that some types of bugs are caught early, prior to the code being pushed to production.

A Separate, but Related Problem

Another source of errors comes from implicit type coercion. That’s a mouthful, but it’s a straightforward idea. Take a look at the example below:


var myNumber = 20; console.log(typeof myNumber); myNumber = true;
console.log(typeof myNumber); 



In the code above we declared a variable called myNumber and assigned it a value—the number 20. Next, we change the value to true and in the process, change its type as well (it’s now a boolean). 

Notice that we didn’t explicitly change the type—JavaScript just did that on its own. In this simple example, it’s easy for us to see what’s happening. In a large code base with multiple developers, however, this implicit type coercion can easily create bugs.

How Flow Solves These Problems

In the case of our first error, having Flow check the file—even without type annotations—will catch the error. We simply install Flow and add the pragma to the top of the file like so:


var myObject = { prop1: 'Some text here'
}
console.log(myObject.prop1());



Now Flow will catch that error and display it either in our editor or when we run Flow from the command line:


6: console.log(myObject.prop1()); ^ call of method prop1. Function cannot be called on
4: prop1: 'Some text here' ^ string



Although we don’t have to provide type annotations in many cases in order for Flow to catch errors, adding them helps Flow work better and find more potential problems. The annotations are also a useful way to document code. Here’s what it would look like if we added the type annotations to our object:


var myObject: {prop1: string} = { prop1: 'Some text here'
}



We signal a type annotation by adding a colon after the variable name. In this example we could have chosen to not add a type for the property and simply wrote:


var myObject: {} = { prop1: 'Some text here'
}



But as mentioned, adding the annotations can provide big benefits in some instances. The point is, they are optional and you can decide what works best in a particular situation.

To fix our example with the implicit type coercion, we can update our variable declaration like so:


var myNumber: number = 20; 

 

Now we’d get an error if we tried to change the value to something with a different type. Very simple, right? With these annotations Flow allows us to get rid of two common types of errors in JavaScript.

Why I Chose Flow

Are you sold on the value of using a static type checker yet? I know I had to really think about it for a while. For most of JavaScript’s existence, static type checking has not been an option. And with so much other tooling to deal with, I wondered if it was worth it.

Here’s a quote from the Flow team that I found compelling:


…underlying the design of Flow is the assumption that most JavaScript code is implicitly statically typed; even though types may not appear anywhere in the code, they are in the developer’s mind as a way to reason about the correctness of the code.



It makes a lot of sense to have a tool help you with this, doesn’t it? Particularly when it isn’t much effort to get started. It’s especially straightforward if you’re already using a tool like Babel to transpile your code.

Here’s another great quote from Facebook:


Facebook loves JavaScript; it’s fast, it’s expressive, and it runs everywhere, which makes it a great language for building products. At the same time, the lack of static typing often slows developers down. Bugs are hard to find (e.g., crashes are often far away from the root cause), and code maintenance is a nightmare (e.g., refactoring is risky without complete knowledge of dependencies). Flow improves speed and efficiency so developers can be more productive while using JavaScript.



If you’re familiar with Flow, then you may have also heard of TypeScript, which is a language created by Microsoft that compiles to JavaScript and also features type annotations. There are  other similar compile-to-JS languages as well—Elm and Reason come to mind—and they offer similar advantages. So why did I choose Flow instead?

The first reason is one that is implied from the examples above—it is incredibly easy to get value from Flow due to its outstanding type inference. Just add /* @flow */ to the top of a file and you’ve already helped yourself out.

A second reason is that at Lullabot, most of our JavaScript projects involve React. Flow and React are both Facebook products and used heavily within that company. Knowing that the tools you use are going to work well with each other now and in the future is reassuring.

Another consideration is that, as a client services company, Flow is an easier sell on the projects we work on. There is a big difference in the mind of a stakeholder between: 


Hey we’d like to use this library to improve code quality on your project


vs


We’d like to write the code for your project in a language your internal team doesn’t know.



I realize that TypeScript is just a superset of JavaScript, but perceptions like this often matter to a client. They may also matter to fellow developers. Being able to say, “Flow is from Facebook” helps increase confidence in the tool and makes it more likely that static type checking gets used on the project.

So my decision was ultimately about easy wins, compatibility with existing technology and client sentiment.

Below is an excerpt from one of the Flow team members on a discussion thread. The topic is Flow vs TypeScript:


We built Flow because we didn't see TypeScript going in the right direction for the community. For example, it purposefully does not attempt to be a sound type system… …documenting the API using types is useful (in fact using Flow types you can generate API documentation using documentation.js). But the point is that you don't need type annotations everywhere in Flow to get something out of it. The places where you do add type annotations Flow ends up doing a lot more work for you because it's able to infer so much. If you aren't telling TypeScript what to do all the time, it won't do anything for you. With TypeScript 2.0, it is starting to do more of this, but it has a long way to go to match how much Flow is able to infer.



I include the quote above not to bash TypeScript, but because it’s an obvious question and useful to hear what Facebook was thinking when they developed Flow.

Personally, I’m sold on the value of static type checking and that includes TypeScript. It offers a way for developers to eliminate an entire category of bugs, and in the case of Flow, with very little extra effort. That’s a big win in my book.


Tag cloud