Pro Yearly is on sale from $80 to $50! »

flowで始める型のあるJavaScript

 flowで始める型のあるJavaScript

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

046baac588d91fd78a85b189847a151d?s=128

Sota Sugiura

June 10, 2017
Tweet

Transcript

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

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

    Mercari, Inc.
  3. Why using JavaScript?

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

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

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

  7. 最新のWebサイト

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

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

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

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

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

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

    .PSF .PSF .PSF .PSF .PSF .PSF .PSF
  14. 最新のWebサイト • WebVR • WebGL • Web Audio API •

    Single Page Application • WebRTC (P2P) • PWA • and more, more, more…
  15. どう実現するのか? • HTML, CSSでは役不不⾜足 • そもそも作られた⽬目的が違う

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

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

  18. Not only on Website • Smartphone App (React Native) •

    Server Side JavaScript (Node.js)
  19. Why using JavaScript? • ⽂文書配布のためのWebはとっくのとうに終 わってる • ちょっとリッチなWebサイトって時代ももう 終わってる •

    JavaScriptは多様な夢を実現する⼿手段として地 位を確⽴立しつつある
  20. JavaScript無くして 次世代のWebは来ない

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

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

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

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

  25. About flow

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

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

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

  31. Precision / 精度 • JavaScriptのバグは⾒見見逃してはいけない • でもエラーを報告しすぎるツールは • 必要最低限のクリティカルなバグを報告する •

    精度をあげることで開発ツールの構築も可能 • 例例. IDEによる型補完 https://flow.org/en/docs/lang/#toc-precision
  32. Speed / 速度 • 速度は精度と相反する • 速度の低下で開発サイクルを遅らせることは JavaScriptの魅⼒力力を損なうことに他ならない • コードを実⾏行行することなく精度の⾼高くバグを

    を⾒見見つけることがベスト https://flow.org/en/docs/lang/#toc-speed
  33. function add(a, b) { return a + b; } add("1",

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

    2); Normal JavaScript ⾔言語仕様的には問題ないが…
  35. Bug? Or not? QP%JTQOGEQPUQNG

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

  37. with flow annotation • サンプルコード // @flow function add(a: number,

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

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

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

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

    b: number): number { return a + b; } add("1", 2); Is it OK…?
  42. Error report by flow

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

  44. Work for not only annotation console.log("1" * 1); function printResponse(data)

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

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

    function printResponse(data) { console.log(data.res); } printResponse('string response'); // Error!
  47. サポートする機能 • 関数の引数、返り値、変数の明示的型宣⾔言 • Interface • Generic Type • Comment

    Type • and so on :)
  48. How to use? • flow-binをインストール • flow initしてconfig⽣生成 • 解析対象ファイルに

    // @flow • flowコマンド実⾏行行
  49. スモールスタートなら5分で使える

  50. When should we use flow?

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

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

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

    1ファイルずつ導⼊入できる
  54. flowのいいところ(2) 捨てやすさ • flowはランタイム、コンパイルに関与しない • 捨てるときはflowコマンドの実⾏行行をやめるだけ • アノテーションを消したければflow-remove- typesを使う •

    https://flow.org/en/docs/tools/flow-remove-types/
  55. flowが有効でない場⾯面 • ⼩小規模プロジェクトの場合 • 本当に型必要? • 1ファイルが巨⼤大過ぎる場合 • 新規プロジェクトの場合 •

    有効だがTypeScriptの使⽤用も視野に
  56. Let’s use flow

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

  58. Sample code function makeMemo(title, text) { const memo = {

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

  60. Start flow // @flow function makeMemo(title, text) { const memo

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

    = { title: title, text: text, }; return memo; } flow導⼊入の 第⼀一歩
  62. 復復習(2) 明示的型宣⾔言 • flowでは引数、返り値の明示的型宣⾔言ができる • nullable型もサポートしている

  63. Declare type // @flow function makeMemo(title: string, text: ?string): Object

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

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

    { const memo = { title: title, text: text, }; return memo; } 引数の型宣⾔言 返り値の型宣⾔言
  66. 天の声 その1 • ここで仕様変更更 • 上司「memoの本⽂文の頭にタイトルをくっつけてくれ」

  67. Fix // @flow function makeMemo(title: string, text: ?string): Object {

    const memo = { title: title, text: `${title}\n${text}`, }; return memo; } textの⼿手前に改⾏行行を挟んで タイトルを挿⼊入
  68. Error occurred

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

  70. 修正ポイントを探す // @flow function makeMemo(title: string, text: ?string): Object {

    const memo = { title: title, text: `${title}\n${text}`, }; return memo; } Oh, text is nullable!
  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で ないことをチェックする
  72. Works!

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

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

  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; } 引数追加 オブジェクトに反映
  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; } しかしここに意図しない⽂文字 列列が⼊入っても気がつけない…
  77. あーーーー 特定の⽂文字列列しか受け取らなくて 意図しない値が来たらエラーでコケるような 素敵な仕組みないかなーーーーーーーー

  78. ENUM

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

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

  81. Original type // @flow type Fukuoka = 'mentaiko'; type syntaxで

    新たな型を定義できる
  82. Original type // @flow type Fukuoka = 'mentaiko'; const omiyage:

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

    Fukuoka = 'mentaiko'; // Works! const nisemono: Fukuoka = 'takoyaki'; // Error
  84. ENUM // @flow // Use or statement for type type

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

    MemoStatus = 'public' | 'private' | 'draft'; どれか1つに当てはまれば MemoStatusとして識別される
  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; }
  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; } 期待する値の型を宣⾔言
  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; } 期待する値の型を宣⾔言 引数に型宣⾔言
  89. 天の声 その3 • なんとユーザからのバグ報告 • 上司「別のメソッドが返すオブジェクトと構造がバラバ ラだからなんとかして」

  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; }
  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; } ここの型が曖昧すぎる
  92. Object type • Objectの構造を型として定義できる • キーをnullableとして定義できる • 余計なキーが⼊入らないように定義することも 可能

  93. Object type // @flow type Item = { id: number,

    name: string, description?: string, }
  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
  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
  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 型違反
  97. Don’t allow extra key // @flow type Item = {|

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

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

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

    id: number, name: string, |} const validItem: Item = { id: 32, name: 'item_name', extraProp: 'extra!', // Error }; 型宣⾔言していないキーが エラーになる
  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, 型として宣⾔言できそう
  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, |}
  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; }
  104. It’s flow! • 型を宣⾔言することでコードが⾃自然と型安全に なっていく • ⼯工夫次第で堅牢で保守性の⾼高いJavaScriptを書 くことが可能

  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; }
  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; } ҙਤ͠ͳ͍ܕɺ஋͕ ౉͞Εͯ΋ؾ͚ͮͳ͍
  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ͷߏ଄͕ มΘͬͯ΋࡯஌Ͱ͖ͳ͍
  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; }
  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͕ڭ͑ͯ͘ΕΔ
  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ͷߏ଄͕໌֬ʹ
  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ͷߏ଄͕໌֬ʹ ಠࣗͷܕఆٛͰ ΑΓݎ࿚ͳίʔσΟϯάΛ
  112. more and more • 今回話したのはflowのほんのほんの⼀一部 • ⼯工夫と発想次第でより表現豊かなコーディング ができる • 特にObject型の定義はJavaScriptと相性が抜群

  113. 終わりに

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

    を実現できる
  115. Thank you!