
「수년이 걸릴 이전을 수주 만에 끝낸다」를 47만 행의 Struts 유적에서 확인하다 (전편)
요약
Anthropic이 Claude Code를 활용한 대규모 코드 이전(Migration) 가이드와 스타터 키트를 공개했습니다. 96만 행의 코드를 2주 만에 이식한 사례를 바탕으로, 레거시 코드 현대화를 위한 에이전트 활용 전략과 실무적인 분석 도구 사용법을 다룹니다.
핵심 포인트
- Claude Code 기반의 대규모 코드 이전 가이드 및 스타터 키트 공개
- 96만 행의 코드를 2주 만에 이식한 Anthropic의 실전 사례 공유
- 레거시 현대화를 위한 code-modernization 플러그인 활용법
- 코드 결함이 아닌 빌드 환경 및 인프라 문제를 식별하는 preflight 단계의 중요성
TL;DR
- Anthropic이 2026년 7월 16일, 대규모 코드 이전(Migration)을 위한 공식 가이드와 스타터 키트(Starter kit)를 공개했다. 「약 96만 행을 2주 이내에」(Bun의 Zig→Rust 이식)라는 실적을 동반하고 있다.
- 공식 프로세스의 핵심 사상은
「코드를 고치지 마라. 코드를 낳은 루프를 고쳐라」. 이는 진실이며, 레거시(Legacy) 프로젝트에도 그대로 도입할 수 있다. 단, 이전 키트에는 3가지 전제가 있다.
강력한 심판(Judge)의 존재 · 구조 보존형 이전 · 룰북(Rulebook)을 쓸 수 있는 사람. 47만 행의 Struts 유적에서는 이 세 가지가 모두 무너진다 -
그리고, 무너질 것이라는 점을 Anthropic 스스로도 파악하고 있었다. 같은 시기에 공개된 code-modernization 플러그인은 이 세 가지 전제 모두에 대한 수용책을 마련해 두었다. 빌드가 통과하지 않는 것을 「차단하지 않는다」고 명시하고, 특성 테스트(Characterization test)를 직접 작성하며, 비즈니스 로직을 코드에서 채굴(Mining)한다. 따라서 전편의 결론은 "전제가 무너지기 때문에 공식 가이드를 사용할 수 없다"가 아니다.
쟁점은 「공식이 준비한 채우는 방식이, 47만 행의 빌드 불통 실물에서 정말로 기능하는가」로 옮겨간다 - 전편에서는 그 한 점을 실측했다.
/modernize-preflight단계를 47만 행에 적용한 결과, 판정은 선언대로 Not ready 0건. 그리고 제4회부터 "빌드가 통과하지 않는다"라고만 치부했던 상태의 내역이 처음으로 밝혀졌다. 코드의 결함이 아니라, 15년 된 JDK 버전 문제와 조직과 함께 사라진 사내 Nexus였다 - preflight가 "없다"고 말한 분석 도구 5개를 전부 넣고 다시 측정했더니, 이 유적은
Java만 47만 행, 모든 언어 합계 996,334행(Bun의 이식과 거의 비슷한 규모)이었다. CCN 15 이상의 함수는 단 152개뿐이었으며, 파헤쳐야 할 곳을 숫자로 압축할 수 있었다. 덤으로 리포지토리(Repository) 자체의 문제도 발견되었다..gitignore가 DB 스키마를 측정 대상에서 제외하고 있었다.
무엇이 공개되었는가
2026년 7월 16일, Anthropic이 「How Anthropic runs large-scale code migrations with Claude Code」를 공개했다.
사내 실적이 구체적이다. Bun의 공동 창업자인 Jarred Sumner(현 Anthropic 소속)가 Bun을 Zig에서 Rust로 이식—약 96만 행을 2주 이내에, API 비용 약 16.5만 달러, 머지(Merge) 전에 기존 테스트 스위트(Test suite) 100% 통과. 또 다른 사례로, Mike Krieger가 Python 코드베이스를 주말 동안 16.5만 행의 TypeScript로 이식했다. 수백 명의 에이전트(Agent), 8개의 페이즈 게이트(Phase gate), 3라운드의 적대적 리뷰(Adversarial review), 마지막으로 Python 버전과 모든 커맨드의 출력을 diff 하는 패리티 체크(Parity check) 구성이었다고 한다.
함께 2개의 자산(Asset)이 공개되었다.
- Migration starter kit (anthropics/code-migration-kit-with-claude-code) — 8개의 프롬프트(Prompt), RULEBOOK.md 템플릿, 의존성 맵 생성 스크립트, 빌드 데몬(Build daemon) 등
- code-modernization 플러그인 (anthropics/claude-plugins-official 내) — 언어 이식이 아니라 프레임워크 업데이트나 레거시 현대화용. 즉, Struts 프로젝트에 가까운 것은 이쪽이다.
이 연재는 제2회에서 「GPT-5.6 Sol은 Fable 5를 초과한다」라는 공식 발표를 실제 레거시에서 확인했다. 이번에는 검증 대상이 Anthropic 자신의 공식 수법이다. 하는 일은 같다—발표된 주장을 47만 행의 실물에 가져가서 확인하는 것.
공식 프로세스 해부—6단계와 1가지 사상
가이드가 제시하는 프로세스를 레거시 전문가의 눈으로 요약하면 다음과 같다.
| 단계 | 할 일 | 레거시 전문가의 번역 |
|---|---|---|
| 전제 | 강력한 심판(Judge)을 준비한다 | 신구(新舊)를 동일한 조건으로 채점할 수 있는 테스트 또는 패리티 하네스(Parity harness) |
| ... | ... | ... |
핵심 사상은 하나다. 동일한 실패가 반복된다면, 개별 코드를 고치는 것이 아니라 룰북(Rulebook) 측을 고쳐서 해당 파일을 재생성한다. 「코드를 고치지 마라, 코드를 낳은 프로세스를 고쳐라」—이는 이전에 국한되지 않고, AI에게 레거시를 다루게 하는 작업 전반의 설계 원칙으로서 도입할 가치가 있다. 인간의 공수는 룰북 작성에 앞당겨 투입하고, 거대 모델은 양산이 아니라 리뷰와 룰 개정에 아껴두는 식의 역할 분담도 명확하다.
여기서 레거시 전문가로서 눈길을 끄는 설계가 하나 있다. Bun의 576행짜리 룰북(PORTING.md)은 이전을 Phase A와 Phase B로 나누고 있었다. **Phase A는 "Rust가 아직 컴파일되지 않더라도 좋으니, Zig의 로직을 파일 단위로 충실하게 옮긴다"**는 것이다. 컴파일, 빌드, 실행 시의 문제는 Phase B에서 크레이트(Crate) 단위로 해결한다. 즉, 공식 프로세스는 중간에 빌드가 통과되지 않는 상태를 공정으로서 허용하고 있다. 빌드 불통에 대한 내성이 처음부터 설계에 포함되어 있는 것이다. 이 점은 나중에 다시 다루겠다.
전제를 읽다—이전 키트가 서 있는 3가지 조건
가이드와 키트를 정독하면, 이 프로세스는 3가지 전제 위에 서 있다.
전제 1: 강력한 심판이 존재한다. 가이드 스스로 명시하고 있다—심판이 없다면 종료 조건도 성공의 척도도 없다고. Bun의 이전은 "기존 테스트 스위트 100% 통과"가 심판이었다. 즉, 이전 전부터 포괄적인 테스트가 존재하며, 심지어 CI에서 돌아가고 있음이 출발점이다.
전제 2: 구조 보존형 이전이다. 키트의 README에 명시되어 있다. 이 키트의 기본값은 "동일한 아키텍처·동일한 데이터 구조·새로운 언어"의 이전이며, 그것이 템플릿화할 수 있을 정도로 기계적인 케이스이기 때문이라고. 설계를 바꾸면서 이전하는 경우에는 키트의 일부가 무효가 된다고도 적혀 있다.
전제 3: 룰북을 쓸 수 있는 사람이 있다. 키트의 프롬프트는 실제 운용의 재구성판이며, Bun의 실물은 576행의 룰북(PORTING.md)과 한 문장의 킥오프였다고 한다. 파일 명명 규칙, 크레이트(Crate) 참조, tokio/rayon/hyper/futures의 금지, async fn의 금지, unsafe 블록에 대한 SAFETY 주석 필수, 판단할 수 없는 부분은 추측하지 말고 TODO를 남길 것—이 정도의 입도를 쓸 수 있는 사람은 해당 코드베이스의 설계 판단을 속속들이 알고 있는 사람이다. Jarred Sumner는 Bun의 제작자 본인이다.
47만 행의 유적에 대입하다—3가지 모두 무너짐
그렇다면 이 전제를, 이 연재에서 발굴해 온 Mifos 구버전·약 47만 행의 Struts 코드베이스에 대입해 보자.
전제 1(심판)은 존재하지 않는다. 제4회에서 썼듯이, 이 유적은 애초에 빌드가 통과되지 않는 상태에서 시작되었다. 테스트 스위트는커녕이다. 제4회에서 수행한 특성 테스트(Characterization Test)를 통한 동작 동결은, 지금 돌이켜보면 "심판을 사후에 만드는" 공정이었다.
전제 2(구조 보존)는 성립하지 않는다. Struts 1에서 Spring Boot로의 이전은 Action/ActionForm/struts-config.xml이라는 구조 자체를 버리는 이전이다. 언어는 같지만 아키텍처가 변한다—이전 키트의 상정과는 정확히 반대다. 키트 스스로도 "그 경우에는 일부가 무효"라고 인정하고 있다.
전제 3(룰북의 작성자)은 이제 없다. 20년 된 SIer 레거시에서 설계 판단을 아는 사람은 퇴직했다. 사양서는 구현과 괴리되어 있고, 결함이 사양으로 정착되어 있다(제3회·제4회에서 보았듯이). 576행의 룰북을 쓰려면, 먼저 코드로부터 설계 판단을 발굴하는 수밖에 없다.
3가지 모두 무너졌다. 여기서 "그러니까 공식 가이드는 일본의 레거시에는 쓸 수 없다, 그 전 단계야말로 고고학이다"라고 쓸 생각으로, 나는 플러그인 측의 README를 읽었다. 결론이 바뀌었다.
무너진 끝에, 공식은 이미 손을 쓰고 있었다
code-modernization 플러그인은 이전 키트와는 별개인 10개 커맨드 구성이다. 대상으로 명시된 것은 COBOL, 레거시 Java/C++, 모놀리식(Monolithic) 웹 앱—즉, Struts 안건 그 자체다. 그리고 내용을 읽어보니, 방금 무너진 3가지 전제에 대해 각각 수용책이 마련되어 있었다.
전제 1(심판)에 대한 수용책. README에 이렇게 적혀 있다. 빌드 툴체인(Toolchain)이 있다면 최강의 등가성 증명(실행 시 신구 버전 동시 실행)을 할 수 있다. "필수는 아니다. 없는 경우에는 등가성을 기록 트레이스(Record Trace)를 통한 테스트로 폴백(Fallback)하며, preflight는 차단하지 않고 Ready-with-gaps로 보고한다". 빌드가 통과되지 않는 상태가 상정 범위 내에 있으며, 심지어 멈추지 말라고 명시되어 있다. 나아가 /modernize-transform 공정은 "계획(승인 게이트) → 특성 테스트를 작성한다 → 이디엄틱(Idiomatic)한 구현 → 테스트를 돌려 등가성을 증명"하는 방식이다. 제4회에서 내가 수동으로 작성한 특성 테스트는, 공식 플러그인이 공정으로서 내장하고 있는 작업이었다.
전제 2(구조 보존)에 대한 수용책. /modernize-transform
는 Strangler-fig 패턴을 통한 단일 모듈의 외과적 재작성(surgical rewrite)으로, 레거시를 작동시킨 상태에서 일부를 교체한다. 나아가 /modernize-reimagine
는 「추출된 의도로부터의 그린필드(Greenfield) 재구축」—구조를 보존하지 않는 이전을 정면으로 대응하는 별도의 명령어로 준비되어 있다. 구조 보존은 키트의 기본값(default)이지 플러그인의 제약이 아니다.
전제 3(룰북의 작성자)에 대한 수용책. /modernize-extract-rules
는 계산, 검증(validation), 적격 판정, 상태 전이와 같은 비즈니스 로직을 코드로부터 채굴하여, Given/When/Then 형식의 Rule Card로서 BUSINESS_RULES.md
에 출력한다. "먼저 코드로부터 설계 판단을 발굴할 수밖에 없다"라고 썼던 공정이 그대로 명령어가 되었다.
그리고 Bun의 룰북 자체도 인간이 백지에서 576줄을 쓴 것이 아니었다. Jarred Sumner는 코드를 작성하기 전에 약 3시간 동안 Claude와 대화하며 Zig의 패턴을 어떻게 멤버들에게 옮길지 논의했고, Claude가 그 대화를 PORTING.md에 문서화했다. 룰북을 쓸 수 있었던 것은 그가 Bun의 제작자이기 때문이지만, 생성 그 자체는 대화와 모델의 작업이었다. 게다가 키트에는 룰북 자체를 적대적으로 감사(audit)하여 패치를 제안하는 메타 워크플로우(meta-workflow)가 들어있다. 룰북은 인간의 성과물이 아니라, 육성해야 할 대상으로 설계되어 있다.
실측: preflight를 47만 행에 흘려보내다
여기까지는 전부 README 이야기다. "블록하지 않는다"라고 적혀 있는 것과, 실제 물건이 블록하지 않는 것은 별개의 문제이므로, 전편 중에 한 가지만 실측해 두겠다. 이 기사의 주장은 사실상 여기에 모든 무게가 실려 있다.
조건을 먼저 적는다. 대상은 mifos/head
의 5f794ce8
(2019-02-04), 제1회·제5회와 **동일 커밋(commit)**인 신규 shallow clone이다. Java 파일 3,319개, 470,162행, JSP 310장. 환경은 JDK 21.0.10 + Maven 3.9.11이다. 한 가지만 솔직히 말하자면, 나는 슬래시 명령어로 /modernize-preflight
를 실행하지 않았다. 플러그인의 명령 정의(commands/modernize-preflight.md)를 읽어 들여, Check 0~6의 절차를 그대로 실행하고 있다. 플러그인의 실체는 프롬프트(prompt)이므로 절차는 동일하지만, 대화 세션에서의 실행과는 경로가 다르다.
Check 2: 분석 도구는 0개—단, 10분이면 채워진다
| 도구 | 용도 | 이 환경 |
|---|---|---|
scc / cloc | assess | ❌ 없음 |
lizard | assess --portfolio | ❌ 없음 |
glow | 모든 명령어 | ❌ 없음 |
delta | transform | ❌ 없음 |
이 시점에서는 5개 모두 설치되어 있지 않다. 하지만 명령 정의는 각 도구에 대해 "없을 경우 어떻게 저하되는가"를 기술한 뒤, "부족한 것에는 플랫폼 설치용 원라이너(one-liner)를 병기하라"고 지시하고 있다 (이후 측정 장에서 전부 사용한다).
brew install scc, apt install cloc, pip install lizard, … 등이다. 실제로 그대로 했더니 5개 모두 설치되었다. 이 부분을 오해하면 위험하다. 이 ❌는 "막다른 길"이 아니라 10분짜리 작업 리스트다. 똑같은 ❌라도 다음에 볼 Check 3—사라진 사내 Nexus—는 10분 만에 고칠 수 없다. preflight 출력에서 읽어야 할 것은 ❌의 개수가 아니라 ❌의 종류이다. 원라이너가 병기되어 있는 것은 소요 시간의 문제일 뿐이며, 병기할 수 없는 것만이 진짜 장애물이다. 이 구분을 처음에 직면하게 해주는 것이 이 명령어의 실무적인 가치라고 생각한다.
Check 3a: 빌드 정의를 먼저 읽어라,가 통했다
Check 3a는 "추측하기 전에 빌드 정의를 찾아 읽어라. 무언가가 이 시스템을 빌드하고 있으니, 그 방법을 찾아내라"라고 지시한다. 이를 따르면 다음과 같다.
- CI 정의는 리포지토리 내에 존재하지 않는다 (
.github/,Jenkinsfile,.gitlab-ci.yml모두 없음). pom이 가리키는 CI는ci.mifos.org/hudson
그리고, 이것은 이제 없다 - 의존성 공급처는
http://maven.mifos.org/nexus/content/groups/public/
조직 내제 Nexus이며, HTTP만 지원하고, 이 환경에서는 도달할 수 없다 - 루트 pom은
<source>1.6</source><target>1.6</target>
을 고정. 24개의 pom, 23개 모듈 구성
Check 3a가 "자체 제작한 의존성 해결 메커니즘은, 이전을 진행하는 도중에 발견해서는 안 되는 것"이라고 경고하고 있는 바로 그 내용이, 이 3행에 전부 적혀 있었다. 이 시점에서 적신호가 보인다.
Check 3b: 스모크 테스트(Smoke Test)를 3단계로 실시했다
명령어 정의에는 "양쪽 레벨의 실패야말로 이 명령어의 최대 산물"이라고 적혀 있다. 실제로 그러했다.
Level 1 (대표 파일 1개를 순수 javac로 구문 컴파일) → 35개 에러. 내용은 package javax.servlet does not exist
계열로 클래스패스(Classpath) 미해결 상태이므로 예상 범위 내이다. 다만 부산물로서 결정적인 것이 나왔다. pom 고정 설정을 그대로 시도하면 다음과 같이 된다.
$ javac -source 1.6 -target 1.6 T.java
error: Source option 6 is no longer supported. Use 8 or later.
error: Target option 6 is no longer supported. Use 8 or later.
pom이 고정하고 있는 툴체인(Toolchain)이, 현행 JDK에서는 받아들여지지 않는다. 코드의 문제가 아니라, 15년이라는 시간의 문제다.
Level 2 (CI가 수행하는 방식대로 1개 모듈을 실제로 빌드) → 순수 상태에서는 첫 번째 모듈인 mifos-common에서 위의 에러로 즉사. 여기서 끝내버리면 "빌드가 통과되지 않음"으로 치부되어 버리므로, 1.6을 8로 치환했다. <source>/<target>을 직접 가진 pom이 14개, <jdk.version> 프로퍼티가 2개, 치환 대상은 총 30개소에 달한다. 원라이너(One-liner)이긴 하지만 "한 줄"은 아니다.
치환 후, mvn compile은 여기까지 진행되었다.
| # | 모듈 | 결과 |
|---|---|---|
| 1-6 | Parent / Common / DB Management / Domain DTO / Test Framework / Service Interfaces | ✅ SUCCESS |
| 7 | User Interface | ❌ FAILURE |
| 8-24 | 이후 17개 모듈 | SKIPPED |
통과한 것은 39,112행 / 470,162행 = 전체의 8%. 7번째 실패 이유가 이것이다.
[ERROR] Plugin org.mifos.maven:mifos-pseudoloc-gen:1.0 or one of its
dependencies could not be resolved
org.mifos.maven:mifos-pseudoloc-gen — Mifos 조직이 자체 제작한 Maven 플러그인(의사 로컬라이제이션 생성기)으로, 사라진 사내 Nexus에만 존재한다. 루트 pom을 포함한 6개의 pom에서 참조되고 있으며, 중단되는 지점은 userInterface · accounting · application · cashflow · questionnaire, 즉 업무 로직의 본체 측이다.
나아가, 이것을 제거하고 다시 한번 돌려보았다. 벽이 한 장인지 아니면 층(Layer)인지 알고 싶었기 때문이다.
[ERROR] The following artifacts could not be resolved:
org.springframework.mobile:spring-mobile-device:pom:1.0.0.M3 (absent)
동일한 모듈에서 다음이 나왔다. 1.0.0.M3 — Maven Central로 승격되지 않은 마일스톤(Milestone) 버전이다. 사내 Nexus가 "Central에 없는 것 전부"의 미러(Mirror)를 겸하고 있었기에, 그곳이 사라지자 층을 이루며 나타나기 시작했다. gap은 한 개가 아니다.
판정: Not ready는 제로였다
명령어 정의의 판정 규칙에 그대로 대입한다.
| 명령어 | 판정 | 근거 |
|---|---|---|
assess / map / extract-rules | ⚠️ Ready-with-gaps | Check 2는 0/5이지만 열화(degradation)일 뿐이며, 도입을 통해 해소 가능하다. 기술자(Descriptor)는 갖춰져 있다 (struts-config.xml, hibernate.cfg.xml 4개, Action 74개) |
brief | ✅ Ready | 상류(Upstream) 3개 산출물만 필요, 도구 불필요 |
transform / reimagine | ⚠️ Ready-with-gaps | 레거시(Legacy) 측 툴체인(Toolchain)이 적색(Red). 규칙대로 Not ready로 떨어지지 않고, 등가성(Equivalence)을 기록 트레이스(Record trace)/골든 마스터(Golden master)로 폴백(Fallback)한다. 이전 대상 스택 측은 JDK 21+Maven으로 녹색(Green) |
harden | ⚠️ Ready-with-gaps | Check 2 + SAST 도구 없음 |
uplift | — | 동일 스택의 버전 업그레이드(Version bump)가 아니므로 대상 제외 (Struts 1 → Spring Boot는 transform 건임) |
Not ready가 하나도 없다. README의 "빌드되지 않더라도 차단하지 않고 Ready-with-gaps로 보고한다"는 내용은, 47만 행·빌드 불가·사내 Nexus 소멸이라는 조건 하에서 선언대로 기능했다. 전편의 주장이 가진 단 하나의 지지점은 실물로 증명되었다.
그리고 또 하나, 이 연재물 측의 수확이 있다. 제4회부터 계속 "애초에 빌드가 통과되지 않는다"라고 한 줄로 써왔지만, 그 내역이 이번에 처음으로 밝혀졌다.
- JDK 핀(Pin)이 15년 전 것이다 (15곳의 교체로 해결 가능)
- 사내 Nexus가 조직과 함께 사라졌다 (계층을 이루고 있다. 내장 플러그인, Central에 승격되지 않은 마일스톤 버전)
둘 다 코드의 결함이 아니라, 환경의 결핍이다. 47만 행의 Java 자체는, 적어도 8%에 대해서는 현행 JDK에서 그대로 통과되었다. "레거시라서 작동하지 않는다"가 아니라 "레거시를 작동시키기 위한 주변 환경이 상실되었다"가 맞다. Check 4에서 발견된 소스 없는 jar 140개, Pentaho/BIRT의 리포트 정의 119개 파일(.ktr 67 / .prpt 44 / .rptdesign 8)도 같은 성질의 블랙박스다. preflight가 처음에 이것을 말하게 한 점은 솔직히 유용했다.
또한 web.xml이 reporting/src/unused/ 아래에만 있다는 발견도 Check 4의 부산물이다. 배포 기술자(Deployment descriptor)가 "unused" 디렉토리에 있는 코드베이스에서, /modernize-map의 엔트리 포인트(Entry point) 검출이 어떻게 동작할지는 후편의 관전 포인트가 될 것이다.
계측: 분석 도구 5개를 넣어 47만 행을 측정하다
preflight는 부재한 5개 도구에 대해 "멈추지는 않지만, 정밀도가 떨어질 뿐"이라고 말했다. 그렇다면 실제로 넣었을 때 어디까지 정밀해질 것인가. 5개 모두 도입할 수 있었다.
| 도구 | 버전 | 입수 방법 |
|---|---|---|
scc | 3.7.0 | go install github.com/boyter/scc/v3@latest |
cloc | 1.98 | apt install cloc |
lizard | 1.23.0 | pip install lizard |
glow | (source build) | go install github.com/charmbracelet/glow@latest |
delta | 0.19.2 | cargo install git-delta |
계측은 git clean -xdf로 빌드 생성물을 제거한 순수 상태에 대해 수행했다 (처음 측정했을 때는 전 장의 Maven 실행이 남긴 target/을 포함하고 있었다. 다시 계산했다).
규모: "47만 행"은 Java만의 숫자였다
| 언어 | 파일 | 총 행 | 코드 | 복잡도 |
|---|---|---|---|---|
| Java | 3,319 | 470,955 | 323,521 | 28,740 |
| ... | 합계 | 5,955 | 996,334 | 816,838 |
이 연재에서 6회에 걸쳐 「47만 행」이라고 불러온 것은 Java만의 숫자였다. 유적의 총량은 996,334행, 파일 5,955개. Properties 1,280개와 XML 411개—설정 파일만으로 27만 행에 달하며, Struts/Spring/Hibernate의 3계층이 설정에 밀려 나온 분량이 그대로 부피가 되고 있다. 이행(Migration) 시에 읽어야 할 양으로서는 이쪽이 본체에 가깝다.
그리고 우연이지만, 비교 대상으로 삼기에 적절하다. Bun의 이식은 약 96만 행. 이 유적은 996,334행. 거의 동일한 규모이다.
COCOMO: 처음부터 다시 만든다면 무엇이 드는가
scc가 산출하는 COCOMO (organic)는 다음과 같다.
| 범위 | 개발비 | 기간 | 인원 |
|---|---|---|---|
| Java만 (323,521행) | $11,667,906 | 34.98개월 | 29.63명 |
| 전 언어 (816,838행) | $30,855,888 | 50.62개월 | 54.15명 |
COCOMO가 거친 추정치라는 점은 인지하고, 자릿수만 확인한다. 54명으로 50개월, $30.8M. 동일한 약 96만 행을, Bun은 2주·API 비용 $16.5만으로 이식했다. 자릿수가 두 단계 다르다.
물론 공평한 비교는 아니다. Bun에는 Zig로 작성된 동작하는 코드와 테스트 스위트(Test Suite) 100%가 있지만, 이쪽은 심판(Test)도 빌드(Build)도 없다. 하지만 「수년이 걸릴 이전이 수주 만에 끝난다」라는 주장이 가리키는 격차의 정체는, 이 두 숫자 사이에 있다. 그리고 전장에서 보았듯이, 이 유적에서 그 격차를 메우러 가는 경로는 공식적으로 막혀 있지 않다.
복잡도: 33,038개 함수 중, 독해가 필요한 것은 152개
lizard의 결과. 함수 33,038개, 평균 NLOC 7.5, 평균 순환 복잡도 (CCN) 1.6, 평균 토큰 56.5. 평균만 보면 매우 건전하다. 분포를 본다.
| CCN | 함수 수 | 비율 |
|---|---|---|
| 50 이상 | 7 | 0.02% |
| ... | ||
| CCN 15 이상은 불과 152개, 전체의 0.46%. 47만 행 중 인간이 진지하게 독해해야 하는 부분은 152개 함수에 집중되어 있다. 발굴의 목표를 숫자로 좁힐 수 있었다—이것이 툴을 도입하여 얻은 가장 실용적인 성과다. |
상위 10개는 다음과 같다.
| CCN | NLOC | 함수 |
|---|---|---|
| 73 | 380 | XlsClientsImporter::parse |
| ... | ||
| 읽어낼 수 있는 점이 세 가지 있다. |
1. InterceptHelper가 상위 9개 중 5개를 차지한다. scc의 파일별 복잡도에서도 1,696행·복잡도 795로 당당히 1위(2위는 LoanBO.java의 4,277행·복잡도 640). 서로 다른 알고리즘의 두 툴이 같은 파일을 지목했다. 게다가 이것은 업무 로직이 아니라, Hibernate의 메타데이터를 읽어 감사 추적(Audit Trail)을 구성하는 횡단적 관심사(Cross-cutting Concerns)의 코드다. 이 유적의 복잡도 피크는 대출 계산이 아니라 인프라 계층에 있다.
2. BranchReportStaffSummaryBO::equals의 CCN이 45.
equals()에 45개의 분기(Branch)가 있다. 객체의 동일성 판정이 누구도 설명할 수 없는 상태가 되어 있다는 뜻이다. 제3회·제4회에서 추적해 온 「사양화된 결함(Specified Bug)」의 전형적인 서식지는 이곳이며, 등가 판정이 하나만 어긋나도 장부가 틀어진다. 리라이트(Rewrite) 시 가장 먼저 망가질 장소로서 이 이상의 후보는 없다.
3. 업무 규칙은 대출·저축의 검증기(Validator)에 집중되어 있다. CCN 72인 validateEnterAccountDetailsStep, 56인 SavingsProductFormValidator::validate, 그 아래로 LoanScheduleFormBean::validateCalculateAndReviewLoanSchedule(CCN 24)이 이어진다. /modernize-extract-rules가 Given/When/Then 형식의 Rule Card로 옮겨야 할 대상은 이미 지목할 수 있다.
툴을 두 개 사용하는 이유가 여기서 나왔다
명령어 정의는 분석 툴을 scc (또는 cloc) 로 병기하고 있다. 중복처럼 보였으나 의미가 있었다. 같은 나무를 측정해도 결과가 엇갈린다.
| 항목 | scc | cloc |
|---|---|---|
| Java (코드 행) | 323,521 | 323,488 |
| SQL | 검출되지 않음 | 11개 파일 / 20,017행 |
Java는 33행 차이로 거의 일치한다. 문제는 SQL이다. scc의 전체 스캔은 SQL을 단 한 줄도 보고하지 않는다. 원인을 추적해 보니 다음과 같았다.
$ cat .gitignore
...
*.sql
리포지토리(Repository) 자체의 .gitignore가 *.sql을 제외하고 있다. SQL 파일은 git 관리 하에 커밋되어 있고 (git status는 클린 상태), ignore 규칙만 나중에 남아 있는 것이다. scc는 기본적으로 .gitignore를 존중하기 때문에 묵묵히 누락시키고, cloc은 존중하지 않기 때문에 잡아낸다. ./db를 직접 지정하여 scc를 실행하면, 정상적으로 9개 파일, 22,843행을 보고한다. 누락되었던 내용물은 상태가 좋지 않았다. base-schema.sql (스키마 정의), base-data.sql, 그리고 2MB 크기의 load_mifos_datawarehouse.sql이었다. 마이크로파이낸스(Microfinance)의 기간계 시스템에서, DB 스키마 자체가 측정에서 사라져 있었다. preflight의 Check 4는 'Data definitions — DDL, 스키마'를 명시적인 확인 항목으로 꼽고 있으므로, 한쪽 도구만 믿고 있었다면 이 부분은 조용히 공란으로 남았을 것이다.
레거시 고고학의 교훈은 다음과 같다. 유적에서는 제외 규칙(Exclude rules)이야말로 의심해야 한다. 20년 된 리포지토리의 .gitignore는 20년 전 누군가의 편의가 남긴 화석이다. 이를 존중하는 현대의 도구는 그 화석을 따라 가장 중요한 것을 숨겨버린다.
이 측정에 소요된 비용
전장의 preflight와 이번 측정을 합쳐, $9.40 / 7,418,607 토큰 (Claude Opus 5, ccusage
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기