はじめに
基本データ型への執着(Primitive Obsession) とは、文字列・数値・真偽値といった「プリミティブ型(基本型)」を乱用し、
本来は専用のオブジェクトや値型で表現すべき概念を、単なる基本型で済ませてしまう悪臭です。
例:
- 電話番号をただの
Stringで表す - 金額をただの
Doubleで表す - ユーザー権限をただの
Intで表す
→ 意味が不明確になり、バリデーションやロジックが分散する ことが問題です。
9.1 特徴
-
ドメイン概念(電話番号、住所、金額など)をすべて
StringやIntで済ませてしまう - マジックナンバー/マジック文字列 が散在する
- バリデーションや変換ロジックがあちこちに重複
- 引数の型が同じため、誤って引き渡してもコンパイル時に検出できない
9.2 解決手法
-
値オブジェクト(Value Object)の導入
→ 専用クラスで意味を明確化し、型安全性を高める - 列挙型(Enum)で状態や区分を表現
- 型エイリアス(Typealias) を活用し意図を明確にする
- バリデーションを値オブジェクトに閉じ込める
9.3 Kotlin 例
Before:プリミティブ乱用
fun transferMoney(fromAccount: String, toAccount: String, amount: Double) {
require(amount > 0) { "Amount must be positive" }
println("Transferring $amount from $fromAccount to $toAccount")
}
fun setUserRole(userId: String, role: Int) {
if (role == 1) println("Admin") else println("User")
}
- 口座番号もただの
String - 金額もただの
Double - 役割もただの
Int
→ すべて 意味不明なプリミティブ になっている。
After:値オブジェクト導入
@JvmInline
value class AccountNumber(val value: String) {
init { require(value.matches(Regex("\\d{10}"))) { "Invalid account number" } }
}
@JvmInline
value class Money(val value: Double) {
init { require(value > 0) { "Amount must be positive" } }
}
enum class UserRole { ADMIN, USER }
fun transferMoney(from: AccountNumber, to: AccountNumber, amount: Money) {
println("Transferring ${amount.value} from ${from.value} to ${to.value}")
}
fun setUserRole(userId: String, role: UserRole) {
println("Role of $userId is $role")
}
-
AccountNumberによって 10桁制約 を強制 -
Moneyによって 金額の正数制約 を強制 -
UserRoleによって 安全な状態遷移 を保証
→ 型で表現することで「意味が伝わりやすく、誤用を防げる」。
9.4 実務での指針
- ドメイン固有の概念を プリミティブではなく専用クラス/値オブジェクト で表す
- 「型で表現できる制約は型に閉じ込める」
- 「マジックナンバー/マジック文字列」を排除し、Enumや定数で表現
- Kotlin では inline class / value class を活用してパフォーマンスコストなく型安全性を得る
まとめ
- 基本データ型への執着 は「型安全性の欠如」と「意味不明なコード」の温床
- 値オブジェクト / Enum / 型エイリアス でドメイン概念を明確にする
- 基本思想:「型はドキュメント」。型で意図を表現し、コンパイル時に誤用を防ぐ