1. 2024년 3월, 작동만 하던 주문 수집기를 만나다
저는 2024년 3월에 소규모 쇼핑몰의 주문 수집기를 맡으면서 리팩토링을 미루면 어떤 일이 생기는지 제대로 겪었습니다. 화면에서는 주문이 정상적으로 들어왔지만, 운영자가 상품명을 조금만 바꿔도 엉뚱한 카테고리로 분류됐고 같은 주문이 두 번 저장되는 문제도 일어났습니다. 당시 파일 하나에 900줄이 넘는 함수가 들어 있었고, 변수 이름은 a, b, temp처럼 의미를 짐작하기 어려웠습니다. 처음에는 기능만 고치면 된다고 생각해 조건문 한 줄을 추가했는데, 그날 저녁 배송비 계산이 전부 어긋났습니다. 이때 저는 코드가 실행된다는 사실과 다른 사람이 안심하고 수정할 수 있다는 상태가 전혀 다르다는 점을 체감했습니다.
제가 제일 먼저 한 일은 멋진 구조를 만드는 게 아니라 고장 난 지점을 기록하는 일이었습니다. 3월 11일에는 주문 50건을 고정한 테스트 데이터를 만들고, 주문 저장·카테고리 분류·배송비 계산 결과를 파일로 남겼습니다. 테스트가 없던 상태에서 내부 코드를 한꺼번에 바꾸면 무엇이 망가졌는지 알아내기 어려웠기 때문입니다. 그다음 함수 안에서 외부 API를 호출하는 부분과 금액을 계산하는 부분을 따로 표시했습니다. 이 작업에는 반나절이 걸렸지만, 이후 수정 때마다 눈으로 결과를 비교하지 않아도 됐습니다. 저는 리팩토링을 시작할 때 코드부터 고치는 습관을 버리고, 먼저 현재 동작을 고정하는 편이 훨씬 안전하다는 걸 배웠습니다.
2. 이름과 조건문을 바꾸자 읽는 시간이 줄었다
첫 번째 변경은 변수 이름이었습니다. order_data를 raw, discount를 d, 배송비를 fee처럼 줄여 쓴 부분을 customer_order, discount_rate, shipping_fee로 바꿨습니다. 예전에는 함수 중간에 나온 d가 할인율인지 할인 금액인지 확인하려고 위쪽까지 계속 올라가야 했습니다. 이름을 고친 뒤에는 코드 리뷰에서 설명하는 시간이 눈에 띄게 줄었습니다. 물론 이름만 바꾼다고 프로그램이 빨라지지는 않았고, 처음에는 이런 수정이 업무 성과로 보이지 않아 동료에게 우선순위를 의심받기도 했습니다. 하지만 4월에 다른 개발자가 배송비 규칙을 수정할 때 질문이 크게 줄었고, 저는 읽는 비용도 유지보수 비용이라는 사실을 경험으로 확인했습니다.
중첩 조건문도 기억에 남는 실패 지점입니다. 기존 함수는 회원 여부, 주문 금액, 재고 상태를 네 단계로 감싸고 있어 마지막 else가 어느 조건에 대응하는지 알기 어려웠습니다. 제가 처음 리팩토링한 버전은 조건을 함수 여러 개로 나누면서도 기존 분기를 그대로 복사해 코드 길이만 늘어났습니다. 리뷰에서 같은 검사가 세 군데 반복된다는 지적을 받고, 유효하지 않은 주문이면 즉시 오류를 반환하는 가드 클로즈 방식으로 다시 고쳤습니다. 입력값이 없으면 바로 반환하고, 재고가 부족하면 그 자리에서 중단한 뒤 정상 처리 흐름을 마지막에 배치했습니다. 120줄짜리 함수가 68줄로 줄었고, 예외 상황을 추가할 때 어느 위치를 건드려야 하는지도 분명해졌습니다.
3. 성능 문제를 추측하다가 시간을 낭비한 경험
성능 개선에서는 제 추측이 자주 틀렸습니다. 2024년 5월 초, 주문 8만 건을 처리하는 배치가 17분씩 걸리자 저는 문자열 변환과 로그 출력이 원인이라고 생각했습니다. 그래서 로그를 줄이고 f-string으로 코드를 바꿨지만 실행 시간은 16분 40초로 거의 그대로였습니다. 하루를 더 쓴 뒤 cProfile을 실행해 보니 병목은 전혀 다른 곳에 있었습니다. 상품 목록을 찾을 때마다 8만 건의 주문마다 3,200개 상품 리스트를 처음부터 훑고 있었고, 전체 호출 시간이 그 부분에 몰려 있었습니다. 막연한 감으로 최적화하면 그럴듯한 코드만 늘어날 뿐, 실제 대기 시간은 줄지 않는다는 걸 이때 뼈저리게 배웠습니다.
자료구조를 바꾼 뒤 결과는 꽤 달라졌습니다. 기존에는 상품 목록을 리스트로 들고 있다가 for문으로 product_id를 비교했는데, 시작 단계에서 상품 정보를 딕셔너리로 색인해 product_by_id.get(product_id) 형태로 조회하도록 바꿨습니다. 변경 전 배치 시간은 17분 12초였고, 첫 수정 뒤에는 2분 51초까지 내려갔습니다. 제가 처음부터 복잡한 병렬 처리나 캐시 서버를 붙였다면 설정과 장애 지점만 늘었을 가능성이 큽니다. 실제로 필요한 검색 방식과 자료구조가 맞지 않았던 단순한 문제가 먼저 보였습니다. 다만 딕셔너리로 바꾸면서 메모리 사용량이 410MB에서 690MB로 늘었기 때문에, 서버 메모리와 데이터 규모를 함께 확인해야 했습니다.
4. 캐시를 넣었다가 오래된 가격을 보여준 실수
캐시도 만능 해결책으로 생각했다가 곤란을 겪었습니다. 6월 4일에는 같은 상품의 재고 조회가 반복된다는 이유로 10분짜리 메모이제이션을 추가했습니다. 테스트 환경에서는 API 호출이 줄고 응답 시간이 1.8초에서 0.4초로 내려가서 성공했다고 판단했습니다. 그런데 운영에서 가격 변경이 발생한 뒤 약 7분 동안 예전 가격이 화면에 남았습니다. 고객이 결제 직전에 본 금액과 실제 주문 금액이 달라질 위험이 생긴 겁니다. 저는 캐시 만료 시간만 정하면 된다고 생각했지만, 가격·재고처럼 변경 시점이 민감한 값에는 무효화 조건과 최신성 기준을 먼저 정해야 한다는 걸 놓쳤습니다.
그 뒤에는 모든 조회에 캐시를 적용하지 않고 성격별로 나눴습니다. 거의 바뀌지 않는 카테고리 목록은 30분 동안 보관했지만, 가격과 재고는 주문 화면 진입 때 다시 조회하도록 했습니다. 가격 변경 이벤트가 들어오면 해당 상품 키를 지우는 코드도 추가했고, 캐시 적중 여부와 오래된 응답 시간을 로그로 남겼습니다. 캐시를 넣기 전 평균 응답 시간은 1.8초, 수정 후에는 0.7초였고, 무조건 빠르게 만드는 대신 잘못된 정보를 보여주지 않는 선에서 타협했습니다. 제 경험상 성능 수치 하나만 보고 캐시를 넣으면 운영 데이터의 시간성까지 놓치기 쉽습니다.
5. 테스트와 Git으로 되돌릴 여지를 남기다
리팩토링 중 가장 아찔했던 순간은 6월 18일에 발생했습니다. 주문 금액을 Decimal로 통일하는 작업을 하다가 일부 쿠폰 계산이 float 값을 전제로 작성됐다는 사실을 뒤늦게 발견했습니다. 테스트가 주문 총액만 확인하고 할인 전후의 세부 금액을 검증하지 않아 처음에는 오류를 잡지 못했습니다. 저는 작은 단위 테스트를 추가해 10원 단위 할인, 소수점이 생기는 비율 할인, 쿠폰 중복 적용을 각각 확인했습니다. 그 결과 1,000원짜리 주문에서 0.1원이 잘못 반올림되는 사례도 발견했습니다. 테스트는 코드를 예쁘게 보이게 하는 장식이 아니라, 제가 변경 범위를 통제하도록 도와주는 작업 기록에 가까웠습니다.
Git 브랜치 운영 방식도 그때 바꿨습니다. 예전에는 이름 변경, 자료구조 교체, 캐시 추가를 하나의 커밋에 몰아 넣어 문제가 생기면 어느 변경을 되돌려야 할지 애매했습니다. 이후에는 rename-order-fields, replace-product-index, add-cache-invalidation처럼 한 커밋에 한 의도만 담았습니다. pull request에는 변경 전후의 배치 시간, 메모리 사용량, 실행한 테스트 명령을 적었습니다. 동료가 캐시 무효화 누락을 찾아낸 것도 이 기록 덕분이었습니다. 저는 리뷰를 통과 의식처럼 여기지 않고, 혼자 작업할 때 놓친 가정을 드러내는 두 번째 실행 환경으로 받아들이게 됐습니다.
6. 제가 지금도 지키는 리팩토링 순서
현재 저는 기능 요청을 받으면 먼저 재현 가능한 입력과 기대 결과를 만들고, 그다음 이름과 함수 경계를 손봅니다. 성능 문제가 제기되면 cProfile이나 간단한 시간 측정으로 실제 병목을 확인한 뒤 자료구조, 호출 횟수, 네트워크 대기 순서로 원인을 좁힙니다. 변경은 작은 커밋으로 나누고 각 단계마다 테스트를 실행합니다. 2024년 봄에 한꺼번에 고치려다 하루를 되돌린 경험 이후 생긴 개인 규칙입니다. 코드가 지저분하다는 이유만으로 큰 구조 변경부터 시작하지 않고, 운영 장애와 수정 비용을 줄이는 변화부터 선택합니다. 독자 여러분도 자신의 코드에서 가장 긴 함수 하나를 골라 현재 동작을 테스트로 고정한 뒤, 변수 이름 하나와 조건문 한 덩어리만 바꿔 보시기 바랍니다. 작은 변경의 결과를 직접 기록해 두면 다음 리팩토링에서 무엇을 믿고 진행할지 훨씬 선명해집니다.
'IT & 비즈니스' 카테고리의 다른 글
| 파이썬 트레이딩 봇 개발 프로세스: 기획부터 실전 배포까지의 핵심 가이드 (0) | 2026.08.23 |
|---|---|
| 생성형 AI로 생산성을 극대화하는 실전 가이드 (0) | 2026.08.23 |
| 성공적인 트레이딩을 위한 필수 조건: 투자 스트레스의 이해와 관리 (0) | 2026.08.23 |
| 파이썬 자동매매 시스템 구축 가이드 (0) | 2026.08.23 |
| 트레이딩 성공의 열쇠: 프로이드의 정신분석학으로 본 매매 심리 분석 (0) | 2026.08.23 |