Thursday, March 30, 2017

Writing Better JavaScript with Flow

How often have you found yourself tracking down a bug in some code, only to find the error was something simple that should have been avoidable? Maybe you passed the arguments to a function in the wrong order, or perhaps you tried to pass a string instead of a number? JavaScript's weak typing system and willingness to try to coerce variables into different types can be a source of a whole class of bugs that just don't exist in statically typed languages.

March 30th, 2017: The article was updated to reflect changes to the Flow library.

Flow is a static type checker for JavaScript first introduced by Facebook at the Scale Conference in 2014. It was conceived with a goal of finding type errors in JavaScript code, often without having to modify our actual code, hence consuming little effort from the programmer. At the same time, it also adds additional syntax to JavaScript that provides more control to the developers.

In this article, I'll introduce you to Flow and it's main features. We'll look at how to set it up, how to add type annotations to your code, and how to automatically strip out those annotations when running the code.

Installation

Flow currently works on Mac OS X, Linux (64-bit) and Windows (64-bit). The easiest way to install it is via npm:

npm install --save-dev flow-bin

and add it to your project's package.json file, under the scripts section:

"scripts": {
  "flow": "flow"
}

Once this is done, we’re ready to go ahead and explore its features.

Getting Started

A configuration file named .flowconfig must be present at the root of the project folder. We can create an empty config file by running the command:

npm run flow init

Once the config file is present, you can run ad-hoc checks on the code within your project folder and any subfolders by running the following command at the terminal:

npm run flow check

However, this is not the most efficient way to use Flow since it causes Flow itself to recheck the entire project's file structure every time. We can use the Flow server, instead.

The Flow server checks the file incrementally which means that it only checks the part that has changed. The server can be started by running on the terminal the command npm run flow.

The first time you run this command, the server will start and show the initial test results. This allows for a much faster and incremental workflow. Every time you want to know the test results, run flow on the terminal. After you're done with your coding session, you can stop the server using npm run flow stop.

Flow's type checking is opt-in. This means that you don't need to check all your code at once. You can select the files you want to check and Flow will do the job for you. This selection is done by adding @flow as a comment at the top of any JavaScript files you want to be checked by Flow:

/*@flow*/

This helps a lot when you're trying to integrate Flow into an existing project as you can choose the files that you want to check one by one and resolve any errors.

Type Inference

Generally, type checking can be done in two ways:

  • Via annotations: We specify the types we expect as part of the code, and the type checker evaluates the code based on those expectations
  • Via code inference: The tool is smart enough to infer the expected types by looking at the context in which variables are used and checks the code based on that

With annotations, we have to write some extra code which is only useful during development and is stripped off from the final JavaScript build that will be loaded by the browser. This requires a bit of extra work upfront to make the code checkable by adding those extra type annotations.

In the second case, the code is already ready for being tested without any modification, hence minimizing the programmer's effort. It doesn't force you to change how you code as it automatically deduces the data type of the expressions. This is known as type inference and is one of the most important features of Flow.

To illustrate this feature, we can take the below code as an example:

/*@flow*/

function foo(x) {
  return x.split(' ');
}

foo(34);

This code will give an error on the terminal when you run the npm run flow command, as the function foo() expects a string while we have passed a number as an argument.

The error will look something like this:

index.js:4
  4:   return x.split(' ');
                ^^^^^ property `split`. Property not found in
  4:   return x.split(' ');
              ^ Number

It clearly states the location and the cause of the error. As soon as we change the argument from a number to any string, as shown in the following snippet, the error will disappear.

/*@flow*/

function foo(x) {
  return x.split(' ');
};

foo('Hello World!');

As I said, the above code won't give any errors. What we can see here is that Flow understands that the split() method is only applicable to a string, so it expects x to be a string.

Nullable Types

Flow treats null in a different way compared to other type systems. It doesn't ignore null, thus it prevents errors that may crash the application where null is passed instead of some other valid types.

Consider the following code:

/*@flow*/

function stringLength (str) {
  return str.length;
}

var length = stringLength(null);

In the above case, Flow will throw an error. To fix this, we'll have to handle null separately as shown below:

Continue reading %Writing Better JavaScript with Flow%


by Nilson Jacques via SitePoint

No comments:

Post a Comment