레거시 코드 리팩토링 AI

레거시 리팩토링: 한 번에 갈아엎지 않고 바꾸는 방법

레거시 코드를 AI와 리팩토링할 때는 유지할 동작과 변경 범위를 먼저 설명하고, 기존 테스트를 기준으로 작은 단위씩 검토하세요. Mirr Code의 Plan 모드에서 제안된 계획을 확인한 뒤 구현을 요청할 수 있습니다. 각 변경의 결과와 실행한 검사를 살피고, 테스트하지 못한 부분은 별도로 남겨야 합니다.

변경 범위와 유지할 동작을 정하고 작은 단위로 리팩토링하며 검증할 때 고려할 점을 다룹니다.

레거시 리팩토링이 매주 수작업이 되는 이유#

레거시 리팩토링에서는 기존 동작을 유지하면서 변경 범위를 파악해야 합니다. 먼저 유지할 동작과 관련 테스트를 확인하세요.

작업 전 안전 확인: 비밀번호·토큰 등 비밀정보와 고객 데이터는 파일, 프롬프트, 로그에서 제외합니다.

리팩토링 기준변경 전 확인변경 후 확인
공개 동작입력·출력과 오류 메시지같은 조건에서 결과가 유지되는가
호출 관계호출자와 관련 문서바뀐 이름이나 계약이 빠진 곳은 없는가
회귀 검사기존 테스트와 알려진 실패관련 테스트 결과와 미실행 항목
복구원본 백업과 버전 관리되돌리기 전에 보관할 변경 확인

기능 구현 가이드는 유지할 동작과 기존 테스트를 설명하고, 호출 코드와 문서도 함께 확인하도록 안내합니다.

리팩토링 전 계획과 변경 후 검토#

작업 전에는 변경 범위와 확인할 동작을 정하고, 변경 후에는 실제 코드와 테스트 결과를 비교해 검토하세요.

이 표는 검토 기준이지 완료된 리팩토링의 성과가 아닙니다. 실제 변경 전후의 차이와 실행 결과는 아직 확보되지 않았습니다.

변경 유형검토할 범위확인 방법
중복 로직 정리허용 입력과 오류 문구전후 결과를 요구사항과 대조
이름 변경호출자와 관련 문서참조 누락과 변경 diff 확인
여러 부분 수정각 부분의 유지 동작단위별 관련 검사와 미실행 항목 기록

위 항목은 기능 구현 가이드의 리팩토링 예시와 검증 안내를 바탕으로 한 점검표이며 실제 변경 기록이 아닙니다.

단계별로 하기: 입력 규칙 → 실행 → 검증#

  1. 원본을 백업하고 버전 관리 상태를 확인한 뒤 유지할 입력·출력과 변경하지 않을 범위를 정합니다.
  2. 기존 테스트를 실행할 수 있는 환경과 명령을 확인하고, 알려진 실패는 변경 전 상태로 기록합니다.
  3. Plan 모드에서 호출 관계와 작은 변경 계획을 요청하고 사람이 검토합니다.
  4. 한 단위씩 변경한 뒤 차이와 관련 테스트를 확인합니다. 실행하지 못한 검사는 완료로 기록하지 않습니다.

검증: 테스트 출력과 체크리스트#

각 변경의 실제 코드와 테스트 결과를 살피고, 기존 실패와 새 실패를 구분하세요. Plan 모드의 실제 제안, 승인된 변경 목록, 전후 diff와 테스트 출력은 이 글의 사례 증거로 아직 필요합니다.

주의할 점과 한계#

체크포인트가 없거나 일부 파일을 복구할 수 없는 경우가 있습니다. 작업 전에 백업과 버전 관리를 준비하고 복구 전에 보관할 변경을 확인하세요.

복구의 범위와 외부 작업의 한계는 세션과 복구를 확인하세요.

바꾸지 않을 동작을 먼저 적는 법#

