Flowによる型チェックでJavaScriptのつまらないミスを防ぐ方法

2017/05/10

Nilson Jacques

74

Articles in this issue reproduced from SitePoint
Copyright © 2017, All rights reserved. SitePoint Pty Ltd. www.sitepoint.com. Translation copyright © 2017, KADOKAWA ASCII Research Laboratories, Inc. Japanese syndication rights arranged with SitePoint Pty Ltd, Collingwood, Victoria,Australia through Tuttle-Mori Agency, Inc., Tokyo

バグの原因を探ってみると、つまらないミスだったということはありませんか。たとえば、関数に引数を渡す順番を間違える、数字の代わりに文字列を渡す、といったことはありませんか。 JavaScriptの貧弱な型システムが静的型付き言語では起こらないバグの一因になることがあります。

Flowは、2014年のScale ConferenceでFacebookがはじめて発表したJavaScript用の静的型チェッカーです。JavaScriptコードの型エラーを見つけるのを目的として考案され、実際のコードを変更する必要があまりなく、プログラマーの労力をほとんど使いません。同時に、JavaScriptに構文を追加し、開発者がより簡単に型を管理できるようにもできます。

この記事では、Flowと主な特徴を紹介します。セットアップ方法、コードに型アノテーションを追加する方法、追加したアノテーションをコードを実行するときに自動的に取り除く方法について説明します。

インストール

現在、FlowはOS X、Linux(64ビット)、Windows(64ビット)で動きます。もっとも簡単なインストール方法はnpmを使う方法です。

npm install --save-dev flow-bin

scriptsセクションで、プロジェクトのpackage.jsonファイルにFlowを加えます。

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

準備ができたので、Flowの特徴を説明します。

はじめに

プロジェクトフォルダーのルートに.flowconfigという名前のコンフィグレーションファイルが必要です。 次のコマンドを実行して、空のコンフィグファイルを作ります。

npm run flow init

コンフィグファイルができて、ターミナルから次のコマンドを実行すると、とりあえずプロジェクトフォルダーやサブフォルダーにあるコードをチェックできます。

npm run flow check

しかし、この方法は、毎回Flowがプロジェクト全体のファイル構造をチェックするので、あまり効率的な方法ではありません。代わりの方法として、Flowサーバーを使います。

Flowサーバーはファイルの増分チェック、すなわち変更があった部分だけをチェックします。サーバーは、ターミナルからコマンドnpm run flowで実行できます。

最初にこのコマンドを実行したとき、サーバーが起動して初回のテスト結果を表示します。こうしておけば、その後の増分を速くチェックできるワークフローが作れます。テスト結果が知りたいときは、ターミナルからflowを実行します。 コーディングが終わってサーバーを止めるにはnpm run flow stopとします。

Flowの型チェックはオプトインです。つまり、一度にすべてのコードをチェックする必要はないということです。チェックしたいファイルを選べば、あとはFlowがチェックします。ファイルを選ぶには、FlowでチェックしたいJavaScriptファイルの先頭に@flowをコメントとして加えるだけです。

/*@flow*/

この方法は、チェックしたいファイルを1つ1つ選びながらエラーを解決できるので、既存のプロジェクトにFlowを統合するときにとても便利です。

型推論

通常、型チェックは次の2つの方法を使います。

  • アノテーションを使う方法:予測する型をコードの一部として指定し、型チェッカーがその予測に基づいてコードを評価する
  • コード推論による方法:ツールが変数が使われている文脈から型を賢く推論し、推論に基づいてコードをチェックする

アノテーションを使った方法は、開発中にしか役に立たない追加のコードを書かなければなりません。そのうえ、追加したコードはブラウザーに読み込まれる最終版のJavaScriptから取り除かなければなりません。型アノテーションを加えてコードをチェックするので、前もって余計な作業が少し増えます。

2番目のコード推論による方法は、コードの修正なしでテストの準備ができます。したがって、プログラマーの労力は最小化できます。コードのデータ型を自動で推測するので、コーディングを変える必要はありません。これは型推論と呼ばれ、Flowのもっとも重要な特徴です。

下のコードを例として、この特徴について説明します。

/*@flow*/

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

foo(34);

