Skip to main content

제17장 실전 2: 레거시 마이그레이션

새 프로젝트를 만드는 일은 신난다. 폴더는 깨끗하고, 구조는 예쁘고, 실수해도 남 눈치 볼 일이 적다. 하지만 현실의 개발은 대개 거기서 시작하지 않는다. 이미 7년째 돌아가는 서비스, 아무도 건드리기 싫어하는 배치 코드, 이상한 날짜 포맷과 반올림 규칙이 숨어 있는 정산 로직, 그리고 "이거 잘못 건드리면 월요일 장애 난다" 같은 문장이 진짜 출발점이다.

그래서 레거시 마이그레이션은 Agent-First 시대를 시험하는 가장 정직한 과제다. 새 앱 데모에서는 그럴듯한 화면이 나오면 박수가 나오지만, 레거시 이전에서는 박수 대신 질문이 먼저 나온다. 정말 같은 동작인가? 숨은 규칙은 안 깨졌나? 문제 생기면 되돌릴 수 있나?

이 장의 핵심은 단순하다. 레거시 이전에서 에이전트를 믿는 방법은 "알아서 잘하겠지"가 아니라, 어댑터를 먼저 세우고, 기존 동작을 테스트로 얼리고, 권한을 좁게 열고, PR에서 사람이 마지막 판단을 내리게 만드는 것이다. 점진적 교체와 검증 가능한 결과물을 중심에 두는 접근이 훨씬 안전하다.12

제5부 실전 프로젝트 — 실전 2: 레거시 마이그레이션

레거시 이전은 왜 새 프로젝트보다 훨씬 어렵나

레거시 시스템이 무서운 이유는 코드가 오래돼서만이 아니다. 문서보다 운영 경험에 의존하는 규칙이 많기 때문이다. 예를 들어 주문 취소 API가 겉으로는 단순해 보여도, 실제로는 취소 시각이 자정 직전인지, PG 응답이 부분 성공인지, 고객 등급이 VIP인지에 따라 후속 처리 방식이 달라질 수 있다. 이런 규칙은 함수 이름만 봐서는 절대 안 보인다.

AI 에이전트도 여기서 똑같이 부딪힌다. 소스 코드를 읽고 구조를 바꾸는 일, 반복적인 변환을 수행하는 일, 테스트 뼈대를 만드는 일은 잘한다. 하지만 문서화되지 않은 비즈니스 의도를 추측해서 맞히는 일은 사람의 책임으로 남겨야 한다. 그래서 레거시 이전에서는 "코드를 얼마나 잘 쓰는가"보다 "무엇을 바꾸면 안 되는가를 얼마나 명확히 묶어 두는가"가 더 중요하다.2

쉽게 말해, 새 프로젝트는 설계 게임이고 레거시 이전은 발굴 작업에 가깝다. 멋지게 짓기 전에, 먼저 어디에 지뢰가 묻혀 있는지부터 알아야 한다.

이번 실전 시나리오: 주문 정산 모듈을 안전하게 갈아끼우기

상황을 하나 가정해 보자. 오래된 Spring MVC 기반 운영 시스템 안에 주문 정산 모듈이 있다. 이 모듈은 다음 문제를 안고 있다.

  • 계산 로직이 컨트롤러, 서비스, SQL에 흩어져 있다.

  • 외부 결제사 응답 포맷을 내부 도메인 규칙과 직접 섞어 쓴다.

  • 테스트가 거의 없어서 리팩터링할 때마다 사람이 수동으로 확인한다.

  • 야간 배치와 관리자 화면이 같은 테이블 구조에 강하게 묶여 있다.

목표는 이것을 한 번에 폐기하고 다시 쓰는 것이 아니다. 먼저 새 정산 서비스를 만들고, 기존 호출부는 어댑터를 통해 새 구현으로 조금씩 우회시키는 것이다. 이른바 스트랭글러 피그 패턴은 낡은 시스템을 한 번에 베어내지 않고, 옆에서 감싸며 기능을 조금씩 대체하는 방식이다. 구 시스템과 신 시스템이 일정 기간 함께 살아도 되기 때문에, 실패 비용을 크게 낮출 수 있다.1