오래된 코드를 정리할 때는 보기 싫은 함수부터 고르기보다 바꾸지 않을 동작을 고정하세요. 입력 형식, 반환값, 오류의 종류, 호출 순서와 외부 부작용을 짧게 적습니다. 예를 들어 주문 요약 함수라면 빈 주문에서 어떤 값을 내는지, 취소 주문을 합계에서 빼는지, 잘못된 금액을 거부하는지부터 정합니다. 함수 이름과 내부 구조는 달라도 이 약속은 유지해야 하는지 담당자와 합의하세요. 요구사항이 없으면 현재 동작을 관찰하되 그것을 곧바로 올바른 동작이라고 부르지 않습니다.

현재 구현이 이상해 보여도 다른 코드가 그 동작에 의존할 수 있습니다. 오류를 던지는 대신 빈 목록을 반환하던 함수를 정리하면서 예외로 바꾸면 호출자의 분기가 달라집니다. 공백을 자동으로 제거하거나 정렬 순서를 바꾸는 것도 사용자에게 보이는 변경일 수 있습니다. 동작 보존과 결함 수정을 같은 diff에 섞지 않으면 리뷰하기 쉽습니다. 의도적인 동작 변경이 필요하다면 별도 과제로 분리하고 요구사항과 새 기대값을 먼저 승인받으세요.

보존할 계약가상의 주문 요약에서 질문할 것비교할 증거
입력빈 목록과 누락 필드를 허용하는가요구사항과 호출자가 만드는 실제 입력
결과금액 단위와 정렬 순서가 같은가독립적으로 정한 예시 결과
오류어떤 입력을 어느 오류로 거부하는가오류 타입·메시지와 호출자의 처리
부작용파일·DB·외부 요청을 언제 수행하는가호출 경계와 격리된 대체 구현의 기록
호환성다른 모듈에서 같은 함수를 부르는가호출자·공개 API·문서의 참조

표는 테스트를 고르는 기준입니다. 모든 항목을 한 번에 자동화할 필요는 없지만 확인하지 않은 항목은 남기세요. 단위 테스트가 없는 코드라면 가장 위험한 입력과 결과를 먼저 기록하고 작은 특성화 테스트를 검토하세요. 실제 시스템의 현재 출력과 담당자가 원하는 출력이 다르면 둘을 구분해 적습니다. 기존 결함을 유지하는 테스트에 그 사실을 표시하면 나중에 누군가 테스트를 요구사항으로 오해할 가능성을 줄일 수 있습니다.

작은 커밋 다섯 개로 나누는 설계 예시#

다섯 개는 이 글의 작업 분해 예시이며 Mirr가 자동으로 만들어 준 커밋 기록이 아닙니다. 첫 단위는 현재 동작과 알려진 실패를 기록하는 테스트만 다룹니다. 둘째 단위는 이름과 파일 위치를 바꾸지 않고 중복된 계산을 한곳으로 모읍니다. 셋째 단위는 그 계산의 입력과 출력을 분리합니다. 넷째 단위는 호출자와 오류 처리를 명시적으로 연결합니다. 마지막 단위는 사용하지 않는 코드와 오래된 문서를 정리합니다. 실제 코드에 맞지 않으면 순서와 개수를 바꾸세요.

각 단위가 왜 필요한지 한 문장으로 설명할 수 있어야 합니다. “전체 구조 개선”보다 “금액 검증이 호출자마다 달라지는 부분을 한 함수로 통일”처럼 범위를 좁히는 편이 낫습니다. 이름 변경과 제어 흐름 변경을 동시에 하면 diff에서 실제 동작 차이를 찾기 어렵습니다. 가능하면 기계적인 이동과 의미가 바뀌는 수정을 분리하세요. 커밋은 승인된 작업 범위에서만 만들고 이 예시 자체가 커밋·push·PR 게시를 허가하는 것은 아닙니다.

다음 단위로 넘어가기 전에 현재 diff가 설명한 목표만 담는지 확인합니다. 테스트가 실패하면 이전 단위의 같은 명령과 환경을 비교하세요. 이미 실패하던 테스트인지 새로 깨진 테스트인지 모르면서 기대값을 바꾸면 회귀를 숨길 수 있습니다. 실행 환경이 없어 검사를 못 했다면 단위를 작게 유지하고 필요한 환경을 인계합니다. 수정 파일이 적다는 이유만으로 검증 없는 변경을 안전하다고 판단하지 마세요.

