Make it to make it

いろいろ作ってアウトプットするブログ

TypeScriptで使いまわしやすいコードを書く

Node.js環境上でCSVファイルを取り込んで何かしらの加工をして吐き出すプログラムを書きながら、使い回しのし易いTypeScriptの書き方を学ぶ。

セットアップ

Packages

yarn add -D @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser concurrently eslint eslint-config-prettier eslint-config-standard-with-typescript eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard nodemon prettier rimraf typescript

npm scripts

  "scripts": {
    "start:build": "tsc -w",
    "start:run": "nodemon build/index.js",
    "start": "concurrently yarn:start:*",
    "clean": "rimraf build"
  },

CSVファイル

一例としてサッカーの試合結果一覧のCSVデータを利用する。

football.csv

10/08/2018,Man United,Leicester,2,1,H,A Marriner
11/08/2018,Bournemouth,Cardiff,2,0,H,K Friend
11/08/2018,Fulham,Crystal Palace,0,2,A,M Dean
11/08/2018,Huddersfield,Chelsea,0,3,A,C Kavanagh
11/08/2018,Newcastle,Tottenham,1,2,A,M Atkinson
11/08/2018,Watford,Brighton,2,0,H,J Moss
11/08/2018,Wolves,Everton,2,2,D,C Pawson
12/08/2018,Arsenal,Man City,0,2,A,M Oliver
12/08/2018,Liverpool,West Ham,4,0,H,A Taylor
12/08/2018,Southampton,Burnley,0,0,D,G Scott
18/08/2018,Cardiff,Newcastle,0,0,D,C Pawson
18/08/2018,Chelsea,Arsenal,3,2,H,M Atkinson
18/08/2018,Everton,Southampton,2,1,H,L Mason
18/08/2018,Leicester,Wolves,2,0,H,M Dean
18/08/2018,Tottenham,Fulham,3,1,H,A Taylor
18/08/2018,West Ham,Bournemouth,1,2,A,S Attwell
19/08/2018,Brighton,Man United,3,2,H,K Friend
19/08/2018,Burnley,Watford,1,3,A,P Tierney
19/08/2018,Man City,Huddersfield,6,1,H,A Marriner
20/08/2018,Crystal Palace,Liverpool,0,2,A,M Oliver

最初のコード

マンUの試合結果を取得してくるロジックをハードコードしたもの。

src/index.ts

import * as fs from 'fs'

const matches = fs
  .readFileSync('football.csv', {
    encoding: 'utf-8',
  })
  .split('\n')
  .map((row: string): string[] => row.split(','))

let manUnitedWins = 0

for (const match of matches) {
  if (match[1] === 'Man United' && match[5] === 'H') {
    manUnitedWins++
  } else if (match[2] === 'Man United' && match[5] === 'A') {
    manUnitedWins++
  }
}

console.log(`Man United won ${manUnitedWins} games`)

リファクタ1

enumを用いるのと、別のロジッククラスを用意してリファクタする。

src/index.ts

import { CsvFileReader } from './CsvFileReader'

const reader = new CsvFileReader('football.csv')
reader.read()

enum MatchResult {
  HomeWin = 'H',
  AwayWin = 'A',
  Draw = 'D',
}

let manUnitedWins = 0

for (const match of reader.data) {
  if (match[1] === 'Man United' && match[5] === MatchResult.HomeWin) {
    manUnitedWins++
  } else if (match[2] === 'Man United' && match[5] === MatchResult.AwayWin) {
    manUnitedWins++
  }
}

console.log(`Man United won ${manUnitedWins} games`)

src/CsvFileReader.ts

import * as fs from 'fs'

export class CsvFileReader {
  data: string[][] = []

  constructor(public filename: string) {}

  read(): void {
    this.data = fs
      .readFileSync(this.filename, {
        encoding: 'utf-8',
      })
      .split('\n')
      .map((row: string): string[] => row.split(','))
  }
}

いつenumを使うべきか?

  • Follow near-identical syntax rules as normal objects
  • Creates an object with the same keys and values when converted from TS to JS
  • Primary goal is to signal to to other engineers that these are all closely related values
  • Use whenever we have a small fixed set of values that are all closely related and known at compile time

enumを導入するかどうかの実例

  • カラーピッカーで色指定をするとき:yes
    • 既に決まっていてわかっているから
  • 動画配信サイトのカテゴリを指定するとき:no
    • 変わり続けてアップデートされるものだから
  • メニュー上のドリンクのサイズ:yes
    • 選択肢が数種類しかないので
  • 1750年以降の年数:no
    • 値の数が大きすぎる
  • テキストメッセージの既読フラグ:yes
    • 想定しうるステータスが決まっているから