AI 지원 Flutter 개발: 프로덕션 환경에서의 Claude Code 활용
요약
Flutter 이커머스 앱 마이그레이션 프로젝트에서 Claude Code를 활용한 실전 사례를 다룹니다. 단순한 코드 생성을 넘어 CLAUDE.md와 커스텀 스킬을 통해 프로젝트 컨텍스트를 정의하고 아키텍처 가드레일을 구축하는 'AI 지원 개발'의 중요성을 강조합니다.
핵심 포인트
- 단순 프롬프팅(Vibe coding)과 구조화된 AI 지원 개발의 차이점 설명
- CLAUDE.md를 통한 프로젝트 컨텍스트 및 아키텍처 가드레일 설정
- 커스텀 스킬(Slash commands)을 활용한 개발 모드 전환 전략
- AI 활용을 통해 마이그레이션 타임라인 약 30% 단축 및 안정성 향상
AI 지원 Flutter 개발: 프로덕션 환경에서의 Claude Code 활용
AI 코딩 도구가 실제로 프로덕션(Production) 환경에서 작동하는지, 아니면 그저 인상적인 데모만 만들어내는지 궁금해하는 시니어 개발자, 테크 리드(Tech Lead), 그리고 CTO를 위하여.
요약(TL;DR): 저는 스위스 소매업체를 위한 Flutter 이커머스 앱의 827개 커밋(Commit) 전반에 걸쳐 Claude Code를 사용했습니다. "바이브 코딩 (Vibe coding)"과 AI 지원 개발(AI-assisted development)의 차이는 AI 자체가 아니라, 그 주변을 둘러싼 인프라에 있습니다. CLAUDE.md 파일이 프로젝트 컨텍스트(Context)를 정의합니다. 커스텀 스킬(Slash commands)은 AI를 마이그레이션 모드, 레거시 모드, 테스트 모드 사이에서 전환시킵니다. 아키텍처 가드레일(Architecture guardrails)은 AI가 건드려서는 안 될 코드를 "개선"하는 것을 방지합니다. 결과: 마이그레이션 타임라인 약 30% 단축. 이는 AI가 완벽한 코드를 작성했기 때문이 아니라, 제가 판단이 필요한 결정을 내리는 동안 AI가 기계적인 부분들을 처리했기 때문입니다.
바이브 코딩 (Vibe Coding) vs. AI 지원 개발 (AI-Assisted Development)
2026년에는 이 두 용어가 혼용되어 사용되고 있습니다. 하지만 혼용해서는 안 됩니다.
바이브 코딩(Vibe coding)은 프롬프팅(Prompting)하고 커밋(Committing)하는 것입니다. 원하는 것을 설명하면 AI가 코드를 생성하고, 당신은 그것을 붙여넣습니다. 어쩌면 작동할 수도 있고, 어쩌면 컴파일될 수도 있습니다. 어쩌면 코드베이스의 나머지 부분과 동일한 패턴을 따를 수도 있습니다. 하지만 무언가 깨지기 전까지는 알 수 없으며, 그 시점이 되면 이미 그 위에 세 개의 기능이 더 구축된 상태일 것입니다.
AI 지원 개발(AI-assisted development)은 구조화된 협업입니다. AI는 당신의 아키텍처를 알고 있습니다. 코드베이스의 어느 부분에서 어떤 패턴을 따라야 하는지 알고 있습니다. 언제 TaskEither를 사용해야 하고 언제 try/catch를 사용해야 하는지 알고 있습니다. AI가 이를 아는 이유는 당신이 단 한 줄의 코드를 쓰기 전에 읽게 될 설정 파일(Configuration files)에 명시적으로 알려주었기 때문입니다.
두 방식 모두 동일한 기반 모델(Underlying models)을 사용합니다. 차이점은 전적으로 설정(Setup)에 있습니다. 하나는 PR 디프(PR diff)에서 올바르게 보이는 코드를 생성하고, 다른 하나는 6개월 후 프로덕션 환경에서 작동하는 코드를 생성합니다.
저는 바이브 코딩으로 시작하여 훨씬 더 규율 있는 방식으로 끝난 프로젝트에 10개월을 보냈습니다.
프로젝트: 827개의 커밋, 하나의 코드베이스
고객사는 출시한 지 불과 2개월 된 모바일 이커머스 앱(Mobile E-Commerce App)을 보유한 유명한 스위스 소매업체였습니다. 제가 이들의 Flutter 이커머스 앱에 합류했을 당시, 앱의 crash-free rate(충돌 없는 실행 비율)는 85%였습니다. 즉, 대략 7번의 세션 중 한 번은 충돌로 끝났다는 의미입니다. 위젯 내부의 API 호출, 세 가지나 되는 서로 다른 에러 핸들링(error handling) 스타일, 그리고 튜토리얼 로또(tutorial roulette)식으로 결정된 상태 관리(state management)가 혼재되어 있었습니다.
22주가 넘는 시간 동안, 저는 새로운 기능을 출시하면서 동시에 앱을 클린 아키텍처(Clean Architecture)로 마이그레이션했습니다. 827개의 커밋이 쌓였고, crash-free rate는 97%를 넘어섰습니다. 마이그레이션 전략 자체에 대해서는 별도로 작성했습니다. 이 포스트는 그렇게 빠르게 움직이는 것을 가능하게 해준 AI 툴링(tooling)에 관한 이야기입니다.
제가 사용한 유일한 AI 코딩 도구는 Claude Code였습니다. 이는 프로젝트 전체를 읽고 다단계 개발 작업을 수행하는 에이전트형 코딩 CLI(agentic coding CLI)입니다. Copilot도, Cursor도 아닙니다. 이 차이는 매우 중요한데, 이 워크플로우가 CLAUDE.md 설정 파일과 커스텀 스킬(custom skills)이라는 Claude Code의 기능에 의존하기 때문입니다.
컨텍스트 문제 (The Context Problem)
프로젝트 컨텍스트(context) 없이 Flutter 코드베이스에 AI를 투입하면 다음과 같은 일이 발생합니다.
제품 데이터를 가져오라고 요청하면, AI는 try/catch가 포함된 서비스 클래스를 작성합니다. 사용자 데이터를 가져오라고 하면, fpdart의 TaskEither를 사용하는 리포지토리(repository)를 작성합니다. 장바구니 상태를 처리하라고 하면, ChangeNotifier를 생성합니다. 하지만 당신은 이미 Riverpod을 사용 중입니다.
세 번의 요청에 세 가지 서로 다른 패턴이 나왔습니다. 각각은 개별적으로 보면 방어 가능할지 모르나, 합쳐지면 유지보수의 악몽이 됩니다.
AI는 당신의 프로젝트가 에러 핸들링을 위해 TaskEither를 사용하고, 상태 관리를 위해 AsyncNotifier를 사용한다는 사실을 알 방법이 없습니다. AI는 자신이 본 모든 Flutter 코드의 총합에서 답을 찾으며, 그 총합에는 존재하는 모든 패턴과 안티 패턴(anti-pattern)이 포함되어 있습니다.
해결책은 더 똑똑한 모델을 쓰는 것이 아닙니다. 해결책은 프로젝트 컨텍스트입니다.
CLAUDE.md: 프로젝트를 위한 AI 설정
Claude Code는 매 세션이 시작될 때 프로젝트 루트에 있는 CLAUDE.md 파일을 읽습니다. 아키텍처 결정 사항, 명명 규칙(naming conventions), 임포트 규칙(import rules) 등 AI가 코드를 건드리기 전에 알아야 할 모든 정보가 여기에 담깁니다.
소매업(retailer) 프로젝트에서 가져온 간소화된 버전은 다음과 같습니다:
## Architecture (아키텍처)
이 프로젝트는 기능 우선 조직화(feature-first organization)를 적용한 클린 아키텍처 (Clean Architecture)를 사용합니다.
...
이것은 사람을 위한 문서가 아닙니다. 사람은 스탠드업 미팅(standups)이나 PR 리뷰(PR reviews)를 통해 문맥(context)을 파악합니다. CLAUDE.md는 AI를 위한 문서입니다. 즉, 가이드라인 대신 의사결정 트리(decision trees)를 사용하여 명시적이고 모호함이 없어야 합니다.
의사결정 매트릭스(decision matrix)가 가장 중요한 부분입니다. 이것이 없다면 AI는 "가능한 최선의 코드를 작성하라"는 기본값으로 동작합니다. 마이그레이션(migration) 과정에서 "가능한 최선의 코드"는 문맥에 따라 달라집니다. 마이그레이션되지 않은 코드에서의 버그 수정은 레거시 패턴(legacy patterns)을 따라야 합니다. 반면, 마이그레이션 중에 작성되는 동일한 로직은 클린 아키텍처 (Clean Architecture)를 따라야 합니다. 동일한 기능이라도 의도에 따라 두 가지의 올바른 접근 방식이 존재할 수 있습니다. 어떤 모델 학습도 이러한 차이를 다루지는 못합니다. 이는 프로젝트마다 별도로 설정되어야 합니다.
Custom Skills (커스텀 스킬): 서로 다른 작업, 서로 다른 AI 동작
CLAUDE.md는 기본 문맥(baseline context)을 설정합니다. 하지만 서로 다른 개발 작업에는 AI가 다르게 동작해야 할 필요가 있습니다. 바로 이 지점에서 커스텀 스킬 (custom skills)이 등장합니다.
커스텀 스킬은 Claude Code의 슬래시 명령어 (slash commands)입니다. 각 명령어는 기본 문맥을 덮어쓰거나 확장하는 특정 지침 세트를 로드합니다. 소매업 프로젝트에서 저는 다섯 가지를 사용했습니다:
/migration은 엄격한 순서를 강제합니다:
기능을 마이그레이션할 때:
1. 도메인 레이어 (domain layer)를 먼저 생성합니다 (entities, repository interfaces, use cases)
...
AI는 단순히 "더 쉽다"는 이유로 프레젠테이션 레이어 (presentation layer)로 건너뛰지 않습니다. 도메인에서 시작하여 인터페이스를 작성하고, 바깥쪽으로 구축해 나갑니다. 마이그레이션된 모든 기능은 동일한 구조를 갖게 됩니다.
/legacy-code는 그 반대입니다. "기존 패턴을 따르세요. Clean Architecture 임포트(import)를 도입하지 마세요. 주변 코드의 스타일과 일치시키세요." 이것이 제대로 구현하기 가장 어려운 기술이었습니다. AI의 본능은 개선하는 것입니다. AI는 try/catch를 보면 이를 TaskEither로 리팩터링(refactor)하고 싶어 합니다. 그 본능은 마이그레이션(migration) 모드에서는 옳지만, 레거시(legacy) 모드에서는 재앙적입니다. 레거시 서비스 클래스에 가해진 "빠른 개선"이 해당 인터페이스에 의존하는 5개의 위젯(widget)을 망가뜨릴 수 있습니다.
/ui-component는 디자인 시스템 규칙을 로드합니다. /riverpod는 @riverpod 어노테이션(annotation)과 AsyncNotifier를 사용한 코드 생성(code generation)을 강제합니다. /test-workflow는 테스트 컨벤션(convention)을 설정합니다. 즉, 유스케이스(use case)를 위한 유닛 테스트(unit test), 조합된 컴포넌트(component)를 위한 위젯 테스트(widget test), 도메인 인터페이스(domain interface)를 통한 모의 저장소(mock repository) 설정 등이 포함됩니다.
각 기술은 AI 자체를 바꾸지 않고도 AI의 동작을 변화시킵니다. 모델은 동일합니다. 컨텍스트(context)가 다를 뿐입니다. (AI 에이전트(agent)와 이러한 도구 시스템의 작동 방식에 대한 자세한 내용은 AI Agents, MCP, and Tools Explained를 참조하세요.)
아키텍처 가드레일(Architecture Guardrails): 가장 큰 성과
이 전체 설정 중에서 가장 큰 가치를 제공한 개념을 하나만 꼽아야 한다면, 그것은 바로 아키텍처 가드레일(architecture guardrails)입니다.
마이그레이션 기간 동안에는 두 가지 유효한 아키텍처 스타일이 몇 달 동안 공존합니다. 모든 작업에는 결정이 필요합니다. "여기에는 어떤 스타일을 적용해야 하는가?" 인간은 판단력을 통해 이를 처리합니다. AI는 그런 판단력이 없습니다. 명시적인 가드레일이 없다면, AI는 레거시 코드를 보고 이를 "개선"해 버립니다. 이는 세 번째 스타일, 즉 어느 컨벤션도 일관되게 따르지 않는 절반만 마이그레이션된 코드를 만들어냅니다. 이는 예측 불가능하기 때문에 레거시 코드보다 더 나쁩니다.
CLAUDE.md에 정의된 결정 매트릭스(decision matrix)가 이 문제를 해결했습니다. 이것은 제안이 아니라 규칙입니다. "마이그레이션되지 않은 코드를 수정하나요? 레거시 패턴을 따르세요." 모호함이 없습니다.
다음은 가드레일이 있을 때와 없을 때 동일한 작업이 어떻게 달라지는지를 보여줍니다.
가드레일이 없는 경우 — 레거시 서비스의 버그 수정:
// AI가 버그를 수정하면서 레거시 코드를 "개선"합니다
class ProductService {
TaskEither<AppFailure, Product> getProduct(String id) {
...
가드레일이 있는 경우 — 동일한 버그, 동일한 서비스:
// AI가 기존 패턴을 사용하여 버그를 수정합니다
class ProductService {
Future<Product> getProduct(String id) async {
...
두 번째 버전이 추상적으로 "더 나은 코드"인 것은 아닙니다. 그것은 이 컨텍스트(context)에 맞는 올바른 코드입니다. 즉, 컨텍스트 의존적 정답성(context-dependent correctness)을 의미합니다.
AI가 잘하는 것 (그리고 부족한 점)
Claude Code를 사용하여 827번의 커밋을 수행한 결과, 저는 AI 페어 프로그래밍(pair programming)이 어디에서 가치를 제공하고 어디에서 그렇지 않은지에 대한 명확한 그림을 갖게 되었습니다.
뛰어난 점
보일러플레이트(Boilerplate)는 명백한 장점입니다. 클린 아키텍처(Clean Architecture)는 설계상 장황합니다. 엔티티 클래스(entity classes), 레포지토리 인터페이스(repository interfaces), 유스케이스(use cases), 데이터 소스(data sources), DTO, 매퍼(mappers), 프로바이더 선언(provider declarations) 등이 포함됩니다. 이 구조는 정형화되어 있습니다. AI가 마이그레이션된 두 개의 기능을 확인하고 나면, 최소한의 수정만으로 세 번째 기능의 스캐폴딩(scaffolding)을 생성합니다.
테스트 생성(Test generation)이 그 뒤를 바짝 쫓습니다. 프로젝트의 테스트 컨벤션(testing conventions)이 로드된 상태에서 "이 유스케이스에 대한 유닛 테스트를 작성해줘"라고 요청하면, 80%의 확률로 사용 가능한 테스트를 생성합니다. 나머지 20%는 수동 조정이 필요하며, 대개 AI가 인터페이스만으로는 추론할 수 없는 엣지 케이스(edge cases)와 관련이 있습니다.
패턴 일관성(Pattern consistency)은 AI가 인간보다 더 잘 처리하는 부분입니다. 일주일 동안 15번째 프로바이더를 선언하고 있는 개발자는 지름길을 찾기 시작합니다. 하지만 AI는 지치지 않습니다. 모든 AsyncNotifier는 동일한 구조를 따릅니다. 이러한 일관성은 수개월에 걸쳐 복리로 쌓입니다.
부족한 점
아키텍처 결정(Architectural decisions)은 여전히 확고한 인간의 영역입니다. "여기에 캐싱 레이어(caching layer)를 도입해야 할까요?"라는 질문은 트래픽 패턴, 백엔드 SLA, 그리고 AI가 관찰할 수 없는 다른 6가지 요소에 달려 있습니다. AI는 당신에게 자신감 있는 답변을 줄 것입니다. 하지만 그 답변은 틀릴 수도 있습니다.
비즈니스 컨텍스트 (Business context)는 AI에게 보이지 않습니다. AI는 독일 시장의 가격 표시 법적 요구사항이 스위스 시장과 다르다는 점이나, 백엔드 재설계가 완료되지 않아 특정 API 필드가 오래된 데이터 (stale data)를 반환한다는 사실을 알지 못합니다. 바로 이런 것들이 실제 버그를 유발하는 요소들입니다.
미묘한 버그 (Subtle bugs)는 위험한 범주에 속합니다. AI는 컴파일이 되고, 자신이 생성한 테스트를 통과하며, 리뷰 시 올바르게 보이는 코드를 작성합니다. 하지만 통화 계산을 위해 잘못된 비교 연산자를 사용하거나, 시간대 (timezone)의 엣지 케이스 (edge case)를 잘못 처리할 수도 있습니다. AI가 생성한 코드는 사람이 작성한 코드와 동일한 수준의 검토가 필요합니다. 어쩌면 그 이상의 검토가 필요할 수도 있는데, AI의 확신에 찬 태도가 코드를 무비판적으로 승인 (rubber-stamp)하게 만들기 쉽기 때문입니다.
또한 AI는 코드를 작성하지 말아야 할 판단력이 부족합니다. 때로는 "이것은 기존 기능을 중복합니다"라고 답하는 것이 올바른 대응일 때가 있습니다. 하지만 AI는 항상 무언가를 만들어낼 것입니다.
실제 영향: 마법이 아닌 약 30%의 속도 향상
마이그레이션에는 22주가 소요되었습니다. AI 지원 개발이 없었다면 제 예상으로는 3032주가 걸렸을 것입니다. 이는 대략 68주를 절약한 셈입니다. 컨퍼런스 강연에서 보는 '10배의 생산성 향상' 같은 주장은 아닙니다.
30%의 향상은 두 가지 원천에서 나왔습니다. 각 마이그레이션 단계의 기계적인 부분들, 즉 스캐폴딩 (scaffolding), 보일러플레이트 (boilerplate), 초기 테스트 스위트 (test suites) 작성이 더 빨라졌습니다. 그리고 패턴의 일관성 (pattern consistency) 덕분에 리뷰 사이클이 줄어들었습니다. "왜 이 기능은 에러를 다르게 처리하나요?"와 같은 대화가 줄어든 것입니다.
AI가 속도를 높이지 못한 부분은 다음과 같습니다: 아키텍처 설계 (architectural planning), Crashlytics 트레이스를 통한 프로덕션 이슈 디버깅, 그리고 각 단계에서의 최종 QA 단계입니다.
솔직한 계산을 해보자면: 속도 향상의 70%는 보일러플레이트 감소에서 왔고, 30%는 일관성 강제 (consistency enforcement)에서 왔습니다. AI가 제가 했을 법한 것보다 더 나은 아키텍처 선택을 해서 얻은 이득은 0%였습니다. 저는 serverless-to-monolith migration에서도 유사한 Claude Code 워크플로우를 적용했으며, 그 결과 또한 비슷했습니다.
팀에서 유사한 마이그레이션을 계획하거나 AI 도구가 프로덕션 워크플로우에 어떻게 적합한지 평가하는 경우, 저는 이전에 이런 작업을 수행했습니다.
프로젝트를 위한 설정 방법
이는 Flutter뿐만 아니라 모든 프로젝트에서 작동합니다. 저는 웹 앱, 백엔드 서비스, 인프라 프로젝트에 동일한 CLAUDE.md와 커스텀 스킬 접근 방식을 사용합니다. 앱 개발에서의 AI에 대한 더 넓은 관점을 보려면 AI를 활용한 앱 빌딩을 참고하세요.
CLAUDE.md부터 시작하세요. 아키텍처를 기계가 읽을 수 있는 용어로 문서화해야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기