의존성이 많은 함수를 분리할 때#

DB나 외부 API를 직접 부르는 함수는 계산과 통신을 나눌 수 있는지 살펴봅니다. 먼저 호출자가 받는 계약을 유지하면서 데이터 읽기 경계와 순수 계산의 경계를 찾으세요. 테스트에서 외부 호출을 대체할 때는 실제 인터페이스의 입력·출력·오류를 반영해야 합니다. 편리한 가짜 응답만 만들면 실제 서비스에서 생기는 빈 응답이나 시간 초과를 놓칠 수 있습니다. 대체 구현의 통과는 외부 서비스 연동 성공과 다릅니다.

캐시와 전역 상태가 있는 코드라면 호출 순서에 따라 결과가 달라지는지도 봅니다. 테스트 하나만 실행하면 통과하고 여러 개를 함께 실행하면 실패할 수 있기 때문입니다. 상태 초기화를 어디서 담당하는지 정하고, 정리 과정에서 초기화 시점을 바꾸지 않았는지 확인하세요. 비동기 작업은 반환 시점과 오류 전파를 함께 검토합니다. 이름을 정리하는 과정에서 기다리던 작업을 기다리지 않게 바꾸면 타입 검사가 통과해도 동작은 달라질 수 있습니다.

추상화를 추가할 때는 현재 중복을 실제로 줄이는지 물어보세요. 호출자 하나를 위해 여러 계층을 새로 만들면 읽는 경로만 길어질 수 있습니다. 기존 저장소에 같은 책임을 가진 모듈이 있으면 먼저 재사용 가능성을 확인합니다. 새로운 공통 함수가 필요하다면 어떤 입력을 허용하고 무엇을 책임지지 않는지 적으세요. 서로 다른 업무 규칙을 같은 함수에 억지로 합치지 않는 것도 리팩토링의 판단입니다.

diff와 테스트를 함께 읽는 검토 순서#

먼저 공개 함수와 호출자의 변경을 봅니다. 그다음 조건문, 반복 순서, 오류 처리, 반환 경로를 읽고 마지막에 이름과 형식을 검토하세요. 큰 이동은 파일이 바뀐 사실만 보고 넘기지 말고 이동 전후 코드가 같은지 살펴보세요. 테스트에서는 새 기대값이 요구사항에서 나온 것인지 확인합니다. 구현의 현재 출력으로 스냅샷을 다시 만들고 모두 승인하는 방식은 동작 보존을 독립적으로 검증하지 못합니다.

성능을 보존해야 한다면 어떤 입력 크기와 자원 사용을 비교할지도 따로 정합니다. 이 글은 성능 향상이나 회귀 없는 실행 시간을 아직 측정하지 않았습니다. 알고리즘이 더 간단해 보인다는 인상과 실제 처리 비용은 다를 수 있습니다. DB 호출이 반복문 안으로 들어갔는지, 같은 파일을 여러 번 읽는지처럼 코드에서 확인할 수 있는 위험을 먼저 조사하세요. 측정이 필요하면 승인된 합성 입력과 동일 조건으로 별도 실험을 계획합니다.

문서와 설정도 호출자입니다. 함수 이름을 바꾸면 예제 코드, 작업 스크립트와 운영 안내가 오래된 이름을 쓸 수 있습니다. 직접 호출뿐 아니라 문자열 등록, 플러그인 이름, 직렬화 필드처럼 정적 참조만으로 찾기 어려운 경계도 검토하세요. 관련 문서를 갱신할 권한이 없다면 정확한 경로와 바뀐 계약을 인계합니다. 소스가 컴파일된다는 이유만으로 주변 도구가 모두 호환된다고 보고하지 마세요.

되돌릴지 계속 고칠지 판단하기#

