Conceptly
← 전체 목록
λ

Pure Function

기반 원칙입력만으로 결과가 결정되는 함수

순수 함수는 주어진 입력만으로 결과가 결정되고, 외부 세계에 어떤 흔적도 남기지 않는 함수입니다. 같은 인자를 두 번 넣으면 반드시 같은 값을 돌려주고, 전역 변수나 파일, 네트워크 같은 외부 상태를 읽거나 바꾸지 않습니다. 함수형 프로그래밍에서 계산 로직을 가장 작게 믿을 수 있는 단위로 만들 때 자주 기준이 되는 개념입니다. 순수 함수가 많을수록 코드의 동작을 입력과 출력만으로 설명하고 추론하기 쉬워집니다.

아키텍처 다이어그램

🔍 구조 다이어그램

점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다

왜 필요한가요?

함수 하나가 무슨 일을 하는지 이해하려고 코드를 읽는데, 본문에서 전역 변수를 읽고 데이터베이스를 조회하고 캐시를 업데이트하고 로그를 쓰는 일이 뒤섞여 있으면 실제 반환 값이 무엇에 의존하는지 추적하기 어렵습니다. 같은 인자로 두 번 호출했는데 결과가 달라지면, 버그가 함수 안에 있는지 외부 상태에 있는지조차 판단하기 힘듭니다. 테스트를 작성할 때도 매번 환경을 준비하고 정리해야 해서 검증 비용이 올라갑니다. 결국 코드를 믿을 수 있는 단위로 쪼개려면, 함수의 결과가 외부에 의존하지 않는다는 최소한의 보장이 필요합니다.

왜 이런 방식이 등장했나요?

전역 상태를 바꾸면서 작업을 진행하는 명령형 스타일은 시스템이 작을 때는 큰 문제가 되지 않았습니다. 그러나 시스템이 커지고 스레드가 여러 개 돌아가기 시작하면서, 같은 변수를 여러 곳에서 수정하는 코드가 예측 불가능한 버그의 온상이 됐습니다. 동시성 문제, 디버깅 난이도, 테스트 어려움이 누적되자 업계는 상태 변경을 최소화하는 스타일에 주목하기 시작했습니다. Haskell, Clojure, Scala 같은 언어가 순수 함수를 중심 원리로 삼았고, JavaScript나 Python 같은 범용 언어에서도 React의 함수형 컴포넌트나 Redux 리듀서처럼 순수 함수를 적극적으로 도입하는 흐름이 생겼습니다. 순수 함수가 다시 주목받은 것은 취향의 문제가 아니라 큰 시스템을 안전하게 다루는 현실적인 전략이었기 때문입니다.

내부적으로 어떻게 동작하나요?

순수 함수의 내부를 들여다보면 두 가지 규칙만 지킵니다. 첫째, 결과는 오직 인자에만 의존합니다. 함수 밖의 변수, 현재 시각, 랜덤 값, 파일 내용을 읽지 않습니다. 둘째, 함수 바깥에 영향을 남기지 않습니다. 전역 변수를 바꾸지 않고, 콘솔에 출력하지 않으며, 네트워크 요청도 보내지 않습니다. 이 두 규칙이 지켜지면 참조 투명성이라는 강력한 성질이 따라옵니다. `add(2, 3)`을 코드 어디에서 호출해도 결과는 `5`이므로, 이 호출을 그냥 `5`로 바꿔도 프로그램 의미가 달라지지 않습니다. 이것이 순수 함수가 추론하기 쉽고 테스트하기 쉬운 근본 이유입니다. 함수의 동작을 이해하려면 그 함수의 시그니처(입력과 출력 타입)만 보면 됩니다.

코드로 보면

순수 함수 vs 불순 함수

// 순수 함수
function add(a, b) {
  return a + b;
}

// 불순 함수 — 외부 상태에 의존
let counter = 0;
function increment() {
  counter += 1;
  return counter;
}

// 불순 함수 — 외부 상태를 변경
function addItem(list, item) {
  list.push(item);  // 인자로 받은 배열을 수정
  return list;
}

// 순수 버전 — 새 배열을 반환
function addItemPure(list, item) {
  return [...list, item];
}

add는 인자만으로 결과가 정해지지만, increment는 외부 counter에 의존하고 이를 변경합니다. addItem은 인자로 받은 배열을 직접 수정해 호출자에게 영향을 주는데, addItemPure는 원본을 건드리지 않고 새 배열을 만들어 돌려줍니다.

경계와 구분

순수 함수와 불순 함수의 차이를 묻는 질문은 단순한 용어 구분이 아니라 프로그램을 어떻게 쪼개서 추론할지의 문제입니다. 순수 함수는 입력과 출력만으로 동작을 완전히 설명할 수 있어 테스트와 병렬화가 쉽고, 불순 함수는 외부 세계와 상호작용해야 하는 부분(파일 쓰기, 네트워크 요청, 시간 조회)을 실제로 수행합니다. 둘 중 어느 것이 '더 좋다'기보다는, 프로그램의 핵심 계산 로직은 순수 함수로 유지하고 외부 효과가 필요한 부분을 바깥쪽으로 몰아내는 설계 전략이 일반적입니다. 순수 함수로 계산하고 불순 영역에서 그 결과를 파일이나 DB에 쓰는 구조가 추론하기 쉽고 변경에도 강합니다.

트레이드오프

Gain 함수를 입력과 출력만으로 완전히 이해할 수 있어 테스트 작성, 디버깅, 리팩토링이 크게 쉬워지고 동시성 문제도 근본적으로 줄어듭니다. Cost 실제 애플리케이션은 어딘가에서 파일을 읽고, 네트워크를 호출하고, 상태를 저장해야 하므로 순수성을 유지하려면 효과가 있는 코드를 경계 영역으로 밀어내는 추가 설계 작업이 필요합니다. 단순한 작업도 '새 값을 만들어 반환'하는 스타일로 바꾸면 초기 코드량이 늘어납니다. Decision Scale 비즈니스 규칙이나 데이터 변환 로직처럼 재사용되고 자주 테스트되는 코드는 순수 함수로 유지할 가치가 큽니다. 반대로 한 번 쓰고 버리는 스크립트나 입출력이 본질인 얇은 래퍼 코드는 굳이 순수성을 고집할 필요가 없습니다.

언제 쓰나요?

순수 함수는 React 컴포넌트의 렌더링 로직, Redux 리듀서, 금액 계산, 데이터 변환 파이프라인, 검증 로직처럼 같은 입력에 같은 결과를 보장해야 하는 코드에 특히 잘 맞습니다. 테스트 코드에서 mock이나 stub 없이 값만 넣어 결과를 확인할 수 있고, 버그가 생겨도 함수 내부만 보면 되므로 원인을 좁히기 쉽습니다. 실무에서는 순수 함수로 유지할 부분과 외부 효과가 일어나는 부분의 경계를 명확히 긋는 것이 핵심입니다. 핵심 계산을 순수 함수로 모아 두고, 데이터베이스나 API 호출은 바깥 얇은 층에서 처리하는 구조가 일반적입니다.

비즈니스 로직 핵심단위 테스트병렬 처리캐시와 메모이제이션