型だけでバグを減らそう! Kotlinの型パワーを使った実践タイプセーフエンジニアリング
by
YuitoSato
×
Copy
Open
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Slide 1
Slide 1 text
1 ©2022 Loglass Inc. 型だけでバグを減らそう! Kotlinの型パワーを使った実践タイプセーフ エンジニアリング 2022.12.10 佐藤有斗(Yuiiitoto) 株式会社ログラス
Slide 2
Slide 2 text
2 ©2022 Loglass Inc. 自己紹介 佐藤有斗(Yuiiitoto) ログラス エンジニア # React # 組織・アジャイル # Kotlin
Slide 3
Slide 3 text
3 ©2022 Loglass Inc. ログラスについて(5秒) 企業価値を向上する 経営管理クラウド
Slide 4
Slide 4 text
4 ©2022 Loglass Inc. 目次 ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 5
Slide 5 text
5 ©2022 Loglass Inc. 目次 ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 6
Slide 6 text
6 ©2022 Loglass Inc. なぜ型を使いこなすことでバグが減るのか コンパイル時に実装ミスに気付けるから
Slide 7
Slide 7 text
7 ©2022 Loglass Inc. コンパイル時に実装ミスに気づくとは?
Slide 8
Slide 8 text
8 ©2022 Loglass Inc. ミドルネームにnullを渡す
Slide 9
Slide 9 text
9 ©2022 Loglass Inc. ぬるぽ、ガッ! Λ_Λ \\ ( ・∀・) | | ガッ と ) | | Y /ノ 人 / ) < >_Λ∩ _/し' //. V`Д´)/ (_フ彡 / ←>>1 NullPointerExceptionが起きる
Slide 10
Slide 10 text
10 ©2022 Loglass Inc. と思いきやKotlinはコンパイルで落ちる
Slide 11
Slide 11 text
11 ©2022 Loglass Inc. Kotlinではデフォルトで 引数や変数にnullを渡すことができない KotlinのNull Safety
Slide 12
Slide 12 text
12 ©2022 Loglass Inc. Kotlinではデフォルトで 引数や変数にnullを渡すことができない KotlinのNull Safety
Slide 13
Slide 13 text
13 ©2022 Loglass Inc. 実装ミスをコンパイル時に気づけると何がいいのか? ● 実行時に気付くバグと比較して ... ○ 全ての分岐を実行しなくてもよく 必ず実装ミスに気づくことができる ○ テストカバレッジ100%は現実的ではない
Slide 14
Slide 14 text
14 ©2022 Loglass Inc. では、なぜKotlin? ● Kotlinは型に関する機能が豊富 ○ Generics ○ 型推論 ○ Smart Cast ○ 代数的データ型 ○ 高階関数やラムダ ○ Delegation などなど
Slide 15
Slide 15 text
15 ©2022 Loglass Inc. 要するに、、 Kotlinを駆使して、実装ミスをコンパイルで 気付けるようにしよう!!!
Slide 16
Slide 16 text
16 ©2022 Loglass Inc. 型によってバグが減らせるパターン ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 17
Slide 17 text
17 ©2022 Loglass Inc. 標準の型をラップする ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 18
Slide 18 text
18 ©2022 Loglass Inc. 標準の型をラップする ● よくあるミス: 引数のIDを取り間違えてしまった
Slide 19
Slide 19 text
19 ©2022 Loglass Inc. 標準の型をラップする ● よくあるミス: 引数のIDを取り間違えてしまった
Slide 20
Slide 20 text
20 ©2022 Loglass Inc. Stringをラップする
Slide 21
Slide 21 text
21 ©2022 Loglass Inc. TaskIdとUserIdを区別することでコンパイル時に実装ミスに気付ける
Slide 22
Slide 22 text
22 ©2022 Loglass Inc. TaskIdとUserIdを区別することでコンパイル時に実装ミスに気付ける
Slide 23
Slide 23 text
23 ©2022 Loglass Inc. ちなみに: ラップすることによるオーバーヘッドを避ける ● Stringをクラスでラップするとヒープ領域のオーバーヘッドが増える ● inline classesという機能を使うことで String型として扱えてスタック領域だけの割り当てで済む
Slide 24
Slide 24 text
24 ©2022 Loglass Inc. ここで一つ疑問が 全てのIDに別々の型をつけるの面倒 ではないか?
Slide 25
Slide 25 text
25 ©2022 Loglass Inc. タイプセーフにしたいが、型の増加を抑制したい => Genericsを使おう! Genericsでさらに汎用的にする
Slide 26
Slide 26 text
26 ©2022 Loglass Inc. Genericsでさらに汎用的にする
Slide 27
Slide 27 text
27 ©2022 Loglass Inc. Genericsでさらに汎用的にする
Slide 28
Slide 28 text
28 ©2022 Loglass Inc. Genericsでさらに汎用的にする ● 型を増やさずに、タスク IDとユーザーIDを区別できた!
Slide 29
Slide 29 text
29 ©2022 Loglass Inc. Genericsでさらに汎用的にする ● 型を増やさずに、タスク IDとユーザーIDを区別できた!
Slide 30
Slide 30 text
30 ©2022 Loglass Inc. Genericsでさらに汎用的にする ● 型を増やさずに、タスク IDとユーザーIDを区別できた!
Slide 31
Slide 31 text
31 ©2022 Loglass Inc. Genericsでさらに汎用的にする ● 型を増やさずに、タスク IDとユーザーIDを区別できた!
Slide 32
Slide 32 text
32 ©2022 Loglass Inc. Genericsでさらに汎用的にする ● 型を増やさずに、タスク IDとユーザーIDを区別できた!
Slide 33
Slide 33 text
33 ©2022 Loglass Inc. 標準の型をラップする : まとめ 標準の型をラップして取り間違いを防ぐ 型の増加をGenericsで抑制する
Slide 34
Slide 34 text
34 ©2022 Loglass Inc. 認可処理などの特定の処理をパスしたことを型で示す ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 35
Slide 35 text
35 ©2022 Loglass Inc. 認可処理などの特定の処理をパスしたことを型で示す ● よくあるミス: 認可されていないIDで参照をしてしまった
Slide 36
Slide 36 text
36 ©2022 Loglass Inc. 認可処理などの特定の処理をパスしたことを型で示す ● よくあるミス: 認可されていないIDで参照をしてしまった
Slide 37
Slide 37 text
37 ©2022 Loglass Inc. 認可処理などの特定の処理をパスしたことを型で示す ● よくあるミス: 認可されていないIDで参照をしてしまった
Slide 38
Slide 38 text
38 ©2022 Loglass Inc. 「認可された」ID型を導入する ● 認可されたID型として AuthorizedTaskId を定義する
Slide 39
Slide 39 text
39 ©2022 Loglass Inc. 「認可された」ID型を導入する ● TaskAuthChecker だけが返す特別な型とする
Slide 40
Slide 40 text
40 ©2022 Loglass Inc. 「認可された」ID型を導入する ● TaskAuthChecker だけが返す特別な型とする
Slide 41
Slide 41 text
41 ©2022 Loglass Inc. 「認可された」ID型を導入する ● findTaskByIdはAuthorizedTaskIdしか受け付けないように修正
Slide 42
Slide 42 text
42 ©2022 Loglass Inc. 「認可された」ID型を導入する ● findTaskByIdはAuthorizedTaskIdしか受け付けないように修正
Slide 43
Slide 43 text
43 ©2022 Loglass Inc. 「認可された」ID型を導入する ● 認可処理を通過していない IDはコンパイル時に落ちるようになる
Slide 44
Slide 44 text
44 ©2022 Loglass Inc. 「認可された」ID型を導入する ● 認可処理を通過していない IDはコンパイル時に落ちるようになる
Slide 45
Slide 45 text
45 ©2022 Loglass Inc. 「認可された」ID型を導入する ● 認可処理を通過していない IDはコンパイル時に落ちるようになる
Slide 46
Slide 46 text
46 ©2022 Loglass Inc. 「認可された」ID型を導入する ● 認可処理を通過していない IDはコンパイル時に落ちるようになる
Slide 47
Slide 47 text
47 ©2022 Loglass Inc. しかし油断してはいけない ● AuthorizedTaskIdのコンストラクタが公開されている! ここに public が潜んでいる
Slide 48
Slide 48 text
48 ©2022 Loglass Inc. しかし油断してはいけない ● AuthorizedTaskIdのコンストラクタが公開されている!
Slide 49
Slide 49 text
49 ©2022 Loglass Inc. しかし油断してはいけない ● 不正に認可されていない AuthorizedTaskIdが作成されてしまう
Slide 50
Slide 50 text
50 ©2022 Loglass Inc. しかし油断してはいけない ● 不正に認可されていない AuthorizedTaskIdが作成されてしまう
Slide 51
Slide 51 text
51 ©2022 Loglass Inc. 不正にインスタンスを作成されないためには? data classを使ってコンストラクタの可視性を コントロールする
Slide 52
Slide 52 text
52 ©2022 Loglass Inc. コンストラクタの可視性を data classで制御する
Slide 53
Slide 53 text
53 ©2022 Loglass Inc. コンストラクタの可視性を data classで制御する ● インスタンスを作成できない interface AuthorizedTaskIdを定義する sealed を使えば同ファイルからしか継承できない
Slide 54
Slide 54 text
54 ©2022 Loglass Inc. コンストラクタの可視性を data classで制御する ● コンストラクタ機能だけの data classをprivateで定義する private classは同ファイルからしか参照できない
Slide 55
Slide 55 text
55 ©2022 Loglass Inc. コンストラクタの可視性を data classで制御する ● TaskAuthCheckerからのみprivate data classにアクセスする
Slide 56
Slide 56 text
56 ©2022 Loglass Inc. コンストラクタの可視性を data classで制御する ● 別ファイルから AuthorizedTaskId インスタンスが生成できなくなった!
Slide 57
Slide 57 text
57 ©2022 Loglass Inc. コンストラクタの可視性を data classで制御する ● 別ファイルから AuthorizedTaskId インスタンスが生成できなくなった!
Slide 58
Slide 58 text
58 ©2022 Loglass Inc. コンストラクタの可視性を data classで制御する ● 別ファイルから AuthorizedTaskId インスタンスが生成できなくなった!
Slide 59
Slide 59 text
59 ©2022 Loglass Inc. コンストラクタをprivateにするのではダメ? ● この場合のprivateは同じクラス ファイル のみアクセス可能という意味なので NG
Slide 60
Slide 60 text
60 ©2022 Loglass Inc. 認可処理などの特定の処理をパスしたことを型で示す : まとめ 処理が通過したことを型で示す コンストラクタを非公開にして不正にインスタンスを作らせない
Slide 61
Slide 61 text
61 ©2022 Loglass Inc. 型でデータの不整合をなくす ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 62
Slide 62 text
62 ©2022 Loglass Inc. 型でデータの不整合をなくす ● よくあるミス: あり得ないデータを作成してしまった
Slide 63
Slide 63 text
63 ©2022 Loglass Inc. 型でデータの不整合をなくす ● タスクには以下のステータスがある ● 完了ステータスのときのみ完了時刻を持つ
Slide 64
Slide 64 text
64 ©2022 Loglass Inc. 型でデータの不整合をなくす ● しかし間違えて着手時に完了時刻を入れてしまった ● →着手中なのに完了時刻を持つという不整合データが誕生
Slide 65
Slide 65 text
65 ©2022 Loglass Inc. 型でデータの不整合をなくす ● しかし間違えて着手時に完了時刻を入れてしまった ● →着手中なのに完了時刻を持つという不整合データが誕生
Slide 66
Slide 66 text
66 ©2022 Loglass Inc. データ不整合をなくすには? sealed classを使って データの不整合が起こりえない型を定義する
Slide 67
Slide 67 text
67 ©2022 Loglass Inc. sealed class を導入する ● 完了の時のみ完了時刻をもつというデータ構造を sealed classで定義
Slide 68
Slide 68 text
68 ©2022 Loglass Inc. sealed class を導入する ● sealed class + object, data classのパターンはEnumのように扱える
Slide 69
Slide 69 text
69 ©2022 Loglass Inc. sealed class を導入する ● Enumだとこうなる ● objectはEnumとほぼ同じ扱いで、data classはEnum + 構造体というような振る舞い
Slide 70
Slide 70 text
70 ©2022 Loglass Inc. sealed class を導入する ● Task型からnullableなcompletedAtプロパティが消える
Slide 71
Slide 71 text
71 ©2022 Loglass Inc. sealed class を導入する ● 着手中時に完了時刻を渡せなくなった
Slide 72
Slide 72 text
72 ©2022 Loglass Inc. sealed class を導入する ● 着手中時に完了時刻を渡せなくなった
Slide 73
Slide 73 text
73 ©2022 Loglass Inc. sealed class を導入する ● 完了時には完了時刻の入力を強制できる
Slide 74
Slide 74 text
74 ©2022 Loglass Inc. sealed class を導入する ● 完了時には完了時刻の入力を強制できる
Slide 75
Slide 75 text
75 ©2022 Loglass Inc. OSS: kotlin-resultの事例 ● 成功値かエラー値かどちらかの値をとる Result型を提供するOSS kotlin-result : https://github.com/michaelbull/kotlin-result
Slide 76
Slide 76 text
76 ©2022 Loglass Inc. OSS: kotlin-resultの事例 ● 成功値かエラー値かどちらかの値をとる Result型を提供するOSS kotlin-result : https://github.com/michaelbull/kotlin-result
Slide 77
Slide 77 text
77 ©2022 Loglass Inc. OSS: kotlin-resultの事例 ● NGパターン: nullableな value と errorを持っているわけではないことに注意
Slide 78
Slide 78 text
78 ©2022 Loglass Inc. 型でデータ不整合をなくす : まとめ sealed classを使って データの不整合が起こりえない型を定義する
Slide 79
Slide 79 text
79 ©2022 Loglass Inc. まとめ ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 80
Slide 80 text
80 ©2022 Loglass Inc. まとめ ● なぜ型を使いこなすことでバグが減るのか? ○ →コンパイル時に実装ミスを検出できるから ○ →Kotlinの型パワーを使ってコンパイル時により多くのミスを検出する
Slide 81
Slide 81 text
81 ©2022 Loglass Inc. まとめ ● 型によってバグが減らせるパターン 1. 標準の型をラップする → genericsで型の増加を抑制しよう 2. 認可処理などの特定の処理をパスしたことを型で示す → コンストラクタの可視性を data classを使ってコントロールしよう 3. 型でデータの不整合をなくす → sealed classでデータの不整合をなくそう
Slide 82
Slide 82 text
82 ©2022 Loglass Inc. 最後に: タイプセーフなポストモーテム ● なぜ型を使いこなすことでバグが減るのか? ● 型によってバグが減らせるパターン 1. 標準の型をラップする 2. 認可処理などの特定の処理をパスしたことを型で示す 3. 型でデータの不整合をなくす ● まとめ ● 最後に: タイプセーフなポストモーテム
Slide 83
Slide 83 text
83 ©2022 Loglass Inc. 最後に: タイプセーフなポストモーテム 情報漏洩が起きました!再発防止策は?
Slide 84
Slide 84 text
84 ©2022 Loglass Inc. 最後に: タイプセーフなポストモーテム - レビュワーを2人に増やす - 偉い人のチェックを増やす - GitHubのPRテンプレにチェック項目を足 す - テスト工数を2倍にする etc…
Slide 85
Slide 85 text
85 ©2022 Loglass Inc. 最後に: タイプセーフなポストモーテム それ、型で解決できませんか?
Slide 86
Slide 86 text
86 ©2022 Loglass Inc. 最後に: タイプセーフなポストモーテム ● 障害のたびにテスト工数を増やしたり、「儀式」を追加するとスピードが落ちる ● 最優先で考えるべき再発防止策はコンパイル時に障害のタネを気付けるようにすること ● Kotlinはそのための機能をたくさん有している
Slide 87
Slide 87 text
87