149 lines
5.3 KiB
Markdown
149 lines
5.3 KiB
Markdown
---
|
|
id: null-safety-patterns
|
|
title: Null 안전 패턴 (Null Safety Patterns)
|
|
category: Coding
|
|
status: draft
|
|
canonical_id: null-safety-patterns
|
|
aliases: [optional, maybe, nullable, undefined-handling, NPE]
|
|
duplicate_of: null
|
|
source_trust_level: B
|
|
confidence_score: 0.85
|
|
verification_status: conceptual
|
|
created_at: 2026-05-09
|
|
updated_at: 2026-05-09
|
|
last_reinforced: 2026-05-09
|
|
review_reason: ""
|
|
merge_history: []
|
|
tags: [coding, type-safety, null, undefined, vibe-coding]
|
|
raw_sources: ["P-Reinforce session 2026-05-09 — bulk Coding seed batch 1"]
|
|
tech_stack:
|
|
language: "TypeScript / Kotlin / Rust / Swift"
|
|
applicable_to: ["모든 도메인"]
|
|
applied_in: []
|
|
---
|
|
|
|
# Null 안전 패턴
|
|
|
|
> Null/undefined 는 "값이 없음"이라는 정보를 담지 못하는 가장 빈약한 표현. **타입에서 nullable 을 명시적으로 분리**하고, **언래핑 지점을 명확히** 만들어라.
|
|
|
|
## 📖 핵심 개념
|
|
|
|
Tony Hoare가 "the billion dollar mistake" 라고 부른 null 참조 문제. 모던 언어들은 다음 중 하나로 대응:
|
|
|
|
1. **타입 시스템에서 nullable 분리**: TypeScript `T | null`, Kotlin `T?`, Swift `T?`
|
|
2. **Optional 타입**: Rust `Option<T>`, Haskell `Maybe a`
|
|
3. **Strict mode**: TS의 `strictNullChecks`, Kotlin 디폴트, Swift 디폴트
|
|
|
|
핵심은 "이 값은 없을 수 있다"를 **타입이 명시적으로 표현하고 컴파일러가 강제**하는 것.
|
|
|
|
## 💻 코드 패턴
|
|
|
|
### 1. nullable 명시 (TypeScript with strictNullChecks)
|
|
|
|
```ts
|
|
// ❌ 모호함 — name 이 항상 있는지 가끔 없는지 모름
|
|
function greet(user: { name: string }) { return `Hi, ${user.name}`; }
|
|
|
|
// ✅ 명시
|
|
function greet(user: { name: string | null }) {
|
|
if (user.name === null) return 'Hi, friend';
|
|
return `Hi, ${user.name}`;
|
|
}
|
|
```
|
|
|
|
### 2. Optional Chaining + Nullish Coalescing
|
|
|
|
```ts
|
|
// 깊은 경로 안전 접근
|
|
const city = order?.shipping?.address?.city ?? 'Unknown';
|
|
|
|
// ❌ 주의: || 는 falsy("" / 0) 도 fallback 으로 처리
|
|
const port = config.port || 3000; // port가 0이면 3000 됨!
|
|
const port = config.port ?? 3000; // null/undefined 만 fallback ✓
|
|
```
|
|
|
|
### 3. 타입 가드 + 좁히기
|
|
|
|
```ts
|
|
function isPresent<T>(x: T | null | undefined): x is T {
|
|
return x !== null && x !== undefined;
|
|
}
|
|
|
|
const items = users.map(u => u.email).filter(isPresent); // string[]
|
|
```
|
|
|
|
### 4. Maybe / Option 모나드 패턴 (TS)
|
|
|
|
```ts
|
|
type Some<T> = { kind: 'some'; value: T };
|
|
type None = { kind: 'none' };
|
|
type Maybe<T> = Some<T> | None;
|
|
|
|
const some = <T>(value: T): Maybe<T> => ({ kind: 'some', value });
|
|
const none = (): Maybe<never> => ({ kind: 'none' });
|
|
|
|
function map<T, U>(m: Maybe<T>, f: (t: T) => U): Maybe<U> {
|
|
return m.kind === 'none' ? m : some(f(m.value));
|
|
}
|
|
function getOr<T>(m: Maybe<T>, fallback: T): T {
|
|
return m.kind === 'some' ? m.value : fallback;
|
|
}
|
|
```
|
|
|
|
복잡한 변환 체인이 많을 때만 도입. 단순한 `T | null` 로 충분한 경우엔 over-engineering.
|
|
|
|
### 5. Non-null assertion (`!`) 의 좁은 사용
|
|
|
|
```ts
|
|
const el = document.getElementById('root')!; // OK if you control the HTML
|
|
|
|
// ❌ 위험
|
|
function findUser(id: string) {
|
|
return users.find(u => u.id === id)!; // 못 찾으면 런타임 사고
|
|
}
|
|
|
|
// ✅
|
|
function findUser(id: string): User {
|
|
const u = users.find(u => u.id === id);
|
|
if (!u) throw new NotFoundError(id);
|
|
return u;
|
|
}
|
|
```
|
|
|
|
## 🤔 의사결정 기준
|
|
|
|
| 시그니처 | 의미 | 사용 시점 |
|
|
|---|---|---|
|
|
| `T` | 항상 값 있음 | 강한 보장이 필요할 때 (도메인 invariant) |
|
|
| `T \| null` | 값이 의도적으로 없을 수 있음 | "선택 사항" 의미 명확할 때 |
|
|
| `T \| undefined` | 아직 초기화 안 됨 / 키 없음 | object property optional 표현 |
|
|
| `T \| null \| undefined` | 둘 다 가능 | 외부 데이터 (API 응답) — 즉시 정규화 권장 |
|
|
| `Maybe<T> / Option<T>` | 변환 체이닝이 잦음 | 복잡한 nullable pipeline |
|
|
|
|
## ❌ 안티패턴
|
|
|
|
- **`any` 로 회피**: 타입 시스템 우회. 컴파일은 통과하지만 런타임 폭사.
|
|
- **Non-null assertion 남발 (`x!.y!.z!`)**: 컴파일러를 침묵시키지만 보장 없음. 매번 가드 또는 스키마 검증.
|
|
- **`||` 로 falsy 까지 fallback**: 0, "", false 가 의미 있는 값일 때 사고. `??` 사용.
|
|
- **null 과 undefined 둘 다 보내기**: 한 코드베이스에서 한 컨벤션 (보통 `undefined` 권장 — JSON 직렬화 시 사라져 명시적).
|
|
- **null check 잊고 nested access**: `obj.a.b.c` 가 `obj.a` null 일 때 폭사. optional chaining `?.` 사용.
|
|
- **외부 API 응답을 그대로 nullable 채로 도메인 깊숙이 전파**: 경계에서 zod / io-ts 같은 스키마로 정규화.
|
|
|
|
## 🤖 LLM 활용 힌트
|
|
|
|
- LLM에게 함수 작성: "**strictNullChecks 가정. 가능한 nullable 은 시그니처에 명시**" 라고 요청.
|
|
- 외부 API 파싱: "**zod 같은 라이브러리로 nullable 을 즉시 도메인 타입으로 정규화**" 명시.
|
|
- TypeScript 옵션: 프로젝트 `tsconfig.json` 에 `"strict": true` 또는 최소 `"strictNullChecks": true` 가 있어야 이 모든 패턴이 의미 있음.
|
|
|
|
## 🧪 검증 상태
|
|
|
|
- verification_status: `conceptual`
|
|
- Kotlin / Swift / Rust 의 default null safety 모델, TypeScript strictNullChecks 가 표준.
|
|
- 적용 사례 발견 시 `applied_in` 추가.
|
|
|
|
## 🔗 관련 문서
|
|
|
|
- [[Guard_Clauses]]
|
|
- [[Tagged_Union_Discriminated_Types]]
|
|
- [[Error_Handling_Result_vs_Throw]]
|