型で保証できるものとできないもの
型で保証できるものとできないもの 普段私は Kotlin の型システムを活用して、コンパイル時に不正な状態を表現できない設計を心がけています。 例えば、 sealed class で状態を表現する value class で ID をラップする null を型で排除する といった方法です。 しかし、あるとき「存在する PaymentId しか関数に渡せないようにできないか?」と考えました。 結論から言うと、これは型だけでは保証できません。 なぜなら、 fun observePayment(id: PaymentId) という関数に渡された ID が実際にデータベースに存在するかどうかは、コンパイル時には分からないからです。 型で保証できるのは、 null ではない 状態の種類 値の構造 不変条件 など、コンパイル時に判定できる性質です。 一方で、 DB にレコードが存在する ファイルが存在する API が応答する 他ユーザーによって削除されていない といったものは実行時の状態に依存するため、型だけでは保証できません。 当たり前と言えば当たり前なのですが、今回再確認したことは 型で保証できるのは「コンパイル時に確定できること」 実行時の状態に依存することは、実行時チェックで保証する ということでした。 例えば「一覧画面から選択された ID なので存在するはず」という前提がある場合でも、その後、別の画面からそのデータが削除されている可能性もあるため、 ID が存在していない可能性もあります。 その場合は、 Repository や UseCase で requireNotNull() を使ったり、独自例外を投げたりして不整合を検知することになります。 型安全な設計を考える際は、「これはコンパイル時に分かる情報か?それとも実行時にしか分からない情報か?」を意識すると、どこまでを型で表現すべきか判断しやすくなります。