백엔드 면접 용어 사전
설계기술 부채 · 점진적 리팩터링 · 특성화 테스트

레거시 리팩터링

동작을 바꾸지 않고 구조를 개선하는 작업. 점진적으로 하고 수치로 증명한다.

동작은 그대로 두고 내부 구조만 개선하는 작업. 면접에서는 왜 지금·어떻게 안전하게·무엇으로 증명 세 가지를 본다.

실제 사례 — 12년 된 메시징 서버

  • 문제 — 상태를 담는 Context 클래스가 10개 넘게 얽혀 전역 변수처럼 쓰였다

    • 어딘가에서 A가 set 하고 다른 곳에서 B가 get 한다
    • 수정 시 사용처 추적 불가, 장애 시 디버깅 불가
  • 결과 — Context 10개 → 3개, 수백 라인 삭제, 기능은 동일

    • 순환 의존성 제거, 성능 저하 없이 라이브 반영

기법 1 — 파라미터로 받아 set 하지 말고 반환하라

// ✗ 함수가 바깥 객체 상태를 직접 바꾼다
Result createCar(CarContext ctx, Something a) { ctx.setCar(new Car()); return r; }

// ✓ 만든 값을 돌려준다 — 호출부가 무엇이 바뀌는지 안다
Pair<Result, Car> createCar(Something a) { return new Pair<>(r, new Car()); }

기법 2 — 필요한 것만 명시적으로 받아라

// ✗ 시그니처만 봐선 무엇을 읽고 무엇을 바꾸는지 모른다
void process(CarContext ctx, Something a) { ... }

// ✓ 의도가 드러난다
void process(List<People> passengers, SomeValue v, Something a) { ... }

파라미터가 늘어 불편하면 함수를 더 작은 책임으로 쪼갤 신호로 읽는다.

기법 3 — 순환 의존성을 고차 함수로 끊는다

// Before — ServiceA 가 ServiceB 를 필드로 들고 있다
@Service class ServiceA { @Resource ServiceB b;
    void methodA(Integer p) { b.methodB(p); } }

// After — 의존을 '함수' 로 낮춘다. ServiceA 는 ServiceB 를 모른다
@Service class ServiceA {
    void methodA(Integer p, Function<Integer,Integer> methodB) { methodB.apply(p); } }

// 의존은 상위로 올리고 생성자 주입으로 조립한다
@Component class Handler {
    Handler(ServiceA a, ServiceB b) { ... }
    void execute() { a.methodA(2, b::methodB); } }

클래스 인터페이스보다 함수형 인터페이스가 더 작고 약한 결합이다.

안전하게 하는 법

  • ① 특성화 테스트(characterization test) 먼저

    • 바꾸기 전에 '현재 동작' 을 고정하는 테스트를 만든다
    • 없으면 "동일하게 유지했다" 를 증명할 수 없다
  • ② 점진적으로

    • Context 를 하나씩 줄이고 단계마다 기능 동일성 확인
    • 빅뱅 재작성은 되돌릴 수도 검증할 수도 없다
  • ③ 수치로 증명

    • 복잡도 · 의존성 · 결함률 · 변경 소요 시간 중 하나라도

면접 함정

  • "전면 재작성했습니다" → 위험 신호로 읽힌다. 점진적 접근과 안전망을 말하라.
  • "깔끔해졌습니다" → 근거가 아니다. 수치를 대라.
  • 테스트 없이 시작 → 동작 보존을 증명할 방법이 사라진다.

언제 하지 말아야 하나

  • 곧 폐기될 코드 — 없앨 것을 다듬을 이유가 없다

  • 아무도 안 건드리는 코드 — 변경 비용이 발생하지 않으면 부채가 아니다

  • 이해하지 못한 코드 — 먼저 읽고 테스트로 고정한 다음이다

  • 릴리스 직전 — 검증 시간이 없다

  • "더러워서" 는 이유가 아니다. "변경 비용이 실제로 발생하고 있어서" 가 이유다

설득하는 법

❌ "코드가 지저분합니다"

  • **✓ — "이 모듈은 지난 분기 결함의 40% 가 나왔고, 기능 하나 추가에**
    • 평균 5일이 걸립니다. 다른 모듈은 1.5일입니다"

비용을 숫자로 보이면 리팩터링이 '취향' 이 아니라 '투자' 가 된다

기능 개발과 섞지 않는다

  • 같은 커밋에 리팩터링과 기능 변경을 섞으면

    • 리뷰어가 무엇이 동작 변경인지 구분할 수 없다
    • 장애가 났을 때 어느 쪽이 원인인지 모른다
    • 롤백하면 기능도 함께 사라진다
  • 커밋(가능하면 PR)을 분리한다. '구조만 바꾸는 커밋' 과 '동작을 바꾸는 커밋'

보이스카우트 규칙

  • 대규모 리팩터링 프로젝트를 따로 세우기 어려운 조직이 많다
  • 그 코드를 건드릴 때마다 조금씩 낫게 만든다

다만 '조금' 의 범위를 정해 둔다 — 그러지 않으면 작은 수정이 거대한 PR 이 되어 리뷰가 막힌다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 면접 시나리오·트러블슈팅 — 종합·STAR