大規模改修の裏でTypeScriptとテスト導入をすすめた話

 大規模改修の裏でTypeScriptとテスト導入をすすめた話

4c060f902c6d1baa80466a5931eda477?s=128

Amon Keishima

June 05, 2020
Tweet

Transcript

  1. ⼤規模改修の裏で TypeScriptとテスト導⼊をすすめた話 LINE Growth Technology株式会社 UITチーム けいしま あもん

  2. ⾃⼰紹介 名前 Twitter @pittanko_pta 所属 LINE Growth Technology株式会社 UITチーム (けいしま

    あもん) 慶島亜⾨
  3. 今⽇お話すること 担当しているLINEポイントクラブで メインタスクの合間をぬってTypeScriptやテストの導⼊をしている話 現状 作戦 やったこと 知⾒やハマり の順でお話していく

  4. LINEポイントクラブとは 過去6ヶ⽉間に獲得した「LINEポイント」の総量に応じて決まる 4段階の「マイランク」ごとに、毎⽉LINEの各種サービスが お得に利⽤できる、様々な特典を受けられるプログラム 「LINEポイント」は 「LINE」の各種サービスの利⽤や、LINE公式アカウントの 友だち追加、動画視聴などで貯めることができる 前⾝はLINEフリーコインと呼ばれるもので LINEの中でも⻑く続いているサービスのひとつ ・実はWebベース

    ・Vue.jsで作られている
  5. 現状

  6. プロジェクトが抱えていた問題点 2013年にスタートしたサービスであるため… テストコードが書かれていない  ・Karmaが導⼊だけされているものの、テストコードがない  ・しかし、リリースの度にQAを⾏うので品質は担保できていた いろんな環境が古い  ・Webpackのバージョンが古い  ・Babelのバージョンが古い サブプロジェクトとメインプロジェクトのビルド環境が違う  ・Browserifyでビルドしている(gruntも使ってた)

     ・module.exports / requireを使っており import / export ではない QA: Quality Assuranceのこと
  7. ⻑期的にその問題点を考える 今まで⻑くやってきたプロジェクトなので仕⽅ない⾯もありますが 今後も⻑く続いていくだろうことを考えると… 新しく⼊ってくるメンバーが古い技術を勉強することよりも モダンな技術の知識で 即戦⼒になれるような環境 を整備するべきと考えた

  8. 問題点を解決するために… ・TypeScriptを導⼊すること ・テストを本格的に導⼊すること の2点はマストだと考えた

  9. TypeScriptへのモチベーション コードに型がつくことで潜在的なミスを減らすことができる ・ビルドをする段階でミスに気づけるので、サービスの品質向上につながる ・コードレビューの負荷を軽減(しょうもないミスは事前に解決できそう)

  10. テスト導⼊へのモチベーション 今後やりたいコードのリファクタリングを安⼼して⾏いたい ⼩さいバグを減らすことができれば、QAのコストも下がるかも

  11. 作戦

  12. 短期・中⻑期的な話 古いプロジェクトで機能追加案件も並⾏する場合 TSを導⼊して、テストを導⼊するのは時間がかかってしまうもの その中で早期に環境を整え、運⽤に組み込み基盤を作るのがプロジェクトでの最⼤の課題 そのために必要なのは、中⻑期を⾒据えた上での作戦(計画)を⽴てることだと思う 短期でTS運⽤基盤を構築することと、 中⻑期でのプロジェクトに適応させていくことが⼤きな鍵といえる 今回はまず、短期でのTS運⽤基盤の構築に注⼒した

  13. 作戦 TypeScriptの環境を導⼊ / Vue.js の TypeScript対応 Jestの導⼊ / Babelのアップデート /

    Webpackのアップデート 脱Browserify / 脱grunt など… やりたいこと(やらないといけないこと)はたくさんあるが、 ⼀気にいろいろ変えてしまうとコードの差分が⼤きくなってしまう 動作を確認するにも時間かかるし レビュワーの負担も⼤きくなってしまう 今回の作戦は なるべく⼩さく⼊れること とした
  14. (余談)社内の他案件の状況や傾向 LINEとしては2017年の上半期からTypeScriptの対応をリードエンジニアが始めている 新規に開発するプロジェクトではTypeScriptの採⽤に積極的な⼀⽅で 古くから存在するプロジェクトでは導⼊が遅れているようなものもある TypeScript導⼊済みのプロジェクトを⾒てみると… 社内サービスやSDKといった 社内で閉じたもの や ユーザーが直接⽬にしないもの であることが多い

    ユーザーが直接に⽬にするものついては…
  15. やったこと

  16. まずはTypeScriptをコンパイルできるように ・既存のjsファイルのビルド結果を変えないように ・JavaScript → TypeScript に変換したものや  新規で書いた TypeScript のコードだけが正しくトランスパイルできればOK ・ビルド時間の増加について⼀旦スルー

    ・Vueのファイルについても⼀旦考えない 以下の⽅針でまずははじめてみる
  17. まずはTypeScriptをコンパイルできるように 1. TypeScriptのビルド環境を整えつつ、1つだけ⼩さめのファイルをTS化した  ・ビルド⽣成物を⾒て、そのファイル以外の差分がないことを確認した 2. tsconfig の allowJS オプションを有効にした  ・TS→JSの参照の場合、jsDocがちゃんと書かれていれば

    型チェックをしてくれる /** * @param {Number} id */ export function getArticle(id) { // 略 } import { getArticle } from 'foo' export function doSomething(id: string) { const article = getArticle(id) } foo.js bar.ts
  18. まずはTypeScriptをコンパイルできるように 3. tsconfig の target を es2019 にした  → ts-loader→babel-loaderを通すときに、tsでトランスパイルをしてほしくなかった

     → spread-arrays や rest-spread は、今まで通りbabelでpolyfillを⼊れてほしい       → tsのトランスパイルで差し込まれるpolyfillで挙動が変わるかもしれない      みたいな⼼配をしたくなかった // spread-arrays const nums = [ 1, 2 ]; const newNums = [ 0, ...nums ]; console.log(newNums); // [ 0, 1, 2 ]; // rest-spread let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; console.log(z); // { a: 3, b: 4 }
  19. VueのSFCファイルをTS対応にする ・Vueの型定義を追加 ・まずはVue.extendを使う形に書き換えてみた(1ファイルだけ)  → 差分が出るので、ここは⾃分で動作確認。問題ないことを確認 declare module '*.vue' { import

    Vue from 'vue'; export default Vue; } export default Vue.extend({ props: { propA: { type: Number } } }) export default { props: { propA: { type: Number } } }
  20. VueのSFCファイルをTS対応にする import { Vue, Component, Prop } from 'vue-property-decorator' @Component

    export default class FooComponent extends Vue { @Prop(Number) readonly propA: number | undefined } export default { props: { propA: { type: Number } } } "vue-class-decorator" や "vue-class-component" があるが、導⼊を⾒送った ・書き換え量が多いこと ・Vue3のRFCではrejectされていること  → これは特に⻑く続いているプロジェクトとして気にした    また⼤規模に書き換える必要がありそうなら、ここでやることはないと判断 Example: vue-class-component
  21. ビルド時間の短縮 TypeScriptを導⼊してから、ビルドに倍以上の時間がかかるように "fork-ts-checker-webpack-plugin" を導⼊して、型チェックのプロセスを分離した Before / 117s After / 41s

    ts-loader fork-ts... ts-loader ts js ts js type-check & transpile type-check transpile
  22. ビルド環境のシンプル化 ts-loaderでJavaScript / TypeScript 両⽅ビルドするようにした  → 数秒ビルド時間が伸びる程度・環境のシンプルさを優先 module.exports = {

    module: { rules: [ { test: /\.js$/, use: babelLoader, }, { test: /\.ts$/, loader: [ babelLoader, tsLoader ], } ] } } module.exports = { module: { rules: [ { test: /\.(js|ts)x?$/, loader: [ babelLoader, tsLoader ], } ] } } Before / 41s After / 45s
  23. テストの導⼊ QAをしてもらえるとはいえ、しょうもないエラーを出すのは申し訳ない ⼀定のクオリティーを担保するためにテストを導⼊した ・KarmaからJestに移⾏した  ・windowオブジェクトを使うテストが書きやすい   → location.href を変更するようなコードとか  ・jest.resetModules() が便利だった

      → コード中の変数でキャッシュしているコードなど ・新しく書くコード(特にユーテリティ系とか)を中⼼にテストを追加した
  24. 知⾒やハマり

  25. リファクタ / コード修正したい気持ちはグッとこらえる 書き換える時点で「このコードおかしくない?」とか「改善できそう!!」  など、気づく点がいっぱいあると思う ⼀緒に着⼿してしまうと ・レビューのコストが上がる ・ts化とコード改善を⼀緒になると、問題が起きたときの切り分けしづらくなる みたいな問題があるので、⼼を⻤にしてコード⾃体の改善はしないようにした コード中にコメントを埋め込んだり、PRにメモを残すようにして

    別のコミットやPRとしてコードの改善は⾏った
  26. TODO<T>: any で "攻めながらanyを使う" js → ts に拡張⼦を変えただけでビルドが通らなくなるケースはあるはず そんなときにTODO型を使うと ・とりあえずビルドを通すことができる

    ・やるべき型をメモしておけるので、anyより便利 // 内部的にはany でエラーを回避しつつ、本来当てたい型をメモしておける const foo: TODO<Article> = getArticles();   const foo = getArticles(); // なんかエラー出るんだけど!!! ↓
  27. Vueの型解決エラーはComputedの返り値を書け ・VueのSFCをTS化する最中に、どうしても  this.hogehoge の型が解決できないときがある ・こういうときはcomputedの返り値を明⽰的に書くことでほとんど解決した computed: { getOpacity(): number {

    return this.isActive ? 1 : 0; } } 注釈: 循環参照の都合上こうなってしまうようです。詳しくは公式ドキュメント参照 → https://jp.vuejs.org/v2/guide/typescript.html#戻り値の型にアノテーションをつける
  28. まとめ

  29. まとめ ・TypeScriptは段階的に導⼊することができる ・いきなり全部TSに書き換える必要はない ・可能なら⼀緒にユニットテストも書くとより安⼼かも ・プロジェクトに⼊ったばかりだからこそ、⾔える/思うようなこともある  ・⾃分が困ったことは、今後新しくプロジェクトに⼊るメンバーも困るはず  ・昔の技術を勉強するより、持っている最新の技術スタックで    即戦⼒になれるほうが双⽅ハッピーなはず

  30. お知らせ LINEやLINE Growth Technologyで働くことに興味のある⽅向けに 社員と直接情報交換ができるカジュアル⾯談を実施しています 興味のある⽅は、「LINE Developer meetup」で検索をお願いします!

  31. ありがとうございました!