“이 코드 건드리면 어디서 터질지 몰라서 못 고치겠어요.”
레거시 코드 앞에서 이 말, 다들 한 번쯤 해보셨죠.
결론부터 말씀드릴게요. 리팩터링 전에 해야 할 단 하나는 “지금 코드의 동작을 그대로 붙잡아두는 테스트” 를 먼저 만드는 일입니다.
코드를 예쁘게 고치기 전에, 지금 이 코드가 무슨 짓을 하는지부터 못 박아두는 거예요.
오늘은 제가 실제로 레거시 코드에 테스트를 붙이면서 밟았던 순서를 그대로 풀어드릴게요. 순서만 지켜도 “고치다 터지는” 사고는 확 줄어듭니다.
왜 리팩터링보다 테스트가 먼저일까요?
리팩터링의 정의부터 짚고 갈게요.
리팩터링은 겉보기 동작을 바꾸지 않으면서 내부 구조만 개선하는 작업입니다.
여기서 핵심은 “동작을 바꾸지 않는다”는 약속이에요.
그런데 그 약속을 지켰는지 무엇으로 확인할까요. 바로 테스트입니다.
테스트가 없으면 우리는 “안 바뀐 것 같아요”라는 감으로 배포하게 됩니다. 감으로 배포한 결제 코드… 상상만 해도 아찔하죠.
그래서 마이클 페더스는 책 『레거시 코드 활용 전략』에서 아예 이렇게 정의했어요. “레거시 코드란 테스트가 없는 코드” 라고요.
오래됐느냐 아니냐가 아니라, 안전망이 있느냐 없느냐가 기준이라는 거죠.
리팩터링 전 체크리스트 (핵심 요약)
바쁜 분들을 위해 순서부터 정리했어요.
- 건드릴 코드의 범위(경계) 를 먼저 정한다
- 지금 동작을 기록하는 특성화 테스트를 붙인다
- 테스트가 초록불(성공) 인 걸 확인한다
- 그제서야 작은 단위로 리팩터링한다
- 매 단계마다 테스트를 다시 돌린다
이 다섯 개를 순서대로만 해도 절반은 성공입니다. 아래에서 하나씩 풀게요.
특성화 테스트, 어떻게 붙이나요?
가장 많이 받는 질문이에요. “동작을 모르는데 무슨 테스트를 쓰죠?”
여기서 발상을 뒤집어야 합니다. 정답을 알아서 쓰는 게 아니라, 코드가 뱉는 현재 결과를 그대로 정답으로 박아두는 겁니다.
이걸 특성화 테스트(Characterization Test)라고 불러요.
방법은 의외로 단순합니다. 일단 결과를 대충 아무 값으로 넣고 테스트를 돌려요. 그럼 실패하면서 “실제 값은 이거였어요”라고 알려주거든요. 그 값을 그대로 붙여 넣으면 끝입니다.
// 1) 실제 반환값을 모르니 일부러 틀린 값을 넣는다
@Test func 할인계산_현재동작() {
let result = calcDiscount(user: user, cart: cart)
#expect(result == 0) // 실패하며 실제값을 알려줌
}
// 2) 실패 메시지에 찍힌 실제값(예: 1500)을 그대로 고정한다
// #expect(result == 1500)
이제 이 테스트는 “이 코드는 원래 1500을 뱉는다”는 사실을 지켜주는 파수꾼이 됩니다.
나중에 리팩터링하다 실수로 1200이 나오면, 테스트가 빨간불로 바로 잡아줘요.
좋은 코드인지 나쁜 코드인지는 지금 따지지 않습니다. 일단 현재 모습을 붙잡는 게 목적이니까요.
테스트가 안 붙는 코드는 어떻게 하죠?
레거시의 진짜 벽은 여기예요. DB, 외부 API, 현재 시간처럼 손댈 수 없는 것들이 함수 한가운데 박혀 있으면 테스트가 안 돌아갑니다.
이럴 때 페더스가 말한 “이음새(Seam)” 개념이 도움이 됩니다. 코드 흐름을 살짝 끊어서 가짜 값을 끼워 넣을 수 있는 지점을 만드는 거예요.
가장 안전한 방법은 딱 필요한 한 줄만 함수 파라미터로 빼는 겁니다.
예를 들어 함수 안에서 직접 현재시간()을 부르고 있다면, 그걸 인자로 받게만 바꿔줘요. 그럼 테스트에서 원하는 시간을 넣어볼 수 있죠.
주의할 점 하나. 테스트를 붙이려고 넣는 이 최소한의 수정마저도 최대한 기계적으로, 조심스럽게 해야 합니다. 아직 안전망이 없는 구간이니까요.
자주 나오는 질문 (Q&A)
Q. 커버리지 몇 %까지 채우고 시작해야 하나요?
전체를 채울 필요 없어요. 지금 당장 건드릴 그 부분, 그 경계만 감싸면 충분합니다. 100%는 목표가 아니라 함정일 때가 많아요.
Q. 테스트 붙일 시간이 없으면요?
저도 그 압박 잘 압니다. 그럴 땐 딱 고칠 함수 하나만 특성화 테스트로 감싸고 들어가세요. 5분이면 최악의 사고는 막습니다.
Q. 테스트가 지저분해도 괜찮나요?
괜찮습니다. 특성화 테스트는 임시 비계 같은 거예요. 리팩터링이 끝나 코드가 깔끔해지면 테스트도 자연스럽게 다듬으면 됩니다.
결국 순서 하나로 정리됩니다. 붙잡고(테스트) → 고치고(리팩터링) → 확인한다.
레거시 코드가 무서운 건 코드가 나빠서가 아니라 안전망이 없어서예요. 오늘 딱 함수 하나만 골라 특성화 테스트를 붙여보세요. 그다음부터는 손이 훨씬 가벼워집니다. 응원할게요!
