---
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]]