이 방식이 중요한 이유는 아주 현실적이다. 레거시 이전은 대개 기술 문제가 아니라 사업 연속성 문제이기 때문이다. 서비스는 멈추면 안 되고, 정산은 틀리면 안 되며, 고객센터는 내일부터 바로 써야 한다.

첫 단계: 코드를 바꾸기 전에 현재 동작을 얼려라

레거시 이전에서 가장 흔한 실수는 구조를 보기 좋게 만들고 싶은 마음이 앞서는 것이다. 하지만 보기 좋게 만드는 순간, 기존의 이상하지만 중요한 동작까지 함께 지워 버릴 수 있다.

그래서 첫 작업은 리팩터링이 아니라 동작 보존이다. Antigravity에게 가장 먼저 시킬 일은 "현재 기능을 이해하고, 공개 인터페이스 기준으로 특성 테스트를 만들어 달라"는 것이다. 특성 테스트는 코드가 이상적으로 어떻게 동작해야 하는지가 아니라, 지금 실제로 어떻게 동작하는지를 붙잡아 두는 테스트다.14

예를 들어 이런 프롬프트가 좋다.

정산 모듈의 공개 서비스 메서드와 주요 API 엔드포인트를 분석해 주세요.
기능 변경 없이 현재 동작을 고정하는 characterization test를 먼저 작성해 주세요.
특히 금액 반올림, 취소 수수료 계산, 부분 환불, 타임존 경계값, null 입력 처리, 외부 PG 오류 응답 매핑을 우선 커버해 주세요.
테스트는 기존 코드 수정 없이 붙일 수 있는 방식으로 만들고, 불안정한 부분은 별도로 표시해 주세요.

이 단계에서 중요한 건 테스트의 우아함이 아니다. 낡고 어색해도 괜찮다. 핵심은 "현재 행동을 얼렸다"는 사실이다. 나중에 테스트가 깨지면 두 가지뿐이다. 정말 회귀가 났거나, 기존의 이상한 동작을 의도적으로 바꾸려는 것이다. 둘 다 좋은 신호다. 적어도 이제는 무슨 일이 벌어졌는지 보인다.

PR 워크플로우 관점에서도 이 순서가 중요하다. 기존 코드에 손대기 전에 베이스라인 테스트를 먼저 만드는 방식은 숨어 있던 동작을 보호해 준다. 이후 실패한 테스트는 삭제할 쓰레기가 아니라 해석해야 할 신호가 된다.4

어댑터를 먼저 써라, 핵심 로직 재작성은 그다음이다

레거시 이전에서 에이전트에게 곧바로 "이 모듈을 새 스택으로 옮겨 줘"라고 말하면, 꽤 그럴듯한 결과가 나온다. 문제는 그 결과가 실무적으로 위험하다는 점이다. 호출자와 데이터 포맷, 예외 의미, 로그 형태, 운영 스크립트까지 한꺼번에 바뀌기 때문이다.

더 안전한 순서는 이렇다. 먼저 경계를 만든다. 그 경계가 바로 어댑터다.

기존 시스템이 기대하는 입력과 출력은 그대로 두고, 내부에서만 새 서비스와 연결되도록 얇은 층을 하나 넣는다. 그러면 소비자는 예전 인터페이스를 그대로 사용하고, 우리는 뒤쪽 구현만 점진적으로 바꿀 수 있다. 반대로 새 서비스가 레거시 데이터 구조를 읽어야 한다면, 레거시 쪽 규칙을 새 도메인 모델로 변환하는 역방향 어댑터도 둘 수 있다.

