테스트는 전부 초록불입니다. 그런데 실제로 돌려 보면 결과가 어긋납니다. 이런 장면을 마주한 적 있으실 겁니다.
저도 자동매매 시스템을 굴리다 같은 일을 겪었습니다. 백테스트는 멀쩡히 통과하는데 실제 운용 성과가 그 숫자와 따로 놀았습니다. 원인은 어디였을까요. 파보니 같은 이름의 전략이 서로 다른 두 벌의 코드로 살아 있었습니다. 시스템이 두 단계에 걸쳐 자라는 동안, 옛 경로와 새 경로가 같은 이름표를 달고 따로 돌아간 겁니다. 백테스트가 본 건 한쪽, 실제 주문을 낸 건 다른 쪽. 초록불이 가리킨 대상과 돈이 오간 대상이 애초에 달랐던 셈입니다.
초록불이 거짓말을 하는 순간
테스트가 통과한다는 건 코드가 약속을 지킨다는 뜻입니다. 문제는 그 약속이 더 이상 현실을 가리키지 않을 때. 검증한 코드와 실제로 돌아가는 코드가 갈라져 있으면, 초록불은 엉뚱한 대상을 채점하고 있는 겁니다. 점수는 높은데 시험지가 바뀐 상황이라고 할까요.
이건 버그가 아니라 구조의 문제입니다. 버그는 한 군데를 고치면 사라집니다. 구조 문제는 다릅니다. 고칠 곳이 어디인지조차 흐려집니다. 이름은 하나인데 실체가 둘. 어느 쪽을 고쳐야 맞는 건지 코드만 봐서는 알 수가 없습니다.
패치가 쌓이는 메커니즘
증상이 보일 때마다 그 자리를 막는 방식으로 일하면 어떻게 될까요. 매번 새 분기, 새 예외 처리, 새 플래그가 붙습니다. 한동안은 잘 굴러갑니다. 통과하는 테스트가 늘어나니 마음도 놓입니다. 막상 출시 일정에 쫓기는 1인 개발자에게 "일단 막고 넘어가기"만큼 달콤한 선택지도 없습니다.
문제는 패치가 원인을 비켜 증상에만 붙는다는 점입니다. 같은 뿌리에서 나온 증상이 다섯 군데로 번지면, 패치도 다섯 개. 그러다 여섯 번째가 터지면 어디를 손대야 할지 아무도 모릅니다. 코드를 읽어도 "이 경로가 진짜 쓰이긴 하나" 확신이 안 섭니다. 오진의 비용은 이렇게 복리로 불어납니다. 진짜 무서운 건 시간이 아닙니다. 내 코드를 내가 못 믿게 되는 순간이 옵니다.
오른쪽 질문이 머리에 떠오르기 시작했다면, 패치를 한 장 더 얹을 때가 아닙니다. 멈추고 진단할 때입니다.
갈아엎기와 확장, 무엇이 가르나
그렇다고 모든 부채를 갈아엎어야 하는 건 아닙니다. 정반대 경험도 있습니다. 같은 시스템에 새 기능을 넣어야 했던 때. 그때는 추상 클래스를 하나 더해서 기존 코드를 건드리지 않고 그냥 확장했습니다. 옛 경로는 그대로 돌고 새 경로만 위에 깨끗하게 얹혔던 겁니다. 진단도, 재설계도 부르지 않았던 일. 같은 부채처럼 보여도 한쪽은 통째로 갈아엎어야 했고, 한쪽은 위에 한 겹 얹는 걸로 충분했습니다. 같은 사람이 같은 코드베이스에서 내린 두 결정이 이렇게 갈린 겁니다.
무엇이 둘을 가를까요. 차이는 한 가지였습니다. 기존 구조 위에 새 코드가 깨끗하게 얹히느냐, 기존 구조 자체가 이미 거짓말을 하고 있느냐. 전자면 손댈 게 없습니다. 후자면 손대기 전에 진단부터입니다. 이 한 줄이 패치질과 재설계를 가르는 분기점이었습니다.
- 기존 코드를 안 건드리고 얹힌다
- 옛 경로와 새 경로가 충돌하지 않는다
- 테스트가 가리키는 대상이 여전히 맞다
- 추상 클래스·인터페이스로 깔끔히 분리
- 같은 개념이 두 벌 코드로 존재한다
- 통과하는 테스트가 현실과 무관하다
- 고칠 위치를 특정할 수 없다
- 패치가 또 다른 증상을 부른다
왼쪽이면 확장으로 충분합니다. 오른쪽 신호가 둘 이상 겹치면, 패치를 멈추고 전수 진단으로 넘어가야 합니다. 진단이라고 거창할 건 없습니다. 한 개념이 코드 안에서 몇 군데에 흩어져 있는지, 실제로 실행되는 경로가 어디인지 끝까지 따라가 보는 일. 흩어진 자리를 다 모아 놓고 보면 보통은 답이 보입니다. 그렇게 근본 원인을 찾고 나서, 두 벌로 나뉜 걸 하나로 통합해 줍니다. 통합이 끝나면 백테스트가 검증한 코드와 실제로 돌아가는 코드가 다시 한 몸이 됩니다. 그제야 초록불을 믿을 수 있는 상태로 돌아옵니다. 말은 쉬워도 이 한 번이 패치 열 장보다 멀리 갑니다.
작은 팀이 부채를 다루는 법
큰 조직은 부채를 떠안고도 인력으로 버팁니다. 누군가는 옛 경로를 기억하고, 누군가는 새 경로를 압니다. 1인 창업가에겐 그 여유가 없습니다. 두 벌 코드를 동시에 머릿속에 이고 가는 순간, 판단이 흐려지고 시간이 샙니다.
그래서 작은 팀일수록 "갈아엎기냐 확장이냐" 판단을 빨리 내려 주는 편이 낫습니다. 부채를 끌고 갈 인력이 애초에 없으니까요. 그렇다고 모든 걸 의심하라는 말은 아닙니다. 멀쩡히 얹히는 코드까지 갈아엎으면 그것도 낭비. 의심할 대상은 부채 자체가 아닙니다. 테스트가 보는 대상과 실제로 돌아가는 대상이 갈라진 그 지점입니다.
기준을 한 번 더 짚어 보겠습니다. 둘이 같으면 그냥 확장하세요. 갈라졌다는 신호가 보이면, 패치를 한 장 더 얹기 전에 진단부터 해 보세요. 초록불을 믿어도 될 때와 의심해야 할 때를 가르는 눈, 그게 작은 팀이 부채를 다루는 진짜 기술입니다.
- 기술 부채(Technical Debt)
- 리팩터링과 재설계
- 회귀 테스트(Regression Testing)



