Anthropic이 Claude Code를 활용하여 대규모 코드 마이그레이션을 수행하는 방법

@ClaudeDevs
영어1일 전 · 2026년 7월 21일
350K
3.3K
276
78
4.0K

TL;DR

Anthropic은 Bun의 100만 줄 규모 코드베이스를 Rust로 포팅한 사례를 통해, Claude Code를 활용하여 대규모 언어 마이그레이션을 자동화하는 6단계 프레임워크를 상세히 설명합니다.

코드 마이그레이션, 즉 프로덕션 코드베이스를 새로운 언어로 포팅하는 프로젝트는 최근까지도 수년이 걸리는 작업이었습니다.

지난 한 달 동안, Anthropic의 개별 개발자들이 Claude Fable 5, Claude Opus 4.8, 그리고 동적 워크플로우를 사용하여 수만에서 수십만 줄의 코드로 구성된 10개의 코드 패키지를 마이그레이션했습니다.

Bun의 공동 창립자이자 Anthropic의 기술 스태프 멤버인 Jarred Sumner (@jarredsumner)는 Claude Code를 사용하여 Bun을 Zig에서 Rust로 마이그레이션했습니다. 2주도 안 되는 기간 동안 백만 줄의 코드가 생성되었고, 병합 전에 CI에서 Bun의 기존 테스트 스위트가 100% 통과했습니다. 병합 후 19개의 회귀 오류가 발견되었지만 모두 수정되었습니다. Rust 포트는 6월에 Claude Code 내에서 배포되었습니다.

Anthropic Labs의 공동 리더인 Mike Krieger (@mikeyk)는 주말 동안 Python 코드베이스를 165,000줄의 TypeScript로 마이그레이션했습니다. 여기에는 수백 개의 에이전트, 8개의 단계 게이트, 3번의 적대적 검토 라운드, 그리고 각 명령의 출력을 Python 원본과 비교하는 최종 일치성 검사가 포함되었습니다.

Claude Code의 새로운 기능은 이러한 오랫동안 미뤄져 온 프로젝트의 판도를 바꿉니다. 아래는 이러한 마이그레이션을 통해 얻은 교훈을 바탕으로 현재 사용하는 6단계 프로세스입니다.

핵심 통찰은 코드를 수정하는 것이 아니라, 코드를 생성한 프로세스(루프)를 수정해야 한다는 것입니다.

언어를 마이그레이션해야 하는 이유와 시기

팀은 초기 구축 당시와 현재 프로젝트 간의 환경 변화로 인해 마이그레이션을 시작합니다. 알려진 트레이드오프가 제약이 되거나, 더 나은 접근 방식이 등장하거나, 원래 생태계가 축소되는 경우입니다.

예를 들어, Jarred는 처음에 Zig가 C 수준의 성능과 극도의 단순성을 제공했기 때문에 선택했습니다. 이는 "LLM 이전에 비좁은 오클랜드 아파트에서 1년 만에 Bun을 작성하는" 단독 창업자에게 이상적이었습니다. 이러한 단순성에는 알려진 트레이드오프가 따랐으며, 그는 여기에 대해 글을 썼습니다.

Bun의 CLI는 월간 천만 회 이상의 다운로드를 받고 있으며 Claude Code 내에서 광범위하게 사용됩니다.

불과 지난 분기만 해도 이러한 트레이드오프가 로드맵을 동결시키고 여러 분기에 걸친 프로젝트에 리소스를 투입할 만한 정당성을 제공하지는 못했습니다. 두 개의 병렬 코드베이스를 분기 또는 수년 동안 유지 관리할 수 있었고, 최종 결과가 90%의 일치율을 보인다면 시작했을 때보다 더 큰 골칫거리를 떠안게 되는 것이었습니다.

이제 최악의 시나리오는 브랜치를 삭제하고 다시 시도하는 것입니다.

여전히 정당한 비즈니스 사례가 필요합니다. 백만 줄 마이그레이션에 더 이상 4년 프로젝트 기간 동안 엔지니어링 리소스로 3~4백만 달러가 소요되지는 않지만, 실행하는 데 여전히 수만에서 수십만 달러 이상이 소요됩니다. 예를 들어, Bun 마이그레이션은 59억 개의 캐시되지 않은 입력 토큰과 6억 9천만 개의 출력 토큰을 소비했으며, API 가격 기준으로 약 $165,000입니다. Mike 포트의 주요 부분은 2,700만 개의 토큰이었습니다.

