--- 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; 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 Promise> = { 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) => 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 ; } ``` ### 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 = { 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` 만 — `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 의 정리 |