원인을 아는 작은 회귀는 해당 단위 안에서 수정할 수 있습니다. 원인을 모르고 여러 파일을 더 바꾸기 시작했다면 변경 범위를 다시 줄이는 편을 검토하세요. 되돌리기 전에 지금까지의 필요한 변경, 다른 사람의 수정과 비추적 파일을 별도로 보관합니다. 체크포인트가 있더라도 외부 DB나 이미 보낸 요청은 자동으로 취소되지 않습니다. 파일 복구와 서비스 복구를 같은 작업으로 생각하지 마세요.

되돌리기 판단의 예로, 계산을 분리한 뒤 빈 입력만 달라졌다면 먼저 그 입력의 기존 계약과 바뀐 반환 경로를 비교할 수 있습니다. 제안한 다섯 단위를 실행하거나 복구했다는 의미로 읽지 마세요.

변경 후보를 위험과 검증 비용으로 고르기#

리팩토링 후보가 여럿이면 중복이 가장 눈에 띄는 곳보다 계약을 작게 확인할 수 있는 곳을 먼저 고를 수 있습니다. 가상의 금액 표시 함수와 결제 저장 함수가 있다면 전자는 입력·출력을 합성 자료로 비교하기 쉽지만 후자는 외부 상태와 재시도까지 검토하세요. 이것은 모든 표시 함수가 안전하다는 뜻은 아닙니다. 실제 호출자와 데이터의 의미를 읽고 검증할 수 있는 경계를 찾으세요. 외부 상태를 바꾸는 부분을 같은 작업에 묶으면 실패 원인을 좁히기 어려워질 수 있습니다.

후보별로 목적, 유지 계약과 필요한 검사를 한 줄씩 적습니다. “계층을 깨끗하게”보다 “세 호출자가 서로 다르게 검사하던 누락 금액을 같은 계약으로 처리”처럼 설명하세요. 세 호출자가 정말 같은 규칙을 원하는지도 확인하세요. 비슷하게 생긴 코드라도 취소 주문과 일반 주문의 규칙이 다르면 합치지 않는 편이 맞을 수 있습니다. 공통 함수가 조건 인자를 계속 받아 각 호출자의 예외를 재현한다면 처음 기대했던 단순화가 일어나는지 다시 검토합니다. 코드 줄 수만 줄이는 목표는 충분하지 않습니다.

후보의 상태시작 전에 물을 질문선택 가능한 작은 조치
같은 계산이 여러 곳에 있음업무 의미와 오류 계약도 같은가같은 부분만 추출하고 차이는 호출자에 유지
함수가 너무 많은 일을 함부작용과 계산의 경계가 어디인가반환 계약을 유지하며 계산부터 분리
이름이 모호함외부 문자열 참조가 있는가참조 목록과 문서를 확인한 이름 변경
오래된 분기가 있음실제로 도달하지 않는다는 근거가 있는가삭제보다 관찰·테스트 범위 확인부터

이 표의 조치는 완료한 변경 기록이 아닙니다. 선택을 돕는 질문입니다. 사용되지 않는 것처럼 보이는 분기도 특정 설정이나 배치에서만 실행될 수 있습니다. 정적 참조가 없다는 사실 하나로 삭제하지 마세요. 관련 설정과 운영 안내를 확인할 수 없다면 그 분기는 보류하고 다른 검증 가능한 후보를 진행할 수 있습니다. 정보가 없는 부분을 묶어 전체 작업을 멈추거나 추정으로 삭제하는 두 극단을 피하는 게 좋습니다. 인계에는 보류 이유와 필요한 담당자 입력을 남깁니다.

오류 계약이 달라지는 리팩토링을 발견하기#

다음은 실행하지 않은 가상의 금액 검증 계약입니다. 기존 함수는 잘못된 금액에 INVALID_AMOUNT 결과를 반환하고 호출자가 문구를 정한다고 합시다. 계산을 분리하면서 새 함수가 같은 입력에서 예외를 던지면 계산 규칙이 같아도 호출자의 오류 처리는 달라질 수 있습니다. 화면, 배치와 API가 같은 함수를 사용한다고 정하면 영향이 다음처럼 갈릴 수 있습니다.