ClaudeDevs - inline image

Jarred의 백만 줄 PR(풀 리퀘스트).

하지만 마이그레이션 사례가 더 이상 존재의 근거가 될 필요는 없습니다. 변경 로그에 1년 동안의 메모리 버그 패치가 있거나, 하나의 만성적인 병목 현상이 있다면 이제 마이그레이션을 정당화할 수 있습니다.

컴파일 단계가 Mike 프로젝트의 동기였습니다. 그의 팀이 작업하는 내부 도구는 단일 바이너리로 사용자에게 제공됩니다. Python 툴체인으로 해당 바이너리를 생성하는 데 플랫폼당 약 8분이 걸렸으며, 릴리스마다 빌드 매트릭스 전체에서 총 30분을 기다려야 했습니다. 포팅 후, 동일한 컴파일이 약 2초 만에 완료되고, 바이너리 시작 속도가 6배 빨라졌으며, 팀은 별도의 배포 파이프라인을 폐기할 수 있었습니다.

AI가 코드 마이그레이션의 판도를 바꾸는 이유

Fable과 Opus 4.8은 특히 하위 에이전트를 사용하여 병렬 워크스트림을 위임, 지시 및 검증하고, 명시된 목표를 향한 여러 경로를 찾는 데 능숙합니다.

대규모 코드 마이그레이션은 이러한 고급 모델에 특히 효과적인 사용 사례입니다. 그 이유는 다음과 같습니다.

  • 작업이 병렬적입니다. 작업은 파일 및 크레이트와 같은 수천 개의 독립적인 단위로 실행될 수 있으므로, 에이전트가 서로 기다리지 않고 동시에 작업할 수 있습니다.
  • 컨텍스트가 명확하고 포괄적입니다. 기존 코드는 모델을 위한 훌륭한 명세서 역할을 합니다.
  • 내장된 심판이 있습니다. 많은 대규모 코드베이스에는 에이전트가 작업을 확인하는 데 사용할 수 있는 테스트 스위트가 포함되어 있습니다.
  • 대기열이 스스로 작성됩니다. 컴파일러 또는 테스트 실행이 실패하면, 그것이 에이전트가 수정할 다음 항목이 됩니다.
  • 일관성과 에지 케이스 처리가 필요합니다. 검토자는 모든 발견 사항 뒤에 규칙을 인용하므로, 위반 사항이 조용히 무시되는 대신 대기열 항목이 됩니다.

대규모 코드 마이그레이션을 위한 6단계

자세한 내용은 Jarred의 블로그를 참조하세요.

전제 조건

마이그레이션 프로젝트를 시작하기 전에 강력한 심판을 마련하는 것이 필수적입니다. 그렇지 않으면 종료 조건이나 성공 측정 기준이 없을 것입니다.

이 심판을 구축하려면 다음을 수행하세요.

  • 기존 테스트를 분류합니다. Claude를 사용하여 어떤 테스트가 외부 호출로 표현 가능한지, 어떤 테스트가 포팅되지 않을 내부 요소에 의존하는지 식별합니다.
  • 이식성을 위해 다시 작성합니다. 외부 지향 테스트를 원본과 포트 모두에 대해 실행할 수 있는 어설션으로 변환합니다. 적대적 에이전트를 사용하여 다시 작성된 테스트가 어설션을 약화시키지 않는지 확인합니다.
  • 심판을 검증합니다. 원본 코드에 대해 실행하여 통과하는지 확인합니다. 그런 다음 의도적으로 손상된 코드에 대해 실행하여 실패하는지 확인합니다. 손상을 감지하지 못하는 심판은 심판이 아닙니다.

이 방법은 기본적으로 Jarred의 방법론을 따르며, 각 단계마다 검토와 게이트가 있습니다. Mike는 유사한 루프 워크플로우를 사용하여 유사한 전체 구조를 따랐지만, 그는 전체 마이그레이션을 처음부터 끝까지 실행하고, 결과에 따라 규칙과 워크플로우를 수정한 후 다시 실행했습니다. 세 번째 실행까지 매번 출력을 폐기했습니다.

ClaudeDevs - inline image

1단계 — 규칙집, 의존성 맵, 갭 인벤토리 생성

순서가 중요합니다. 규칙집은 갭 인벤토리보다 먼저 나와야 합니다. 갭 인벤토리는 규칙집의 기본값이 다루지 못할 내용으로 정의되며, 둘은 공동 감사에서 함께 테스트됩니다.

