Upgrade to Pro — share decks privately, control downloads, hide ads and more …

flowで始める型のあるJavaScript

 flowで始める型のあるJavaScript

PHPカンファレンス福岡2017で発表しました。

Sota Sugiura

June 10, 2017
Tweet

More Decks by Sota Sugiura

Other Decks in Technology

Transcript

  1. flowで始める
    型のあるJavaScript
    @sota1235
    2017/6/10
    PHPカンファレンス福岡2017

    View Slide

  2. console.log(me);
    • Sota Sugiura
    • @sota1235
    • JavaScript PHP
    • Mercari, Inc.

    View Slide

  3. Why using JavaScript?

    View Slide

  4. Why?
    • 私たちはPHPエンジニア
    • Webと関わる⼈人も多い
    • Webは⽇日々進化を続けている

    View Slide

  5. 最古のWebサイト
    • 1990年年11⽉月
    • ペライチのHTML
    • ⽂文章配布のためのWeb

    View Slide

  6. 最新のWebサイト
    • インタラクティブなUI
    • ⾼高頻度のAjax通信
    • リッチなブラウザアプリケーション

    View Slide

  7. 最新のWebサイト

    View Slide

  8. 最新のWebサイト
    Web Audio API

    View Slide

  9. 最新のWebサイト
    Web Audio API

    View Slide

  10. 最新のWebサイト
    Web Audio API

    View Slide

  11. 最新のWebサイト
    Web Audio API
    .PSF
    .PSF

    View Slide

  12. 最新のWebサイト
    Web Audio API
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF

    View Slide

  13. 最新のWebサイト
    Web Audio API
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF
    .PSF

    View Slide

  14. 最新のWebサイト
    • WebVR
    • WebGL
    • Web Audio API
    • Single Page Application
    • WebRTC (P2P)
    • PWA
    • and more, more, more…

    View Slide

  15. どう実現するのか?
    • HTML, CSSでは役不不⾜足
    • そもそも作られた⽬目的が違う

    View Slide

  16. どう実現するのか?
    • HTML, CSSでは役不不⾜足
    • そもそも作られた⽬目的が違う
    • JavaScriptこそが鍵

    View Slide

  17. With JavaScript
    • 数々のブラウザのAPI
    • インタラクティブなUI
    • ⾮非同期なHTTP通信
    • デバイス(カメラ、マイク、etc)へのアクセス

    View Slide

  18. Not only on Website
    • Smartphone App (React Native)
    • Server Side JavaScript (Node.js)

    View Slide

  19. Why using JavaScript?
    • ⽂文書配布のためのWebはとっくのとうに終
    わってる
    • ちょっとリッチなWebサイトって時代ももう
    終わってる
    • JavaScriptは多様な夢を実現する⼿手段として地
    位を確⽴立しつつある

    View Slide

  20. JavaScript無くして
    次世代のWebは来ない

    View Slide

  21. But…
    • 歴史的経緯に基づくツライ⾔言語仕様
    • あまりに多い実⾏行行環境の多さ
    • 終わらないベンダー戦争
    • 速すぎるとも⾔言える進化と成⻑⾧長

    View Slide

  22. And more…
    ツラミと戦うために…

    View Slide

  23. And more…
    ツラミと戦うために…

    View Slide

  24. Today’s theme
    今⽇日の話はそんな数ある解決策のうちの1つ、flowの話

    View Slide

  25. About flow

    View Slide

  26. flow
    • JavaScriptのための静的
    型解析ツール
    • Facebook製
    • OCamlで書かれている

    View Slide

  27. flow is not AltJS
    • あくまで静的型解析ツール
    https://flow.org/en/

    View Slide

  28. Goal of flow
    Flow is a static type checker for JavaScript that we
    built at Facebook. The overall mission of Flow is to
    deliver an immersive coding experience for JavaScript
    developers—a fast edit-refresh cycle—even as the
    codebase evolves and grows. In engineering terms,
    we identify two concrete goals that are important to
    this mission: precision and speed. These goals
    pervasively drive the design and implementation.
    https://flow.org/en/docs/lang/#toc-flow-goals

    View Slide

  29. Goal of flow
    Flow is a static type checker for JavaScript that we
    built at Facebook. The overall mission of Flow is to
    deliver an immersive coding experience for JavaScript
    developers—a fast edit-refresh cycle—even as the
    codebase evolves and grows. In engineering terms,
    we identify two concrete goals that are important to
    this mission: precision and speed. These goals
    pervasively drive the design and implementation.
    https://flow.org/en/docs/lang/#toc-flow-goals

    View Slide

  30. Goal of flow
    • コードの成⻑⾧長に負けない開発スピードを保ちたい
    • flowはその⼿手助けをする
    • そのためにflowは精度と速度を重視する

    View Slide

  31. Precision / 精度
    • JavaScriptのバグは⾒見見逃してはいけない
    • でもエラーを報告しすぎるツールは
    • 必要最低限のクリティカルなバグを報告する
    • 精度をあげることで開発ツールの構築も可能
    • 例例. IDEによる型補完
    https://flow.org/en/docs/lang/#toc-precision

    View Slide

  32. Speed / 速度
    • 速度は精度と相反する
    • 速度の低下で開発サイクルを遅らせることは
    JavaScriptの魅⼒力力を損なうことに他ならない
    • コードを実⾏行行することなく精度の⾼高くバグを
    を⾒見見つけることがベスト
    https://flow.org/en/docs/lang/#toc-speed

    View Slide

  33. function add(a, b) {
    return a + b;
    }
    add("1", 2);
    Normal JavaScript

    View Slide

  34. function add(a, b) {
    return a + b;
    }
    add("1", 2);
    Normal JavaScript
    ⾔言語仕様的には問題ないが…

    View Slide

  35. Bug? Or not?
    QP%JTQOGEQPUQNG

    View Slide

  36. Bug? Or not?
    ほんとは”3”が欲しかった…
    QP%JTQOGEQPUQNG

    View Slide

  37. with flow annotation
    • サンプルコード
    // @flow
    function add(a: number, b: number): number {
    return a + b;
    }
    add("1", 2);

    View Slide

  38. with flow annotation
    • サンプルコード
    // @flow
    function add(a: number, b: number): number {
    return a + b;
    }
    add("1", 2);
    flowの解析対象の⽬目印

    View Slide

  39. with flow annotation
    • サンプルコード
    // @flow
    function add(a: number, b: number): number {
    return a + b;
    }
    add("1", 2); 引数の
    明示的型宣⾔言

    View Slide

  40. with flow annotation
    • サンプルコード
    // @flow
    function add(a: number, b: number): number {
    return a + b;
    }
    add("1", 2);
    関数の返り値の
    明示的型宣⾔言

    View Slide

  41. with flow annotation
    • サンプルコード
    // @flow
    function add(a: number, b: number): number {
    return a + b;
    }
    add("1", 2);
    Is it OK…?

    View Slide

  42. Error report by flow

    View Slide

  43. Error report by flow
    number型引数に
    stringを渡さないでね

    View Slide

  44. Work for not only annotation
    console.log("1" * 1);
    function printResponse(data) {
    console.log(data.res);
    }
    printResponse('string response');

    View Slide

  45. Work for not only annotation
    console.log("1" * 1); // Error!
    function printResponse(data) {
    console.log(data.res);
    }
    printResponse('string response');

    View Slide

  46. Work for not only annotation
    console.log("1" * 1); // Error!
    function printResponse(data) {
    console.log(data.res);
    }
    printResponse('string response'); // Error!

    View Slide

  47. サポートする機能
    • 関数の引数、返り値、変数の明示的型宣⾔言
    • Interface
    • Generic Type
    • Comment Type
    • and so on :)

    View Slide

  48. How to use?
    • flow-binをインストール
    • flow initしてconfig⽣生成
    • 解析対象ファイルに // @flow
    • flowコマンド実⾏行行

    View Slide

  49. スモールスタートなら5分で使える

    View Slide

  50. When should we use flow?

    View Slide

  51. こんなことで困ってませんか?
    • コードベースが巨⼤大
    • テスト不不能
    • でも負債返済の時間は⼗十分にない

    View Slide

  52. こんなことで困ってませんか?
    • コードベースが巨⼤大
    • テスト不不能
    • でも負債返済の時間は⼗十分にない
    flowはこういう場⾯面で
    威⼒力力を発揮できる

    View Slide

  53. flowのいいところ(1) 導⼊入が簡単
    • 解析するだけなら何もいらない
    • 必要なのはflowコマンドとJSファイルだけ
    • babelが導⼊入済みであればannotationも瞬時に
    導⼊入可能
    • 1ファイルずつ導⼊入できる

    View Slide

  54. flowのいいところ(2) 捨てやすさ
    • flowはランタイム、コンパイルに関与しない
    • 捨てるときはflowコマンドの実⾏行行をやめるだけ
    • アノテーションを消したければflow-remove-
    typesを使う
    • https://flow.org/en/docs/tools/flow-remove-types/

    View Slide

  55. flowが有効でない場⾯面
    • ⼩小規模プロジェクトの場合
    • 本当に型必要?
    • 1ファイルが巨⼤大過ぎる場合
    • 新規プロジェクトの場合
    • 有効だがTypeScriptの使⽤用も視野に

    View Slide

  56. Let’s use flow

    View Slide

  57. 実際にflowを使ってみよう
    • サンプルコードにflowを導⼊入していく
    • 今回はシンプルに値を渡すとObjectを返す関
    数をいじっていく

    View Slide

  58. Sample code
    function makeMemo(title, text) {
    const memo = {
    title: title,
    text: text,
    };
    return memo;
    }

    View Slide

  59. 復復習(1) flowの始め⽅方
    • flowは全てのファイルを解析しない
    • // @flowが書いてあるファイルのみ解析する

    View Slide

  60. Start flow
    // @flow
    function makeMemo(title, text) {
    const memo = {
    title: title,
    text: text,
    };
    return memo;
    }

    View Slide

  61. Start flow
    // @flow
    function makeMemo(title, text) {
    const memo = {
    title: title,
    text: text,
    };
    return memo;
    }
    flow導⼊入の
    第⼀一歩

    View Slide

  62. 復復習(2) 明示的型宣⾔言
    • flowでは引数、返り値の明示的型宣⾔言ができる
    • nullable型もサポートしている

    View Slide

  63. Declare type
    // @flow
    function makeMemo(title: string, text: ?string): Object {
    const memo = {
    title: title,
    text: text,
    };
    return memo;
    }

    View Slide

  64. Declare type
    // @flow
    function makeMemo(title: string, text: ?string): Object {
    const memo = {
    title: title,
    text: text,
    };
    return memo;
    }
    引数の型宣⾔言

    View Slide

  65. Declare type
    // @flow
    function makeMemo(title: string, text: ?string): Object {
    const memo = {
    title: title,
    text: text,
    };
    return memo;
    }
    引数の型宣⾔言
    返り値の型宣⾔言

    View Slide

  66. 天の声 その1
    • ここで仕様変更更
    • 上司「memoの本⽂文の頭にタイトルをくっつけてくれ」

    View Slide

  67. Fix
    // @flow
    function makeMemo(title: string, text: ?string): Object {
    const memo = {
    title: title,
    text: `${title}\n${text}`,
    };
    return memo;
    }
    textの⼿手前に改⾏行行を挟んで
    タイトルを挿⼊入

    View Slide

  68. Error occurred

    View Slide

  69. Error occurred
    nullを⽂文字列列連結するな
    undefinedを⽂文字列列連結するな

    View Slide

  70. 修正ポイントを探す
    // @flow
    function makeMemo(title: string, text: ?string): Object {
    const memo = {
    title: title,
    text: `${title}\n${text}`,
    };
    return memo;
    }
    Oh, text is nullable!

    View Slide

  71. Check argument
    // @flow
    function makeMemo(title: string, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    };
    return memo;
    }
    textがnull, undefinedで
    ないことをチェックする

    View Slide

  72. Works!

    View Slide

  73. 天の声 その2
    • ここでまたまた仕様変更更
    • 上司「memoの公開/⾮非公開/下書きステータスを持たせて
    くれ」

    View Slide

  74. 仕様検討
    • オブジェクトにstatusキーを追加する
    • statusにはpublic, private, draftのいずれかが⼊入る

    View Slide

  75. Add argument
    // @flow
    function makeMemo(title: string, status: string, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    引数追加
    オブジェクトに反映

    View Slide

  76. Add argument
    // @flow
    function makeMemo(title: string, status: string, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    しかしここに意図しない⽂文字
    列列が⼊入っても気がつけない…

    View Slide

  77. あーーーー
    特定の⽂文字列列しか受け取らなくて
    意図しない値が来たらエラーでコケるような
    素敵な仕組みないかなーーーーーーーー

    View Slide

  78. ENUM

    View Slide

  79. flowでENUM
    • flowでは固定値を型として宣⾔言できる
    • また、複数の型をOR形式で宣⾔言できる
    • これを組み合わせるとENUMができる

    View Slide

  80. Original type
    // @flow
    type Fukuoka = 'mentaiko';

    View Slide

  81. Original type
    // @flow
    type Fukuoka = 'mentaiko';
    type syntaxで
    新たな型を定義できる

    View Slide

  82. Original type
    // @flow
    type Fukuoka = 'mentaiko';
    const omiyage: Fukuoka = 'mentaiko';
    const nisemono: Fukuoka = 'takoyaki';

    View Slide

  83. Original type
    // @flow
    type Fukuoka = 'mentaiko';
    const omiyage: Fukuoka = 'mentaiko'; // Works!
    const nisemono: Fukuoka = 'takoyaki'; // Error

    View Slide

  84. ENUM
    // @flow
    // Use or statement for type
    type MemoStatus = 'public' | 'private' | 'draft';

    View Slide

  85. ENUM
    // @flow
    // Use or statement for type
    type MemoStatus = 'public' | 'private' | 'draft';
    どれか1つに当てはまれば
    MemoStatusとして識別される

    View Slide

  86. Use ENUM
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    function makeMemo(title: string, status: MemoStatus, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }

    View Slide

  87. Use ENUM
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    function makeMemo(title: string, status: MemoStatus, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    期待する値の型を宣⾔言

    View Slide

  88. Use ENUM
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    function makeMemo(title: string, status: MemoStatus, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    期待する値の型を宣⾔言
    引数に型宣⾔言

    View Slide

  89. 天の声 その3
    • なんとユーザからのバグ報告
    • 上司「別のメソッドが返すオブジェクトと構造がバラバ
    ラだからなんとかして」

    View Slide

  90. コードとにらめっこ
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    function makeMemo(title: string, status: MemoStatus, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }

    View Slide

  91. コードとにらめっこ
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    function makeMemo(title: string, status: MemoStatus, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    ここの型が曖昧すぎる

    View Slide

  92. Object type
    • Objectの構造を型として定義できる
    • キーをnullableとして定義できる
    • 余計なキーが⼊入らないように定義することも
    可能

    View Slide

  93. Object type
    // @flow
    type Item = {
    id: number,
    name: string,
    description?: string,
    }

    View Slide

  94. Object type
    // @flow
    type Item = {
    id: number,
    name: string,
    description?: string,
    }
    const validItem: Item = {
    id: 32,
    name: 'item_name',
    }; // Works
    validItem.description = 'description'; // Works

    View Slide

  95. Object type
    // @flow
    type Item = {
    id: number,
    name: string,
    description?: string,
    }
    const validItem: Item = {
    id: 32,
    name: 'item_name',
    }; // Works
    validItem.description = 'description'; // Works
    validItem.id = '32'; // Error

    View Slide

  96. Object type
    // @flow
    type Item = {
    id: number,
    name: string,
    description?: string,
    }
    const validItem: Item = {
    id: 32,
    name: 'item_name',
    }; // Works
    validItem.description = 'description'; // Works
    validItem.id = '32'; // Error
    型違反

    View Slide

  97. Don’t allow extra key
    // @flow
    type Item = {|
    id: number,
    name: string,
    |}

    View Slide

  98. Don’t allow extra key
    // @flow
    type Item = {|
    id: number,
    name: string,
    |}
    定義していないキーを
    許容しない

    View Slide

  99. Don’t allow extra key
    // @flow
    type Item = {|
    id: number,
    name: string,
    |}
    const validItem: Item = {
    id: 32,
    name: 'item_name',
    extraProp: 'extra!',
    };

    View Slide

  100. Don’t allow extra key
    // @flow
    type Item = {|
    id: number,
    name: string,
    |}
    const validItem: Item = {
    id: 32,
    name: 'item_name',
    extraProp: 'extra!', // Error
    };
    型宣⾔言していないキーが
    エラーになる

    View Slide

  101. It’s time to fix!
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    function makeMemo(title: string, status: MemoStatus, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    このObject,
    型として宣⾔言できそう

    View Slide

  102. It’s time to fix!
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    function makeMemo(title: string, status: MemoStatus, text: ?string): Object {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    // @flow
    type Memo = {|
    title: string,
    text: ?string,
    status: MemoStatus,
    |}

    View Slide

  103. It’s time to fix!
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    type Memo = {|
    title: string,
    text: ?string,
    status: MemoStatus,
    |}
    function makeMemo(title: string, status: MemoStatus, text: ?string): Memo {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo: Memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }

    View Slide

  104. It’s flow!
    • 型を宣⾔言することでコードが⾃自然と型安全に
    なっていく
    • ⼯工夫次第で堅牢で保守性の⾼高いJavaScriptを書
    くことが可能

    View Slide

  105. Without flow
    function makeMemo(title, status, text) {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }

    View Slide

  106. Without flow
    function makeMemo(title, status, text) {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    ҙਤ͠ͳ͍ܕɺ஋͕
    ౉͞Εͯ΋ؾ͚ͮͳ͍

    View Slide

  107. Without flow
    function makeMemo(title, status, text) {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    ҙਤ͠ͳ͍ܕɺ஋͕
    ౉͞Εͯ΋ؾ͚ͮͳ͍
    0CKFDUͷߏ଄͕
    มΘͬͯ΋࡯஌Ͱ͖ͳ͍

    View Slide

  108. With flow
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    type Memo = {|
    title: string,
    text: ?string,
    status: MemoStatus,
    |}
    function makeMemo(title: string, status: MemoStatus, text: ?string): Memo {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo: Memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }

    View Slide

  109. With flow
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    type Memo = {|
    title: string,
    text: ?string,
    status: MemoStatus,
    |}
    function makeMemo(title: string, status: MemoStatus, text: ?string): Memo {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo: Memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    ҙਤ͠ͳ͍ܕɺ஋͕
    ౉͞ΕͨΒqPX͕ڭ͑ͯ͘ΕΔ

    View Slide

  110. With flow
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    type Memo = {|
    title: string,
    text: ?string,
    status: MemoStatus,
    |}
    function makeMemo(title: string, status: MemoStatus, text: ?string): Memo {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo: Memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    ҙਤ͠ͳ͍ܕɺ஋͕
    ౉͞ΕͨΒqPX͕ڭ͑ͯ͘ΕΔ
    ಠࣗఆٛͷܕએݴʹΑΓ
    0CKFDUͷߏ଄͕໌֬ʹ

    View Slide

  111. With flow
    // @flow
    type MemoStatus = 'public'|'private'|'draft';
    type Memo = {|
    title: string,
    text: ?string,
    status: MemoStatus,
    |}
    function makeMemo(title: string, status: MemoStatus, text: ?string): Memo {
    let content = text;
    if (text) {
    content = `${title}\n${text}`;
    }
    const memo: Memo = {
    title: title,
    text: content,
    status: status,
    };
    return memo;
    }
    ҙਤ͠ͳ͍ܕɺ஋͕
    ౉͞ΕͨΒqPX͕ڭ͑ͯ͘ΕΔ
    ಠࣗఆٛͷܕએݴʹΑΓ
    0CKFDUͷߏ଄͕໌֬ʹ
    ಠࣗͷܕఆٛͰ
    ΑΓݎ࿚ͳίʔσΟϯάΛ

    View Slide

  112. more and more
    • 今回話したのはflowのほんのほんの⼀一部
    • ⼯工夫と発想次第でより表現豊かなコーディング
    ができる
    • 特にObject型の定義はJavaScriptと相性が抜群

    View Slide

  113. 終わりに

    View Slide

  114. 終わりに
    • flowは静的型解析ツール
    • ゆえに既存コードへの反映が⾮非常に簡単
    • 1ファイルずつ導⼊入ができる、かつ捨てるのも
    ⼀一瞬
    • 型プログラミングで堅牢で型安全なJavaScript
    を実現できる

    View Slide

  115. Thank you!

    View Slide