대규모 Python 서비스의 리팩토링 로그

유지보수성, 안전성, 성능을 위한 리팩토링 전략

최근 Python 기반의 LangGraph + VectorDB 서비스의 코드 전반을 리팩토링했다. 코드 구조가 점점 커지고, 다양한 서비스와 통합되면서 가독성, 디버깅, 재사용성이 떨어지는 문제가 있었고, 이를 개선하기 위해 범위 별로 리팩토링을 진행했다. 아래는 구체적인 리팩토링 방향과 주요한 패턴들이다.

예외 처리 및 로깅 개선

예외 처리는 단순한 try/except 구문에서 벗어나, 에러가 발생했을 때 전체 stack trace가 함께 로깅되는 logger.exception() 방식으로 전환해 디버깅의 효율을 높였다. 특히 에러 메시지에는 실패 지점과 입력 값 등 동적 정보를 포함시켜, 로그만으로도 문제를 빠르게 파악할 수 있도록 구성했다. 또한, 각 로그에는 [함수명]과 같은 prefix를 붙여서 운영 환경에서 로그를 검색하고 추적하기 쉽게 만들었으며, 상황에 따라 info, warning, error로깅 레벨을 세분화하여 운영 중 모니터링 시 이슈의 영향도를 쉽게 파악할 수 있도록 했다.

입력값 및 환경 변수 처리 안정화

입력값을 다룰 때는 모든 dict 접근을 .get() 방식으로 바꾸어, KeyError가 발생하지 않도록 처리했고, 필드 누락 시 적절한 fallback 값으로 이어지도록 방어 코드를 구성했다. 환경 변수의 경우에는 누락된 설정으로 인한 실행 오류를 방지하기 위해 validate_env() 함수를 통해 명시적인 검증 절차를 추가했고, 테스트 환경이나 컨테이너 내부에서도 설정 파일이 누락되지 않도록 .env 파일을 명확히 로드하는 load_dotenv() 호출을 추가했다. 또한, 입력값이 잘못되었거나 빈 문자열, 혹은 None과 같은 값일 때는 최대한 빠르게 감지하여 조기에 반환하는 Fail Fast 패턴을 적용함으로써, 불필요한 로직 실행을 방지하고 전체 서비스 안전성을 높였다.

코드 구조 일관성과 선언형 구성 방식 도입

코드 구조 측면에서는 기존에 함수를 직접 호출하며 구성되던 LangGraph 흐름을, 노드와 엣지를 선언형 구조로 재설계했다. 노드 수가 많아져도 유지보수가 쉽도록 NODE_DEFINITIONS와 같은 딕셔너리 구조를 활용했으며, 조건부 분기(edge) 또한 선언적으로 표현해 그래프의 흐름을 하나의 데이터 구조로 표현할 수 있도록 개선했다. 이런 구성 방식은 추후 그래프 시각화나 상태 흐름 추적에도 매우 유리하며, 기능 로직과 설정 값을 분리해 코드의 가독성과 재사용성 역시 높여준다.

함수 분리 및 책임 명확화 (SRP)

함수의 책임이 명확하지 않고 한 함수가 여러 작업을 처리하던 기존 코드를 개선하여, 번역, 쿼리 전처리, 로깅, 예외 처리 등 각 기능을 별도 함수로 분리했다. 이로 인해 단일 책임 원칙(SRP)이 지켜졌고, 각 함수에 대한 단위 테스트도 가능해졌다. 또한, 중복되는 문자열이나 조건문은 모두 상수로 분리하여, 변경이 필요한 값들이 코드 곳곳에 퍼져있지 않도록 정리했다. 이를 통해 유지보수와 리팩토링의 효율성을 동시에 확보할 수 있었다.

타입 힌팅 강화 및 코드 가독성 개선

함수별로 명확한 타입 힌트를 부여해 IDE의 자동완성과 정적 분석 기능을 적극 활용할 수 있도록 했고, 특히 복잡한 dict 구조에는 TypedDict, Optional, List[...] 등의 구조를 명시해 개발자가 코드를 빠르게 이해하고 실수 없이 다룰 수 있도록 설계했다. 문자열 가공 시에는 일관되게 f-string을 사용하고, 리스트 처리도 리스트 컴프리헨션 기반으로 통일해 성능과 가독성을 동시에 확보했다. 함수 내 로직 역시 중간 변수를 명확히 분리해, 흐름이 자연스럽게 읽히도록 구성했다.

IO/성능 최적화

파일 로직 처리에서 성능을 높이기 위해, 반복 루프 태에서 매번 생성되던 Splitter 객체를 루프 밖으로 이동시켜 불필요한 객체 생성과 메모리 낭비를 줄였다. 또한, 빈 파일이나 존재하지 않는 경로에 대해서는 사전에 체크하여 무의미한 작업이 수행되지 않도록 방지했다. 다수의 파일을 동시에 다룰 때에도 개별 실패는 전체 실패로 이어지지 않도록, 각 파일 단위로 예외 처리를 분리해 전체 작업의 안정성을 높였다.

멀티스레드 환경 대응

멀티스레드 환경에서도 안전하게 작동하도록 threading.Lock을 활용하여 공유 자원에 대한 race condition 문제를 방지했고, 모델 초기화 시에는 double-checked locking 패턴을 적용해 불필요한 락 획득 없이도 thread-safe한 초기화가 가능하도록 개선했다. 이로 인해 병렬 환경에서도 안정적으로 NLP 모델이 로딩되고 재사용되는 구조가 완성되었다.

FastAPI 메인 구조 정비

FastAPI의 진입점 코드를 리팩토링하여 create_app() 함수를 도입했고, 이를 통해 테스트 시에는 TestClient(create_app()) 형태로 손쉽게 단위 테스트를 구성할 수 있게 되었다. 라우터 등록, 미들웨어 설정 등도 명시적으로 분리하여 진입점과 기능 구성이 한눈에 보이도록 정리했으며, 헬스체크용 기본 응답도 명확히 분리해 배포 후 기본 점검이 용이하도록 구성했다.

리팩토링 효과 요약

항목개선 전개선 후
테스트 실행 시간353.59s (42 passed)310.05s (42 passed)
코드 복잡도중간 ~ 높음낮음 ~ 중간
유지보수 난이도신규 기여자에게 진입 장벽 있음구조 명확, 진입 용이
디버깅/오류 추적로그만으로 파악 어려움로그에서 원인까지 추적 가능
테스트 가능성일부 로직만 가능모듈 단위로 유닛테스트 가능

마무리하며

이번 리팩토링은 기능을 바꾸기보다는 “더 안정적으로, 더 읽기 쉽게, 더 테스트 가능하게” 만드는 데 초점을 맞췄다. 확장성 높은 구조를 위해서는 지금 보기에 다소 과해 보여도 구조적 개선을 해두는 것이 향후 유지보수와 팀 협업에 큰 도움이 된다.

사소한 리팩토링이 쌓이면 결국 서비스의 품질도 함께 올라간다.