Files
2nd/10_Wiki/Topics/Architecture/Primitive Obsession (기본 타입 집착).md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

151 lines
4.6 KiB
Markdown

---
id: wiki-2026-0508-primitive-obsession-기본-타입-집착
title: Primitive Obsession (기본 타입 집착)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Primitive Obsession, 기본 타입 집착, stringly-typed]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [refactoring, code-smell, type-system, ddd]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: python-typescript-rust
framework: refactoring
---
# Primitive Obsession (기본 타입 집착)
## 매 한 줄
> **"매 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)`.
### 매 응용
1. Domain modeling (DDD) — Bounded context의 매 first-class concept.
2. Money handling (currency + amount tied).
3. Identifier safety (UserId vs OrderId mix-up 방지).
## 💻 패턴
### Python NewType + dataclass
```python
from typing import NewType
from dataclasses import dataclass
from decimal import Decimal
UserId = NewType("UserId", int)
OrderId = NewType("OrderId", int)
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if self.amount < 0: raise ValueError("negative")
if len(self.currency) != 3: raise ValueError("ISO-4217")
def transfer(src: UserId, dst: UserId, m: Money): ...
# transfer(OrderId(1), UserId(2), Money(...)) # 매 mypy error
```
### Rust newtype
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(u64);
#[derive(Debug, Clone, Copy)]
pub struct OrderId(u64);
fn fetch_user(id: UserId) { /* ... */ }
// fetch_user(OrderId(7)); // 매 compile error
```
### TypeScript branded types
```typescript
type Brand<T, B> = T & { readonly __brand: B };
type Email = Brand<string, "Email">;
type UserId = Brand<number, "UserId">;
function parseEmail(s: string): Email {
if (!/^[^@]+@[^@]+$/.test(s)) throw new Error("invalid");
return s as Email;
}
```
### Value object with invariant
```python
@dataclass(frozen=True)
class Email:
value: str
def __post_init__(self):
if "@" not in self.value: raise ValueError("invalid email")
object.__setattr__(self, "value", self.value.lower())
```
### Refactor: Replace Type Code with Subclass
```python
# Before — magic int status
class Order:
status: int # 0=pending, 1=paid, 2=shipped
# After — sealed states
class OrderStatus: pass
class Pending(OrderStatus): pass
class Paid(OrderStatus): pass
class Shipped(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.
## 🔗 Graph
- 부모: [[Code-Smell]] · [[Refactoring_Best_Practices|Refactoring]]
- 변형: [[Value-Object]] · [[Branded-Types]]
- 응용: [[DDD]] · [[Type-Safety]]
- Adjacent: [[Stringly-Typed]]
## 🤖 LLM 활용
**언제**: domain model 설계, 매 API boundary type 선택, 매 refactoring 제안.
**언제 X**: 매 1-time script.
## ❌ 안티패턴
- **매 Stringly-typed everything**: 매 모든 domain concept를 매 str.
- **매 Validation lazy**: 매 boundary 통과 후 매 raw str 그대로 흘림.
- **매 Tuple as struct**: 매 `(int, str, bool)` 매 의미 모름.
- **매 매 시점 validation**: 매 매번 caller가 매 validate 수행.
## 🧪 검증 / 중복
- Verified (Fowler "Refactoring" 2nd ed. 2018, Evans "DDD" 2003).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — newtype/value-object patterns + refactoring guide |