📛
インスタンス変数をバリデーションしたくなったら値オブジェクトを検討する
私はまだDDD的な練度が低いため、脳から出てきたコードをそのまま書くとこういう感じになる。
コンストラクタ内でnameのバリデーションをしている。
class User
attr_reader :name
def initialize(name)
raise ArgumentError, '名前が長すぎます' if name.length > 20
@name = name
end
end
例はシンプルなのでこれでいいようにも感じるが、業務で出会うコードはもっと複雑だろう。たとえば人名なら、姓名の間に全角スペースが入っていないといけないかもしれないし、戸籍法で仕様が禁止されている漢字は拒否したいかもしれない。
私はそういったロジックをUserクラスにダラダラ書いて、知らず知らずのうちに責務の範囲を広げてしまう。
コードを書く前にしっかりクラス設計できる人はこんなことないのかもしれないが、私のような人は「インスタンス変数に編集する前にバリデーションしている」ことを、値オブジェクトとして切り出せるサインとして覚えておくといいかも、という提案。
なぜバリデーションしたくなったのだろう
単に受け取ったstringをそのまま編集することは気持ち悪いからこういったコードにしたいわけだが、なんで気持ち悪いんだろうか?
それは、「Userのnameは単なるstringであればいいのではなく、ドメイン上の意味・制約を伴った値である」という示唆だと思う。
なんでもいいならバリデーションしなくていい。
バリデーションをしたいなら、なんでもよくはない。
なので、値オブジェクトを使ってこんな感じに分割することを考える。
class UserName
attr_reader :value
def initialize(value)
raise ArgumentError, '名前が長すぎます' if value.length > 20
@value = value
end
end
class UserGood
attr_reader :name
def initialize(name)
@name = name
end
end
name = UserName.new('Suzuki')
UserGood.new(name)
おわり
コードレビューで「ここでごちゃごちゃバリデーションするならVOにしたほうがよくない?」というような指摘を受け、確かにと思った。
あくまで「示唆」であって全部が全部切り出したほうがいいわけではないだろうが、思考のフックとして今後持っておきたい。
Discussion
過去の経験、感情、価値観、意見から成る素晴らしいリフレクション