📚

契約プログラミングを意識するとオブジェクト指向になるかも知れないと思った話

2020/09/19に公開

はじめに

契約プログラミングもオブジェクト指向も正しい意味で理解できてるか怪しいので、そのへん大目に見てほしい

契約プログラミング?

https://ja.wikipedia.org/wiki/契約プログラミング には

事前条件 (precondition)
サブルーチンの開始時に、これを呼ぶ側で保証すべき性質。
事後条件 (postcondition)
サブルーチンが、終了時に保証すべき性質。
不変条件 (invariant)
クラスなどのオブジェクトがその外部に公開しているすべての操作の開始時と終了時に保証されるべき、オブジェクト毎に共通した性質。

とある。
この内、事前条件と事後条件は静的型付け言語の型で示せるかも知れないなぁと思った。

仕様

メールアドレスとパスワードでサインアップできるシステム。
DB にはハッシュ化されたパスワードを保存する。

普通に実装

言語は kotlin

class UserRepository (
  private userDao: UserDao
) {
  fun signUp(email: String, password: String): User =
    userDao.create(email, password)
}

問題点

引数の email がメールアドレスとして正しいか、 password がハッシュ化されているか、がわからないので、別の実装者によって誤った実装が行われる可能性がある。

契約プログラミングを意識して実装する

class Email(val value: String) {
  init {
    require(EmailValidator.getInstance().isValid(value))
  }
}

class HashedPassword(_value: String) {
  val value = BCryptPasswordEncoder().encode(_value)
}

class UserRepository (
  private userDao: UserDao
) {
  fun signUp(email: Email, password: HashedPassword): User =
    userDao.create(email.value, password.value)
}

こうすることで、「Repository に届ける前に Email の検証やパスワードのハッシュ化を済ませておかないといけないんだな」という表明ができる(事前条件)
事後条件は返り値で示せる。例えば、先程の例だと HashedPassword のコンストラクタでやってた処理をファクトリに移すこともできる。

class HashedPassword private constructor (val value: String) {
  companion object {
    fun of(str: String): HashedPassword = HashedPassword(BCryptPasswordEncoder().encode(str))
  }
}

普通に実装すると、

object PasswordUtil {
  fun hashed(str: String): String = BCryptPasswordEncoder().encode(str)
}

どうだろう、 String が返ってくるのと HashedPassword が返ってくるので少し印象が違うのではなかろうか。

ところで、さっきの仕様を思い出すと、

メールアドレスとパスワードでサインアップできるシステム。
DB にはハッシュ化されたパスワードを保存する。

メールアドレス、ハッシュ化されたパスワード、がコードにも型として現れているのがわかると思う。

うーん、こうやって仕様を型にしていくのがオブジェクト指向? ValueObject?なんだと思う。

間違ってるかもしれないけど、個人的にはしっくり来ている。

Discussion