「オブジェクト指向でコードが書けるようになろう」に参加して来ました
「オブジェクト指向でコードが書けるようになろう」に参加して来ました。
会を通して考えることがあったのでメモ。
設問
内容は、事前に出題されていたCodeIQの設問「クラス名を考えてみよう」について数人で議論しました。
設問は以下(いろいろはしょってます)。
あるWebサービスの会員登録の画面で、氏名の入力が必要です。
氏名は20文字以内である必要があります。
この入力チェックを実現するためのクラスを考えてください。
氏名をクラスにするか?
議論になったところは、氏名をUserNameのような形でクラス化するか、組み込みの文字列型として持つか、というところ。
ユーザー名について、クラス化した場合とそうでない場合について考えてみます。
(ここでいうクラスとは、フィールドに氏名を表す文字列型を持ち、20文字制約のロジックを実装しているクラス。クラス化しない場合は、XXXValidatorクラスを作って氏名を利用する側がバリデーションを行う。私は後者を選択しました。)
クラス化のメリット
クラス化するメリットは、
- バリデーションロジックが複数箇所に出てきた場合でもクラスにバリデーションロジックが隠蔽されているので重複することはない。
- ミドルネームやラストネームといった新たな仕様が氏名に持ち込まれた場合、対応しやすい。
という主張がありました。これは納得です。
クラス化しないメリット
私ももちろん、設計時にはこれらは想定しましたが、クラス化しないほうを選択した理由として、
- 要求の単純さからして、わざわざ会員の1属性である氏名のようなものをクラス化するのは冗長だし複雑性を持ち込む。
- 実際にロジックが複雑になったり重複が発生したら、その時ベストな対応をしたら良い。
があります。
「柔軟性は必要な箇所のみ」という原則はコードをシンプルに保つ上で大事だと思っているので、クラス化しないほうが後々保守する際にも良いだろうと考えました。
再考
が、よくよく考えてみると、議論のあとの増田さんのフィードバックでもあったとおり、
「必要になったら柔軟性を持ち込む」
という選択は1つの有効な選択ではありますが、いざ柔軟性が必要になったタイミングが出てきても、時間の都合やそのときに実装を行うエンジニアの能力次第では対応できなっかたりする、というのが私のこれまでの実践感覚です。
また、設計の一貫性が崩れる(他のフィールドはバリデータクラスでバリデートしてるのに、ユーザー名だけユーザー名クラスでバリデートすることになっちゃう)という問題もあると思いました。
増田さんのスタイルは、どうせ必要になった時にちゃんと対応できないんだったら最初から用意しておけ、だそうです。
ただ、だからといってじゃあフィールド1個1個をクラスにしてしまって複雑になったり可読性が落ちてしまったりしないかという不安もあります。(やったことないのでどうなるかわからない)
まぁこの辺のバランス感覚はコンテキストによって変わってきそうですが、増田さんのフィードバックや自分のこれまでの経験を踏まえると、「そのデータ対して1つでもロジックが入った場合はクラス化する」という方針がいいのかな、と思いつつあるところです。
追記
社内でこの話をしたら、「UserNameでバリデーションする場合、バリデーションメッセージどうするの?」という疑問があがりました。
どうしましょうかね?
例外方式
UserNameがプレゼン層にいるオブジェクトならまだしも、ここでは(多分)ドメイン層にいる前提なので、画面表示用のメッセージ返すわけにはいきません。
思いついたのは、エラーコードとパラメーター(ここでは20という数字)を情報に含んだ例外を飛ばして、プレゼン側でエラーコードから適切なバリデーションメッセージ(「氏名は{max_length}文字以内で入力してください」とか)を取得し、パラメータでプレースホルダーになっているところを埋める、という手です。
プレゼンの変更にモデルが影響を受けてしまう
上記の例外方式を採用したとしましょう。
ここで、例えばパスワードのチェックで以下の様な仕様があるとしましょう。
パスワードは半角英数字と記号の組み合わせ。
違反した場合は画面に「半角英数字と記号の組み合わせを指定してください」と表示する。
この仕様をパスワード文字列とバリデーションロジックを持つPasswordクラスとして実装しました。違反した場合は、英数と記号のどちらかが欠けていることを表すエラーコードを情報として保持した例外を投げます。
ところが実装後、以下の様な仕様変更がありました。
英数字を含んでいなかった場合は「半角英数字が含まれていません。」というメッセージを出し、記号がない場合は「記号が含まれていません。」というメッセージを出す。
この場合、Passwordクラス内のバリデーションロジックで記号が含まれていない場合と英数字が含まれていない場合を別々に判定しなければいけなくなるので修正が入ります。
つまり、画面メッセージの変更がモデルに影響しているわけで、これはちょっと頂けないなと感じます。
プレゼンのバリデーションとモデルのバリデーション
ここまで書いてて、プレゼンでのバリデーションと、モデルでのバリデーションを同じものとして考えないほうがいいんではないか、という気がしてきました。
つまり、モデル層のPasswordとは別に、プレゼン層にPasswordInputというクラスを定義し、そこに入力されたパスワードとバリデーションロジックを実装する、という形式です。
ただ、バリデーションロジックが重複してしまう気がする。うーん。