규칙집

규칙집의 정확한 형태는 시작 시 내려야 하는 주요 아키텍처 결정에 따라 달라집니다. 가장 중요한 것은 새 코드가 동일한 구조를 따를 것인지, 아니면 완전히 재설계될 것인지입니다.

전자의 경우(Jarred), 규칙집은 주로 유형과 관용구를 언어 간에 변환하는 조회 테이블이며, 변환하기 어려운 구성 요소를 위해 갭 인벤토리를 가리킵니다. 후자의 경우(Mike), 설계 문서가 될 것입니다.

Jarred는 Claude와 대화하여 규칙집을 만들고, 각 모호성 영역에 대한 정책을 수립했습니다. 또한 자신의 직관에 기반한 8가지 일반적인 실패 모드 범주를 검토하도록 특별히 설계된 8개의 하위 에이전트를 사용했습니다.

의존성 맵

병렬 마이그레이션을 위해 워크스트림을 효과적으로 분할하려면 파일 의존성을 이해해야 합니다. 어떤 파일을 먼저 마이그레이션하고 어떤 파일을 동일한 배치에 포함할지 알아야 합니다. Claude Code는 에이전트를 배치하여 이 맵을 생성하는 결정론적 스크립트를 만들고 실행할 수 있습니다.

갭 인벤토리와 회의적인 검토자

새 언어에는 기존 언어와 다른 요구 사항이 있으며 이를 충족해야 합니다. Zig에서 Rust로의 경우, 차이점은 수동 메모리 관리였습니다(C와 C++도 동일한 방식으로 작동합니다). 예를 들어:

markdown
1// Zig
2
3fn readConfig(allocator: std.mem.Allocator) ![]u8 {
4 const buf = try allocator.alloc(u8, 1024);
5 // ...버퍼를 채움...
6 return buf; // 호출자가 반드시 해제해야 함 — 하지만 주석만 그렇게 말함
7}
8
9// 'defer allocator.free(buf)'를 잊어버린 호출자도 여전히 컴파일됨 — 누수는 런타임에만 표면화됨
rust
1fn read_config() -> Vec<u8> {
2 let buf = vec![0u8; 1024];
3 // ...버퍼를 채움...
4 buf // 소유권이 호출자로 이동하며, 메모리는 자동으로 해제됨
5}
6
7// 이동 후에 사용? 두 번 해제? 둘 다 컴파일되지 않음.
8// 해제를 잊음? 해제할 free 호출이 없음 — drop은 자동임.

Python에서 TypeScript로의 경우, 차이점은 인터페이스와 계약이었습니다. Python은 수락할 객체의 형태나 반환하는 내용을 선언하는 계약을 요구하지 않지만, TypeScript는 요구합니다.

Jarred와 Mike는 모두 이 암시적 지식을 포착하는 갭 인벤토리 파일을 만들었습니다. Jarred는 여기서 우리가 하는 것처럼 이러한 갭을 사전에 목록화했지만, Mike는 먼저 변환한 후 나중에 감사를 통해 갭 인벤토리를 생성하기로 선택했습니다. 두 가지를 모두 수행해야 할 수도 있습니다.

갭 인벤토리 파일을 생성하기 위한 샘플 Claude Code 프롬프트를 확인하세요.

2단계 — 규칙 스트레스 테스트

ClaudeDevs - inline image

이 단계에서 Jarred는 한 명의 에이전트가 규칙집을 사용하여 세 개의 파일을 변환하고, 한 명의 에이전트가 "시니어 Rust 엔지니어처럼" 세 개의 파일을 변환하며, 한 명의 에이전트가 diff를 사용하여 새로운 변환 규칙을 생성하도록 했습니다. 이 단계에서 그는 총 1,448개 파일에 퍼져 나갔을 경우 수많은 문제를 야기했을 두 가지 중요한 문제를 발견했습니다.

이러한 유형의 스트레스 테스트는 구조를 유지하는 마이그레이션에서만 효과적입니다. 동일한 파일의 두 변환 결과를 줄 단위로 비교할 수 있기 때문입니다. 규칙집이 Mike의 경우처럼 재설계인 경우, 동등한 테스트는 설계 문서를 적대적 검토자로 직접 공격한 다음, 일회성 종단 간 실행을 통해 검증하는 것입니다.

어쨌든 변환된 파일은 모두 폐기하세요. 목표는 점진적인 진전이 아니라 규칙을 개선하는 것입니다.

