Spec 주도 개발 시리즈 – 2탄: 스펙 작성부터 구현까지, 실전 가이드

honney·2025년 12월 23일
post-thumbnail

"이 기능 구현해줘"에서 "이 스펙이 충분한지 봐줘"로 질문이 바뀌었다.
이제는 "이 스펙을 어떻게 작성하지?"로 질문이 한 단계 더 깊어졌다.


시작하기 전에

1탄에서는 Spec-Driven Development를 도입하게 되었는지, 기존 개발 방식에서 느낀 문제의식을 공유했습니다. 이번 글에서는 어떻게 실제로 스펙을 작성하고 사용하는지, 프로젝트에 적용한 구체적인 방법을 공유합니다.

이 글은 교과서식 설명이 아니라, "내가 프로젝트에서 실제로 이렇게 쓰고 있다"는 실전 사례입니다. 아직 완벽하지 않고, 실수도 많지만, 그 과정을 솔직하게 기록했습니다.


1. Spec-Driven Development란 무엇인가

Specify CLI를 만나기 전

Spec-Driven Development를 처음 접했을 때, 저는 "문서를 먼저 작성하는 개발 방식" 정도로 이해했습니다. 하지만 실제로는 그보다 훨씬 구조화된 프로세스였습니다.

Spec-Driven Development는:

  • 요구사항을 스펙으로 명확하게 정의한 후
  • 기술적 구현 계획을 수립하고
  • 태스크로 분해한 다음
  • 그에 따라 코드를 작성하는 방식

입니다. 핵심은 "무엇을 만들까"보다 "무엇을 결정해야 할까"에 집중하는 것입니다.

Specify CLI: 구조화된 스펙 작성 도구

Specify CLI는 Spec-Driven Development를 실천하기 위한 도구입니다. AI 코딩 에이전트(Cursor, Claude, Copilot 등)와 함께 사용할 수 있는 슬래시 명령어들을 제공합니다.

핵심 명령어들

Specify CLI는 다음과 같은 핵심 명령어들을 제공합니다:

Core Commands (필수 명령어)

  • /speckit.constitution: 프로젝트의 개발 원칙과 가이드라인 정의
  • /speckit.specify: 무엇을 만들 것인지 정의 (요구사항, 사용자 스토리)
  • /speckit.plan: 선택한 기술 스택으로 기술적 구현 계획 수립
  • /speckit.tasks: 구현을 위한 실행 가능한 태스크 목록 생성
  • /speckit.implement: 계획에 따라 기능 구현 실행

Optional Commands (선택 명령어)

  • /speckit.clarify: 명확하지 않은 영역을 명확히 함 (권장: /speckit.plan 전에 실행)
  • /speckit.analyze: 아티팩트 간 일관성 및 커버리지 분석 (/speckit.tasks 후 실행)
  • /speckit.checklist: 요구사항 완전성, 명확성, 일관성을 검증하는 체크리스트 생성

Specify CLI 초기화

프로젝트에 Specify CLI를 적용하려면 먼저 초기화해야 합니다:

# 기본 프로젝트 초기화
specify init my-project

# 특정 AI 에이전트와 함께 초기화 (예: Cursor)
specify init my-project --ai cursor-agent

# 현재 디렉토리에 초기화
specify init . --ai cursor-agent

초기화하면 .specify/ 디렉토리가 생성되고, AI 에이전트가 사용할 수 있는 슬래시 명령어들이 설정됩니다.

Spec-Driven Development의 철학

Spec-Driven Development는 다음과 같은 철학을 따릅니다:

  • 의도 기반 개발: 스펙이 "무엇"을 정의하고, 구현이 "어떻게"를 정의
  • 가드레일과 조직 원칙을 활용한 풍부한 스펙 생성
  • 일회성 코드 생성이 아닌 다단계 정제 과정
  • 고급 AI 모델 능력에 대한 높은 의존

이 철학은 단순히 "문서를 먼저 작성하는 것"이 아니라, 설계 사고를 구조화하는 것입니다.


2. Spec 주도 개발에서 말하는 "설계"란

화면이나 코드 구조 이전의 사고 정리

처음에는 "설계"라고 하면 화면 디자인이나 코드 구조를 떠올렸습니다. 하지만 Spec-Driven Development에서 말하는 "설계"는 그보다 훨씬 앞선 단계입니다.

