Upgrade to Pro — share decks privately, control downloads, hide ads and more …

型だけでバグを減らそう! Kotlinの型パワーを使った実践タイプセーフエンジニアリング

YuitoSato
December 10, 2022

型だけでバグを減らそう! Kotlinの型パワーを使った実践タイプセーフエンジニアリング

Kotlin Fest 2022の登壇内容のスライドです。

YuitoSato

December 10, 2022
Tweet

More Decks by YuitoSato

Other Decks in Technology

Transcript

  1. 1
    ©2022 Loglass Inc.
    型だけでバグを減らそう!
    Kotlinの型パワーを使った実践タイプセーフ
    エンジニアリング
    2022.12.10 佐藤有斗(Yuiiitoto) 株式会社ログラス

    View Slide

  2. 2
    ©2022 Loglass Inc.
    自己紹介
    佐藤有斗(Yuiiitoto)
    ログラス エンジニア
    # React
    # 組織・アジャイル
    # Kotlin

    View Slide

  3. 3
    ©2022 Loglass Inc.
    ログラスについて(5秒)
    企業価値を向上する

    経営管理クラウド


    View Slide

  4. 4
    ©2022 Loglass Inc.
    目次
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  5. 5
    ©2022 Loglass Inc.
    目次
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  6. 6
    ©2022 Loglass Inc.
    なぜ型を使いこなすことでバグが減るのか
    コンパイル時に実装ミスに気付けるから

    View Slide

  7. 7
    ©2022 Loglass Inc.
    コンパイル時に実装ミスに気づくとは?

    View Slide

  8. 8
    ©2022 Loglass Inc.
    ミドルネームにnullを渡す

    View Slide

  9. 9
    ©2022 Loglass Inc.
    ぬるぽ、ガッ!
      Λ_Λ  \\
      ( ・∀・)   | | ガッ
     と    )    | |
       Y /ノ    人
        / )    <  >_Λ∩
      _/し' //. V`Д´)/
     (_フ彡        / ←>>1
    NullPointerExceptionが起きる

    View Slide

  10. 10
    ©2022 Loglass Inc.
    と思いきやKotlinはコンパイルで落ちる

    View Slide

  11. 11
    ©2022 Loglass Inc.
    Kotlinではデフォルトで
    引数や変数にnullを渡すことができない
    KotlinのNull Safety

    View Slide

  12. 12
    ©2022 Loglass Inc.
    Kotlinではデフォルトで
    引数や変数にnullを渡すことができない
    KotlinのNull Safety

    View Slide

  13. 13
    ©2022 Loglass Inc.
    実装ミスをコンパイル時に気づけると何がいいのか?
    ● 実行時に気付くバグと比較して ...
    ○ 全ての分岐を実行しなくてもよく 必ず実装ミスに気づくことができる
    ○ テストカバレッジ100%は現実的ではない

    View Slide

  14. 14
    ©2022 Loglass Inc.
    では、なぜKotlin?
    ● Kotlinは型に関する機能が豊富
    ○ Generics
    ○ 型推論
    ○ Smart Cast
    ○ 代数的データ型
    ○ 高階関数やラムダ
    ○ Delegation などなど

    View Slide

  15. 15
    ©2022 Loglass Inc.
    要するに、、
    Kotlinを駆使して、実装ミスをコンパイルで
    気付けるようにしよう!!!

    View Slide

  16. 16
    ©2022 Loglass Inc.
    型によってバグが減らせるパターン
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  17. 17
    ©2022 Loglass Inc.
    標準の型をラップする
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  18. 18
    ©2022 Loglass Inc.
    標準の型をラップする
    ● よくあるミス: 引数のIDを取り間違えてしまった

    View Slide

  19. 19
    ©2022 Loglass Inc.
    標準の型をラップする
    ● よくあるミス: 引数のIDを取り間違えてしまった

    View Slide

  20. 20
    ©2022 Loglass Inc.
    Stringをラップする

    View Slide

  21. 21
    ©2022 Loglass Inc.
    TaskIdとUserIdを区別することでコンパイル時に実装ミスに気付ける

    View Slide

  22. 22
    ©2022 Loglass Inc.
    TaskIdとUserIdを区別することでコンパイル時に実装ミスに気付ける

    View Slide

  23. 23
    ©2022 Loglass Inc.
    ちなみに: ラップすることによるオーバーヘッドを避ける
    ● Stringをクラスでラップするとヒープ領域のオーバーヘッドが増える
    ● inline classesという機能を使うことで String型として扱えてスタック領域だけの割り当てで済む

    View Slide

  24. 24
    ©2022 Loglass Inc.
    ここで一つ疑問が
    全てのIDに別々の型をつけるの面倒
    ではないか?

    View Slide

  25. 25
    ©2022 Loglass Inc.
    タイプセーフにしたいが、型の増加を抑制したい
    => Genericsを使おう!
    Genericsでさらに汎用的にする

    View Slide

  26. 26
    ©2022 Loglass Inc.
    Genericsでさらに汎用的にする

    View Slide

  27. 27
    ©2022 Loglass Inc.
    Genericsでさらに汎用的にする

    View Slide

  28. 28
    ©2022 Loglass Inc.
    Genericsでさらに汎用的にする
    ● 型を増やさずに、タスク IDとユーザーIDを区別できた!

    View Slide

  29. 29
    ©2022 Loglass Inc.
    Genericsでさらに汎用的にする
    ● 型を増やさずに、タスク IDとユーザーIDを区別できた!

    View Slide

  30. 30
    ©2022 Loglass Inc.
    Genericsでさらに汎用的にする
    ● 型を増やさずに、タスク IDとユーザーIDを区別できた!

    View Slide

  31. 31
    ©2022 Loglass Inc.
    Genericsでさらに汎用的にする
    ● 型を増やさずに、タスク IDとユーザーIDを区別できた!

    View Slide

  32. 32
    ©2022 Loglass Inc.
    Genericsでさらに汎用的にする
    ● 型を増やさずに、タスク IDとユーザーIDを区別できた!

    View Slide

  33. 33
    ©2022 Loglass Inc.
    標準の型をラップする : まとめ
    標準の型をラップして取り間違いを防ぐ
    型の増加をGenericsで抑制する

    View Slide

  34. 34
    ©2022 Loglass Inc.
    認可処理などの特定の処理をパスしたことを型で示す
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  35. 35
    ©2022 Loglass Inc.
    認可処理などの特定の処理をパスしたことを型で示す
    ● よくあるミス: 認可されていないIDで参照をしてしまった

    View Slide

  36. 36
    ©2022 Loglass Inc.
    認可処理などの特定の処理をパスしたことを型で示す
    ● よくあるミス: 認可されていないIDで参照をしてしまった

    View Slide

  37. 37
    ©2022 Loglass Inc.
    認可処理などの特定の処理をパスしたことを型で示す
    ● よくあるミス: 認可されていないIDで参照をしてしまった

    View Slide

  38. 38
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● 認可されたID型として AuthorizedTaskId を定義する

    View Slide

  39. 39
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● TaskAuthChecker だけが返す特別な型とする

    View Slide

  40. 40
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● TaskAuthChecker だけが返す特別な型とする

    View Slide

  41. 41
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● findTaskByIdはAuthorizedTaskIdしか受け付けないように修正

    View Slide

  42. 42
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● findTaskByIdはAuthorizedTaskIdしか受け付けないように修正

    View Slide

  43. 43
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● 認可処理を通過していない IDはコンパイル時に落ちるようになる

    View Slide

  44. 44
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● 認可処理を通過していない IDはコンパイル時に落ちるようになる

    View Slide

  45. 45
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● 認可処理を通過していない IDはコンパイル時に落ちるようになる

    View Slide

  46. 46
    ©2022 Loglass Inc.
    「認可された」ID型を導入する
    ● 認可処理を通過していない IDはコンパイル時に落ちるようになる

    View Slide

  47. 47
    ©2022 Loglass Inc.
    しかし油断してはいけない
    ● AuthorizedTaskIdのコンストラクタが公開されている!
    ここに public が潜んでいる

    View Slide

  48. 48
    ©2022 Loglass Inc.
    しかし油断してはいけない
    ● AuthorizedTaskIdのコンストラクタが公開されている!

    View Slide

  49. 49
    ©2022 Loglass Inc.
    しかし油断してはいけない
    ● 不正に認可されていない AuthorizedTaskIdが作成されてしまう

    View Slide

  50. 50
    ©2022 Loglass Inc.
    しかし油断してはいけない
    ● 不正に認可されていない AuthorizedTaskIdが作成されてしまう

    View Slide

  51. 51
    ©2022 Loglass Inc.
    不正にインスタンスを作成されないためには?
    data classを使ってコンストラクタの可視性を
    コントロールする

    View Slide

  52. 52
    ©2022 Loglass Inc.
    コンストラクタの可視性を data classで制御する

    View Slide

  53. 53
    ©2022 Loglass Inc.
    コンストラクタの可視性を data classで制御する
    ● インスタンスを作成できない interface AuthorizedTaskIdを定義する
    sealed を使えば同ファイルからしか継承できない

    View Slide

  54. 54
    ©2022 Loglass Inc.
    コンストラクタの可視性を data classで制御する
    ● コンストラクタ機能だけの data classをprivateで定義する
    private classは同ファイルからしか参照できない

    View Slide

  55. 55
    ©2022 Loglass Inc.
    コンストラクタの可視性を data classで制御する
    ● TaskAuthCheckerからのみprivate data classにアクセスする

    View Slide

  56. 56
    ©2022 Loglass Inc.
    コンストラクタの可視性を data classで制御する
    ● 別ファイルから AuthorizedTaskId インスタンスが生成できなくなった!

    View Slide

  57. 57
    ©2022 Loglass Inc.
    コンストラクタの可視性を data classで制御する
    ● 別ファイルから AuthorizedTaskId インスタンスが生成できなくなった!

    View Slide

  58. 58
    ©2022 Loglass Inc.
    コンストラクタの可視性を data classで制御する
    ● 別ファイルから AuthorizedTaskId インスタンスが生成できなくなった!

    View Slide

  59. 59
    ©2022 Loglass Inc.
    コンストラクタをprivateにするのではダメ?
    ● この場合のprivateは同じクラス ファイル のみアクセス可能という意味なので NG

    View Slide

  60. 60
    ©2022 Loglass Inc.
    認可処理などの特定の処理をパスしたことを型で示す : まとめ
    処理が通過したことを型で示す
    コンストラクタを非公開にして不正にインスタンスを作らせない

    View Slide

  61. 61
    ©2022 Loglass Inc.
    型でデータの不整合をなくす
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  62. 62
    ©2022 Loglass Inc.
    型でデータの不整合をなくす
    ● よくあるミス: あり得ないデータを作成してしまった

    View Slide

  63. 63
    ©2022 Loglass Inc.
    型でデータの不整合をなくす
    ● タスクには以下のステータスがある
    ● 完了ステータスのときのみ完了時刻を持つ

    View Slide

  64. 64
    ©2022 Loglass Inc.
    型でデータの不整合をなくす
    ● しかし間違えて着手時に完了時刻を入れてしまった
    ● →着手中なのに完了時刻を持つという不整合データが誕生

    View Slide

  65. 65
    ©2022 Loglass Inc.
    型でデータの不整合をなくす
    ● しかし間違えて着手時に完了時刻を入れてしまった
    ● →着手中なのに完了時刻を持つという不整合データが誕生

    View Slide

  66. 66
    ©2022 Loglass Inc.
    データ不整合をなくすには?
    sealed classを使って
    データの不整合が起こりえない型を定義する

    View Slide

  67. 67
    ©2022 Loglass Inc.
    sealed class を導入する
    ● 完了の時のみ完了時刻をもつというデータ構造を sealed classで定義

    View Slide

  68. 68
    ©2022 Loglass Inc.
    sealed class を導入する
    ● sealed class + object, data classのパターンはEnumのように扱える

    View Slide

  69. 69
    ©2022 Loglass Inc.
    sealed class を導入する
    ● Enumだとこうなる
    ● objectはEnumとほぼ同じ扱いで、data classはEnum + 構造体というような振る舞い

    View Slide

  70. 70
    ©2022 Loglass Inc.
    sealed class を導入する
    ● Task型からnullableなcompletedAtプロパティが消える

    View Slide

  71. 71
    ©2022 Loglass Inc.
    sealed class を導入する
    ● 着手中時に完了時刻を渡せなくなった

    View Slide

  72. 72
    ©2022 Loglass Inc.
    sealed class を導入する
    ● 着手中時に完了時刻を渡せなくなった

    View Slide

  73. 73
    ©2022 Loglass Inc.
    sealed class を導入する
    ● 完了時には完了時刻の入力を強制できる

    View Slide

  74. 74
    ©2022 Loglass Inc.
    sealed class を導入する
    ● 完了時には完了時刻の入力を強制できる

    View Slide

  75. 75
    ©2022 Loglass Inc.
    OSS: kotlin-resultの事例
    ● 成功値かエラー値かどちらかの値をとる Result型を提供するOSS
    kotlin-result : https://github.com/michaelbull/kotlin-result

    View Slide

  76. 76
    ©2022 Loglass Inc.
    OSS: kotlin-resultの事例
    ● 成功値かエラー値かどちらかの値をとる Result型を提供するOSS
    kotlin-result : https://github.com/michaelbull/kotlin-result

    View Slide

  77. 77
    ©2022 Loglass Inc.
    OSS: kotlin-resultの事例
    ● NGパターン: nullableな value と errorを持っているわけではないことに注意

    View Slide

  78. 78
    ©2022 Loglass Inc.
    型でデータ不整合をなくす : まとめ
    sealed classを使って
    データの不整合が起こりえない型を定義する

    View Slide

  79. 79
    ©2022 Loglass Inc.
    まとめ
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  80. 80
    ©2022 Loglass Inc.
    まとめ
    ● なぜ型を使いこなすことでバグが減るのか?
    ○ →コンパイル時に実装ミスを検出できるから
    ○ →Kotlinの型パワーを使ってコンパイル時により多くのミスを検出する

    View Slide

  81. 81
    ©2022 Loglass Inc.
    まとめ
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    → genericsで型の増加を抑制しよう
    2. 認可処理などの特定の処理をパスしたことを型で示す
    → コンストラクタの可視性を data classを使ってコントロールしよう
    3. 型でデータの不整合をなくす
    → sealed classでデータの不整合をなくそう

    View Slide

  82. 82
    ©2022 Loglass Inc.
    最後に: タイプセーフなポストモーテム
    ● なぜ型を使いこなすことでバグが減るのか?
    ● 型によってバグが減らせるパターン
    1. 標準の型をラップする
    2. 認可処理などの特定の処理をパスしたことを型で示す
    3. 型でデータの不整合をなくす
    ● まとめ
    ● 最後に: タイプセーフなポストモーテム

    View Slide

  83. 83
    ©2022 Loglass Inc.
    最後に: タイプセーフなポストモーテム
    情報漏洩が起きました!再発防止策は?

    View Slide

  84. 84
    ©2022 Loglass Inc.
    最後に: タイプセーフなポストモーテム
    - レビュワーを2人に増やす
    - 偉い人のチェックを増やす
    - GitHubのPRテンプレにチェック項目を足

    - テスト工数を2倍にする etc…

    View Slide

  85. 85
    ©2022 Loglass Inc.
    最後に: タイプセーフなポストモーテム
    それ、型で解決できませんか?

    View Slide

  86. 86
    ©2022 Loglass Inc.
    最後に: タイプセーフなポストモーテム
    ● 障害のたびにテスト工数を増やしたり、「儀式」を追加するとスピードが落ちる
    ● 最優先で考えるべき再発防止策はコンパイル時に障害のタネを気付けるようにすること
    ● Kotlinはそのための機能をたくさん有している

    View Slide

  87. 87

    View Slide