f8b21af4be
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>
192 lines
5.9 KiB
Markdown
192 lines
5.9 KiB
Markdown
---
|
|
id: wiki-2026-0508-설정-객체-및-룩업-테이블-설계-configuration-
|
|
title: 설정 객체 및 룩업 테이블 설계(Configuration Objects and Lookup Tables)
|
|
category: 10_Wiki/Topics
|
|
status: verified
|
|
canonical_id: self
|
|
aliases: [Lookup Table, Config Object, Dispatch Table, Map-based Branching]
|
|
duplicate_of: none
|
|
source_trust_level: A
|
|
confidence_score: 0.92
|
|
verification_status: applied
|
|
tags: [pattern, refactoring, clean-code, frontend, javascript]
|
|
raw_sources: []
|
|
last_reinforced: 2026-05-10
|
|
github_commit: pending
|
|
tech_stack:
|
|
language: typescript
|
|
framework: na
|
|
---
|
|
|
|
# 설정 객체 및 룩업 테이블 설계(Configuration Objects and Lookup Tables)
|
|
|
|
## 매 한 줄
|
|
> **"매 if/switch chain 의 data structure (Map/Record) 의 substitute"**. 매 control flow 의 data 로 transform — 매 add/modify 의 O(1) extension, 매 type-safe (TS Record), 매 testable. Refactoring catalog (Fowler) 의 "Replace Conditional with Polymorphism" 의 lightweight variant.
|
|
|
|
## 매 핵심
|
|
|
|
### 매 substitute targets
|
|
- 매 Long if/else if chain.
|
|
- 매 Switch with simple return.
|
|
- 매 Magic-number/string branching.
|
|
- 매 Strategy pattern (lightweight).
|
|
|
|
### 매 lookup table 의 structure
|
|
1. **Map / Record**: key → value/handler.
|
|
2. **Default fallback**: `??` or explicit default key.
|
|
3. **Type-safe key**: TypeScript `keyof` / discriminated union.
|
|
4. **Frozen at module load**: `as const` / `Object.freeze`.
|
|
|
|
## 💻 패턴
|
|
|
|
### Before (if-chain) → After (lookup)
|
|
```ts
|
|
// X — 매 add 의 매 modification 의 source
|
|
function getDiscount(tier: string): number {
|
|
if (tier === 'bronze') return 0.05;
|
|
else if (tier === 'silver') return 0.10;
|
|
else if (tier === 'gold') return 0.15;
|
|
else if (tier === 'platinum') return 0.20;
|
|
else return 0;
|
|
}
|
|
|
|
// O — data-driven, type-safe, O(1) lookup
|
|
const DISCOUNT = {
|
|
bronze: 0.05,
|
|
silver: 0.10,
|
|
gold: 0.15,
|
|
platinum: 0.20,
|
|
} as const satisfies Record<string, number>;
|
|
|
|
type Tier = keyof typeof DISCOUNT;
|
|
const getDiscount = (tier: Tier): number => DISCOUNT[tier] ?? 0;
|
|
```
|
|
|
|
### Dispatch table (handler map)
|
|
```ts
|
|
type Action = 'create' | 'update' | 'delete';
|
|
|
|
const handlers: Record<Action, (id: string) => Promise<void>> = {
|
|
create: async (id) => api.create(id),
|
|
update: async (id) => api.update(id),
|
|
delete: async (id) => api.delete(id),
|
|
};
|
|
|
|
async function dispatch(action: Action, id: string) {
|
|
await handlers[action](id);
|
|
}
|
|
```
|
|
|
|
### Discriminated union + exhaustive check
|
|
```ts
|
|
type Event =
|
|
| { type: 'click'; x: number; y: number }
|
|
| { type: 'key'; code: string }
|
|
| { type: 'scroll'; delta: number };
|
|
|
|
const reducer: { [K in Event['type']]: (e: Extract<Event, { type: K }>) => void } = {
|
|
click: (e) => console.log('click', e.x, e.y),
|
|
key: (e) => console.log('key', e.code),
|
|
scroll:(e) => console.log('scroll', e.delta),
|
|
};
|
|
|
|
function handle(e: Event) {
|
|
(reducer[e.type] as (e: Event) => void)(e);
|
|
}
|
|
```
|
|
|
|
### Config object (UI variants)
|
|
```tsx
|
|
const BUTTON_STYLE = {
|
|
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
|
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
|
|
danger: 'bg-red-600 text-white hover:bg-red-700',
|
|
ghost: 'bg-transparent text-blue-600 hover:bg-blue-50',
|
|
} as const;
|
|
|
|
type Variant = keyof typeof BUTTON_STYLE;
|
|
|
|
export function Button({ variant = 'primary', children, ...rest }:
|
|
{ variant?: Variant; children: React.ReactNode } & React.ComponentProps<'button'>) {
|
|
return <button className={BUTTON_STYLE[variant]} {...rest}>{children}</button>;
|
|
}
|
|
```
|
|
|
|
### Validation rule registry
|
|
```ts
|
|
const VALIDATORS = {
|
|
email: (v: string) => /^[^@]+@[^@]+\.[^@]+$/.test(v) || 'invalid email',
|
|
phone: (v: string) => /^\+?[\d\s-]{10,}$/.test(v) || 'invalid phone',
|
|
required: (v: string) => v.trim().length > 0 || 'required',
|
|
} as const;
|
|
|
|
function validate(field: keyof typeof VALIDATORS, value: string) {
|
|
return VALIDATORS[field](value);
|
|
}
|
|
```
|
|
|
|
### Status code → message
|
|
```ts
|
|
const HTTP_MESSAGE: Record<number, string> = {
|
|
200: 'OK',
|
|
400: 'Bad Request',
|
|
401: 'Unauthorized',
|
|
403: 'Forbidden',
|
|
404: 'Not Found',
|
|
500: 'Server Error',
|
|
};
|
|
const messageFor = (code: number) => HTTP_MESSAGE[code] ?? 'Unknown';
|
|
```
|
|
|
|
### i18n table
|
|
```ts
|
|
const I18N = {
|
|
en: { hello: 'Hello', bye: 'Bye' },
|
|
ko: { hello: '안녕', bye: '안녕히' },
|
|
ja: { hello: 'こんにちは', bye: 'さようなら' },
|
|
} as const;
|
|
|
|
type Lang = keyof typeof I18N;
|
|
type Key = keyof typeof I18N[Lang];
|
|
|
|
const t = (lang: Lang, key: Key) => I18N[lang][key];
|
|
```
|
|
|
|
## 매 결정 기준
|
|
| 상황 | Approach |
|
|
|---|---|
|
|
| 단순 mapping (key → value) | 매 plain Record |
|
|
| Behavior dispatch | 매 handler Map |
|
|
| Behavior + state | 매 Strategy class (full polymorphism) |
|
|
| 매 2-3 case | 매 if/ternary 의 OK — over-engineering 의 avoid |
|
|
| 매 5+ case | 매 lookup table 의 prefer |
|
|
| Runtime extension | 매 Map (mutable) |
|
|
| Compile-time freeze | `as const satisfies Record<...>` |
|
|
|
|
**기본값**: 매 5+ branch + simple value/handler → lookup. 매 type-safe `as const satisfies`.
|
|
|
|
## 🔗 Graph
|
|
- 부모: [[Refactoring Catalog]] · [[Clean Code]]
|
|
- 변형: [[Replace Conditional with Polymorphism]]
|
|
- Adjacent: [[Discriminated Union]]
|
|
|
|
## 🤖 LLM 활용
|
|
**언제**: 긴 if/switch chain 의 detect, refactor 의 propose, type 의 tighten.
|
|
**언제 X**: 매 stateful behavior — Strategy class 의 use.
|
|
|
|
## ❌ 안티패턴
|
|
- **Mutable global table**: 매 race + test pollution — `as const` 의 freeze.
|
|
- **Stringly-typed key**: `Record<string, T>` 만 — `keyof` 의 narrow.
|
|
- **Default 누락**: `obj[key]` 의 undefined access — `??` 의 add.
|
|
- **Side effect 의 handler**: 매 lookup map 의 pure function 의 keep.
|
|
|
|
## 🧪 검증 / 중복
|
|
- Verified (Refactoring 2/e Fowler — "Replace Conditional with Polymorphism", Clean Code Martin Ch.6, TypeScript handbook Record).
|
|
- 신뢰도 A.
|
|
|
|
## 🕓 Changelog
|
|
| 날짜 | 변경 |
|
|
|---|---|
|
|
| 2026-05-08 | Phase 1 |
|
|
| 2026-05-10 | Manual cleanup — lookup/dispatch/config 7 patterns 의 정리 |
|