이 장면을 떠올리면 이해가 쉽다. 콘센트 모양이 다른 나라에 여행 갔을 때, 가전제품을 전부 새로 사지 않고 멀티 어댑터를 먼저 쓰는 것과 비슷하다. 어댑터는 낡은 세계와 새로운 세계가 당장 싸우지 않게 해 준다.

실전에서는 에이전트에게 어댑터의 책임을 아주 또렷하게 적어 주는 편이 좋다.

기존 SettlementService 인터페이스는 유지하고, 내부 구현만 NewSettlementGateway를 호출하는 어댑터로 분리해 주세요.
예외 타입은 기존 서비스 계약을 유지하고, 로그 필드명도 현행 운영 대시보드와 호환되게 남겨 주세요.
날짜 포맷과 금액 반올림 규칙은 절대 바꾸지 말고, 변환 지점이 보이도록 mapper와 adapter를 분리해 주세요.

이 프롬프트가 좋은 이유는 "새 코드 작성"보다 "호환성 유지"를 먼저 요구하기 때문이다. 레거시 이전에서 가장 비싼 버그는 코드가 더러워서 생기는 버그가 아니라, 익숙한 계약이 몰래 바뀌어서 생기는 버그다.

4시간이 3시간으로 줄어드는 지점은 작성보다 검증이다

많은 사람이 에이전트 도입 효과를 코드 생성 속도로만 생각한다. 하지만 레거시 이전에서는 체감 개선이 다른 곳에서 온다. 코드를 빨리 쓰는 것보다, 똑같은 확인 작업을 덜 반복하게 되는 순간 시간이 줄어든다.

예전에는 이런 흐름이 흔했다. 개발자가 어댑터를 손으로 만든다. 테스트가 거의 없어서 Postman으로 몇 번 두드린다. 로그를 본다. 다른 케이스가 찜찜해서 DB를 뒤져 본다. 빌드를 다시 돌린다. 다시 눌러 본다. 결국 4시간 중 2시간은 "혹시 모르니까 한 번 더"에 쓰인다.

Antigravity를 잘 붙이면 이 시간이 3시간으로 줄어드는 이유는, 에이전트가 반복 검증 루프를 쉬지 않고 대신 돌기 때문이다. 어댑터 초안을 만들고, 특성 테스트를 추가하고, 통합 테스트를 실행하고, 실패한 케이스를 분류하고, 로그를 비교하고, 결과를 Artifact로 정리하는 흐름이 한 덩어리로 이어진다. 사람은 그제야 "무엇이 왜 달라졌는가"에 집중할 수 있다.

속도 향상의 본질은 타이핑 절감이 아니라 재확인 비용 절감이다. 레거시에서는 그 차이가 정말 크다.

통합 테스트 자동화: 진짜 보호막은 여기서 생긴다

단위 테스트만으로는 레거시 이전을 지키기 어렵다. 레거시 문제는 대개 경계면에서 터지기 때문이다. 외부 API 응답, DB 스키마, 메시지 큐, 배치 입력 파일, 관리자 화면에서 발생한 상태 변경이 서로 엉켜 있다. 따라서 통합 테스트가 필요하다.1

여기서 에이전트에게 맡기기 좋은 일은 네 가지다.

  • 현재 핵심 시나리오를 기준으로 통합 테스트 뼈대를 만든다.

  • 외부 의존성을 스텁이나 목으로 분리해 재현 가능한 환경을 만든다.

  • 레거시 구현과 새 구현의 결과를 같은 입력으로 비교하는 패리티 테스트를 작성한다.

  • 실패 시 어떤 필드가 달랐는지 사람이 읽기 쉬운 리포트를 남긴다.

예를 들어 주문 취소 정산 모듈이라면, 단순 성공 케이스보다 경계값을 더 집요하게 잡아야 한다. 부분 환불, 세금 포함 여부, 쿠폰 중복 적용, 결제 취소 지연 응답, 재시도 중복 처리 같은 케이스가 바로 사고 지점이다. AI에게 "테스트 더 써 줘"라고 하지 말고, "이 모듈의 사업상 비싼 실패가 무엇인지"를 알려 주면 훨씬 좋은 테스트가 나온다.2