3단계 — 모든 것 변환

ClaudeDevs - inline image

나머지 단계에서는 동일한 다중 에이전트 루프 아키텍처를 실행합니다: 구현, 검토, 수정.

구현자 작업을 더 작은 모델에 오프로드하고 검토자는 더 큰 모델에 유지할 수 있습니다. 예를 들어, Mike는 주요 마이그레이션을 위해 12개의 하위 에이전트를 분산시킬 때 Claude Sonnet을 사용했습니다.

작업 대기열은 기계적이어야 합니다. 배치 스크립트는 변환된 파일이 디스크에 존재하는지 확인하여 완료된 작업을 결정한 다음, 보류 중인 파일을 구현자 에이전트를 위한 배치로 분할합니다. 대기열은 매번 디스크에서 다시 빌드되므로, 마이그레이션은 구조적으로 재개 가능합니다.

변환기가 자신 있게 실행할 수 없는 항목은 "// TODO(port): <reason>"으로 플래그 지정되어 4단계에서 처리됩니다.

두 명의 적대적 검토자가 별도의 컨텍스트를 사용하여 구현자의 작업을 평가하고, 검토자 간의 의견 불일치는 세 번째 에이전트에게 전달됩니다. 검토자가 여러 파일에서 동일한 실수를 계속 발견하면, 수정은 파일별로 이루어지지 않습니다. 규칙집에 한 문장을 추가하고 영향을 받은 배치를 재생성합니다. 규칙집은 이 단계를 통해 계속 성장합니다. 코드가 수동으로 패치되는 일은 없습니다.

이 단계에서 주목해야 할 중요한 설계 결정은 컴파일러의 위치입니다. Mike는 모든 루프 내에서 TypeScript 컴파일러를 실행했습니다. 단위를 몇 초 만에 확인하기 때문입니다. Jarred는 루프에서 컴파일러를 완전히 금지하고 다음 단계로 연기했습니다. cargo가 몇 분이 걸리기 때문입니다.

4, 5, 6단계 — 컴파일, 실행, 동작 일치

ClaudeDevs - inline image

이 세 단계는 동일한 루프 아키텍처를 공유하며 점진적으로 더 적은 인간의 판단이 필요하므로 함께 다룹니다.

Jarred는 전체 워크스페이스에서 한 번 컴파일러를 호출하는 오케스트레이터 스크립트로 이를 실행했습니다. 그런 다음 "수정 에이전트"가 오류 목록을 병렬로 처리하고 적대적 검토를 수행했습니다. 빌드가 다시 실행되고, 이 과정을 반복합니다.

오류 목록을 검토하면 조정이 필요할 수 있는 시스템적 문제를 파악하는 데 도움이 됩니다. 예를 들어, Jarred는 Zig의 지연된 컴파일이 용인했던 순환 의존성을 수정한 후 표면화된 수천 개의 Rust 모듈 오류에 직면했습니다. 그는 어떤 의존성을 삭제, 이동 또는 경계를 재구성할지 분류하는 로직을 인코딩하여 루프를 수정했습니다.

5단계에는 컴파일러 오류 목록과 유사한 기계적 진실 공급원이 있습니다: 스모크 테스트의 충돌입니다. 다시 말하지만, 루프 수정은 문제를 범주별로 그룹화하는 것이었습니다. 이 경우에는 적대적 하위 에이전트가 검토할 근본 원인별로 원인을 그룹화했습니다.

6단계이자 우리 이야기의 끝은 두 코드베이스 간의 프로그램 동작을 비교하는 것입니다.

이제 파일이 변환, 컴파일 및 스모크 테스트되었습니다.

이제 파일을 분할하고 (전제 조건 단계의) 테스트 스위트를 실행할 차례입니다. 두 코드베이스 모두에 대해 실패한 테스트를 검토하는 "수정 에이전트"로 실패를 처리합니다. 적대적 검토자가 수정 사항을 확인합니다.

이 루프의 다음 단계는 빌드 데몬입니다. 이는 바이너리 재빌드가 허용된 유일한 프로세스입니다. 수정자는 패치를 작성합니다. 데몬은 이를 배치로 모아 한 번 재빌드하고, 영향을 받는 테스트를 다시 실행한 후 결과를 피드백합니다. 이렇게 하면 가장 비용이 많이 드는 작업이 직렬화되어 여러 에이전트가 독립적으로 트리거하는 것을 방지할 수 있습니다.

