Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

41장. 대규모 패키지 이동을 Agent에게 맡기는 방법 — 작게, 검증하며

40장에서 목표 구조를 정했다.

이제 파일 84개를 옮겨야 한다.

이 작업은 에이전틱 코딩에서
가장 극적으로 성패가 갈리는 작업이다.

잘하면 하루,
잘못하면 일주일간 컴파일이 안 된다.


왜 어려운가

파일 하나를 옮기면 연쇄가 시작된다.

PointService.kt 이동
  → 이 파일을 import 하던 31개 파일 수정
  → 그중 일부는 패키지 순환이 생김
  → 순환을 풀려면 인터페이스 추출
  → 인터페이스를 어디에 둘지 결정
  → 결정에 따라 또 다른 파일 이동

⚠️ 그리고 중간에 컴파일이 안 되면
피드백 루프가 끊긴다.

24장에서 본 그 문제다.
검증 없이 계속 수정하는 상태가 된다.


Agent가 잘하는 것과 못하는 것

잘한다못한다
import 문 일괄 수정무엇을 어디에 둘지 판단
참조 위치 전수 조사순환이 생길지 예측
기계적 반복중간에 멈추기
컴파일 에러 따라가기큰 그림 유지

🔥 오른쪽 열의 세 번째가 이 장의 핵심 위험이다.

Agent는 컴파일 에러 200개를 보면
200개를 다 고치려 한다.

중간에 “이 방향이 틀렸다” 를 판단하지 않는다.


IDE와 Agent의 역할 분담

여기서 실무적인 판단이 하나 있다.

IntelliJ의 Move/Rename 리팩터링은
이 작업을 정확하게 한다.

작업도구
단순 이동·이름 변경🔥 IDE
참조 자동 갱신IDE
무엇을 옮길지 결정Agent + 사람
옮긴 뒤 구조 조정Agent
순환 의존 해소Agent + 사람
검증테스트

⚠️ 순수한 이동을 Agent에게 시키는 것은
대개 비효율이다.

IDE가 1초에 정확히 하는 일을
Agent는 몇 분간 토큰을 쓰며 한다.

그리고 놓칠 수 있다.
문자열로 참조되는 클래스명, 설정 파일의 FQCN 같은 것들.

기계적으로 정확한 일은 기계에게,
판단이 필요한 일은 Agent에게.

이 원칙이 대규모 이동에서 특히 중요하다.


여섯 단계

1️⃣ 이동 대상을 확정한다

40장의 계층 매핑 결과를 바탕으로 이동 계획표를 만들어줘.

| 현재 경로 | 목표 경로 | 참조 파일 수 | 순환 위험 |

- 참조 파일 수는 실제로 세어줘
- 이동하면 순환이 생길 수 있는 것을 표시해줘
- 이동 순서도 제안해줘 (의존이 적은 것부터)

2️⃣ 순서를 정한다

의존이 적은 것부터 옮긴다.

1. Money, PointAmount        (아무것도 참조 안 함)
2. PointBalance              (Money만 참조)
3. PointPolicy               (위 둘 참조)
4. PointRepository 인터페이스
5. PointBalanceEntity        (Infrastructure)
6. PointApplicationService

⚠️ 반대 순서로 하면
매 단계마다 컴파일이 깨진다.

3️⃣ 한 덩어리씩 옮긴다

한 커밋에 한 덩어리다. 22장의 기준이다.

1단계만 진행하자.

Money, PointAmount 를 point/domain/model/ 로 옮겨줘.

- 이동은 했지만 로직은 한 줄도 바꾸지 마
- import 수정만 해줘
- 옮긴 뒤 컴파일이 되는지 확인해줘

🔥 두 번째 요구가 이 장에서 가장 중요한 규칙이다.

이동과 수정을 섞지 않는다.

Agent는 파일을 옮기면서
“이왕 하는 김에” 코드를 개선하려 한다.

그러면 Diff에서 이동과 변경이 뒤섞여
검토가 불가능해진다.

# 이동만 했다면 이렇게 보인다
git diff -M --stat
  rename point/{ => domain/model}/Money.kt (100%)

100% 가 아니면 뭔가 바뀐 것이다.

4️⃣ 컴파일과 테스트

./gradlew compileKotlin
./gradlew test --tests '*Point*'

단계마다 돈다.
깨지면 그 단계 안에서 해결한다.

5️⃣ 커밋

git commit -m "refactor: Money, PointAmount 를 domain/model 로 이동

로직 변경 없음. import 수정만."

메시지에 “로직 변경 없음” 을 명시한다.
리뷰어가 Diff를 읽는 방식이 달라진다.

6️⃣ 다음 단계

여섯 단계를 여섯 번 반복한다.


이동 중에 발견되는 것들

이 작업의 부수 효과다.

⚠️ PointService 를 옮기려는데
   legacy/PointMigrationJob 이 참조하고 있습니다.
   이 Job은 @Profile("migration") 으로 활성화됩니다.

34장에서 말한 숨은 의존이
이동 단계에서 확실하게 드러난다.

정적 조사에서 놓친 것도
컴파일러가 잡아준다.

발견하면 그 자리에서 고치지 않는다.

발견한 예상 밖 의존을 tasks/point-move.md 에 기록해줘.
이번 이동 범위에서는 제외하고, 컴파일만 되게 최소한으로 처리해줘.

검증을 자동화한다

단계가 많아지면 사람이 매번 확인하기 어렵다.

#!/bin/bash
# scripts/verify-move.sh
set -e
./gradlew compileKotlin compileTestKotlin
./gradlew test --tests '*Point*'
./gradlew ktlintCheck
git diff -M --stat | grep -v "100%" && echo "⚠️ 순수 이동이 아닌 변경 있음"

이 스크립트를 CLAUDE.md 에 적어두면
Agent가 매 단계 실행한다.

49장에서 이것을 Hook으로 만든다.


중단 조건

24장의 원칙이 여기서 특히 필요하다.

컴파일 에러가 20개를 넘으면 멈추고 상황을 알려줘.
고치지 말고, 무엇 때문에 이렇게 됐는지 분석해줘.

에러 200개는 대개 방향이 틀렸다는 뜻이다.

⚠️ 그 상태에서 Agent가 200개를 고치면
코드는 컴파일되지만 구조는 엉망이 된다.

되돌리는 편이 낫다.

git reset --hard HEAD    # 사람이 직접 실행

25장에서 이 명령을 deny 에 넣은 이유이기도 하다.
되돌리기는 사람의 결정이다.


이 장의 핵심

  • 파일 하나를 옮기면 연쇄가 시작되고, 중간에 컴파일이 깨지면 피드백 루프가 끊긴다
  • Agent는 컴파일 에러 200개를 보면 200개를 다 고치려 한다
  • 중간에 방향이 틀렸다고 판단하지 않는다
  • 순수한 이동은 IDE가 1초에 정확히 한다 — Agent에게 시키는 것은 비효율이다
  • 기계적으로 정확한 일은 기계에게, 판단이 필요한 일은 Agent에게 맡긴다
  • 의존이 적은 것부터 옮긴다 — 반대 순서면 매 단계 컴파일이 깨진다
  • 이동과 수정을 섞지 않는다 — git diff -M 이 100%가 아니면 뭔가 바뀐 것이다
  • 이동 중 드러나는 숨은 의존은 기록만 하고 범위에서 제외한다
  • 컴파일 에러가 임계치를 넘으면 고치지 말고 멈춘다
  • 에러 200개는 대개 방향이 틀렸다는 뜻이다 — 되돌리는 편이 낫다