아주 실용적인 요청 예시는 이렇다.

기존 정산 구현과 새 어댑터 구현을 동일 입력으로 비교하는 parity integration test를 만들어 주세요.
최소 20개 케이스를 포함하고, 다음을 우선 검증해 주세요: 부분 환불, 할인 중첩, 음수 방지, 반올림 오차, PG 오류 코드 매핑, 타임존 경계, 중복 요청 idempotency.
출력 차이가 있으면 어떤 필드가 달랐는지 표 형태로 보여 주세요.

이렇게 하면 통합 테스트는 단순한 초록불 수집기가 아니라, "새 구현이 레거시와 어디서 갈라지는가"를 보여 주는 비교 엔진이 된다.

멀티 윈도우로 빌드, 로그, 스펙, 에디터를 동시에 붙들기

레거시 이전은 창 하나로 하기 어렵다. 스펙을 읽으면서 코드를 봐야 하고, 코드를 보면서 빌드 로그를 확인해야 하며, 로그를 보면서 테스트 결과를 비교해야 한다. 사람이 이걸 탭 몇 개로만 하려 들면 금방 길을 잃는다.

Antigravity의 좋은 점은 이 복잡함을 숨기지 않는다는 데 있다. Planning 모드는 작업을 단계로 쪼개고, Manager 쪽에서는 여러 작업공간과 대화를 병렬로 다룰 수 있다.3 실전에서는 보통 창을 이렇게 나누는 편이 효율적이다.

  • 첫 번째 창에는 스펙과 기존 동작 메모를 둔다.

  • 두 번째 창에는 현재 수정 중인 어댑터와 테스트 파일을 연다.

  • 세 번째 창에는 빌드 로그와 테스트 러너를 고정한다.

  • 네 번째 창에는 레거시 앱 또는 스테이징 화면을 띄워 실제 동작을 확인한다.

여기에 에이전트도 역할을 나누면 더 좋다. 한 에이전트는 특성 테스트 생성에 집중시키고, 다른 에이전트는 어댑터 구현을 맡기고, 또 다른 에이전트는 로그 분석과 실패 원인 요약만 시킬 수 있다. 사람은 그 셋을 오가며 우선순위를 조정한다.

이때 개발자의 역할이 확실히 달라진다. 직접 모든 파일을 쓰는 장인이 아니라, 작업 단위를 나누고 충돌을 줄이고 결과를 검증하는 현장 관리자에 가까워진다. 레거시 이전에서 이 역할 전환은 특히 강력하다. 원래도 복잡해서, 손이 아니라 판단이 병목이기 때문이다.

권한은 넓게 주지 말고, 단계적으로 연다

레거시 프로젝트에서 가장 위험한 순간은 에이전트가 똑똑하지 않을 때가 아니라, 너무 많은 권한을 가진 상태에서 틀릴 때다. 특히 오래된 저장소에는 삭제하면 안 되는 스크립트, 실수로 실행하면 곤란한 운영 명령, 민감한 설정 파일이 자주 섞여 있다.

Antigravity는 터미널 자동 실행 정책을 나눠 제공한다. 복잡하고 위험한 마이그레이션 작업에서는 처음부터 완전 자율로 두기보다, Planning 모드와 보수적인 실행 정책을 함께 쓰는 편이 안정적이다.35

실전에서는 보통 이렇게 시작하는 것을 권한다.

초기 탐색 단계에서는 Off 또는 보수적인 자동 실행 정책으로 둔다. 읽기 전용 성격의 명령과 테스트 명령만 Allow List에 넣는다. 예를 들어 git status, git diff, ls, cat, npm test, pytest, gradle test 같은 것들이다. 반대로 삭제, 권한 변경, 외부 전송, 원격 푸시는 막아 둔다. 프로젝트를 어느 정도 파악하고 테스트 베이스라인이 생긴 뒤에야 필요한 범위만 조금씩 넓힌다.5