설계는 "무엇을 결정해야 할까"를 정리하는 것입니다.

예를 들어, "로그인 기능"을 만들 때:

  • 이전: 로그인 폼 UI부터 설계 → API 엔드포인트 설계 → 코드 작성
  • 이후: "로그인 실패 시 어떻게 처리할까?" → "세션은 어떻게 관리할까?" → "권한은 어떻게 체크할까?" → 스펙에 정리 → UI/API 설계 → 코드 작성

설계는 결정 사항을 명확하게 정리하는 과정입니다.

"무엇을 만들까"보다 "무엇을 결정해야 할까"에 집중

가장 큰 변화는 질문의 방향이 바뀐 것입니다.

이전:

  • "로그인 기능을 만들자" → 바로 구현 시작

이후:

  • "로그인 기능을 만들자" → "어떤 결정을 해야 할까?"
    • 인증 방식은? (OAuth? JWT? 세션?)
    • 실패 시 처리 방법은?
    • 권한 체크는 어디서?
    • 에러 메시지는 어떻게?

이런 질문들을 먼저 정리하고, 스펙에 기록합니다. 그러면 구현 단계에서 "아, 이건 어떻게 해야 하지?"라는 상황이 줄어듭니다.

스펙을 기준점으로 삼게 된 이유

스펙을 기준점으로 삼은 이유는 의사결정의 일관성을 유지하기 위해서입니다.

이전에는:

  • 구현 중간에 결정이 필요할 때마다 "이렇게 하면 되겠지?"라고 생각하며 진행
  • 나중에 "왜 이렇게 만들었지?"라는 질문에 답할 수 없음

이후에는:

  • 스펙에 "왜 이렇게 결정했는지"를 미리 기록
  • 구현 중간에 결정이 필요할 때 스펙을 참고
  • 나중에 "왜 이렇게 만들었는지" 스펙에서 확인 가능

스펙이 의사결정의 기준점이 되었습니다.


3. Epic → Feature → User Story 구조 설명

왜 이 구조를 선택했는지

Specify CLI는 명시적으로 "Epic → Feature → User Story" 구조를 강제하지는 않습니다. 하지만 프로젝트를 진행하면서 자연스럽게 이런 구조가 필요하다고 느꼈습니다.

Epic (에픽)

  • 큰 단위의 기능 그룹
  • 예: "인증 및 대시보드 시스템"

Feature (기능)

  • Epic 안의 구체적인 기능
  • 예: "Kakao 로그인", "콘티 생성", "에디터 편집"

User Story (사용자 스토리)

  • Feature를 사용자 관점에서 서술한 것
  • 예: "Kakao 계정으로 로그인하고 자동 프로필 동기 후, 콘티를 생성한다"

이 구조를 선택한 이유는 우선순위를 명확하게 나눌 수 있기 때문입니다.

각 단계에서 던졌던 질문들

Epic 단계

  • "이 프로젝트의 핵심 가치는 무엇인가?"
  • "사용자가 이 시스템을 통해 무엇을 달성할 수 있는가?"

Feature 단계

  • "이 Epic을 구현하기 위해 어떤 기능들이 필요한가?"
  • "각 기능의 독립성은 어느 정도인가?"

User Story 단계

  • "이 기능을 사용하는 사용자는 누구인가?"
  • "사용자가 이 기능을 통해 무엇을 달성하려고 하는가?"
  • "이 스토리가 독립적으로 테스트 가능한가?"

실제 프로젝트 예시: Worship Flow

현재 진행 중인 Worship Flow 프로젝트에서 실제로 사용한 구조입니다.

Epic

"예배 준비를 위한 콘티 관리 시스템"

Feature

specs/001-auth-dashboard-editor/ 디렉토리로 관리:

  • 001-auth-dashboard-editor: 인증 + 대시보드 + 에디터

User Story (spec.md에서 정의)

### User Story 1 - Kakao 로그인 및 대시보드 콘티 생성 (Priority: P1)

Kakao 계정으로 로그인하고 자동 프로필 동기 후, 콘티를 제목/주제말씀/날짜로 생성하며
생성된 콘티 목록을 즉시 확인한다.

**Why this priority**: 인증과 기본 콘티 생성이 모든 이후 흐름의 전제이므로 최우선.

