ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SideEffect 는 무엇이고 어떻게 활용될까?
    Programming🧑‍💻 2025. 6. 1. 21:20

    사이드 이펙트(Side Effect)의 일반적인 의미 

    프로그래밍에서 사이드 이펙트(Side Effect) 또는 부수 효과는 어떤 함수나 표현식(expression)이 주어진 입력값을 반환하는 것 외에, 프로그램의 다른 부분에 관찰 가능한(observable) 변경을 일으키는 것을 말합니다. 즉, 함수의 실행이 함수 외부의 상태를 변경하거나, 외부 시스템과 상호작용하는 경우를 의미합니다.

    조금 더 쉽게 말해, 함수가 "나에게 주어진 일만 하고 그 결과만 내놓는 것"이 아니라, "외부 세계에 영향을 미치거나, 외부 세계로부터 무언가를 가져오는 모든 행위"가 바로 사이드 이펙트입니다.

    let totalSum = 0; // 외부 변수
    
    function add(a, b) {
        return a + b; // 입력(a,b)을 받아 출력(a+b)만 반환하는 '순수 함수'
    }
    
    function addAndLog(a, b) {
        const result = a + b;
        console.log(`Adding ${a} and ${b}, result is ${result}`); // 콘솔에 로그를 남김 (사이드 이펙트)
        return result;
    }
    
    function updateGlobalSum(a, b) {
        const result = a + b;
        totalSum = result; // 외부 변수 'totalSum'을 변경 (사이드 이펙트)
        return result;
    }
    • 데이터베이스 쓰기/읽기
      • 데이터를 저장하거나 불러오는 작업.
    • 네트워크 요청 (API 호출)
      • 외부 서버에서 데이터를 가져오거나 서버로 데이터를 보내는 작업.
    • 파일 시스템 접근
      • 파일을 읽거나 쓰는 작업.
    • 로그 기록
      • 콘솔에 메시지를 출력하거나 로그 파일에 기록하는 작업.
    • DOM (Document Object Model) 직접 조작
      • 웹 페이지의 HTML 요소를 직접 추가, 삭제, 수정하는 작업.
    • 타이머 설정 (setTimeout, setInterval)
      • 지정된 시간 후에 특정 코드를 실행하거나 반복 실행하는 작업.
    • 애니메이션 시작/중지
      • UI 요소의 애니메이션 상태를 변경하는 작업.
    • 구독 설정 (WebSocket, 이벤트 리스너)
      • 데이터 스트림이나 이벤트에 대한 리스너를 등록하는 작업.
    • 사용자 인증 (로그인/로그아웃)
      • 인증 상태를 변경하고 토큰을 저장하는 작업.

    side effect는 왜 중요한가?

    사이드 이펙트는 프로그램이 외부 세계와 상호작용하기 위해 필수적입니다.

    사용자 입력을 받거나, 서버에서 데이터를 가져오거나, 화면에 UI를 그려내는 모든 행위가 넓은 의미에서는 사이드 이펙트를 포함합니다.

    하지만 사이드 이펙트는 예측하기 어렵고 테스트하기 어려운 코드를 만들 수 있습니다.
    함수를 여러 번 호출해도 항상 같은 결과를 반환하고 외부 상태를 변경하지 않는 함수를 순수 함수(Pure Function)라고 하는데, 순수 함수는 테스트가 쉽고 안정적입니다.

    이에 반해 사이드 이펙트가 있는 함수는 같은 입력이라도 외부 상태에 따라 다른 결과를 내거나, 특정 호출 순서에 의존할 수 있기 때문에 디버깅이 더 어렵습니다.

    따라서 많은 아키텍처나 프로그래밍 패러다임(특히 함수형 프로그래밍)에서는 사이드 이펙트를 최소화하거나, 특정 영역(edge)으로 분리하여 관리하려고 노력합니다.

    React에서의 사이드 이펙트

    React의 핵심 철학 중 하나는 선언적 UI(Declarative UI)입니다.

    즉, 개발자는 props와 state가 주어졌을 때 화면이 어떻게 보일지를 "선언"하고,

    React는 그 선언에 따라 효율적으로 UI를 "렌더링"합니다.

    React 컴포넌트 함수 자체는 props와 state를 받아 JSX(UI)를 반환하는 순수 함수처럼 동작하는 것을 지향합니다.

     

    하지만 위에서 언급한 것처럼, UI 애플리케이션은 필연적으로 네트워크 요청이나 DOM 조작과 같은 사이드 이펙트가 필요합니다.

    React에서는 이러한 렌더링 결과에 직접적으로 관련 없는 작업, 즉 렌더링 이후에 발생하는 부가적인 작업들을 사이드 이펙트로 간주하고, 이를 처리하기 위해 useEffect 훅을 제공합니다.

     

    React에서 useEffect를 사용하는 이유 (사이드 이펙트 처리)

    • 렌더링과 분리: useEffect 내부의 코드는 컴포넌트가 렌더링된 후에 실행됩니다. 이는 React가 UI를 빠르게 업데이트하고, 복잡한 비동기 작업을 UI 렌더링 로직과 분리하여 관리할 수 있게 합니다.
    • 생명주기 매핑: 클래스 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount와 같은 생명주기 메서드의 역할을 useEffect의 의존성 배열([])과 반환 함수를 통해 유연하게 처리할 수 있습니다.
    • 예측 가능성: useEffect는 의존성 배열이 변경될 때만 재실행되도록 제어할 수 있어, 불필요한 사이드 이펙트 실행을 방지하고 예측 가능한 동작을 유도합니다.
    import React, { useState, useEffect } from 'react';
    
    function MyComponent() {
      const [data, setData] = useState(null);
    
      useEffect(() => {
        // 이 부분은 사이드 이펙트: 네트워크에서 데이터를 가져옴
        fetch('https://api.example.com/data')
          .then(response => response.json())
          .then(result => setData(result));
    
        // 이 부분은 클린업 함수 (사이드 이펙트 정리): 컴포넌트가 언마운트될 때 실행
        return () => {
          // 구독 해제, 타이머 취소 등
        };
      }, []); // 의존성 배열이 비어있으므로 컴포넌트가 처음 마운트될 때 한 번만 실행
    
      return (
        <div>
          {data ? <p>데이터: {data.message}</p> : <p>로딩 중...</p>}
        </div>
      );
    }
    

    여기서 fetch를 이용한 네트워크 요청은 컴포넌트의 렌더링 결과(JSX)와 직접적으로 관련 없는 외부 작업이므로 사이드 이펙트입니다. useEffect 훅은 이런 사이드 이펙트를 안전하고 React의 생명주기에 맞춰 실행하고 정리할 수 있도록 돕습니다.

    TCA (The Composable Architecture)에서의 사이드 이펙트

    TCA는 함수형 프로그래밍 원칙을 강력하게 적용한 아키텍처입니다.

    특히, 리듀서(Reducer)는 현재 상태(State)와 액션(Action)을 받아서 새로운 상태(State)와 함께 발생할 수 있는 사이드 이펙트(Effect)를 반환하는 순수 함수로 설계되어 있습니다.

     

    TCA의 사이드 이펙트 관리 방식

    1. 리듀서의 순수성 유지: TCA에서 Reducer는 순수 함수여야 합니다. 즉, Reducer는 외부 상태를 직접 변경하거나 네트워크 요청과 같은 사이드 이펙트를 직접 수행해서는 안 됩니다.
    2. Effect 타입의 도입: Reducer가 상태를 변경한 후, 외부 세계와 상호작용해야 할 필요가 있을 때 Effect라는 특별한 타입의 객체를 반환합니다. 이 Effect는 "앞으로 이런 사이드 이펙트를 실행해야 합니다"라는 "명령(command)" 또는 "설명(description)"에 해당합니다.
    3. 런타임(Runtime)에서의 실행: Reducer가 반환한 Effect는 TCA의 Store(런타임)에 의해 구독되고, 실제 외부 작업(네트워크 요청, 파일 접근 등)은 Store가 해당 Effect를 받아서 실행합니다. Effect가 실행된 후 그 결과는 다시 Action으로 변환되어 Store로 전달되고, Reducer에 의해 처리되어 상태를 업데이트합니다.

    TCA에서 사이드 이펙트가 중요한 이유

    • 테스트 용이성: Reducer는 순수 함수이므로, 입력(State, Action)만 주어지면 항상 동일한 State와 Effect를 반환함을 쉽게 테스트할 수 있습니다. 실제 네트워크 요청 없이 Effect를 모의(mock)하여 테스트할 수 있습니다.
    • 예측 가능성 및 디버깅: 모든 사이드 이펙트가 Reducer를 통해 명시적으로 정의되고 Effect로 반환되기 때문에, 앱의 모든 동작 흐름을 쉽게 추적하고 디버깅할 수 있습니다.
    • 격리: 비순수한 작업(사이드 이펙트)을 순수한 로직(리듀서)으로부터 명확하게 분리하여 코드의 복잡성을 줄이고 관심사 분리를 달성합니다.
    // TCA 예시 (가상 코드)
    struct Feature: Reducer {
        struct State { var count: Int = 0 }
        enum Action {
            case incrementButtonTapped
            case decrementButtonTapped
            case fetchFactButtonTapped
            case factResponse(String) // 네트워크 요청 결과 액션
        }
    
        // 의존성 주입 (네트워크 클라이언트 등)
        var body: some ReducerOf<Self> {
            Reduce { state, action in
                switch action {
                case .incrementButtonTapped:
                    state.count += 1
                    return .none // 사이드 이펙트 없음
    
                case .fetchFactButtonTapped:
                    // 이 부분은 사이드 이펙트를 '설명'하는 Effect를 반환
                    return .run { send in // Effect를 실행하고 결과로 Action을 send (비동기)
                        let (data, _) = try await URLSession.shared.data(from: URL(string: "http://numbersapi.com/\(state.count)/trivia")!)
                        let fact = String(decoding: data, as: UTF8.self)
                        await send(.factResponse(fact)) // 결과를 액션으로 다시 보냄
                    }
    
                case let .factResponse(fact):
                    // 네트워크 요청 결과로 상태를 업데이트 (순수함)
                    state.currentFact = fact
                    return .none
                }
            }
        }
    }
    

    여기서 fetchFactButtonTapped 액션이 들어왔을 때, Reducer는 직접 네트워크 요청을 하는 것이 아니라, .run { ... }이라는 Effect를 반환합니다. 이 Effect는 Store에 의해 실행되고, 네트워크 요청이 완료되면 factResponse 액션을 다시 Store로 보냅니다. 이렇게 사이드 이펙트의 실행과 그 결과 처리를 분리함으로써 Reducer의 순수성을 유지하는 것입니다.

    결론

    React의 useEffect와 TCA의 Effect는 모두 "함수/컴포넌트의 주된 역할(UI 렌더링 또는 상태 변화) 외에 발생하는 외부와의 상호작용이나 비동기 작업"이라는 사이드 이펙트를 다루기 위한 메커니즘입니다.

    • React (useEffect): 컴포넌트 렌더링 이후에 발생하는 사이드 이펙트를 "실행"하고 "정리"하는 데 중점을 둡니다.
    • TCA (Effect): 리듀서의 순수성을 유지하면서 사이드 이펙트를 "선언"하고, 런타임(Store)이 이를 "실행"하며, 그 결과가 다시 리듀서로 "피드백"되는 체계적인 흐름을 만듭니다.

    두 프레임워크/아키텍처 모두 사이드 이펙트의 복잡성을 관리하고, 코드의 예측 가능성과 테스트 용이성을 높이기 위한 설계 철학을 공유하고 있습니다.

     

    'Programming🧑‍💻' 카테고리의 다른 글

    빚쟁이가 되어버린 코더들  (2) 2025.05.29
    XML 알아보기  (0) 2025.04.21
    static 키워드 정복하기  (0) 2025.03.25
    타입은 왜 중요할까?  (0) 2025.01.29
    web application은 어떻게 작동하는가?  (0) 2022.10.28

    댓글

Designed by Tistory.