브라우저 쪽도 마찬가지다. URL Allowlist는 기본적으로 localhost 중심으로 관리하고, 필요한 스테이징 도메인만 명시적으로 추가하는 편이 좋다. 공식 문서에서도 allowlist는 로컬 파일로 관리되며 기본값이 localhost라고 설명한다.6

핵심은 이거다. 레거시 이전의 목표는 에이전트에게 자유를 주는 것이 아니라, 사고 나도 복구 가능한 자유를 주는 것이다.

Allow List 전략은 "안전한 반복"을 위해 설계한다

권한 전략을 조금 더 실무적으로 정리해 보자. 많은 팀이 처음엔 모든 것을 묻는 방식으로 시작했다가, 승인 팝업에 지쳐서 갑자기 완전 자율로 넘어간다. 둘 다 극단이다.

더 나은 방법은 작업 성격별로 나누는 것이다.

  • 정보 조회 명령은 넓게 허용한다.

  • 테스트와 빌드는 제한적으로 허용한다.

  • 변경 파괴성이 있는 명령은 끝까지 수동 승인으로 남긴다.

예를 들어 git status, git diff, find, grep, ls는 거의 항상 안전하다. npm test, pytest, ./gradlew test도 신뢰 가능한 워크스페이스라면 허용 후보가 된다. 반면 rm, git push, curl, ssh, 권한 변경 명령, 배포 스크립트는 레거시 이전에서는 특히 조심해야 한다. 이유는 간단하다. 이 장의 목적은 이전이지, 사고 실험이 아니기 때문이다.

한 가지 더. 레거시 저장소에는 한두 번만 돌릴 생각이었던 임시 스크립트가 곳곳에 숨어 있다. 에이전트에게 자동 실행 권한을 줄수록, 그런 스크립트의 존재를 더 먼저 의심해야 한다.

에이전트가 PR을 올리고, 사람은 "변경 의미"를 검증한다

이전 작업의 마지막 관문은 결국 PR이다. 여기서 사람이 해야 할 일은 모든 코드를 한 줄씩 읽는 것이 아니다. 물론 핵심 로직은 읽어야 한다. 하지만 진짜 중요한 것은 다음 질문들이다.

이 PR이 기존 계약을 보존했는가.

새 어댑터가 어떤 입력과 출력을 변환하는지 명확히 드러나는가.

베이스라인 테스트와 패리티 테스트가 어떤 차이를 보여 주는가.

실패한 테스트가 의도된 변경인지, 우발적 회귀인지 설명되어 있는가.

좋은 에이전트 PR은 코드만 올리지 않는다. 변경 요약, 테스트 결과, 남은 위험, 롤백 포인트, 검증 방법까지 함께 남긴다. 그리고 사람은 그 문맥을 보고 승인한다. 이때 리뷰 기준도 조금 달라진다. "코드가 예쁜가"보다 "위험이 통제 가능한가"가 더 중요해진다.

특히 테스트가 깨졌을 때 반응이 중요하다. 구조만 바뀌고 의미는 유지됐다면 테스트를 수리하면 된다. 진짜 회귀라면 코드를 고쳐야 한다. 요구사항이 바뀐 것이라면 테스트를 재생성하거나 갱신하면 된다. 그냥 실패한 테스트를 지워 버리는 것은 가장 나쁜 선택이다. PR은 저장소 전체의 역사 평가장이 아니라, 이번 변경의 위험을 밝히는 장소여야 한다.4

실전 프롬프트: 한 번에 크게 시키지 말고, 책임을 나눠 던져라

레거시 이전에서 잘 먹히는 프롬프트는 늘 비슷한 구조를 가진다. 범위를 좁히고, 보존해야 할 계약을 쓰고, 검증 방법을 붙이고, 금지사항을 넣는다.

