사이드 이펙트(Side Effect) 핸드북

1. 만들어진 이유: 왜 ‘부수 효과’라는 개념이 생겨났을까?

컴퓨터 과학의 초기, 프로그램은 주로 명령형 프로그래밍(Imperative Programming) 방식으로 작성되었습니다. 이는 컴퓨터에게 “무엇을” 할지보다 “어떻게” 할지를 순서대로 명령하는 방식입니다. 예를 들어, “변수 a에 5를 더하고, 그 결과를 변수 b에 저장한 후, b의 값을 화면에 출력하라”와 같이 구체적인 절차를 나열합니다.

이 방식은 직관적이지만, 프로그램의 규모가 커지면서 문제가 발생하기 시작했습니다. 여러 함수가 공유된 상태(Shared State), 즉 전역 변수나 같은 메모리 공간을 동시에 수정하면서 예측 불가능한 결과들이 나타나기 시작한 것입니다.

함수를 호출했는데, 예상했던 반환값 외에 프로그램의 다른 부분에 예상치 못한 변경이 일어나는 현상. 이것이 바로 사이드 이펙트입니다. 마치 약을 먹었는데, 병을 고치는 주된 효과 외에 두통이나 소화불량 같은 부수적인 효과가 나타나는 것과 같습니다.

이러한 문제를 해결하고 프로그램의 동작을 더 쉽게 예측하고 제어하기 위해, 함수형 프로그래밍과 같은 패러다임에서는 사이드 이펙트를 최소화하고 **순수 함수(Pure Function)**를 지향하는 개념이 중요해졌습니다.

2. 구조: 사이드 이펙트는 어떻게 구성되는가?

사이드 이펙트는 함수가 자신의 스코프(Scope) 바깥에 있는 무언가와 상호작용할 때 발생합니다. 함수의 주된 임무는 입력값을 받아 계산 후 결과값을 반환하는 것이지만, 이 과정 외에 다른 일을 할 때 사이드 이펙트가 생깁니다.

주요 사이드 이펙트 유형

  • 전역 상태 변경 (Modifying Global State): 함수 바깥에 선언된 변수나 객체의 값을 직접 수정하는 경우.
    let globalValue = 10;

    function addAndModify(num) {
    // 사이드 이펙트: 함수 외부의 globalValue를 직접 변경
    globalValue += num;
    return num + 5;
    }

  • 입출력 (I/O - Input/Output): 파일 시스템을 읽거나 쓰고, 데이터베이스에 쿼리를 보내고, 네트워크를 통해 API를 호출하거나, 콘솔에 로그를 출력하는 모든 행위.
    def save_user_to_db(user_data):
    # 사이드 이펙트: 데이터베이스에 데이터를 저장 (외부 시스템과 상호작용)
    db.save(user_data)
    print(“User saved!“) # 이것 또한 콘솔 출력이라는 사이드 이펙트

  • DOM 조작 (DOM Manipulation): 웹 개발에서 JavaScript 함수가 HTML 문서의 구조나 스타일을 직접 변경하는 경우.

  • 인자로 받은 객체 수정 (Mutating Arguments): 함수에 참조(Reference)로 전달된 객체나 배열의 원본을 직접 수정하는 경우.
    function addToArray(arr, item) {
    // 사이드 이펙트: 인자로 받은 배열 원본을 직접 수정
    arr.push(item);
    return arr;
    }

이와 반대되는 개념이 바로 **순수 함수(Pure Function)**입니다. 순수 함수는 다음과 같은 두 가지 조건을 만족합니다.

  1. 동일한 입력에 대해 항상 동일한 출력을 반환한다.
  2. 사이드 이펙트가 없다. (함수 외부의 어떤 상태도 변경하지 않는다.)

3. 사용법: 사이드 이펙트, 어떻게 다뤄야 할까?

사이드 이펙트는 버그의 온상이 될 수 있지만, 현실적으로 모든 사이드 이펙트를 없앨 수는 없습니다. 화면에 무언가를 보여주고, 사용자 입력을 받고, 데이터를 저장하는 등 프로그램이 세상과 소통하려면 반드시 필요하기 때문입니다.

중요한 것은 사이드 이펙트를 **“관리”하고 “격리”**하는 것입니다.

관리 전략

  1. 최대한 순수 함수로 만들기: 핵심적인 비즈니스 로직은 순수 함수로 작성하여 테스트와 예측이 쉽게 만듭니다.

  2. 사이드 이펙트 격리하기: 사이드 이펙트가 꼭 필요한 코드는 프로그램의 특정 계층이나 모듈로 분리합니다. 예를 들어, 데이터베이스와 통신하는 코드는 ‘Repository’ 계층에, API 호출 코드는 ‘ApiService’ 모듈에 모아두는 식입니다. 이렇게 하면 프로그램의 나머지 부분은 사이드 이펙트로부터 안전하게 유지됩니다.

  3. 불변성(Immutability) 활용: 원본 데이터를 직접 수정하는 대신, 데이터의 복사본을 만들어 변경하고 새로운 결과물을 반환합니다. 이는 예기치 않은 상태 변경을 막는 강력한 방법입니다.
    // 나쁜 예: 원본 배열을 수정 (사이드 이펙트 발생)
    const originalArray = [1, 2, 3];
    function addItem_bad(arr, item) {
    arr.push(item);
    return arr;
    }

    // 좋은 예: 새로운 배열을 반환 (사이드 이펙트 없음)
    const originalArray2 = [1, 2, 3];
    function addItem_good(arr, item) {
    return […arr, item]; // 스프레드 문법으로 복사본 생성
    }

4. 심화 내용: 더 깊이 알아보기

  • 참조 투명성 (Referential Transparency): 어떤 표현식(Expression)을 그 표현식의 결과값으로 대체해도 프로그램의 동작에 아무런 영향이 없다면 ‘참조에 투명하다’고 말합니다. 순수 함수는 항상 참조 투명성을 만족합니다. 이는 코드의 추론을 매우 쉽게 만들어 줍니다.
  • 멱등성 (Idempotence): 같은 연산을 여러 번 수행해도 결과가 한 번 수행한 것과 같은 특성. 예를 들어, HTTP PUT이나 DELETE 요청은 멱등성을 가집니다. 사이드 이펙트를 다룰 때, 특히 네트워크 요청과 같은 비동기 작업에서 멱등성을 보장하면 시스템의 안정성이 크게 향상됩니다.
  • 동시성 문제 (Concurrency Issues): 여러 스레드나 프로세스가 공유된 자원을 동시에 수정하려고 할 때 사이드 이펙트는 ‘경쟁 상태(Race Condition)‘와 같은 심각한 문제를 일으킵니다. 사이드 이펙트를 최소화하고 상태 변경을 신중하게 관리하는 것이 동시성 프로그래밍의 핵심입니다.

사이드 이펙트를 이해하고 제어하는 능력은 깨끗하고, 유지보수하기 쉽고, 확장 가능한 소프트웨어를 만드는 데 필수적인 기술입니다.

이 핸드북이 사이드 이펙트라는 개념을 이해하는 데 도움이 되셨나요? 혹시 특정 프로그래밍 언어에서 사이드 이펙트를 관리하는 구체적인 방법에 대해 더 궁금한 점이 있으신가요?