このコードは、npm run flowコマンドを実行するとターミナルでエラーを表示します。なぜならfoo()関数は文字列を引数とするのに、数字を引数として渡してしまったからです。

エラーは次のように表示されます。

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

エラーの場所と原因がはっきり示されています。引数を数字から文字列に変更すると、次のスニペットに示すようにエラーはなくなります。

/*@flow*/

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

foo('Hello World!');

先に述べたように、上のコードにはエラーがありません。ここで分かるのは、split()メソッドはstringにのみ使えることをFlowが理解し、xstringだと推論したということです。

Null許容型

ほかの型システムと比較してFlowはnullを異なった方法で取り扱います。 Flowはnullを無視しないので、別の正当な型の代わりにnullが渡されてアプリケーションをクラッシュさせるエラーを防ぎます。

次のコードを例とします。

/*@flow*/

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

var length = stringLength(null);

上の場合、Flowはエラーを表示します。解決するには、下に示すようにnullを別に扱わなければなりません。

/*@flow*/

function stringLength (str) {
  if (str !== null) {
    return str.length;
  }

  return 0;
}

var length = stringLength(null);

nullのチェック方法を導入して、常にコードが正常に動作するように担保します。 Flowはこの最後のスニペットが有効なコードだと判断します。

型アノテーション

上で述べたように、型推論はFlowのもっとも優れた特徴です。なぜなら、型アノテーションを書く必要なしに有用なフィードバックを得られるからです。しかし、コードにアノテーションを加えると、より詳細なチェックが可能となり、あいまいさを排除できることもあります。

以下のコードで説明します。

/*@flow*/

function foo(x, y){
  return x + y;
}

foo('Hello', 42);

Flowは上のコードにエラーがないと判断します。なぜなら+(プラス)演算子は文字列と数字に使用可能で、add()のパラメーターは数字に限るという指定がないからです。

この場合、望ましい動作を指定するのに型アノテーションが使えます。型アノテーションは:(コロン)を接頭辞とし、関数パラメーター、戻り値型、変数宣言に使えます。

上のコードに型アノテーションを加えると下のようになります。

/*@flow*/

function foo(x : number, y : number) : number {
  return x + y;
}

foo('Hello', 42);

このコードはエラーを表示します。なぜなら、数字を引数とする関数に文字列を渡しているからです。

ターミナルに表示されたエラーは次のようになります。

