도마뱀의 꼬리
생물학에서 autotomy는 더 이상 유용하지 않은 신체 일부를 스스로 떼어 내는 행동입니다. 도마뱀은 포식자를 피하려고 꼬리를 버립니다. 한때는 유용했지만, 살아남으려면 놓아줘야 합니다. 그리고 새 꼬리가 다시 자랍니다.
Vibe-coded React Native 앱을 리팩터링할 때도 이렇게 생각해야 합니다.
AI-coded 초기 MVP에서는 첫 auth flow가 대개 누구 예상보다 훨씬 빨리 ship됩니다. 문제는 몇 주 뒤 product가 바뀌고 team이 실제로 설계한 적 없는 code를 조심스럽게 refactor하려 할 때 시작됩니다. 그 순간 login이 깨지고, user state가 drift하고, bug 수가 늘어납니다. 그들은 교체했어야 할 코드를 보존하려고 하는 것입니다.
AI-generated code를 리팩터링하는 일이 비싼 이유는, 애초에 내가 갖고 있지 않았던 의도를 이해해야 하기 때문입니다. Claude Code는 내가 설계하지 않은 abstraction을 만들었습니다. 내가 승인하지 않은 coupling 결정을 내렸습니다. 그런 코드를 리팩터링한다는 것은 의도적인 아키텍처가 아니라 확률적으로 만들어진 결정을 거꾸로 추적한다는 뜻입니다.
리팩터링하지 말고 교체하라
대안은 이것입니다. 어떤 module이 같은 interface를 만족하고 같은 test를 통과한다면, 이전 implementation을 이해하지 못해도 그대로 교체할 수 있습니다. 이전 module은 삭제합니다. 새 module이 그 자리를 차지합니다. 그 사실을 아는 파일은 composition root 하나뿐입니다.
// Module A (Auth v1) -> Interface Contract -> Module A' (Auth v2)
// The old auth module. You don't need to understand it.
// Claude Code generated it. It worked. Now it's done.
class SupabaseAuthService implements AuthService {
// ... implementation you inherited ...
}
// The new auth module. You designed it. Claude Code helps write it.
class CustomAuthService implements AuthService {
// ... your clean implementation ...
}
// The composition root is the ONLY place that changes:
const auth: AuthService = useSupabase
? new SupabaseAuthService()
: new CustomAuthService()
login screen은 바뀌지 않습니다. route guard도 바뀌지 않습니다. hook도 바뀌지 않습니다. swap이 일어났다는 사실을 composition root만 압니다. AI-generated code와 함께 일할 때 interface-driven architecture가 강력한 이유가 여기에 있습니다. interface는 technical debt에 대한 보험입니다.
안전한 삭제를 위한 네 가지 전제조건
이 방식은 네 가지가 갖춰져 있을 때만 동작합니다. 이것들이 없으면 코드를 지우고 운에 맡기는 것에 가깝습니다. 이것들이 있으면 수술이 됩니다.
- 정의된 interface: 모든 module은 contract를 노출한다. TypeScript type, API schema, 명확한 boundary가 있어야 한다. Cursor는 이 기준에 맞춰 생성하고, Claude Code는 이것을 읽는다. 소유권은 당신에게 있다.
- boundary enforcement: module이 서로의 내부 구현을 직접 건드릴 수 없어야 한다. dependency-cruiser, ESLint, 혹은 convention으로 막아야 한다.
- contract test: test는 implementation이 아니라 interface를 검증해야 한다. 새 module이 같은 test를 통과하면, 행위적으로 동등하다.
- 깨끗한 dependency graph: circular dependency가 없어야 한다. global을 통한 암묵적 coupling도 없어야 한다. import graph는 spiderweb가 아니라 DAG여야 한다.
Screen 하나 건드리지 않고 Analytics 교체하기
처음에는 Claude Code 추천대로 Segment를 썼다고 합시다. 이제 PostHog로 바꾸고 싶습니다. 이전 module을 삭제하고, 새 module이 같은 interface를 구현합니다. 모든 call site는 그대로 남습니다.
// Before
const analytics = new SegmentAnalytics(writeKey)
// After — same interface, different implementation
const analytics = new PostHogAnalytics(apiKey)
// Every call site stays the same.
// Cursor-generated screens don't change.
analytics.track('purchase_completed', { productId: '123' })
analytics.identify(userId, { plan: 'premium' })
보존하려는 본능은 강합니다. 기존 코드의 동작을 하나도 잃지 않으면서 조심스럽게 앞으로 가져가고 싶어집니다. 하지만 AI-generated code로 작업할 때는, 가장 안전한 경로가 종종 놓아주는 것입니다. 좋은 interface를 만들고, boundary를 강제하고, 그다음에는 과감히 삭제할 수 있어야 합니다.
Autotomy Expo Starter Pack은 이 철학을 중심으로 설계됐습니다. 모든 module은 보존이 아니라 교체를 전제로 만들어집니다. Vibe-coded 앱이 scaling wall에 부딪히면, Claude Code가 만든 아키텍처 결정을 하나하나 풀어내는 대신 그냥 교체할 수 있습니다.