TypeScriptでexhaustive check

TypeScriptでexhaustive check

TypeScriptでexhaustive check


テストのLT会なのにTypeScriptの話?なぜ?
テスティングトロフィーの考えに基づくと、静的解析もテスト手法である。
End to End (E2E) テスト
ユーザー視点でのブラウザ操作をシミュレーションして、アプリが意図した動作しているかテストする。
表示が崩れていないことを保証するvisual regression testなどもある。
テストが安定しないフレイキーテスト、テストに時間がかかる場合もある。
ESLintTypeScriptなど。
受け入れテストやユーザー視点でのテストは扱わない。
高速、コードを書いたその場でTypeScriptがエラーを教えてくれたりする。
E2Eテストを使わずにできることは、なるべく高速で低次なテスト手法に任せたい。


TypeScriptを使って、プロダクションコードの不具合を事前に見つける方法を1つLTさせてください。hata6502


コードの改修に強い列挙型
列挙型 (enum) に後から新しい値を加えたとき、列挙型を扱う処理の修正を忘れることがある。
Product(製品)という列挙型を定義する。
apple りんご
eraser 消しゴム
const products = ["apple", "eraser"] as const;
Productの配列から、果物だけを抽出する処理pickFruitsを書く。
ts
Copied!
const pickFruits =
(products: Product[]) => products.filter(product => ["apple"].includes(product));
しばらく経ったあと新製品bananaが誕生したので、コードも修正する。
✅列挙型Productにbananaを追加した。
const products = ["apple", "eraser", "banana"] as const;
❌しかし pickFruits() 関数の修正を忘れた。
新製品bananaが果物として扱われない不具合発生!

果物をconsole.log()するサンプルコード(bananaが表示されない不具合あり)
ts
Copied!
/** 製品 */
const products = [
"apple",
"eraser",
// 新製品バナナ
"banana"
] as const;
type Product = typeof products[number];

/** 製品の配列から、果物だけを抽出する。 */
const pickFruits =
// 新製品bananaを追加し忘れた!
(products: Product[]) => products.filter(product => ["apple"].includes(product));

console.log(pickFruits([...products]));


列挙型に後から値を追加すると不具合を起こしたり、不具合が起きないかコードレビューするのが大変。
網羅チェックexhaustive checkと呼ばれる方法で対策する。

switch文のdefaultを利用する。
型定義上、defaultの処理には到達しないことを保証する。
Productにbananaを追加したとき、 case "banana" を追記しないと型エラーになる。
ts
Copied!
switch (product) {
case "apple":
// このcaseを追記しないと、TypeScriptが型エラーを起こす。
case "banana":
return [product];

case "eraser":
return [];

// exhaustiveCheck。
// 型定義によって、defaultには処理が到達しないことを保証している。
default:
const exhaustiveCheck: never = product;
throw new Error("unreachable");
}


果物をconsole.log()するサンプルコード(bananaも表示される)
ts
Copied!
/** 製品 */
const products = [
"apple",
"eraser",
// 新製品バナナ
"banana"
] as const;
type Product = typeof products[number];

/** 製品の配列から、果物だけを抽出する。 */
const pickFruits = (products: Product[]) =>
// filter()ではなくflatMap()を使うことで、
// pickFruits()の返り値は"apple"[]と推論される。
products.flatMap((product) => {
switch (product) {
case "apple":
// このcaseを追記しないと、TypeScriptが型エラーを起こす。
case "banana":
return [product];

case "eraser":
return [];

// exhaustiveCheck。
// 型定義によって、defaultには処理が到達しないことを保証している。
default:
const exhaustiveCheck: never = product;
throw new Error("unreachable");
}
});

console.log(pickFruits([...products]));


プロダクションコードの不具合を事前に見つけられるように、TypeScriptを書きたい。hata6502
次回は5分ではなく10分でLTしようと思います!
Powered by Helpfeel