Slide 1

Slide 1 text

TS で型安全なエラーハンドリング まずはBranded Type から始めてみては? 1 / 25

Slide 2

Slide 2 text

今日持ち帰って欲しいこと TS の例外処理(try catch ) 、型安全じゃないから何か対策をしてみよう Branded Type で手軽に型安全なエラーハンドリングを始めよう 小さく始めて、チームに浸透させよう 2 / 25

Slide 3

Slide 3 text

TS の try catch の問題点 3 / 25

Slide 4

Slide 4 text

1. try catch のerror は型安全じゃない catch で何が補足されるかは全くわからない… const invalidJson = '{"name": "John",}'; try { const data = JSON.parse(invalidJson); console.log(data) } catch (error) { // error is unknown 😱 console.error(error.message); // unknown にはアクセスできない } 4 / 25

Slide 5

Slide 5 text

2. 型から例外の可能性が読み取れない 事前に例外を予期することができない type User = {name: string, age: number}; // この関数、例外投げるかどうか分からない... const parseUserData = (data: {name: string, age: number}): User => { // ... バリデーションの処理 const {name, age} = data return {name, age} } // 使う側は知らずに爆死 const = parseUserData({name: 'hoge', age: 100}); // 💥 user 5 / 25

Slide 6

Slide 6 text

型安全、予測可能なエラーハン ドリングがしたい…! 6 / 25

Slide 7

Slide 7 text

一旦別の話題 7 / 25

Slide 8

Slide 8 text

型の一致を判定する方法 1. 構造的部分型 2. 公称型 8 / 25

Slide 9

Slide 9 text

TS は構造的部分型 構造的部分型 part1 type UserId = string; type Email = string; const sendEmail = (email: Email) => { /* ... */ } const userId: UserId = "user123"; sendEmail(userId); 9 / 25

Slide 10

Slide 10 text

TS は構造的部分型 型のエイリアス・名前に意味はない 部分的に同じ構造なら別の型でも受け入れる 構造的部分型 part2 type Person = { name: string; age: number } type User = { id: UserId; email: Email; name: string; age: number; } const doSomethingPerson(u: Person) => {/** */} const user: User = {id: "id", email: "[email protected]", name: "John", age: 30}; doSomethingPerson(user) // エラーにならない 10 / 25

Slide 11

Slide 11 text

公称型は? 型のエイリアス自体が一致しているかが重要 同じ構造だからって許さんぞ! type UserId string type Email string func sendEmail(email Email) { // メール送信処理 } uid := UserId("id") sendEmail(uid) // コンパイルエラー!✅ email := Email("[email protected]") sendEmail(email) // ちゃんとパスする 11 / 25

Slide 12

Slide 12 text

TS で公称型っぽいやつしたい 12 / 25

Slide 13

Slide 13 text

Branded Type 型名を表す一意なタグをつけてあげる type UserId = string & { __brand: 'UserId' }; type Email = string & { __brand: 'Email' }; type = { User __brand: "User" id: UserId; email: Email; } 13 / 25

Slide 14

Slide 14 text

Branded Type で公称型みたいなことができる type UserId = string; type Email = string & { __brand: 'Email' }; const Email = (email: string): Email => Object.assign(email, { __brand: 'Email' as const}); const sendEmail = ( : Email) => { /* ... */ } email const userId: UserId = 'userId1234'; sendEmail(userId); // コンパイルエラー!✅ const email: Email = Email("[email protected]"); sendEmail(email); // ちゃんとパスする 14 / 25

Slide 15

Slide 15 text

Branded Type で公称型みたいなことができる 普通に Branded Type type UserId = string; type Email = string; const sendEmail = ( : Email) => { /* ... */ } email const userId: UserId = "user123"; sendEmail(userId); const : Email = "[email protected]"; email sendEmail(userId); 15 / 25 type UserId = string; type Email = string & { __brand: 'Email' }; const Email = (email: string): Email => Object.assign(email, { __brand: 'Email' as const}); const sendEmail = ( : Email) => { /* ... */ } email const userId: UserId = 'userId1234'; sendEmail(userId); // コンパイルエラー!✅ const email: Email = Email("[email protected]"); sendEmail(email); // ちゃんとパスする 15 / 25

Slide 16

Slide 16 text

エラーハンドリングの話に戻ります 16 / 25

Slide 17

Slide 17 text

エラーハンドリングに Branded Type を活用 17 / 25

Slide 18

Slide 18 text

Error 型を定義 エラーも値として扱う type UserNameValidationError = {__brand: 'UserNameValidationError', message: string} const UserNameValidationError = (message: string): UserNameValidationError => ({ __brand: 'UserNameValidationError', message, }); type UserAgeValidationError = {__brand: 'UserAgeValidationError', message: string} const UserAgeValidationError = (message: string): UserAgeValidationError => ({ __brand: 'UserAgeValidationError', message, }); 18 / 25

Slide 19

Slide 19 text

関数の戻り値でエラーの可能性を表現 一目でエラーの可能性がわかる 型安全 const parseUser = (name: string, age: number): User | UserNameValidationError | UserAgeValidationError => { if (name.length > 10) return UserNameValidationError('name must be less than 10 characters') if (age < 0) return UserAgeValidationError('age must be positive') return User(name, age) } const = () => { main const result = parseUser('John Doe', 30) if (result.__brand === 'UserNameValidationError') { toast.error(`UserName が不正です: ${result.message}`) return } else if (result.__brand === 'UserAgeValidationError') { toast.error(`UserAge が不正です: ${result.message}`) return } const user = result console.log(user.name, user.age); } 19 / 25

Slide 20

Slide 20 text

AI コーディングとの相性も良い 正しいシグネチャを書いてあげると実装の品質が良くなる 20 / 25

Slide 21

Slide 21 text

なぜBranded Type から始めるのか? Result 型など他にもエラーハンドリングの方法はある TS の基礎知識があればすぐ使える 小さく始められる 意識していないだけでBranded Type みたいなことは既にやってるはず(type Narrowing ) 21 / 25

Slide 22

Slide 22 text

まとめ 22 / 25

Slide 23

Slide 23 text

まとめ 1. TS の例外処理(try catch )は型安全じゃない error はunknown 型から例外が読み取れない 2. Branded Type で解決 エラーも値として扱う 型を一意に特定するタグフィールド 型の絞り込みでエラーハンドリングを強制 23 / 25 23 / 25

Slide 24

Slide 24 text

型安全なエラーハンドリング 今日から始めませんか? 24 / 25

Slide 25

Slide 25 text

🙏 ありがとうございました! 25 / 25