실제 엔터프라이즈 앱에서의 명세 기반 AI 개발: 현장에서 얻은 교훈
요약
엔터프라이즈 규모의 AI 보조 개발에서 발생하는 '바이브 코딩'의 한계와 이를 극복하기 위한 명세 기반 개발(SDD)의 중요성을 다룹니다. AI 에이전트 사용 시 발생하는 의도 드리프트, 환각 인터페이스, 컨텍스트 붕괴 문제를 해결하기 위해 명확한 유스 케이스 중심의 명세가 필요함을 강조합니다.
핵심 포인트
- AI 보조 개발 시 코드 뒤에 숨겨진 의도(intent)를 유지하기 위해 명세가 필수적임
- 의도 드리프트, 환각 인터페이스, 컨텍스트 붕괴 등 AI 코딩의 3대 실패 모드 주의
- 단순 프롬프트 개선보다 에이전트 작업 전 명확한 명세를 작성하는 SDD 방식 권장
- 애자일의 유저 스토리보다 구체적인 유스 케이스 중심의 명세가 AI 개발에 유리함
Simon Martinelli는 스위스에서 17년 동안 보험사, 도매, 소매 및 정부 기관과 협력해 온 Java 컨설턴트입니다. 그는 데모 앱이 아닌 수천 개의 데이터베이스 테이블을 가진 ERP 시스템을 다룹니다.
최근 Java 커뮤니티 이벤트의 강연에서, Martinelli는 그가 6개의 고객 프로젝트에 명세 기반 개발 (Spec-Driven Development, SDD)을 도입했을 때 어떤 일이 일어났는지 공유했습니다. 이는 실험이 아니었습니다. 스위스 최대 도매 기업, 스위스 의회, 그리고 여러 보험사를 위한 프로덕션 현대화 작업이었습니다.
그의 결론은 다음과 같습니다: 명세(specs)는 대규모 AI 보조 개발을 망가뜨리는 단 한 가지 문제를 해결합니다. 하지만 이를 위해서는 먼저 세 가지, 즉 아키텍처 (architecture), 팀 구조 (team structure), 그리고 시스템 프롬프트 (system prompt)에 무엇을 넣을지를 재고해야 합니다.
전체 강연은 여기에서 확인할 수 있습니다:
.엔터프라이즈 규모에서 '바이브 코딩 (Vibe Coding)'의 문제점
Martinelli는 많은 개발자가 그러하듯 똑같은 방식으로 시작했습니다. 그의 스포츠 클럽 누군가가 자원봉사 관리 시스템을 만들어 달라고 요청했습니다. 그는 AI 에이전트 (Windsurf)를 사용했고, 작업은 빨랐습니다. 2024년 가을 무렵, 그는 작동하는 애플리케이션을 보유하게 되었습니다.
그 후 다른 클럽에서 다른 기능들을 포함하여 동일한 것을 원했습니다. 그때 그는 자신이 무엇을 만들었는지 전혀 모른다는 사실을 깨달았습니다. 코드는 존재했지만, 그 뒤에 숨겨진 의도(intent)는 사라졌습니다. 수정할 명세(specification)가 없었습니다. 기능을 변경한다는 것은 자신이 AI로 생성한 코드를 역공학 (reverse-engineering) 해야 함을 의미했습니다.
이것이 바로 Andrej Karpathy가 2025년 2월에 언급한 '바이브 코딩 (vibe coding)'의 핵심적인 실패 사례입니다. 이는 고립된 작업에는 효과적입니다. 하지만 누군가가 "X 기능도 추가할 수 있나요?"라고 묻는 순간 무너집니다. 현재 시스템이 무엇을 하는지 기록해 두지 않았기 때문에 답변할 수 없기 때문입니다.
DevToolLab의 분석에 따르면, 팀이 AI 보조 코딩을 토이 프로젝트 이상의 규모로 확장하면 세 가지 실패 모드가 지속적으로 나타납니다:
- 의도 드리프트 (Intent drift): 모델이 팀이 의도한 것과는 다른 합리적인 기본값(defaults)을 선택합니다. 코드가 올바르게 보이기 때문에 아무도 이를 잡아내지 못합니다.
- 환각 인터페이스 (Hallucinated interfaces): 에이전트가 존재하지 않는 API 메서드나 데이터베이스 컬럼을 만들어냅니다. 컴파일은 되지만, 런타임(runtime)에서 실패합니다.
- 컨텍스트 붕괴 (Context collapse): 여러 세션이나 파일에 걸친 작업에서 에이전트가 이전의 결정을 놓치고 스스로 모순된 행동을 합니다.
명세 기반 개발 (spec-driven development) 운동에서는 해결책이 더 나은 프롬프트(prompt)가 아니라고 말합니다. 해결책은 에이전트가 코드에 손을 대기 전에 원하는 바를 미리 글로 적어두는 것입니다. DevToolLab의 2026년 6월 분석에 따르면, 이 방식을 사용하는 초기 도입자들은 까다로운 작업에서 AI 에이전트의 초도 통과 성공률(first-pass success rates)이 3~10배 더 높다고 보고했습니다.
유저 스토리(User Stories)가 아닌 유스 케이스(Use Cases)를 사용해야 하는 이유
대부분의 애자일(agile) 팀은 유저 스토리(user stories)를 기반으로 작업합니다. Martinelli는 이것이 AI 보조 개발에 있어 문제라고 주장합니다.
유저 스토리는 너무 작고 너무 비정형적입니다. "사용자로서, 나는 예약을 하기 위해 수의사를 검색하고 싶다"라는 문장은 AI에게 거의 아무런 정보도 주지 않습니다. 어떤 필드를 사용해야 할까요? 검색 결과가 없을 때는 어떻게 되나요? 정렬 순서는 무엇인가요? 에이전트는 추측할 것입니다. 때때로 그 추측은 틀립니다.
Martinelli는 다시 유스 케이스(use cases)로 돌아갔습니다. 이는 1987년 Ivar Jacobson이 처음 고안하고 이후 UML에서 공식화된 개념입니다.
Martinelli는 Tessl의 AI 네이티브 개발 사이트(ainative.dev)에 게재된 기사와 Simon Maple의 글을 통해 AI를 활용한 명세 기반 개발 (spec-driven development)을 접하게 되었다고 밝혔습니다. 당시 이 개념은 매우 생소했기에, 그는 비즈니스 애플리케이션을 위한 "명세(specs)"가 무엇을 의미하는지 스스로 파악해야 했습니다.
유스 케이스는 다음과 같이 정의된 구조를 가집니다:
- 사전 조건 (Preconditions): 유스 케이스가 실행되기 전에 반드시 충족되어야 하는 사항
- 주 성공 시나리오 (Main success scenario): 단계별로 진행되는 해피 패스 (happy path)
- 대안 흐름 (Alternative flows): 상황이 벗어날 때 발생하는 일
- 사후 조건 (Postconditions): 성공적인 실행 후에 반드시 충족되어야 하는 사항 (이는 수락 기준 (acceptance criteria)의 역할도 겸함)
여기 핵심적인 통찰이 있습니다: AI는 이미 유스케이스 (use case)를 작성하는 방법을 알고 있습니다. 이 형식은 거의 40년 동안 존재해 왔습니다. 소프트웨어 엔지니어링 데이터로 학습된 모든 주요 언어 모델 (language model)은 수천 개의 유스케이스 사례를 보았습니다. 형식을 가르칠 필요는 없습니다. 그저 내용을 채워 넣기만 하면 됩니다.
Martinelli는 Spring Framework의 표준 데모 애플리케이션인 Spring Pet Clinic을 사용하여 이를 입증했습니다. 그는 기존 애플리케이션을 유스케이스 다이어그램 (use case diagrams)과 엔티티 모델 (entity models)로 역공학 (reverse-engineered)한 다음, AI에게 구현 코드를 다시 생성하도록 요청했습니다. 결과는 약 90초 만에 생성된 작동 가능한 코드였습니다.
Martinelli는 실제 ERP 시스템에서도 이 작업을 수행하고 있습니다. 유스케이스 구조는 확장 가능하기 때문에, 수백 개의 테이블과 수십 개의 모듈을 가진 애플리케이션에서도 동일한 접근 방식이 작동합니다.
유스케이스는 동작 (behavior)을 정의합니다. 하지만 동작은 데이터 위에서 작동합니다. Martinelli는 모든 유스케이스를 데이터 구조를 정의하는 엔티티 모델 (entity model, 사실상 도메인 모델 (domain model))과 쌍을 이룹니다. 유스케이스가 "전공 분야를 포함한 모든 수의사 목록을 나열하라"고 말한다면, 엔티티 모델은 수의사 (Veterinarian)가 이름, 성, 그리고 전공 분야 (Specialties) 목록을 가지고 있다고 말합니다. 이 두 가지 산출물 (artifacts)이 결합하여 명세 (specification)를 형성하며, AI는 이 둘 모두로부터 코드를 생성합니다.
이 지점이 Martinelli의 접근 방식이 GitHub Spec Kit이나 Amazon Kiro와 같은 도구들과 갈라지는 부분입니다. 해당 도구들은 제품 요구 사항 문서 (product requirements document)에서 계획 (plan)으로, 다시 작업 (tasks)을 거쳐 구현 (implementation)으로 이어지는 다단계 파이프라인 (multi-phase pipeline)을 따릅니다. Martinelli는 중간 단계들을 건너뜁니다. 그는 유스케이스와 엔티티 모델로부터 코드 단계로 직접 넘어갑니다.
코드와 테스트 생성의 순서는 시스템의 유형에 따라 달라집니다. API의 경우, Martinelli는 테스트 주도 개발 (TDD, test-driven development)을 사용합니다. 즉, 테스트를 먼저 작성하고 그 테스트가 코드 생성을 주도하게 합니다. UI가 있는 풀스택 (full-stack) 애플리케이션의 경우, 테스트를 작성하기 전에 UI가 어떻게 보일지를 먼저 파악해야 하므로 TDD는 비실용적입니다. 그런 경우에는 코드가 먼저 나오고, 테스트가 그 다음에 옵니다.
자립형 시스템 (Self-Contained Systems): AI를 작동하게 만드는 아키텍처
Martinelli는 아키텍처가 AI 지원 개발(AI-assisted development)의 성공과 실패를 결정한다고 주장합니다. 그리고 대부분의 기업은 잘못된 아키텍처를 가지고 있습니다.
문제는 이렇습니다. 지난 15년 동안 팀들은 마이크로서비스(microservices)를 구축해 왔습니다. 많은 경우 이것이 과도하게 진행되었습니다. Martinelli는 약 500개의 마이크로서비스와 그에 대응하는 500개의 마이크로 프론트엔드(micro frontends)를 보유한 한 보험사의 사례를 언급합니다. 이는 프론트엔드와 백엔드 사이의 N-to-M 관계를 의미합니다.
시스템의 일부에 AI를 사용하려고 할 때, 컨텍스트(context)가 필요합니다. AI는 모든 관련 코드를 한 곳에서 볼 수 있어야 합니다. 저장소(repositories) 전반에 500개의 마이크로서비스가 흩어져 있다면, 그 컨텍스트를 수집하는 것은 악몽과 같습니다. 에이전트(agent)가 코드를 생성하는 시간보다 컨텍스트를 에이전트에게 제공하는 데 더 많은 시간을 소비하게 됩니다.
반대편의 경우, 거대한 모놀리스(monolith) 역시 마찬가지로 좋지 않습니다. Martinelli의 ERP 현대화 프로젝트에는 수천 개의 데이터베이스 테이블이 있습니다. 컨텍스트가 너무 커서 어떤 에이전트도 효과적으로 작업할 수 없습니다.
그의 권장 사항은 **자립형 시스템 (Self-Contained Systems, SCS)**이라고 불리는 아키텍처입니다. 마이크로서비스와 비슷한 시기에 만들어졌지만 훨씬 덜 알려진 SCS는 애플리케이션을 수직적 슬라이스(vertical slices)로 나눕니다. 각 슬라이스는 하나의 저장소 안에 자체적인 UI, 비즈니스 로직(business logic), 그리고 데이터베이스를 가집니다. 공식 정의는 scs-architecture.org에서 확인할 수 있습니다.
AI 지원 개발을 위한 최적의 지점(sweet spot)은 AI가 컨텍스트 윈도우(context window) 내에서 모든 관련 코드를 볼 수 있을 만큼 충분히 작은 자립형 시스템입니다. Martinelli의 팀들은 한 번에 하나의 SCS에서 작업하며, 기술 스택(technology stacks)은 슬라이스마다 다를 수 있습니다. 인벤토리(inventory) 모듈은 Vaadin과 Spring Boot를 사용할 수 있고, 주문 관리(order management) 모듈은 React와 Spring Boot를 사용할 수 있습니다. 각 모듈은 고유한 기술과 가이드라인을 가집니다.
모든 것을 CLAUDE.md에 쑤셔 넣는 것을 멈추세요
Martinelli는 팀들이 AI 에이전트를 구성하는 방식에 대해 강한 의견을 가지고 있습니다. 대부분의 개발자들은 프로젝트의 모든 규칙, 컨벤션(conventions), 그리고 문서를 하나의 커다란 파일에 집어넣습니다. Claude Code는 CLAUDE.md를 사용합니다. 다른 도구들은 AGENTS.md 또는 이와 유사한 파일을 사용합니다.
Martinelli는 이것이 잘못되었다고 말하며, 시스템 프롬프트(System Prompt)가 커질수록 모델이 더 많은 환각 (Hallucination)을 생성한다는 ETH Zurich의 연구를 인용했습니다. (강연에서 특정 논문 명칭은 언급되지 않았습니다.) Martinelli의 결론은 다음과 같습니다: 비대해진 지침 파일을 갖는 것보다 아예 지침 파일이 없는 것이 더 나을 수 있습니다.
대신, 그는 **기술 (Skills)**을 사용합니다. 기술은 에이전트가 관련이 있을 때만 로드하는 모듈화되고 집중된 지침입니다. Spring Boot REST 컨트롤러를 생성하기 위한 기술, Vaadin UI 패턴을 위한 기술, Liquibase 마이그레이션을 위한 기술 등이 있습니다. 각 기술은 작고 구체적이며 시간이 지남에 따라 반복적으로 개선됩니다.
그는 또한 방대한 문서를 위해 MCP (Model Context Protocol) 서버를 사용합니다. 프레임워크 문서를 프롬프트에 억지로 집어넣는 대신, MCP 서버를 통해 에이전트가 필요할 때마다 문서를 검색할 수 있게 합니다. 이를 통해 에이전트에게 필요한 정보에 대한 접근 권한을 제공하면서도 컨텍스트 (Context) 크기를 작게 유지할 수 있습니다.
프로젝트 스캐폴딩 (Scaffolding)에 대해 Martinelli는 단호하게 말합니다: 절대로 AI에게 프로젝트를 생성하게 하지 마십시오. 공식 도구를 사용하십시오. Spring의 경우 start.spring.io를 사용하는 것을 의미합니다. 다른 프레임워크의 경우 해당 CLI를 사용하십시오. AI가 생성한 프로젝트 스캐폴딩은 토큰 (Token)을 낭비하며 종종 오래된 설정을 생성합니다. 프로젝트는 직접 설정한 다음, AI가 그 안에서 작업하도록 하십시오.
브라운필드 현대화 (Brownfield Modernization): 코드로부터 명세 역공학하기
Martinelli 업무의 대부분은 그린필드 (Greenfield) 개발이 아닙니다. 그는 기존 시스템을 현대화합니다. COBOL 애플리케이션, 레거시 Java 모놀리스 (Monolith), 수십 년 동안 운영되어 온 ERP 시스템 등이 대상입니다.
그의 접근 방식은 다음과 같습니다. 코드를 한 기술에서 다른 기술로 직접 변환하려고 시도하는 대신 (그는 30년 동안의 시도에도 불구하고 이것이 성공한 적이 없다고 말합니다), 기존 시스템으로부터 명세 (Specifications)를 역공학 (Reverse-engineering)합니다.
프로세스:
- 기존 코드, 테스트, 문서 (Confluence, Jira 등 존재하는 모든 것)로부터 유스케이스 (Use cases)와 엔티티 모델 (Entity models)을 추출합니다.
- 비즈니스 이해관계자들과 함께 명세를 검토하고 수정합니다.
- 수정된 명세로부터 새로운 코드를 생성합니다.
이것은 부수적인 효과(side effect)를 가져옵니다. 팀이 명세(specification) 단계를 거치기 때문에, 원래 시스템에는 없었던 새로운 기능들을 통합할 수 있게 됩니다. 현대화(modernization)는 단순한 기술 교체 그 이상이 됩니다. 그것은 사용자가 소프트웨어와 상호작용하는 방식을 재고할 수 있는 기회가 됩니다.
Martinelli는 현재 진행 중인 ERP 현대화의 초기 가이드라인이 매우 명시적이었다고 언급합니다: "우리는 버그를 도입하고 싶지 않기 때문에, 새로운 기능 없이 새로운 기술로 정확히 동일한 시스템을 원한다." 하지만 명세 기반(spec-driven) 접근 방식을 통해, 그들은 코드를 직접 수정하는 것이 아니라 명세를 변경하는 것이기 때문에 마침내 개선 사항들을 추가할 수 있게 되었습니다.
팀의 변화
그의 팀 규모는 모듈당 57명의 개발자에서, 독립된 시스템당 12명의 개발자로 줄어들었습니다. 그는 지식 공유를 위해 2명을 선호하지만, 이제는 업무 규모가 충분히 작아져서 강력한 AI 도구(AI tooling)를 갖춘 개발자 한 명이 하나의 시스템을 처리할 수 있습니다.
그들은 더 이상 스크럼 스프린트(Scrum sprints)를 운영하지 않습니다. 구현(implementation)에 몇 주가 아닌 몇 분이 걸릴 때는 2주 단위의 스프린트 주기가 의미가 없기 때문입니다. 대신, 그들은 유스케이스(use cases)를 추적하는 칸반(Kanban) 스타일의 보드를 사용하여 연속적인 흐름(continuous flow)을 사용합니다. 명세 단계는 여전히 몇 주가 걸리지만, 그 뒤를 잇는 구현 단계는 극적으로 짧아집니다.
Martinelli는 이러한 변화를 다음과 같이 설명합니다: "명세를 작성하는 데는 아마 2주 정도가 필요할지 모르지만, 소프트웨어를 만드는 데 2주가 필요하지는 않습니다." 업무가 요구사항 공학(requirements engineering)과 명세(specification) 쪽으로, 즉 왼쪽으로 이동(shift left)한 것입니다.
그는 또한 이러한 프로젝트에서 풀 리퀘스트(pull requests)를 중단했다고 언급했습니다. 대신, 그들은 지속적인 동료 검토(continuous peer review)를 동반한 트렁크 기반 개발(trunk-based development)을 사용합니다. 두 명의 개발자가 함께 작업하며, 자신이 무엇을 만들었는지 설명하고, 실시간으로 서로의 작업을 검토합니다.
리스크 기반 검토 (Risk-Based Review)
AI가 90초 만에 코드를 작성한다면, 사람이 그만큼 빠르게 검토할 수 있을까요? Martinelli의 답변은 리스크 기반 검토 (Risk-Based Review)입니다. 그의 ERP 시스템에서 재고 모듈 (inventory module)이 작동하지 않는 것은 작업자들이 커피 휴식을 취한다는 것을 의미합니다. 하지만 주문 관리 모듈 (order management module)이 작동하지 않는 것은 회사가 돈을 잃는다는 것을 의미합니다. 인간의 검토 양은 리스크의 크기와 일치해야 합니다. 이는 수동 개발 (manual development)과 다르지 않으며, 단지 새로운 맥락에 리스크 관리 (risk management)를 적용한 것뿐입니다.
도구 환경 (The Tools Landscape)
Martinelli의 프로세스 중심적 (process-centric) 접근 방식은 명세 기반 개발 (spec-driven development)을 위한 여러 도구 중심적 접근 방식과 공존합니다:
| 도구 | 접근 방식 | 주요 세부 사항 |
|---|---|---|
| AI Unified Process (Martinelli) | 프로세스 중심, 엔터프라이즈 중심 | 유스케이스 (Use cases) + 엔티티 모델 (entity models), 계획/태스크 단계 생략 |
| ... |
2026년 1월에 발표된 한 arXiv 논문은 명세 엄격도 (specification rigor)의 세 가지 수준인 spec-first, spec-anchored, spec-as-source를 식별하며 이 분야를 공식화했습니다. Martinelli의 접근 방식은 명세가 주요 산출물 (artifact)이며 코드가 명세로부터 생성되는 spec-as-source 범주에 확고히 자리 잡고 있습니다.
이것이 의미하는 바
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기