--- id: pure-functions-in-practice title: 순수 함수 실전 (Pure Functions in Practice) category: Coding status: draft canonical_id: pure-functions-in-practice aliases: [pure function, side-effect-free, referential transparency, 참조 투명성] duplicate_of: null source_trust_level: C 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, functional, testability, refactoring, vibe-coding] raw_sources: ["P-Reinforce session 2026-05-09 — bulk Coding seed batch 1"] tech_stack: language: "TypeScript / JavaScript / Python" applicable_to: ["모든 도메인"] applied_in: [] --- # 순수 함수 실전 > 부작용을 함수의 가장자리로 밀어내고, 핵심 로직은 입력 → 출력 매핑으로만 만든다. 이게 테스트와 리팩터링을 가장 싸게 만든다. ## 📖 핵심 개념 **순수 함수**의 두 조건: 1. 같은 입력에는 항상 같은 출력 (참조 투명성) 2. 외부 상태에 부작용을 주지 않음 (no side effects) 실전에서는 "100% 순수" 보다 **순수 영역과 비순수 영역의 경계를 명확히 그리는 것**이 중요하다. 핵심 패턴: **"불순한 껍데기, 순수한 알맹이(impure shell, pure core)"**. - 함수 가장자리 (HTTP, DB, 시간, 랜덤, 로그) → 불순 - 핵심 비즈니스 로직 → 순수 - 불순한 부분이 순수한 부분에 데이터를 넘겨주고, 결과를 다시 받아 부작용을 처리 ## 💻 코드 패턴 **나쁜 예 — 비즈니스 로직과 부작용이 섞임**: ```ts async function processOrder(orderId: string) { const order = await db.findOrder(orderId); if (order.total > 100) { order.discount = order.total * 0.1; } order.processedAt = new Date(); await db.saveOrder(order); await sendEmail(order.customerId, '주문이 처리되었습니다'); return order; } ``` 이 함수는 테스트하려면 DB 모킹 + 시간 모킹 + 이메일 모킹이 모두 필요. **Impure Shell + Pure Core**: ```ts // 순수 — 입출력만 있음 export function applyDiscount(order: Order): Order { if (order.total <= 100) return order; return { ...order, discount: order.total * 0.1 }; } export function markProcessed(order: Order, now: Date): Order { return { ...order, processedAt: now }; } // 불순한 껍데기 — 외부 시스템과 통신하는 곳 async function processOrder(orderId: string) { const order = await db.findOrder(orderId); const discounted = applyDiscount(order); const finalized = markProcessed(discounted, new Date()); await db.saveOrder(finalized); await sendEmail(finalized.customerId, '주문이 처리되었습니다'); return finalized; } ``` 이제 `applyDiscount` 와 `markProcessed` 는 모킹 없이 단위 테스트 가능. `processOrder` 는 통합 테스트. ## 🤔 의사결정 기준 | 상황 | 순수 함수 강제 | 불순 허용 | |---|---|---| | 비즈니스 규칙 (할인, 검증, 변환) | ✅ | ❌ | | 외부 시스템 호출 (DB, HTTP, FS) | ❌ | ✅ | | 시간/랜덤 의존 | ❌ (파라미터로 주입) | — | | 캐시/메모이제이션 적용 대상 | ✅ | ❌ | | 동시성 안전 필요 | ✅ | — | ## ❌ 안티패턴 - **숨은 시간 의존**: 함수 본문에 `new Date()` / `Date.now()` 직접 호출. 같은 입력에도 다른 결과 → 비순수. 시간을 파라미터로 주입. - **숨은 랜덤**: `Math.random()` 직접 호출. 시드를 받거나 randomFn을 주입. - **공유 가변 상태 수정**: `globalCache.set(key, value)` 같은 숨은 mutation. 함수 시그니처가 거짓말을 한다. - **순수 함수 안에서 console.log**: 디버깅 흔적. 출력은 호출자 책임. - **"semi-pure"**: input을 직접 mutate하면서 return하기. 호출자가 alias를 가지면 사고. 항상 새 객체 반환. ## 🤖 LLM 활용 힌트 - LLM에게 비즈니스 로직 작성을 시킬 때 "**시간 / 랜덤 / DB / HTTP 사용 금지, 필요하면 파라미터로 받아라**" 라고 못 박기. 그러면 자연스럽게 순수 함수가 나옴. - 기존 함수 리팩터링: "**부작용을 함수 양 끝으로 밀어내고, 가운데는 순수하게**" 패턴 명시. - 테스트 작성: "**모킹 없이 테스트할 수 있는 형태로 분리해줘**" 라고 요청 → 자동으로 shell/core 분리. ## 🧪 검증 상태 - verification_status: `conceptual` - functional core / imperative shell 패턴 (Gary Bernhardt) 의 변형. - 적용 사례 발견 시 `applied_in` 추가. ## 🔗 관련 문서 - [[Guard_Clauses]] - [[Defensive_Copying]] - [[Dependency_Inversion_in_Practice]]