명세 기반 개발(SDD): 바이브 코딩을 멈추고 AI가 생성한 코드를 프로덕션에 올리는 방법
데모는 다들 봤을 거예요. 엔지니어가 AI 코딩 에이전트에 "SaaS 대시보드 만들어줘"라고 입력하면 3분 만에 인증, 데이터베이스, 결제 플로우까지 갖춘 React 앱이 뚝딱 만들어져요. 박수가 터지고, 트윗은 바이럴을 타죠.
근데 실제 프로젝트에서 해보면 얘기가 달라져요. 파일 200개에 커스텀 인증 레이어, 외부 API 인테그레이션 3개, 모노레포 구조인 프로젝트에서 에이전트는 자신만만하게 DB 스키마를 갈아엎고, 핵심 미들웨어를 삭제하고, Ctrl+C 누르기도 전에 보안 취약점 4개를 만들어내거든요.
이게 바이브 코딩의 함정이에요. "데모에서 되는 것"과 "프로덕션에서 되는 것" 사이의 거리는 도구 문제가 아니라 방법론 문제예요. 그리고 이 격차를 좁히는 방법론에는 이름이 있어요. 바로 **명세 기반 개발(Specification-Driven Development, SDD)**이에요.
SDD는 문서를 더 쓰자는 게 아니에요. AI 에이전트한테 정확한 제약 조건을 걸어주는 거예요. 스펙 파일, 테스트 우선 루프, 그리고 "생성하고 기도하기"를 "명세 → 설계 → 분할 → 구현"으로 대체하는 4단계 워크플로우로요. SDD 도입한 팀들은 AI 회귀 버그가 60-80% 줄었고, 남이 쓴 코드 디버깅하는 시간이 확 줄었다고 해요.
이 가이드에서는 전체 방법론을 다뤄요. 바이브 코딩이 스케일에서 왜 실패하는지, 4단계 SDD 루프, 스펙 파일 구조화 방법(CLAUDE.md, cursor rules, AGENTS.md), AI 에이전트와 TDD 통합, 그리고 실제 코드베이스에서 에이전트를 오케스트레이션하는 프로덕션 패턴까지.
바이브 코딩은 왜 무너질까
정확히 뭐가, 왜 실패하는지 짚어볼게요. 바이브 코딩은 AI에 자연어로 프롬프트를 던지고 바로 배포하는 방식인데, 그린필드 프로토타입에서는 놀랍게도 잘 돼요. 하지만 프로덕션 코드베이스에서는 네 가지 구체적인 이유로 체계적으로 실패해요.
1. 컨텍스트 붕괴 문제
AI 코딩 에이전트는 상태가 없어요. 매 세션이 제로에서 시작해요. 200개 파일짜리 프로젝트에는 암묵적 지식이 곳곳에 숨어 있거든요. 네이밍 컨벤션, 에러 핸들링 패턴, 인증 레이어의 userId는 UUID인데 레거시 API의 user_id는 순차 정수라는 사실. 명시적으로 알려주지 않으면 이런 건 에이전트 컨텍스트에 존재하지 않아요.
바이브 코딩에는 이걸 해결할 메커니즘이 없어요. 프롬프트를 입력하면 에이전트는 학습 데이터 기반으로 코드를 뱉는데, 프로젝트 패턴이 아니라 에이전트 자기 패턴을 따르거든요. 단독으로는 "동작"하지만 미묘한 불일치가 쌓여서 아키텍처 드리프트가 생겨요.
컨텍스트 붕괴 실전 예시:
프로젝트 컨벤션: 에이전트가 만드는 코드:
────────────── ────────────────────
camelCase 통일 새 파일에 snake_case
Zod으로 유효성검사 수동 if 체크
커스텀 에러 클래스 throw new Error() 직접 사용
Repository 패턴 DB 직접 호출
UUID 기본 키 Auto-increment 정수
2. 해피 패스 편향
LLM은 주로 튜토리얼 코드, 문서 예제, Stack Overflow 답변으로 학습돼요. 이 학습 데이터는 압도적으로 "잘 되는 경우"만 보여주죠. 결과적으로 메인 플로우는 깔끔하게 처리하지만 경계 상황에서 무너지는 코드가 나와요.
- 네트워크 타임아웃? 처리 안 함.
- 같은 리소스에 동시 요청? 레이스 컨디션.
- DB 커넥션 풀 고갈? 크래시.
- 잘못된 사용자 입력? 유효성 검사 없음.
- 외부 API 레이트 리밋? 무시.
2025년 Endor Labs 연구 결과가 좀 충격적인데요. AI가 만든 코드의 62%에서 보안 약점이나 설계 결함이 발견됐고, AI가 추천한 의존성의 44-49%가 이미 알려진 취약점을 품고 있었어요. Verizon DBIR 2025에서도 서드파티 관련 침해가 전년 대비 두 배인 30%로 뛰었고요. 검증 안 된 AI 코드가 공급망으로 그대로 흘러들어가고 있다는 얘기예요.
3. 소유권 공백
바이브 코딩은 정작 본인이 이해 못 하는 코드를 양산해요. 배포는 했는데, 컨트롤 플로우가 어떻게 도는지, 에러가 어떻게 처리되는지, 왜 이 라이브러리를 골랐는지 하나도 설명을 못 하죠. 새벽 2시에 프로덕션이 터지면? 에러가 어떻게 퍼지는지 완전 다른 가정으로 작성된, 사실상 외계인이 쓴 코드를 까봐야 해요.
이건 이론적인 우려가 아니에요. 2026년 기준 EM들이 AI 지원 개발에서 가장 많이 하는 불만이 딱 이거거든요. 코드가 잘 돌다가 갑자기 안 돌아요. 근데 그 코드를 깊이 아는 사람이 없으니까 아무도 빠르게 못 고쳐요.
4. 복리로 쌓이는 기술 부채
바이브 코딩 세션을 할 때마다 프로젝트 컨벤션이 아니라 에이전트 자기 패턴을 따르는 코드가 쌓여요. 10번 돌리고 나면 코드베이스가 아니라 미묘하게 다른 10가지 코딩 스타일이 뒤섞인 퇴적물이 되는 거예요. 이 기술 부채는 복리로 불어나요. 새 세션이 이전 세션의 혼란을 고스란히 물려받으니까요.
4단계 SDD 루프
명세 기반 개발은 "프롬프트 → 코드"라는 단일 스텝을 4단계 루프로 대체해요. 각 단계는 AI 에이전트가 다음 단계에서 참조할 수 있는 문서 산출물을 만들어요.
SDD 루프:
1단계: 요구사항 정의 ──→ requirements.md
↓ "뭘 만드는 거지?"
2단계: 설계 ──→ design.md
↓ "어떻게 만들 거지?"
3단계: 작업 분할 ──→ tasks.md
↓ "어떤 순서로 뭘 구현하지?"
4단계: 구현 ──→ 코드 + 테스트
↓ "만들고, 테스트하고, 검증한다."
└──── 피드백 ──────→ 스펙 업데이트, 반복.
1단계: 요구사항 정의
모든 기능은 "뭘 만들 건지" 명확히 하는 것부터 시작해요. 코드를 달라고 하지 마세요. 요구사항 분석을 달라고 하세요.
AI 에이전트에 던질 프롬프트: "SaaS 앱에 팀 초대 시스템을 추가해야 해. 코드 쓰기 전에 requirements.md를 만들어줘: - 초대 플로우의 사용자 스토리 - 엣지 케이스 (만료된 초대, 중복 이메일, 역할 충돌) - 보안 요구사항 (레이트 리미팅, 토큰 검증) - 기존 인증 시스템과의 연동 포인트 - 이번 단계에서 만들지 않는 것"
"이번 단계에서 만들지 않는 것" 제약이 핵심이에요. 명시적인 스코프 경계 없이는 AI가 과도하게 만드는 경향이 있거든요. 요청하지 않은 기능 추가, 이른 추상화 도입, 코드베이스 절반에 닿을 때까지 스코프를 확장하죠.
2단계: 설계
요구사항이 확정되면 기술 결정으로 변환해요. 여전히 코드는 아직이에요. 아키텍처만.
AI 에이전트에 던질 프롬프트: "requirements.md를 읽어. 그다음 design.md를 만들어줘: - DB 스키마 변경 (테이블/컬럼, 마이그레이션 전략 포함) - API 엔드포인트 (메서드, 경로, 요청/응답 형태) - 서비스 레이어 아키텍처 (함수, 의존성) - 에러 핸들링 전략 (에러 종류, HTTP 코드, 메시지) - 초대 라이프사이클 상태 머신 (대기 → 수락/만료/취소) src/services/와 src/api/의 기존 패턴을 참고해. 구현 코드는 절대 작성하지 마."
이 단계에서 아키텍처 실수를 잡아요. 코드가 존재하기 전에. 설계 문서를 고치는 건 반쯤 구현된 기능을 디버깅하는 것보다 비교할 수 없이 싸요.
3단계: 작업 분할
설계를 개별적이고 의존성 순서가 있는 구현 스텝으로 나눠요. 각 태스크는 AI 에이전트가 한 번의 집중 세션에서 완료할 수 있을 만큼 작아야 해요.
tasks.md 예시: ## 팀 초대 시스템 — 구현 태스크 ### 데이터베이스 레이어 - [x] Task 1 (S): `team_invitations` 마이그레이션 생성 - 컬럼: id, team_id, email, role, token, status, expires_at - 테스트: 마이그레이션 up/down, 제약조건 검증 ### 서비스 레이어 - [ ] Task 2 (M): `InvitationService.create()` 구현 - 의존: Task 1 - 검증: 이메일 형식, 중복 체크, 팀 인원 제한 - 테스트: 행복 경로, 중복 거부, 인원 제한 강제 - [ ] Task 3 (M): `InvitationService.accept()` 구현 - 의존: Task 2 - 검증: 토큰 존재, 미만료, 미사용 - 테스트: 정상 수락, 만료 토큰, 사용 완료 토큰 ### API 레이어 - [ ] Task 4 (M): POST /api/teams/:id/invitations - 의존: Task 2 - 인증: 팀 관리자 권한 필요 - 테스트: 201 성공, 403 비관리자, 409 중복, 429 레이트 리밋 - [ ] Task 5 (M): POST /api/invitations/:token/accept - 의존: Task 3 - 인증: 로그인 필수, 이메일 일치 필수 - 테스트: 200 성공, 404 잘못된 토큰, 410 만료
4단계: 구현
이제, 비로소, AI가 코드를 작성해요. 하지만 거대한 프롬프트 하나가 아니라, 한 번에 하나의 태스크를 전체 컨텍스트와 함께 넘겨요.
AI 에이전트에 던질 프롬프트: "requirements.md, design.md, tasks.md를 읽어. Task 2를 구현해: InvitationService.create() 요구사항: - src/services/TeamService.ts의 기존 서비스 패턴을 따라 - 입력 유효성 검사에 Zod 사용 - 에러 핸들링에 커스텀 AppError 클래스 사용 - 테스트 먼저 작성 (테스트 파일, 그다음 구현) - 구현 후 테스트를 실행해서 검증 기존 파일은 import 추가 외에는 수정하지 마. Task 3-5는 아직 구현하지 마."
이 프롬프트의 제약 조건들이 핵심이에요. "기존 패턴을 따라"가 컨텍스트 붕괴를 막아요. "테스트 먼저 작성"이 TDD를 강제해요. "기존 파일 수정하지 마"가 에이전트의 "친절한" 리팩토링을 막아요. "Task 3-5는 아직"이 스코프 크리프를 막아요.
스펙 파일 구조화하기
SDD 루프는 세션이 끝나도 살아남는 컨텍스트에 의존해요. 어떤 AI 에이전트를 쓰든 "이 프로젝트에서는 이렇게 해"라고 알려주는 파일들이에요.
CLAUDE.md / Cursor Rules / AGENTS.md
이 파일들은 도구마다 이름만 다를 뿐 같은 역할이에요. 매 세션 시작 시 AI가 읽는 프로젝트 "브리핑 문서"예요.
# CLAUDE.md (또는 .cursor/rules, 또는 AGENTS.md) ## 프로젝트 개요 Next.js 16, TypeScript, Drizzle ORM, PostgreSQL 기반 이커머스 SaaS 플랫폼. Turborepo로 모노레포 관리. ## 기술 스택 - 프레임워크: Next.js 16 (App Router, Server Components) - 언어: TypeScript 6.0 (strict 모드) - 데이터베이스: PostgreSQL 17 + Drizzle ORM - 유효성 검사: Zod v3 - 스타일링: Tailwind CSS v4 - 테스팅: Vitest + Playwright - 인증: 커스텀 JWT + 리프레시 토큰 로테이션 ## 아키텍처 규칙 1. 모든 DB 접근은 src/repositories/의 리포지토리 클래스를 통해 2. 비즈니스 로직은 src/services/의 서비스 클래스에 3. API 라우트는 서비스를 호출하는 얇은 컨트롤러 4. 모든 입력은 src/schemas/의 Zod 스키마로 검증 5. 에러는 커스텀 AppError 클래스 사용 (src/lib/errors.ts 참고) 6. 모든 ID는 crypto.randomUUID()로 생성된 UUID ## 코딩 컨벤션 - named export 사용, default export 금지 - 모든 public 함수에 명시적 반환 타입 - 에러 메시지 패턴: "[엔티티].[액션] failed: [이유]" - 파일명: kebab-case, 클래스명: PascalCase - import 그룹핑: 외부 → 내부 → 타입 ## 자주 쓰는 명령어 - `pnpm test` — 전체 테스트 실행 - `pnpm test:watch` — 워치 모드 - `pnpm db:migrate` — 마이그레이션 실행 - `pnpm lint` — Biome 린트 + 포맷 체크 ## 절대 하면 안 되는 것 - `any` 타입 사용 금지. `unknown` + 타입 내로잉 사용. - default export 금지. named export만. - 마이그레이션 파일 수정 금지. 변경 시 새 파일 생성. - .env 파일 커밋 금지. .env.example로 문서화.
핵심 원칙: 경로를 알려주되, 다 쏟아붓지 마세요
루트 스펙 파일은 200-300줄 이하로 유지하세요. 에이전트에게 "뭘 해야 하는지"와 "더 자세한 정보를 어디서 찾는지"를 알려줘야지, 프로젝트의 모든 규칙을 담으면 안 돼요.
## 참고 문서 - 아키텍처 결정: /docs/architecture.md - API 설계 컨벤션: /docs/api-conventions.md - 데이터베이스 스키마: /docs/schema.md - 기능 스펙: /docs/specs/[기능명].md
디렉토리별 규칙
많은 AI 도구가 디렉토리 단위로 스코프된 규칙을 지원해요. 도메인별 제약 조건에 활용하세요:
# src/services/.rules (또는 src/services/.cursorrules) ## 서비스 레이어 규칙 - 모든 서비스 메서드는 async - 서비스는 생성자 주입으로 의존성을 받음 - 서비스에서 src/api/ import 금지 (순환 의존 방지) - 모든 public 메서드에 대응하는 테스트 필수 - 다중 테이블 작업은 트랜잭션 사용 - 중요 연산의 진입/종료를 logger.info()로 로깅
TDD와 AI 에이전트: Red-Green-Refactor 루프
TDD는 AI 에이전트와 단순히 호환되는 게 아니에요. AI 에이전트에 이상적인 워크플로우예요. 테스트가 AI 에이전트가 가장 필요로 하는 것을 제공하거든요. 바로 "맞는지" 객관적으로 검증할 수 있는 정의.
TDD가 AI와 함께할 때 더 잘 동작하는 이유
테스트 없이는 AI에게 코드를 생성시키고 수동으로 정확성을 확인해야 해요. 이건 인지적으로 지치고 에러가 나기 쉬워요. 생소한 패턴의 코드를 읽으면서 버그를 찾으려는 거니까요.
TDD에서는 먼저 정확성을 정의한 다음 AI가 테스트를 통과할 때까지 코드를 생성하게 해요. 구현 세부사항을 리뷰하는 게 아니라 결과를 리뷰하는 거예요. 테스트 스위트가 자동 검증자.
전통적 흐름 (불안정):
프롬프트 → 코드 → 수동 리뷰 → "맞는 거 같은데?" → 배포 → 버그
TDD 흐름 (안정적):
스펙 → 테스트 (실패) → AI에게 지시 → 코드 → 테스트 실행 → 통과? → 배포
↓
실패 → AI가 자동으로 반복
스펙 → 테스트 → 구현 패턴
Step 1: 테스트 스펙 작성 (사람이 주도)
// __tests__/services/invitation-service.test.ts import { describe, it, expect, beforeEach } from 'vitest'; import { InvitationService } from '@/services/invitation-service'; describe('InvitationService.create', () => { it('유효한 이메일과 역할로 초대를 생성해야 한다', async () => { const result = await service.create({ teamId: 'team-uuid-1', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1', }); expect(result.id).toBeDefined(); expect(result.status).toBe('pending'); expect(result.token).toHaveLength(64); expect(result.expiresAt).toBeInstanceOf(Date); }); it('같은 이메일에 대한 중복 초대를 거부해야 한다', async () => { await service.create({ teamId: 'team-uuid-1', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1' }); await expect( service.create({ teamId: 'team-uuid-1', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1' }) ).rejects.toThrow('Invitation.create failed: duplicate invitation'); }); it('팀 멤버 수 제한을 강제해야 한다', async () => { await expect( service.create({ teamId: 'full-team', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1' }) ).rejects.toThrow('Invitation.create failed: team member limit reached'); }); it('만료일을 생성 시점으로부터 7일 후로 설정해야 한다', async () => { const before = new Date(); const result = await service.create({ teamId: 'team-uuid-1', email: '[email protected]', role: 'member', invitedBy: 'admin-uuid-1', }); const after = new Date(); const sevenDaysMs = 7 * 24 * 60 * 60 * 1000; expect(result.expiresAt.getTime()).toBeGreaterThanOrEqual(before.getTime() + sevenDaysMs); expect(result.expiresAt.getTime()).toBeLessThanOrEqual(after.getTime() + sevenDaysMs); }); });
Step 2: AI가 구현 (에이전트 주도)
프롬프트: "__tests__/services/invitation-service.test.ts의 테스트 파일이 기대 동작을 정의하고 있어. InvitationService.create()를 src/services/invitation-service.ts에 구현해서 모든 테스트를 통과시켜. src/services/team-service.ts에 있는 서비스 패턴을 따라. 입력 유효성 검사에 Zod 사용 (스키마: src/schemas/invitation.ts). 구현 후에 `pnpm test __tests__/services/invitation-service.test.ts` 실행. 모든 테스트가 통과할 때까지 반복해."
에이전트가 코드를 생성하고, 테스트를 돌리고, 실패를 보고, 반복해요. 자동으로. Red-Green 루프인데 AI가 돌리는 거예요. "뭘"은 테스트로 정의했고, "어떻게"는 AI가 알아서 해결하는 거죠.
Step 3: 사람이 리뷰 (결과 중심)
테스트가 통과하면 구현을 확인해요:
- 프로젝트 패턴을 따르고 있나? (CLAUDE.md 기준으로 체크)
- 성능 문제는 없나? (N+1 쿼리, 빠진 인덱스)
- 보안 문제는 없나? (입력 검증, 인증 체크)
생소한 코드를 한 줄 한 줄 읽으면서 버그를 찾아 헤매는 게 아니라, 목적을 가지고 리뷰하는 거예요.
실패 패턴과 SDD의 방어 메커니즘
가장 흔한 AI 코딩 실패를 SDD의 어떤 메커니즘이 방지하는지 매핑해볼게요.
| 실패 패턴 | 근본 원인 | SDD 방어 |
|---|---|---|
| 컨텍스트 붕괴 | 에이전트가 프로젝트 컨벤션 모름 | CLAUDE.md의 아키텍처 규칙 + 코딩 컨벤션 |
| 해피 패스 편향 | 에이전트가 에러 핸들링 미생성 | 테스트가 에러 시나리오를 명시적으로 정의 |
| 스코프 크리프 | 과도하게 만들거나 무관한 코드 수정 | 작업 분할 + "하지 마" 제약 |
| 아키텍처 드리프트 | 프로젝트 패턴 대신 자기 패턴 사용 | 설계 문서 + 디렉토리별 규칙 |
| 리그레션 | 새 코드가 기존 기능 깨뜨림 | 테스트 우선 워크플로우가 즉시 잡음 |
| 보안 허점 | 위협 모델 미고려 | 1단계 보안 요구사항 + 보안 테스트 케이스 |
| 소유권 공백 | 이해 못하는 코드를 배포 | 2단계 설계 리뷰가 구현 전 이해를 강제 |
| 기술 부채 | 세션마다 불일치 패턴 | CLAUDE.md가 모든 세션 일관성 보장 |
프로덕션 워크플로우: 전부 합치기
기능 요청부터 머지까지의 실전 SDD 워크플로우:
1. 기능 브리프
## 기능: 팀 역할 관리 팀원의 역할을 변경(admin → member, member → admin)하고 팀에서 멤버를 제거하는 기능. 팀 관리자만 이 작업을 수행 가능.
2. AI 지원 요구사항 정의
프롬프트: "위의 기능 브리프와 src/services/auth-service.ts의 기존 인증 시스템을 읽어. 사용자 스토리, 엣지 케이스, 보안 요구사항, 그리고 만들지 않을 것을 커버하는 requirements.md를 생성해줘. 마지막 관리자가 자기 역할을 변경하려 하면 어떻게 되는지도 고려해."
3. AI 지원 설계
프롬프트: "requirements.md를 읽어. DB 스키마 변경, API 엔드포인트, 서비스 메서드, 역할 전환 상태 다이어그램이 포함된 design.md를 만들어줘. 기존 코드베이스의 패턴을 따라. 사람의 리뷰가 필요한 설계 결정에 플래그 달아줘."
4. 사람 리뷰 체크포인트
여기가 human-in-the-loop의 결정적 순간이에요. 설계 문서를 리뷰해요:
- DB 스키마가 합리적인가?
- API 엔드포인트가 RESTful하고 기존 API와 일관성이 있는가?
- AI가 "마지막 관리자" 엣지 케이스를 잡았는가?
- AI가 놓친 보안 이슈가 있는가?
코드 한 줄 쓰기 전에 설계 문서를 수정해요.
5. AI 지원 작업 분할
프롬프트: "requirements.md와 design.md를 읽어. 의존성 순서가 있는 구현 스텝이 포함된 tasks.md를 만들어줘. 각 태스크에 테스트 커버리지 요구사항을 포함해줘."
6. 반복 구현
프롬프트: "tasks.md의 Task 1을 구현해. 테스트 먼저 쓰고, 그다음 구현. 테스트 돌려서 검증. 완료되면 tasks.md에 완료 체크 표시. Task 2로 넘어가지 마."
각 태스크마다 반복. 각 태스크를 독립적으로 리뷰할 수 있으니 거대한 PR 하나를 마주하는 것보다 코드 리뷰가 훨씬 관리 가능해요.
7. 통합 검증
프롬프트: "tasks.md의 모든 태스크 완료. `pnpm test`로 전체 테스트 스위트 실행해. 실패가 있으면 수정. 그 다음 `pnpm lint` 실행하고 문제가 있으면 수정해."
SDD 확장하기: 팀 패턴
공유 스펙 저장소
팀에서는 스펙 파일을 코드와 함께 버전 관리해요.
project/
├── .claude/
│ └── rules/
│ ├── general.md # 프로젝트 전체 규칙
│ ├── api-conventions.md # API 관련 규칙
│ └── testing.md # 테스팅 표준
├── docs/
│ └── specs/
│ ├── team-invitations/
│ │ ├── requirements.md
│ │ ├── design.md
│ │ └── tasks.md
│ └── role-management/
│ ├── requirements.md
│ ├── design.md
│ └── tasks.md
├── CLAUDE.md # 루트 브리핑 (.claude/rules/로 라우팅)
├── AGENTS.md # 모든 AI 도구용 범용 규칙
└── src/
PR 템플릿
AI 생성 코드를 포함하는 PR은 반드시 스펙을 참조하도록:
## PR 체크리스트 (AI 지원) - [ ] /docs/specs/[기능명]/에 기능 스펙 존재 - [ ] requirements.md 테크 리드 리뷰 완료 - [ ] design.md 테크 리드 리뷰 완료 - [ ] tasks.md의 모든 태스크 완료 및 체크 - [ ] 요구사항의 모든 경로에 대한 테스트 커버리지 - [ ] 기능 범위 바깥 파일 수정 없음 - [ ] `pnpm test` 통과 - [ ] `pnpm lint` 통과
효과 측정 지표
| 지표 | SDD 도입 전 | SDD 도입 후 | 의미 |
|---|---|---|---|
| 스프린트 당 AI 생성 리그레션 | 8-15건 | 1-3건 | 코드 품질 직접 측정 |
| AI 코드 디버깅 시간 | 버그당 2-4시간 | 버그당 15-30분 | 소유권 공백 해소 |
| PR 리뷰 시간 | 45-60분 | 15-20분 | 리뷰 효율 |
| 기능 전체 딜리버리 | 동일 (빠른 코딩, 느린 디버깅) | 순수 30-40% 단축 | 실질 생산성 향상 |
| 보안 이슈 탐지율 | 20-30% | 80-90% | 보안 개선 |
자주 하는 반론 (과 반박)
"SDD는 문서 오버헤드일 뿐 아닌가요?"
아니에요. SDD 문서는 AI가 생성하고, 사람이 리뷰하고, AI가 소비해요. 사람이 투입하는 문서화 노력은 기능당 10-15분(리뷰 시간)이에요. 대안은 이해하지 못하는 코드를 디버깅하느라 버그당 2-4시간 쓰는 거고요.
"우리 프로젝트는 이거 할 만큼 크지 않은데요"
프로젝트가 충분히 작아서 전체 코드베이스를 머릿속에 담을 수 있다면 바이브 코딩이 괜찮을 수도 있어요. SDD는 코드베이스가 워킹 메모리를 넘어서는 시점에 필수가 돼요. 대략 서로 연결된 로직이 있는 10-20개 파일이 그 경계선이에요.
"테스트가 개발을 느리게 하잖아요"
테스트는 첫 한 시간을 느리게 해요. 그 이후의 모든 시간을 빠르게 하죠. AI 에이전트에서는 이 트레이드오프가 더 유리해요. 에이전트가 테스트에 맞춰 구현을 작성하는데, 보통 첫 번째나 두 번째 시도에 맞추거든요. 테스트 작성은 사람이 5-10분, 구현은 AI가 30-60초.
"더 좋은 프롬프트를 쓰면 되는 거 아닌가요?"
프롬프트 품질은 중요해요. 하지만 프롬프트는 일시적이에요. 세션이 끝나면 사라지니까요. SDD 스펙 파일은 영속적이고, 시간이 지나면서 개선되고, 다양한 AI 도구에서 동작해요. CLAUDE.md는 오늘 세션만 돕는 게 아니라 모든 세션, 모든 팀원, 모든 AI 도구를 도와요.
성숙도 모델
SDD를 도입하는 팀은 보통 세 단계를 거쳐요.
1단계: 반응적 (대부분의 팀 현재 위치)
- AI를 즉석 프롬프트로 코드 생성에 사용
- 빈번한 디버깅, 고통스러운 리뷰
- 영속적 컨텍스트 파일 없음
- 매 세션 제로에서 시작
2단계: 구조화 (SDD 도입)
- CLAUDE.md / AGENTS.md 확보
- 복잡한 기능에 4단계 루프 적용
- AI 워크플로우에 TDD 통합
- 도메인별 디렉토리 규칙
- 구현 전 설계 리뷰
3단계: 체계화 (SDD 성숙)
- 코드와 함께 관리되는 스펙 저장소
- AI 에이전트가 작업하면서 스펙 문서 업데이트
- 지표 추적 및 최적화
- 스펙 문서를 통한 신입 온보딩
- 도구 간 일관성 (Cursor, Claude Code, Copilot 모두 같은 스펙 따름)
대부분의 팀이 단일 스프린트에 2단계에 도달할 수 있어요. 3단계는 스펙 파일이 축적되고 패턴이 안정화되면서 자연스럽게 2-3개월에 걸쳐 발전해요.
엔지니어링의 현실
AI 코딩 에이전트는 역사상 가장 강력한 코드 생성 도구예요. 근데 역사상 가장 강력한 버그 생성 도구이기도 해요. 이 둘을 가르는 건 모델도, 프롬프트도, 도구도 아니에요. 방법론이에요.
바이브 코딩은 AI를 엔지니어링 규율의 대체품으로 써요. SDD는 AI를 엔지니어링 규율의 부스터로 써요. 요구사항 → 설계 → 분할 → 구현, 이 4단계 루프는 관료주의가 아니에요. 시니어 개발자들이 원래 항상 해오던 프로세스를, AI 에이전트도 따라올 수 있게 글로 적은 거예요.
스펙 파일은 셋업에 15분. TDD 루프는 추가 시간 제로(AI가 구현 작성). 설계 리뷰는 기능당 10분. 그 대가로 패턴을 따르고, 엣지 케이스를 처리하고, 테스트를 통과하고, 팀이 유지보수할 수 있는 코드를 얻어요.
AI 에이전트는 지금까지 고용한 주니어 중 가장 생산적인 주니어예요. SDD는 그 주니어를 제대로 온보딩하는 방법이고요. 명확한 요구사항, 문서화된 패턴, 잘 쪼갠 태스크, 검증 가능한 완료 조건. 온보딩 건너뛰면 다들 불평하는 혼란이 오고, 온보딩에 투자하면 다들 꿈꾸는 생산성 부스터가 와요.
AI 에이전트를 쓸지 말지는 이미 선택이 아니에요. 엔지니어링 규율을 가지고 쓸지, 없이 쓸지가 선택이죠. SDD는 그 답을 명확하게 보여줘요.
관련 도구 둘러보기
Pockit의 무료 개발자 도구를 사용해 보세요