**Independent Test**: 신규 계정으로 로그인 → 콘티 생성 → 목록에 표시 여부만으로 독립 검증 가능.

**Acceptance Scenarios**:

1. **Given** Kakao 로그인 화면에서 동의 후, **When** 리디렉트되면,
   **Then** 사용자 정보가 동기화되고 세션이 활성화된다.
2. **Given** 로그인된 상태, **When** 제목/주제말씀/날짜를 입력해 콘티를 생성하면,
   **Then** 대시보드 목록 상단에 새 콘티가 보인다.

이렇게 구조화하면서, "지금 무엇을 구현해야 하는가"가 명확해졌습니다.


4. 스펙을 작성하며 가장 헷갈렸던 지점

너무 자세하거나, 너무 추상적이었던 경험

스펙을 작성하면서 가장 어려웠던 부분은 적정한 수준을 찾는 것이었습니다.

너무 자세했던 경우

처음에는 모든 것을 상세하게 적으려고 했습니다:

### 로그인 기능

- Kakao OAuth 2.0 사용
- authorization code flow
- 서버 사이드 콜백에서 세션 토큰 발급
- 서명 쿠키 사용
- 클라이언트에서는 TanStack Query로 세션 상태 조회
- 세션 만료 시간: 7일
- 리프레시 토큰: 사용 안 함
- ...

이렇게 적으니 스펙이 너무 길어지고, 구현 세부사항이 스펙에 섞여 들어갔습니다. 스펙은 "무엇"을 정의해야 하는데, "어떻게"를 너무 자세히 적은 것입니다.

너무 추상적이었던 경우

반대로 너무 추상적으로 적은 경우도 있었습니다:

### 로그인 기능

- 사용자가 로그인할 수 있어야 함
- 로그인 후 대시보드로 이동

이렇게 적으니 구현할 때마다 결정이 필요했습니다. "로그인 실패는 어떻게 처리하지?" "세션은 어떻게 관리하지?" 같은 질문들이 계속 생겼습니다.

적정한 스펙 수준에 대한 고민

적정한 스펙 수준을 찾기 위해 다음과 같은 기준을 세웠습니다:

  1. "무엇"을 명확하게 정의

    • "로그인 기능" → "Kakao 계정으로 로그인하고 자동 프로필 동기 후, 세션을 발급한다"
  2. "왜"를 기록

    • "왜 Kakao OAuth를 선택했는가?" → research.md에 기록
  3. "어떻게"는 최소한만

    • 기술 스택은 plan.md에, 구현 세부사항은 코드에
  4. 엣지 케이스는 명시

    • "로그인 실패 시 어떻게 처리할까?" → 스펙에 명시

이렇게 나누면서, 스펙의 역할이 명확해졌습니다: 의사결정의 기준점.


5. AI에게 내리는 명령어의 의미

코드 생성 명령과 스펙 기반 명령의 차이

AI를 사용할 때 가장 큰 변화는 명령어의 목적이 바뀐 것입니다.

이전: 코드 생성 명령

"로그인 기능 구현해줘"
"대시보드 목록 컴포넌트 만들어줘"
"API 엔드포인트 작성해줘"

이런 명령은 "구현"을 요청하는 것입니다. AI가 코드를 생성하고, 우리는 그것을 검토하고 수정합니다.

이후: 스펙 기반 명령

"/speckit.specify 로그인 기능이 필요해요. Kakao OAuth를 사용하고,
실패 시 재시도 동선을 제공해야 해요."

"/speckit.clarify 이 스펙에서 로그인 실패 케이스가 충분히 다뤄졌는지 봐줘"

"/speckit.plan 이 스펙을 Next.js App Router로 구현할 계획을 세워줘"

이런 명령은 "설계"를 요청하는 것입니다. AI가 스펙을 생성하거나 검토하고, 우리는 그것을 바탕으로 결정을 내립니다.

자주 사용하는 프롬프트 유형과 목적

프로젝트를 진행하면서 자주 사용하는 프롬프트 유형들:

1. 스펙 작성 프롬프트

"/speckit.specify {기능 설명}

요구사항:
- {요구사항 1}
- {요구사항 2}

제약사항:
- {제약사항 1}
- {제약사항 2}
"