Mike의 접근 방식이 여기서 중요합니다. 많은 개발자가 완전히 갖춰진 테스트 스위트를 보유하지 않거나 포팅하지 않았을 수 있기 때문입니다. Mike는 Claude가 새 포트와 원본 Python 코드베이스 모두에 대해 7가지 실제 시나리오를 실행하고 결과를 비교(diff)하는 작은 스크립트를 만들도록 했습니다. 실패한 각 시나리오에는 자체 수정 에이전트가 할당되었고, 7개 모두 통과할 때까지 루프가 실행되었습니다.

그런 다음 그는 한 단계 더 나아갔습니다. Claude는 자체적인 종단 간 테스트 스위트를 설계하고 밤새 자율적으로 실행하여, 깨진 부분을 수정하고 4일 연속으로 다시 실행했습니다. 그 결과, 어떤 시나리오 목록도 예측하지 못했을 사소한 문제들을 잡아낼 수 있었습니다.

교훈은 테스트 스위트가 없어도 이 단계를 막을 수 없다는 것입니다. 심판을 상속받을 수 없다면 Claude가 하나를 만들도록 하세요. 어느 쪽이든 원본 코드베이스가 절대적인 진실입니다.

코드 마이그레이션 모범 사례

매번 실행을 통해 이전에는 알지 못했던 새로운 것을 배웠습니다. 그러나 모든 프로젝트에서 유효했던 몇 가지 관행이 있습니다.

  • 이 가이드를 맹목적으로 따르지 마세요. 각 마이그레이션은 다릅니다. 이것을 시작점으로 삼고, 실행에 앞서 Claude와 함께 특정 마이그레이션을 계획하세요.
  • 개별 실패에 집중하지 마세요. 개별 실패는 루프의 몫입니다. 당신의 관심은 패턴에 있어야 합니다.
  • 검토는 적대적으로, 검증은 기계적으로 만드세요. 스크립트(컴파일러, diff, 테스트 스위트)가 심판 역할을 하도록 하세요.
  • 가장 큰 모델을 모든 것에 사용하지 마세요. 더 작은 모델은 대량의 구현 작업 분산을 잘 처리합니다. 가장 큰 모델은 검토자와 다른 에이전트가 따를 규칙을 작성하는 모든 작업에 남겨두세요.
  • 인간의 시간을 전면에 투자하세요. 규칙집과 스트레스 테스트가 가장 시간이 많이 걸립니다. 그 이후의 모든 것은 대부분 대기열이 소진되는 것입니다.

코드가 아닌 루프 결과 검토

Jarred의 Bun 마이그레이션은 이제 프로덕션에 있습니다. 모든 마이그레이션에는 트레이드오프가 있습니다. 예를 들어, Rust 코드의 약 4%는 "unsafe" 블록 내에 있으며, 대부분 C/C++ 경계에서의 한 줄 포인터 연산입니다.

하지만 새로운 코드베이스는 측정 가능하게 더 좋습니다. 팀의 도구가 감지할 수 있는 모든 메모리 누수가 수정되었습니다. 2,000회 반복 빌드의 한 벤치마크에서 메모리 사용량이 6,745 MB에서 609 MB로 감소했습니다. 바이너리 크기는 Linux와 Windows에서 19% 더 작아졌습니다. 그리고 교차 언어 최적화 덕분에 HTTP 서빙 및 next build, tsc와 같은 실제 워크로드에서 2~5% 더 빨라졌습니다.

참고로 견디고 있던 코드베이스를 선택하고, Claude에게 그 코드베이스의 마이그레이션 프로세스가 어떻게 보일지 물어보세요.

원클릭 저장

YouMind로 바이럴 글을 AI 심층 읽기

소스를 저장하고, 핵심 질문을 던지고, 주장을 요약해 바이럴 글을 다시 활용할 수 있는 노트로 바꾸세요. 하나의 AI 워크스페이스에서 모두 할 수 있습니다.

YouMind 둘러보기
크리에이터를 위해

당신의 Markdown을 깔끔한 𝕏 글로

직접 쓴 장문을 올릴 때 이미지, 표, 코드 블록을 𝕏에 맞게 정리하는 일은 번거롭습니다. YouMind는 전체 Markdown 초안을 깔끔하고 바로 게시할 수 있는 𝕏 글로 바꿔 줍니다.

Markdown → 𝕏 사용해 보기

분석할 패턴 더 보기

최근 바이럴 아티클

더 많은 바이럴 아티클 보기