예를 들어 첫 번째 의뢰는 분석용으로 이렇게 갈 수 있다.

billing/settlement 모듈을 분석해 주세요.
공개 API, 주요 서비스 메서드, 외부 연동 지점, DB 테이블 의존성, 날짜/금액 변환 지점을 식별해 주세요.
결과는 위험도, 변경 난이도, 테스트 부재 영역 중심의 보고서로 정리해 주세요.
아직 코드는 수정하지 마세요.

두 번째 의뢰는 베이스라인 확보용이다.

위 분석 결과를 바탕으로 기능 변경 없이 현재 동작을 고정하는 characterization test를 추가해 주세요.
flaky 가능성이 있는 테스트는 별도로 표시하고, 원인 후보를 남겨 주세요.

세 번째 의뢰는 어댑터 구현용이다.

기존 SettlementService 계약을 유지한 채 내부 구현을 NewSettlementGateway 기반 어댑터로 분리해 주세요.
기존 로그 키와 예외 타입은 유지하고, 변환 로직은 mapper로 분리해 주세요.
변경 후 parity integration test를 실행해 결과 차이를 요약해 주세요.

네 번째 의뢰는 PR 마무리용이다.

이번 변경을 PR 리뷰 관점에서 요약해 주세요.
포함 내용: 변경 범위, 보존된 계약, 새 테스트 목록, 실패/보류 이슈, 롤백 방법, 사람이 직접 확인할 체크포인트.

이렇게 쪼개면 에이전트는 훨씬 덜 헤매고, 사람도 각 단계에서 신뢰를 쌓을 수 있다. 큰 작업을 잘게 나누는 이유는 에이전트가 멍청해서가 아니라, 레거시가 교활해서다.

결국 레거시 이전에서 믿어야 하는 것은 "자율성"이 아니라 "감사 가능성"이다

이 장의 결론은 의외로 소박하다. 레거시 이전에서 에이전트는 영웅이 아니라 아주 빠르고 성실한 이행 관리자에 가깝다. 분석하고, 비교하고, 테스트를 만들고, 어댑터를 쓰고, 로그를 읽고, PR을 정리한다. 하지만 무엇을 보존해야 하는지, 어디까지를 허용할지, 어떤 차이는 의도된 것인지 판단하는 마지막 책임은 여전히 사람에게 있다.

그렇다고 실망할 필요는 없다. 오히려 이 조합이 가장 강하다. 사람은 비즈니스 맥락과 위험 감각을 쥐고, 에이전트는 반복 검증과 구현 속도를 맡는다. 이 역할 분담이 맞아떨어질 때, 레거시 이전은 비로소 무모한 재개발이 아니라 관리 가능한 전환 프로젝트가 된다.

그리고 바로 그 점에서 Antigravity는 의미가 있다. 새 프로젝트에서 빠른 코딩을 보여 주는 도구는 많다. 하지만 낡은 시스템을 건드릴 때 필요한 것은 화려한 자동완성이 아니라, 단계적 위임과 검증, 권한 통제, 그리고 PR까지 이어지는 감사 가능한 흐름이다.

현실의 개발은 늘 레거시로 돌아온다. 그래서 이 장이야말로, Agent-First 개발이 데모를 넘어 실무로 들어오는 문턱이다.

참고

1framework-migration-legacy-modernize SKILL

2Best AI Prompts for Legacy Code Migration with Google Antigravity

3Google Antigravity 시작하기 | Google Codelabs

46 Steps for Embedding Test Generation Agents Into Your PR Workflow

5(2025-11-20) Google Antigravity 사용법 - #3. 모드 선택 및 자동실행 옵션 상세

6Google Antigravity Documentation - allowlist/denylist

책 목록

Google Antigravity: 코드를 쓰지 말고, 에이전트를 지휘하라

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기