가상 호출자기존 오류 결과를 처리하는 방식예외로 바꾸면 생길 수 있는 차이보존을 확인할 기대 동작
입력 화면오류 코드를 해당 필드에 표시공통 오류 화면으로 빠질 수 있음잘못된 필드와 사용자의 다른 입력 유지
파일 배치실패 행의 식별자를 기록하고 업무 규칙에 따라 중단준비한 오류 요약을 만들지 못할 수 있음같은 오류 위치·중단 조건 유지
API정해진 검증 오류 형식으로 변환의도하지 않은 서버 오류가 될 수 있음공개 오류 코드·응답 형식 유지

이 예시에서 계산 분리만 승인됐다면 기존 경계가 계속 INVALID_AMOUNT 결과를 받도록 할지 먼저 검토합니다. 내부 함수를 예외 기반으로 바꾸는 설계가 필요할 수도 있지만 그 경우에는 어댑터와 호출자 변경, 새 오류 계약을 별도 단위로 다뤄야 합니다. 모든 예외를 잡아 같은 오류 코드로 바꾸면 예상하지 못한 결함까지 사용자 입력 오류로 숨길 수 있으므로 어떤 오류만 변환하는지도 구분하세요.

오류 메시지를 외부 클라이언트가 읽고 있다면 문자열도 사실상 호환 계약일 수 있습니다. 내부 로그의 표현 변경과 공개 코드·문구 변경을 같은 것으로 보지 마세요. 테스트가 전체 메시지를 고정한 이유도 읽어 보세요. 실제 공개 계약에 맞춰 판단하고, 검사를 쉽게 통과시키려는 목적만으로 assertion을 느슨하게 만들지 마세요. 그러면 위 호출자들의 동작을 검증하지 못할 수 있습니다.

검토자가 한 단위를 거절해야 하는 경우#

승인한 범위가 계산 분리인데 인증 설정이나 패키지 버전까지 바뀌었다면 이유를 확인하세요. 자동 포맷 변경이 핵심 diff를 가리고 있다면 분리를 요청할 수 있습니다. 새 테스트가 구현의 반환값을 그대로 정답으로 쓰거나 알려진 실패를 건너뛰도록 바뀌었다면 동작 보존의 근거가 약해집니다. 작은 변경이라는 설명보다 코드의 전후 차이와 검사 결과를 기준으로 판단하세요. 새 의존성이나 실행 명령은 보안과 유지 비용도 함께 검토하세요.

앞 표의 입력 화면 호출자에서 정상 금액의 계산 결과는 같더라도 잘못된 금액의 오류 표시가 사라졌다면 계산 테스트 통과만으로 승인할 수 없습니다. 검토자는 잘못된 금액을 화면 호출 경계에 넣었을 때의 기대 결과를 먼저 정하세요. 배치 호출자는 같은 오류에서 다음 행을 계속 처리하는지 멈추는지도 업무 계약과 대조합니다. 한 호출자의 수정으로 다른 호출자의 처리까지 바뀌지 않았는지 각각 확인하는 것이 필요합니다. 이것은 실제 실행 로그가 아닌 보강할 검사 계획입니다.

반대로 공개 함수 이름만 바꾸는 단위라면 위 오류 계약을 함께 바꿀 이유가 없습니다. 실제 호출, 문자열 등록과 문서 예제에서 옛 이름을 찾고 이름 변경의 소비자 목록을 만듭니다. 확인할 수 없는 외부 소비자가 있으면 호환 이름을 유지할지 담당자에게 질문하고 답이 나올 때까지 그 경계를 보류합니다. 이름 변경과 오류 설계의 성공 기준을 분리해야 각 단위를 독립적으로 거절하거나 채택할 수 있습니다.

문자열 이름으로 접근하는 호출 찾기#

Python의 getattr는 속성 이름을 문자열로 받아 그 속성의 값을 돌려줍니다. 이름은 문자열이어야 합니다(Python 내장 함수 문서). 그래서 함수 이름으로 검색하면 getattr(obj, name)처럼 문자열 변수로만 접근하는 호출자가 빠질 수 있습니다. 이름을 바꾸기 전에 문자열 키, 직렬화 필드, 설정 파일에 적힌 이름을 따로 목록화하세요.