목적: 자연어로 된 요구사항을 구조화된 스펙으로 변환

2. 스펙 검토 프롬프트

"/speckit.clarify 이 스펙에서 {특정 영역}이 충분히 명확한지 봐줘.
특히 {구체적 질문}에 대한 답이 있는지 확인해줘."

목적: 스펙의 모호한 부분을 명확히 함

3. 계획 수립 프롬프트

"/speckit.plan 이 스펙을 {기술 스택}으로 구현할 계획을 세워줘.
특히 {고려사항}을 반영해줘."

목적: 스펙을 바탕으로 기술적 구현 계획 수립

4. 태스크 생성 프롬프트

"/speckit.tasks 이 계획을 바탕으로 구현 태스크를 생성해줘.
각 User Story가 독립적으로 테스트 가능하도록 나눠줘."

목적: 구현 계획을 실행 가능한 태스크로 분해

프롬프트 하나가 사고를 어떻게 정리해주는지

프롬프트를 작성하는 과정 자체가 사고를 정리하는 과정입니다.

예를 들어, "/speckit.specify 로그인 기능"이라고 입력하려면:

  • 어떤 요구사항이 필요한가?
  • 어떤 제약사항이 있는가?
  • 어떤 엣지 케이스를 고려해야 하는가?

이런 질문들을 먼저 생각해야 합니다. 그리고 프롬프트를 작성하면서 자신의 사고가 정리됩니다.

특히 AI가 생성한 스펙을 검토할 때:

  • "이 부분이 명확한가?"
  • "이 요구사항이 측정 가능한가?"
  • "이 엣지 케이스가 빠진 것은 없는가?"

이런 질문들을 AI에게 물어보면서, 스펙의 품질을 높일 수 있었습니다.


6. Spec → 구현으로 넘어가는 기준

언제 구현을 시작한다고 판단했는지

가장 어려웠던 부분은 "스펙이 어느 정도면 충분한가?"를 판단하는 것이었습니다.

처음에는 "스펙이 완벽해야 구현을 시작할 수 있다"고 생각했습니다. 하지만 실제로는:

  • 스펙을 완벽하게 만들려고 하면 시간이 너무 오래 걸립니다.
  • 구현하면서 발견하는 것들도 많습니다.
  • 스펙과 코드는 항상 100% 일치하지는 않습니다.

그래서 다음과 같은 기준을 세웠습니다:

구현 시작 기준

  1. 핵심 User Story가 명확하게 정의되어 있다

    • 각 User Story가 독립적으로 테스트 가능한가?
    • Acceptance Scenarios가 구체적인가?
  2. 주요 결정 사항이 기록되어 있다

    • 기술 스택 선택 이유 (research.md)
    • 데이터 모델 (data-model.md)
    • API 계약 (contracts/openapi.yaml)
  3. 엣지 케이스가 최소한 명시되어 있다

    • 실패 시나리오는?
    • 권한 체크는?
  4. 구현 계획(plan.md)과 태스크(tasks.md)가 있다

    • 어떻게 구현할 것인가?
    • 어떤 순서로 구현할 것인가?

이 기준을 만족하면 구현을 시작했습니다.

스펙이 어느 정도면 충분하다고 느꼈는지

"충분한 스펙"의 기준은 "구현 중간에 '어떻게 해야 하지?'라는 상황이 최소화되는 것"이었습니다.

처음에는 스펙이 부족해서:

  • 구현 중간에 결정이 필요할 때마다 멈춰서 고민
  • "이건 나중에 추가하면 되지"라고 생각하며 임시방편 처리
  • 리뷰 단계에서 "이건 이렇게 해야 할 것 같은데?"라는 피드백

이후에는 스펙이 충분해서:

  • 구현 중간에 스펙을 참고하여 결정
  • "이건 스펙에 명시되어 있지 않으니 추가해야겠다"라고 판단
  • 리뷰는 "스펙대로 구현되었는가?"를 확인하는 수준

이렇게 변화했습니다.

일부러 구현을 멈췄던 경험

가장 인상 깊었던 경험은 일부러 구현을 멈추고 스펙을 다시 작성한 것입니다.

처음에는 "로그인 기능" 스펙을 작성하고 바로 구현을 시작했습니다. 하지만 구현 중간에:

  • "로그인 실패 시 에러 메시지는 어떻게 표시하지?"
  • "세션 만료 시 어떻게 처리하지?"
  • "권한이 없는 사용자가 접근하면 어떻게 하지?"

