1. 導入:なぜデータ定義の「正規化」が重要なのか
プログラミングにおいて、私たちは日々「状態」を管理しています。しかし、設計が甘いと「あり得ない状態(例:開始日がないのに終了日がある)」を表現できてしまい、実行時エラーや予期せぬバグの温床になります。データ定義の正規化とは、「不正な状態を表現不可能にする」ための設計指針です。これにより、複雑なバリデーションコードを書く手間が省け、堅牢なシステムを構築できます。
2. 基礎知識:型の正規化とは何か
DBの正規化が「テーブルの重複を排除する」ことであるのと同様に、型の正規化は「型システム上で重複や矛盾を排除する」ことです。
例えば、Webフォームの入力をそのままデータクラスに詰め込むのではなく、プログラム内で扱う際は、論理的に正しい状態だけを保持する型に変換します。これにより、ビジネスロジックは「データが正しいか」を毎回チェックする必要から解放されます。
3. 実装・解決策:独立した状態を分ける
正規化の第一歩は「冗長な情報を削る」ことです。例えば、ある期間を表すデータで、「開始日」と「終了日」と「期間(日数)」をすべて持つと、値の整合性が崩れるリスクがあります。これらを「開始日」と「期間」だけに絞る、あるいは「期間」を表す専用の型を定義することで、データの整合性を型レベルで保証します。
4. サンプルプログラム
以下は、Scalaによる「正規化されたデータ定義」の例です。不正な状態を作れない工夫を施しています。
// 不正な状態を作れないよう、コンストラクタを隠蔽し、ファクトリメソッド経由にする
case class Period private (start: java.time.LocalDate, days: Int)
object Period {
// 不正な期間(マイナス日数など)を許容しないための正規化ロジック
def create(start: java.time.LocalDate, days: Int): Option[Period] = {
if (days >= 0) Some(new Period(start, days))
else None // 不正な状態は生成させない
}
}
// 利用例
val validPeriod = Period.create(java.time.LocalDate.now(), 5)
// val invalidPeriod = Period.create(java.time.LocalDate.now(), -1) // これだとNoneが返り、不正なインスタンスは作られない
5. 応用・注意点:現場での活用と落とし穴
正規化の究極は、「正しい状態しかコンパイルできない型」を作ることです。例えば、ユーザーの権限を文字列(String)で管理せず、列挙型(Enum)や直和型(Sum Type)にすることで、未知の権限が混入するバグを未然に防げます。
ただし、過剰な正規化は、ドメインモデルを複雑にしすぎるリスクもあります。現場では「そのデータ定義が、現在のビジネスルールにおいて不正な状態を許容していないか?」という視点を持ち、必要最小限の複雑さで正規化を行うのが、最もコストパフォーマンスの良い設計です。

コメント