저는 Claude를 이용해 상당히 침습적인(invasive) 리팩토링 작업을 공동으로 수행했습니다. 즉, 적응형 iOS 게임의 재사용 가능한 AI 엔진을 앱 레포지토리에서 분리하여 버전이 지정된 Swift package로 소비하도록 변경한 것입니다.
이 변경 사항은 47개의 파일을 건드렸습니다. 앱 커밋에서는 2,664줄을 제거하고 112줄을 추가했습니다.
단순히 클린 빌드를 하는 것만으로는 이 작업이 제대로 작동했다는 충분한 증거가 되지 못했습니다.
엔진에는 행동에 영향을 미치는 부분들(telemetry, 플레이어 모델링, 적응형 Director, 난이도 가드레일, 렌더링 지원 및 영속성)이 포함되어 있습니다. 반면 앱은 자체적인 레벨 규칙, 점수 계산, 업그레이드, 룸 템플릿을 유지합니다. 의존성(dependency)은 컴파일될 수 있지만, 이동된 기본값(moved default), 변경된 데이터 경계(changed data boundary) 또는 미묘하게 다른 시드 경로(seed path)는 AI의 동작 방식을 바꿀 수 있습니다.
따라서 아키텍처 규칙은 다음과 같았습니다:
- 재사용 가능한 정책 및 인프라는 엔진 패키지(engine package)로 이동시키고,
- 게임별 규칙은 로컬
StationRules 패키지에 남겨두며,
- 게임별 룸 데이터는 앱에 유지하고,
- 앱이 엔진을
0.1.0으로 고정합니다.
그 후 저는 헤드리스 시뮬레이터(headless simulator)를 행동 검증 체크섬(behavioural checksum)으로 사용했습니다.
저는 분리 작업 직전과 직후에 동일한 결정론적(deterministic) 25인 플레이어 시뮬레이션을 빌드하고 실행했습니다. 두 번의 실행 모두 정확히 다음 결과를 산출했습니다:
levels completed: 237
run overs: 1
boss attempts: 317
...
숫자는 완벽하게 일치했습니다.
여덟 개의 StationRules 테스트도 모두 통과했지만, 이 테스트들은 격리된 점수 계산, 업그레이드 및 진행 규칙만을 다룹니다. 시뮬레이터는 더 유용한 경계를 넘나듭니다: 생성된 룸, 봇 행동, 전투, telemetry, 적응형 결정 및 참여 레이블이 모두 함께 실행됩니다.
이는 AI 코딩 에이전트가 리팩토링을 수행할 때 더욱 중요합니다. 에이전트는 심볼 이동, import 수정, 컴파일러를 녹색(green)으로 만드는 데 능숙합니다. 하지만 소유권(ownership)이 패키지 전반에 걸쳐 이동한 후에도 상태를 가진 적응형 시스템이 여전히 동일하게 동작하는지 증명하는 것은 덜 할 수 있습니다.
가장 유용한 패턴은 “변경된 모든 줄을 더 열심히 검토하는 것”이 아니었습니다. 그것은 다음과 같았습니다:
- 시뮬레이션을 결정론적으로 만듭니다.
- 행동 수준의 출력(behaviour-level outputs)의 작은 세트를 캡처합니다.
- 아키텍처 변경을 수행합니다.
- 정확한 출력 동등성(exact output equality)을 요구합니다.
그것은 대규모 AI 지원 리팩토링을 지루한 비교로 바꿉니다.
결정론적 시스템(deterministic systems)의 경우, 행동 검증 체크섬(behavioural checksum)이 또 다른 모의 객체(mock) 더미보다 더 나은 리팩토링 테스트인 경우가 많습니다. 컴파일러는 패키지 경계(package boundary)가 유효함을 증명했습니다. 시뮬레이터는 제가 그 뒤에 숨겨진 게임 자체를 변경하지 않았음을 증명했습니다.