"매 string/int이 매 domain concept를 매 가장한다". Primitive Obsession은 매 UserId, Email, Money 같은 매 domain concept를 매 raw str/int로 매 표현해 매 type system이 매 invariant 보장 못 하는 매 code smell. Fowler "Refactoring" (1999, 2nd ed. 2018) 의 매 classic smell — 매 modern fix는 매 newtype / value object.
매 핵심
매 증상
매 String 폭주: 매 email: str, phone: str, country_code: str 매 swap 가능.
매 Magic numbers: 매 status: int = 3 매 의미 불명.
매 Validation duplication: 매 every callsite마다 매 if "@" in email.
매 Type confusion: 매 transfer(from_id, to_id) 매 인자 swap 매 컴파일러 못 잡음.
매 Fix 전략
매 Newtype: Rust struct UserId(u64);.
매 Value Object: DDD 의 매 Email, Money immutable class.
매 Branded type: TypeScript type UserId = string & { __brand: "UserId" }.
매 NewType pattern: Python typing.NewType("UserId", int).
매 응용
Domain modeling (DDD) — Bounded context의 매 first-class concept.
Money handling (currency + amount tied).
Identifier safety (UserId vs OrderId mix-up 방지).
💻 패턴
Python NewType + dataclass
fromtypingimportNewTypefromdataclassesimportdataclassfromdecimalimportDecimalUserId=NewType("UserId",int)OrderId=NewType("OrderId",int)@dataclass(frozen=True)classMoney:amount:Decimalcurrency:strdef__post_init__(self):ifself.amount<0:raiseValueError("negative")iflen(self.currency)!=3:raiseValueError("ISO-4217")deftransfer(src:UserId,dst:UserId,m:Money):...# transfer(OrderId(1), UserId(2), Money(...)) # 매 mypy error
# Before — magic int statusclassOrder:status:int# 0=pending, 1=paid, 2=shipped# After — sealed statesclassOrderStatus:passclassPending(OrderStatus):passclassPaid(OrderStatus):passclassShipped(OrderStatus):tracking:str
매 결정 기준
상황
Approach
Domain identifier
Newtype
Domain value (Money, Email)
Value Object
매 enum-like int code
Sealed subclass / Enum
Throwaway script
매 raw primitive OK
기본값: Newtype for IDs, Value Object for domain values.