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