이런 질문들이 계속 생겼습니다.

그래서 구현을 멈추고 스펙을 다시 작성했습니다:

  • 엣지 케이스를 명시적으로 추가
  • 실패 시나리오를 Acceptance Scenarios에 추가
  • 권한 체크를 Requirements에 명시

이렇게 하니 구현이 훨씬 수월해졌습니다. "일부러 멈추는 것"이 오히려 빠르게 진행하는 방법이었습니다.


7. 현재 느끼는 한계와 고민

시간 비용

솔직히 말하면, Spec-Driven Development를 도입하면서 개발 속도가 느려졌습니다.

  • 이전: 요구사항 받음 → 바로 코드 작성 → 1~2일 만에 기능 완성
  • 이후: 요구사항 받음 → 스펙 작성 (1~2일) → 태스크 정리 (반나절) → 코드 작성 → 3~4일

특히 처음에는 스펙 작성 자체가 익숙하지 않아서, "이렇게 적는 게 맞나?"라는 고민에 시간을 많이 썼습니다.

하지만 시간이 지나면서:

  • 스펙 작성이 익숙해지면서 속도가 빨라졌습니다.
  • 구현 중간에 "어떻게 해야 하지?"라는 상황이 줄어들어서 전체적으로는 비슷하거나 더 빠를 수도 있습니다.
  • 리뷰 단계에서 피드백이 줄어들어서 전체 개발 시간은 오히려 단축될 수 있습니다.

팀 확장 시 예상되는 문제

아직 혼자 진행하는 프로젝트라서 팀 확장 시 문제는 경험하지 못했지만, 예상되는 문제들:

  1. 스펙 작성 방식의 일관성

    • 팀원마다 스펙 작성 방식이 다를 수 있음
    • 스펙 리뷰 프로세스가 필요할 것
  2. 스펙과 코드의 동기화

    • 코드가 변경되었을 때 스펙도 업데이트해야 함
    • 누가 책임지는가?
  3. 학습 곡선

    • 새로운 팀원이 Spec-Driven Development를 배우는 데 시간이 필요
    • 온보딩 프로세스가 필요할 것

이런 문제들을 해결하기 위해서는:

  • 스펙 템플릿과 가이드라인 필요
  • 스펙 리뷰를 PR 프로세스에 포함
  • 온보딩 문서 작성

이런 것들이 필요할 것 같습니다.

여전히 답을 찾는 부분

아직 완벽하지 않고, 여전히 답을 찾는 부분들:

  1. 스펙의 적정 수준

    • 너무 자세하면 유지보수가 어렵고, 너무 추상적이면 구현 시 혼란
    • 프로젝트마다, 기능마다 적정 수준이 다를 수 있음
  2. 스펙과 코드의 동기화

    • 코드가 변경되었을 때 스펙도 업데이트해야 하는데, 누락되는 경우가 있음
    • 자동화 도구가 필요할 수도 있음
  3. 스펙 작성 시간

    • 작은 기능도 스펙을 작성해야 하는가?
    • 버그 수정도 스펙이 필요한가?

이런 질문들에 대한 답은 아직 찾는 중입니다. 하지만 "완벽하지 않아도 기록하고 공유하는 것"이 중요하다고 생각합니다.


마무리

Spec-Driven Development를 실전에 적용하면서, 저는 "설계를 생각하는 습관"을 배우고 있습니다.

아직 완벽하지 않고, 실수도 많습니다. 하지만 이 과정 자체가 설계를 배우는 연습이었고, 이 연습이 주니어 개발자로서 성장하는 데 도움이 되고 있습니다.

특히 AI를 설계 파트너로 활용하면서, 프롬프트를 작성하는 과정이 사고를 정리하는 과정이 되었습니다. "이 기능 구현해줘"에서 "이 스펙이 충분한지 봐줘"로 질문이 바뀌면서, 개발 방식 자체가 변화했습니다.

다음 글에서는 스펙을 코드로 옮기는 과정변경 관리에 대해 더 자세히 공유하려고 합니다.

profile
보이지 않은 것을 보이게 할 때 기쁨을 느낍니다

0개의 댓글