호출자 목록에는 직접 호출, 문자열 등록, 문서 예제, 설정 파일, 테스트 이름을 각각 한 줄씩 적습니다. 각 줄에는 파일 경로와 줄 번호, 바뀌는 이름, 확인 방법을 씁니다. 검색 결과가 0개이면 호출자가 없는지 검색이 실패했는지 구분할 수 없으므로, 검색 명령과 결과 개수를 함께 남기세요. 목록에 없는 소비자를 발견하면 같은 이름 변경 단위를 다시 승인받습니다.

같은 이름이 여러 모듈에 있으면 모듈 경로까지 함께 적어야 어느 정의를 바꾸는지 구분할 수 있습니다. 검색 결과를 정의 위치별로 묶은 뒤, 각 묶음이 같은 함수인지 호출 인자와 반환 형식으로 확인하세요. 같은 이름의 다른 함수를 한 줄로 합치면 승인 범위가 잘못 넓어집니다.

검색 명령은 파일 종류별로 나눠 실행하고, 각 명령의 종료 상태와 결과 개수를 함께 기록하세요.

다음에 읽을 것#

복구 범위와 한계는 세션과 복구 문서에서 확인하세요.

변경에 필요한 테스트를 설계할 때는 테스트 생성과 검증을 참고하세요.

관련#

편집 메모 (2026-11-02): 사례 검증 범위를 다시 구분했습니다. 테스트가 새로 실패하면 변경 전의 같은 명령·환경과 비교하고, 이름 변경 뒤 호출 누락이 있으면 참조와 문서까지 조사하세요. 실제 Plan 화면, git diff·/rewind 실행 화면과 전후 동일 테스트 출력은 아직 필요합니다.

자주 묻는 질문

이 작업을 시작하려면 무엇이 필요하나요?

설치와 로그인 외에 작업 대상 파일의 사용 승인이 필요합니다. 접근을 요청하기 전에 합성 또는 비식별 데이터를 준비하고 고객 데이터와 비밀정보를 제외하세요. 이용하는 서비스와 모델 제공자의 데이터 보관 조건과 명령의 영향을 확인하고 원본을 백업하세요.

실수로 바꾸면 되돌릴 수 있나요?

체크포인트가 있으면 /rewind에서 해당 지점의 대화나 파일을 되돌릴 수 있습니다. 다만 사용할 수 있는 체크포인트가 없을 수 있고 일부 파일은 복구되지 않을 수 있습니다. 복구 전에 체크포인트 이후의 필요한 변경과 추적되지 않은 파일, 다른 사람이나 프로그램의 수정을 별도로 보관하세요. 이미 보낸 메시지, 외부 서비스에 게시한 내용, MCP로 외부 서비스에서 수정하거나 삭제한 내용, 배포 및 데이터베이스 변경은 자동으로 취소되지 않습니다. 작업 전 원본을 백업하고 버전 관리를 사용하세요.

얼마나 걸리나요?

작업 범위와 테스트 환경에 따라 다릅니다. 이 글에서는 작업 시간이나 반복 비용을 측정하지 않았습니다.

기존 테스트가 없으면 어디서 시작하나요?

유지할 입력과 출력, 오류 처리를 먼저 기록하고 대표 사례를 검토하세요. 현재 동작을 기록한 테스트도 그 동작이 옳다는 증거는 아니므로 요구사항과 대조해야 합니다.

Plan 모드의 계획을 그대로 적용해도 되나요?

변경 범위, 호출 관계와 실행할 명령을 먼저 검토하고 필요한 조건을 추가하세요. 계획이나 AI의 제안은 동작 보존을 보장하지 않습니다.

리팩토링 후 무엇을 확인해야 하나요?

변경 diff와 호출자, 유지해야 할 결과, 관련 테스트를 함께 확인하세요. 실행하지 못한 검사와 기존 실패는 구분해 기록합니다.

지금 바로 Mirr(미르)를 경험해 보세요.