index.js:7
7: foo('Hello', 42);
^^^^^^^ string. This type is incompatible with the expected param type of
3: function foo(x : number, y : number) : number{
^^^^^^ number

'Hello'の代わりに数字を渡すとエラーはなくなります。大規模で複雑なJavaScriptファイルでは、望ましい動作を指定するために型アノテーションが便利です。

先の例を念頭に、Flowがサポートするいろいろなアノテーションを説明します。

関数

/*@flow*/

/*--------- Type annotating a function --------*/
function add(x : number, y : number) : number {
  return x + y;
}

add(3, 4);

上のコードは変数と関数のアノテーションを示しています。add()関数の引数は、戻り値と同様、数字であると推測されます。それ以外のデータ型を渡すとFlowはエラーを返します。

配列

/*-------- Type annotating an array ----------*/
var foo : Array<number> = [1,2,3];

配列のアノテーションはArray<T>という形式で、Tは配列の独立した要素のデータ型を示します。上のコードではfooは要素が数字でなければならない配列です。

クラス

クラスとオブジェクトのスキーマの例を下に示します。1つだけ気をつけることは、|記号を使って2つの型の間でOR演算ができることです。変数bar1Barクラスのスキーマに関してアノテートされています。

/*-------- Type annotating a Class ---------*/
class Bar{
  x:string;           // x should be string       
  y:string | number;  // y can be either a string or a number
  constructor(x,y){
    this.x=x;
    this.y=y;
  }
}

var bar1 : Bar = new Bar("hello",4);

オブジェクトリテラル

クラスと同じようにオブジェクトリテラルも、オブジェクトのプロパティの型を指定してアノテートできます。

/*--------- Type annonating an object ---------*/

var obj : {a : string, b : number, c: Array<string>, d : Bar} = {
  a : "hello",
  b : 42,
  c : ["hello", "world"],
  d : new Bar("hello",3)
}

Null

下に示すようにTの代わりに?Tと書くことで、どの型のTnull/undefinedを組み込めるようにできます。

/*@flow*/

var foo : ?string = null;

この場合、fooは文字列またはnullとなります。

Flowの型アノテーション方法について、ほんの少しだけ説明しました。これらの基本的な型が使いやすいと感じたら、 FlowのWebサイトにある 型マニュアルの参照をすすめます。

ライブラリーの定義

サードパーティのライブラリーのメソッドを使わなければならないことがよくあります。この場合、Flowはエラーを表示しますが、自分のコードのエラーチェックに集中できなくなるので、望ましくありません。

幸い、これらのエラーを防ぐためにライブラリーのコードを触る必要はありません。そのかわり、ライブラリー定義(libdef)を作ります。 libdefはサードパーティのコードが提供する関数やメソッドの宣言が書かれたJavaScriptファイルのすばらしい呼び名です。

どのようなことかをよく理解するために、例で説明します。

/* @flow */

var users = [
  { name: 'John', designation: 'developer' },
  { name: 'Doe', designation: 'designer' }
];

function getDeveloper() {
  return _.findWhere(users, {designation: 'developer'});
}

上のコードは次のエラーを表示します。

interfaces/app.js:9
9: return _.findWhere(users, {designation: 'developer'});
^ identifier `_`. Could not resolve name

エラーはFlowが_変数についてなにも関知していないのが原因で起きます。解決するにはアンダースコアをlibdefで定義しなければなりません。

flow-typedを使う

ありがたいことに、人気のある多くのサードパーティライブラリーのために、libdefファイルを含んだflow-typedと呼ばれるレポジトリがあります。これを使うには、関係する定義をプロジェクトのルートの中にあるflow-typedフォルダーにダウンロードするだけです。

手順をもっと効率化するのに、libdefファイルをダウンロードしてインストールするコマンドラインツールがあります。npm経由でインストールします。

npm install -g flow-typed

インストールしたら、flow-typed installを実行してプロジェクトのpackage.jsonファイルを検査し、見つかった依存関係のあるlibdefをダウンロードします。

自分のlibdefを作る

flow-typedレポジトリに自分が使っているライブラリーのlibdefが見つからない場合、自分で作ることもできます。しょっちゅうすることではないので、記事では詳細に説明しませんが、もし興味があればドキュメントを参照してください。

型アノテーションを取り除く

型アノテーションはJavaScript文法の正当な文法ではないため、ブラウザーで実行する前にコードから取り除かなければなりません。the flow-remove-types toolを使うか、トランスパイルにBabelを使っている場合はBabel presetが使えます。記事では最初の方法についてだけ説明します。

最初に、プロジェクトの依存関係としてflow-remove-typesをインストールする必要があります。

npm install --save-dev flow-remove-types

次に、package.jsonファイルにもう1つscriptエントリーを加えます。

"scripts": {
  "flow": "flow",
  "build": "flow-remove-types src/ -D dest/",
}

このコマンドはsrcフォルダーのファイルから型アノテーションをすべて除去し、distフォルダーにコンパイルしたバージョンを保存します。コンパイルされたファイルはほかのJavaScriptファイルと同じようにブラウザーにロードできます。

ビルドプロセスの一環としてアノテーションを除去するプラグインがいくつかのモジュールバンドラーのために用意されています。

最後に

この記事ではFlowの型チェックのいろいろな特徴について紹介し、どのようにエラーを発見し、コードの質を向上させるかを説明しました。また、ファイルごとに「オプトイン」し、型推論をすることで簡単に使い始めることができ、コードにアノテーションを加える必要なく有用なフィードバックが得られることを解説しました。

※2017年3月30日:Flowライブラリーの変更を反映して、この記事は更新されました。

(原文:Writing Better JavaScript with Flow

[翻訳:関 宏也/編集:Livit

Copyright © 2017, Nilson Jacques All Rights Reserved.

Nilson Jacques

Nilson Jacques

フルスタックのWebディベロッパーで10年間コンピューターとWebに携わっています。以前はハードウェアの技術者とネットワークの管理者でしたが、現在は、建設業界のWebやアプリケーションを開発する会社の共同創業者兼開発者です。また、SitePointフォーラムのメンターとしても活動しています。

Loading...