본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] Global State의 3가지 위험성과 Context 객체 패턴을 활용한 클린코드 해결 방법

by Papa Martino V 2026. 2. 22.
728x90

Global State
Global State

 

소프트웨어 아키텍처를 설계할 때 개발자들이 가장 흔히 빠지는 유혹 중 하나는 바로 Global State(전역 상태)의 사용입니다. 파이썬 프로젝트의 규모가 커질수록 global 키워드나 모듈 레벨의 변수는 프로그램의 예측 가능성을 떨어뜨리고, 단위 테스트를 불가능하게 만들며, 멀티스레드 환경에서 치명적인 레이스 컨디션을 유발합니다. 본 포스팅에서는 전역 상태의 폐해를 진단하고, 이를 우아하게 대체할 수 있는 Context 객체 패턴(Context Object Pattern)의 핵심 메커니즘과 실무 적용 사례를 통해 코드의 결합도를 낮추는 구체적인 해결 방법을 제시합니다.


1. Global State가 안티패턴인 3가지 근본적인 이유

전역 상태는 '편리함'이라는 독이 든 사과와 같습니다. 특히 파이썬처럼 유연한 언어에서는 어디서든 접근 가능한 변수가 매력적으로 보일 수 있지만, 다음과 같은 차이로 인해 유지보수 지옥을 만들어냅니다.

  • 예측 불가능한 사이드 이펙트: 함수가 외부 상태에 의존하면, 동일한 입력에 대해 항상 동일한 출력을 보장하는 '참조 투명성'이 깨집니다.
  • 테스트 격리 실패: 전역 변수를 공유하는 테스트 케이스들은 서로 영향을 주고받게 되어, 특정 테스트가 단독으로는 성공하지만 전체 실행 시 실패하는 현상이 발생합니다.
  • 동시성 문제: 파이썬의 GIL 환경에서도 전역 상태에 대한 쓰기 작업은 원자성을 보장하지 않으므로, 데이터 오염의 주범이 됩니다.

2. Context 객체 패턴이란 무엇인가?

Context 객체 패턴은 애플리케이션의 실행 흐름 전반에 걸쳐 공유되어야 하는 정보(설정, 사용자 인증 정보, 데이터베이스 세션 등)를 하나의 객체로 캡슐화하여 명시적으로 전달하는 디자인 패턴입니다.

이 패턴의 핵심은 전역 공간에 정보를 흩뿌리는 대신, 필요한 함수나 클래스에 이 객체를 주입(Dependency Injection)하여 제어의 흐름을 명확히 하는 것입니다.


3. 전역 상태 방식 vs Context 객체 패턴 차이점 분석

아래 표는 전역 상태를 사용할 때와 Context 객체 패턴을 적용했을 때의 아키텍처적 차이를 비교한 내용입니다.

비교 항목 전역 상태 (Global State) Context 객체 패턴
의존성 가시성 암시적 (숨겨져 있음) 명시적 (인자를 통해 전달)
테스트 용이성 매우 어려움 (Monkey Patching 필요) 매우 쉬움 (Mock 객체 주입 가능)
확장성 단일 인스턴스로 제한됨 여러 개의 독립된 컨텍스트 유지 가능
데이터 흐름 파편화되어 추적이 어려움 단방향 또는 명확한 흐름 형성
멀티스레딩 Race Condition 위험 높음 Thread-local 또는 객체 격리로 안전함

4. [PYTHON] Context 객체 패턴 구현 샘플 (Sample Example)

파이썬에서 단순한 dict를 넘기는 것보다 dataclassNamedTuple을 사용하여 타입 힌트를 제공하는 것이 전문적인 접근 방식입니다.

[BAD] 전역 변수를 사용하는 방식


# config.py
API_KEY = "old-key"

# 서비스 로직
def fetch_data():
    global API_KEY
    print(f"Fetching with {API_KEY}")

# 테스트 시 API_KEY를 수동으로 바꿔야 하며 다른 테스트에 영향을 줌

[GOOD] Context 객체를 주입하는 방식


from dataclasses import dataclass
from typing import Optional

@dataclass(frozen=True)
class AppContext:
    api_key: str
    db_session: any
    request_id: str

def fetch_data(ctx: AppContext):
    # 명시적으로 필요한 정보를 컨텍스트에서 꺼내 씀
    print(f"[{ctx.request_id}] Fetching with {ctx.api_key}")

# 실행부
def main():
    my_ctx = AppContext(
        api_key="secure-key-123",
        db_session=None,
        request_id="REQ-001"
    )
    fetch_data(my_ctx)

# 테스트가 매우 간편해짐
def test_fetch_data():
    mock_ctx = AppContext(api_key="test", db_session=None, request_id="T-1")
    fetch_data(mock_ctx) # 성공!

5. 고급 기법: ContextVars를 활용한 비동기 컨텍스트 관리

최신 파이썬(3.7+)에서는 contextvars 모듈을 제공합니다. 이는 특히 FastAPIasyncio 기반의 비동기 프로그래밍에서 전역 상태처럼 편리하게 사용하면서도, 각 요청(Request)마다 독립된 상태를 보장하는 '해결 방법'이 됩니다.

로그 추적용 ID(Correlation ID)를 전파할 때 함수마다 인자를 추가하기 어렵다면, 이 기법이 강력한 대안이 됩니다.


6. 결론: 더 나은 설계를 위한 제언

Global State를 제거하는 것은 단순히 global 키워드를 삭제하는 일이 아닙니다. 시스템의 의존성 관계를 재정립하고, 데이터가 흐르는 통로를 설계하는 과정입니다. 2026년 현재 고도화된 마이크로서비스 환경에서 Context 객체 패턴은 코드의 재사용성과 안정성을 보장하는 가장 강력한 무기 중 하나입니다. 지금 바로 프로젝트의 전역 변수들을 모아 하나의 구조화된 Context 객체로 묶어보십시오. 코드의 가독성이 비약적으로 상승하는 것을 경험하게 될 것입니다.


7. 내용의 출처 및 참고 문헌

  • Martin Fowler: "Dependency Injection and Inversion of Control Patterns"
  • Python Software Foundation: "contextvars — Context Variables Documentation"
  • Clean Code Python Guidelines: "Avoid Global State and Hidden Side Effects"
  • Architectural Patterns in Python (O'Reilly): "Chapter 3. Decoupling with Context Objects"
728x90