2025-08-08 00:17

Tags:

참조 투명성 핸드북

1. 만들어진 이유: 예측 가능한 세상을 향한 열망

컴퓨터 과학에서 **참조 투명성(Referential Transparency)**이라는 개념은 사실 수학과 논리학에서 빌려온 것. 수학에서 2 + 3이라는 표현은 언제 어디서 누가 계산하든 항상 5라는 값과 동일. 이처럼 어떤 표현식(expression)을 그 결과값으로 바꾸어도 전체 시스템의 동작에 아무런 영향을 주지 않는 특성을 의미.

프로그래밍의 세계는 복잡하고 예측 불가능한 요소로 가득함. 같은 함수를 호출해도 내부 상태나 외부 환경에 따라 다른 결과를 내놓는 경우가 비일비재. 이는 코드의 이해를 어렵게 만들고, 예상치 못한 버그의 원인이 됨.

참조 투명성은 이러한 불확실성을 제거하고, 프로그램을 수학 공식처럼 명확하고 예측 가능하게 만들려는 시도에서 탄생. 즉, “프로그램의 특정 부분을 언제나 그 결과값으로 대체할 수 있다면, 코드를 이해하고 추론하기 훨씬 쉬워질 것이다” 라는 아이디어에 기반함. 이는 특히 함수형 프로그래밍(Functional Programming)의 핵심 철학으로 자리 잡음.

2. 구조: 순수 함수와 부수 효과

참조 투명성은 **순수 함수(Pure Function)**라는 개념을 통해 구현되며, **부수 효과(Side Effect)**를 피함으로써 지켜짐.

핵심 원리: 순수 함수

순수 함수는 다음 두 가지 조건을 반드시 만족하는 함수.

  1. 동일한 입력에 대해 항상 동일한 출력을 반환. (No surprises!)

  2. 함수 외부의 어떤 상태도 변경하지 않음 (No side effects).

예를 들어, 두 숫자를 더하는 함수는 대표적인 순수 함수.

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

// add(2, 3)은 언제나 5를 반환하며, 프로그램의 다른 부분에 영향을 주지 않음.
// 따라서 add(2, 3)은 그 결과값인 5로 언제든지 대체 가능.
const result = add(2, 3) * 10; // 5 * 10 과 완전히 동일.

반대 개념: 부수 효과

부수 효과는 함수가 결과값을 반환하는 것 외에 외부 세계와 상호작용하거나 내부 상태를 변경하는 모든 행위를 의미. 부수 효과가 있는 함수는 참조적으로 투명하지 않음.

  • 대표적인 부수 효과의 예:

    • 전역 변수나 정적 변수를 수정하는 경우

    • 파일 시스템에 데이터를 쓰거나 읽는 경우

    • 네트워크를 통해 API를 호출하는 경우

    • 데이터베이스에 접근하는 경우

    • 화면에 무언가를 출력하는 경우 (console.log)

    • Math.random()이나 new Date()처럼 호출 시점에 따라 결과가 달라지는 함수 사용

// 순수하지 않은 함수 (Impure Function)
let counter = 0;

function incrementAndGet() {
  counter += 1; // 부수 효과: 함수 외부의 변수 'counter'를 수정함.
  return counter;
}

// incrementAndGet()을 호출할 때마다 결과가 달라짐.
const result1 = incrementAndGet(); // 1
const result2 = incrementAndGet(); // 2

// 여기서 incrementAndGet()을 그 결과값인 1로 대체하면
// 프로그램의 전체적인 의미와 동작이 바뀌게 됨.
// 따라서 이 함수는 참조적으로 투명하지 않음.

3. 사용법: 어떻게 식별하고 활용하는가

식별하기

어떤 함수나 표현식이 참조적으로 투명한지 확인하는 가장 간단한 방법은 “이것을 계산된 값으로 그냥 바꿔치기해도 괜찮을까?” 라고 질문해보는 것.

  • 투명한 경우: add(2, 3) 5 로 바꿔도 아무 문제가 없음.

  • 불투명한 경우: Math.random() 0.12345 로 바꾸면, 다음 실행 시 프로그램의 동작이 달라지므로 안 됨.

활용하기

실제 프로그래밍에서 모든 것을 순수 함수로만 만들 수는 없음. 파일도 저장해야 하고, 서버와 통신도 해야 하기 때문. 핵심은 ‘관심사의 분리’.

  1. 순수한 로직 분리: 계산, 데이터 변환 등 핵심 비즈니스 로직은 최대한 순수 함수로 작성.

  2. 불순한 처리 위임: 파일 저장, 네트워크 요청 등 부수 효과를 일으키는 코드는 프로그램의 가장자리(entry/exit points)로 몰아서 관리.

이렇게 코드를 분리하면, 프로그램의 대부분을 차지하는 순수한 로직은 테스트와 재사용이 매우 쉬워지고, 예측 불가능한 부분은 최소화하여 관리할 수 있게 됨.

4. 심화 내용: 참조 투명성이 가져오는 이점

참조 투명성을 지향하는 코딩 스타일은 여러 가지 강력한 이점을 제공.

  • 높은 예측 가능성과 테스트 용이성

    • 입력만 주어지면 출력이 정해져 있으므로 테스트가 매우 간단해짐. 복잡한 환경 설정(Mocking) 없이 함수 자체만 독립적으로 검증 가능.
  • 최적화 기회: 메모이제이션 (Memoization)

    • 동일한 입력에 대해 항상 동일한 결과를 반환하므로, 한 번 계산한 결과를 캐시(저장)해두고 다음 호출 시에는 계산 없이 바로 반환할 수 있음. 이는 성능 최적화에 매우 유용.
  • 안전한 병렬 및 동시성 처리

    • 함수가 외부 상태를 변경하지 않으므로, 여러 스레드에서 동시에 호출해도 서로에게 영향을 주지 않음 (Race condition, Deadlock 등의 문제에서 자유로움). 병렬 처리를 통한 성능 향상에 유리.
  • 쉬운 코드 이해와 리팩토링

    • 함수의 동작을 이해하기 위해 그 내부만 보면 되므로 코드 파악이 쉬워짐. 또한, 부수 효과에 대한 걱정 없이 안심하고 코드를 수정하거나 개선(리팩토링)할 수 있음.

참조 투명성은 단순히 학술적인 개념을 넘어, 안정적이고 유지보수하기 쉬운 소프트웨어를 만드는 매우 실용적인 원칙입니다.

혹시 참조 투명성을 실제 코드에 적용하는 구체적인 패턴이나 기법에 대해 더 궁금한 점이 있으신가요?

References

참조 투명성