Home [AI 기초] SDD(Spec-Driven Development): AI 시대, 코딩보다 스펙 작성이 중요한 이유
Post
Cancel

[AI 기초] SDD(Spec-Driven Development): AI 시대, 코딩보다 스펙 작성이 중요한 이유

기획서가 곧 코드가 되는 세상이 왔어요 📋

“이제 개발자는 코딩하지 않아도 되는 걸까요?”

AI가 코드를 작성해주는 시대가 되면서 개발자의 역할이 변하고 있어요. 하지만 “코드를 작성하지 않는다”는 게 아니라, “무엇을 만들지 더 정확히 정의한다”는 것에 가까워요.

오늘은 AI 시대의 새로운 개발 방법론인 SDD(Spec-Driven Development, 스펙 주도 개발)에 대해 알아보겠습니다!


1. SDD란 무엇인가? 🎯

정의

SDD(Spec-Driven Development)는 코드를 작성하기 전에, 자연어(Markdown)나 구조화된 명세(OpenAPI/Swagger)로 요구사항을 완벽하게 정의하는 방법론입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 전통적인 개발 방식
1. 요구사항 듣기 (애매모호)
2. 바로 코드 작성 시작
3. 중간에 요구사항 변경 발견
4. 코드 다시 작성
5. 반복...

// SDD 방식
1. 요구사항을 완벽한 스펙 문서로 작성 (spec.md)
2. 스펙을 검토하고 승인
3. AI에게 스펙 전달  코드 자동 생성
4. TDD로 검증
5. 완성!

개발자 비유: Interface 우선 설계

Java에서 Interface를 먼저 설계하고 Implementation을 나중에 하는 것과 똑같아요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Step 1: Interface 정의 (명세)
public interface UserService {
    /**
     * 사용자를 생성합니다.
     * @param name 사용자 이름 (2-50자)
     * @param email 이메일 (고유값, 검증 필요)
     * @param role 역할 (USER, ADMIN)
     * @return 생성된 사용자 ID
     * @throws DuplicateEmailException 이메일 중복 시
     */
    Long createUser(String name, String email, UserRole role);
    
    /**
     * 사용자를 조회합니다.
     * @param userId 사용자 ID
     * @return 사용자 정보
     * @throws UserNotFoundException 사용자 없을 시
     */
    User getUser(Long userId);
}

// Step 2: Implementation (구현)
public class UserServiceImpl implements UserService {
    // 이제 구현체만 작성하면 됨!
    // "어떤 메서드를 만들어야 하지?"라는 고민 불필요
}

SDD의 핵심:

  • Interface = spec.md (명세 문서)
  • Implementation = AI가 생성하는 코드
  • Javadoc = 상세한 요구사항

2. Why SDD with AI? 왜 AI 시대에 SDD가 필요한가? 🤖

이유 1: AI는 ‘모호함’을 제일 싫어해요

1
2
3
4
5
6
7
8
9
10
// ❌ Bad: 모호한 요청
"User CRUD API 만들어줘"

AI 응답:
public class UserController {
    @PostMapping("/user")  // user? users?
    public void createUser() {  // 반환 타입은?
        // 뭘 받아야 하지? 뭘 리턴해야 하지?
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ✅ Good: 명확한 스펙 제공
"""
## User API Specification

### POST /api/users
- Request Body:
  - name: String (2-50자, 필수)
  - email: String (이메일 형식, 고유값, 필수)
  - role: Enum ["USER", "ADMIN"] (기본값: USER)
  
- Response: 201 Created
  - userId: Long
  - createdAt: ISO8601 timestamp
  
- Error Cases:
  - 400: 유효성 검증 실패
  - 409: 이메일 중복
"""

AI 응답:
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<UserCreateResponse> createUser(
        @Valid @RequestBody UserCreateRequest request
    ) {
        // 명세에 맞춰 정확히 구현됨!
    }
}

핵심: AI는 컴파일러처럼 명확한 입력을 원해요!


이유 2: TDD만으로는 부족해요

TDD(Test-Driven Development)는 “무엇을 테스트할지”는 알려주지만, “어떤 구조로 짜야 할지”는 알려주지 않아요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// TDD: 테스트만 작성
@Test
void 사용자_생성_테스트() {
    // given
    UserCreateRequest request = new UserCreateRequest("김개발", "dev@email.com");
    
    // when
    Long userId = userService.createUser(request);
    
    // then
    assertThat(userId).isNotNull();
}

// ❌ AI가 봤을 때:
// "UserCreateRequest가 뭐지?"
// "userService는 어디 있지?"
// "어떤 필드들이 필요하지?"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# TDD + SDD: 스펙 + 테스트

## spec.md

### Data Model: UserCreateRequest
- name: String (2-50자)
- email: String (이메일 형식)
- role: UserRole (Enum)

### Service Layer: UserService
- createUser(UserCreateRequest): Long
  - 유효성 검증
  - 이메일 중복 체크
  - DB 저장
  - ID 반환

## test.java
@Test
void 사용자_생성_테스트() {
    // spec.md에 정의된 대로 테스트
}

✅ AI가 봤을 때:

  • “아, UserCreateRequest에는 name, email, role이 있구나”
  • “userService는 유효성 검증 → 중복 체크 → 저장 순서로 동작하는구나”
  • “바로 코드 짤 수 있어!”

이유 3: Context Injection으로 생산성 10배 향상 🚀

AI에게 스펙을 던져주면(Context Injection), AI는 고민 없이 구현체만 찍어내면 돼요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 기존 방식: AI와 10번 왔다갔다
개발자: "User API 만들어줘"
AI: "어떤 필드가 필요한가요?"
개발자: "name, email, role"
AI: "role의 타입은 뭔가요?"
개발자: "Enum으로 USER, ADMIN"
AI: "검증 규칙은요?"
개발자: "name은 2-50자"
AI: "이메일 중복 체크는요?"
개발자: "응, 필요해"
... (5 소요)

// SDD 방식: AI에게 한 번에 전달
개발자: "@spec.md 읽고 User API 구현해줘"
AI: [spec.md 분석]
    [30 만에 완성된 코드 생성]

생산성 비교:

방식소요 시간정확도수정 횟수
없이 코딩60분70%5-10회
TDD만45분80%3-5회
SDD + AI10분95%0-1회

3. 실전 SDD 프로세스 📝

1단계: spec.md 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# User Management API Specification

## 1. Data Models

### User Entity
- id: Long (PK, Auto Increment)
- name: String (2-50자, NOT NULL)
- email: String (이메일 형식, UNIQUE, NOT NULL)
- role: Enum ["USER", "ADMIN"] (기본값: USER)
- createdAt: LocalDateTime (자동 생성)
- updatedAt: LocalDateTime (자동 업데이트)

### UserCreateRequest DTO
- name: String (2-50자, 필수)
- email: String (이메일 형식, 필수)
- role: String (선택, 기본값: "USER")

### UserResponse DTO
- userId: Long
- name: String
- email: String
- role: String
- createdAt: String (ISO8601)

---

## 2. API Endpoints

### POST /api/users - 사용자 생성

**Request:**
```json
{
  "name": "김개발",
  "email": "dev@example.com",
  "role": "USER"
}

Response: 201 Created

1
2
3
4
5
6
7
{
  "userId": 1,
  "name": "김개발",
  "email": "dev@example.com",
  "role": "USER",
  "createdAt": "2025-12-13T19:30:00+09:00"
}

Error Responses:

  • 400 Bad Request: 유효성 검증 실패
    1
    2
    3
    4
    5
    
    {
      "error": "VALIDATION_ERROR",
      "message": "이메일 형식이 올바르지 않습니다",
      "field": "email"
    }
    
  • 409 Conflict: 이메일 중복
    1
    2
    3
    4
    
    {
      "error": "DUPLICATE_EMAIL",
      "message": "이미 존재하는 이메일입니다"
    }
    

GET /api/users/{userId} - 사용자 조회

Response: 200 OK

1
2
3
4
5
6
7
{
  "userId": 1,
  "name": "김개발",
  "email": "dev@example.com",
  "role": "USER",
  "createdAt": "2025-12-13T19:30:00+09:00"
}

Error Responses:

  • 404 Not Found: 사용자 없음
    1
    2
    3
    4
    
    {
      "error": "USER_NOT_FOUND",
      "message": "사용자를 찾을 수 없습니다"
    }
    

3. Business Logic

사용자 생성 로직

  1. Request Body 유효성 검증
    • name: 2-50자 체크
    • email: 이메일 형식 체크
    • role: Enum 값 체크
  2. 이메일 중복 확인
    • 중복 시 409 에러 반환
  3. User Entity 생성 및 저장
  4. UserResponse로 변환하여 반환

사용자 조회 로직

  1. userId로 DB 조회
  2. 없으면 404 에러 반환
  3. UserResponse로 변환하여 반환

4. Exception Handling

Custom Exceptions

  • DuplicateEmailException: 이메일 중복 시
  • UserNotFoundException: 사용자 없을 시
  • ValidationException: 유효성 검증 실패 시

Global Exception Handler

  • 모든 예외를 JSON 형식으로 일관되게 반환
  • 500 에러는 상세 정보 숨김

5. Technology Stack

  • Spring Boot 3.2
  • Spring Data JPA
  • Hibernate Validator
  • H2 Database (개발용) ```

2단계: AI에게 스펙 전달

1
2
3
4
5
6
7
8
# Cursor나 ChatGPT에서
"@spec.md 파일을 읽고, 명세대로 Spring Boot User API를 구현해줘.

요구사항:
1. 모든 DTO에 validation 어노테이션 추가
2. Controller에 Swagger 문서화 어노테이션 추가
3. 예외 처리를 GlobalExceptionHandler로 통일
4. 테스트 코드도 함께 작성"

AI가 다음 파일들을 자동 생성해줘요:

  • User.java (Entity)
  • UserCreateRequest.java (DTO)
  • UserResponse.java (DTO)
  • UserController.java (Controller)
  • UserService.java (Service)
  • UserRepository.java (Repository)
  • GlobalExceptionHandler.java (예외 처리)
  • UserControllerTest.java (테스트)

3단계: TDD로 검증

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@SpringBootTest
class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    void 사용자_생성_성공() {
        // given
        UserCreateRequest request = new UserCreateRequest(
            "김개발", 
            "dev@example.com", 
            "USER"
        );
        
        // when
        UserResponse response = userService.createUser(request);
        
        // then
        assertThat(response.getUserId()).isNotNull();
        assertThat(response.getName()).isEqualTo("김개발");
        assertThat(response.getEmail()).isEqualTo("dev@example.com");
    }
    
    @Test
    void 이메일_중복_시_예외_발생() {
        // given
        userService.createUser(
            new UserCreateRequest("김개발", "dev@example.com", "USER")
        );
        
        // when & then
        assertThatThrownBy(() -> 
            userService.createUser(
                new UserCreateRequest("박개발", "dev@example.com", "USER")
            )
        ).isInstanceOf(DuplicateEmailException.class);
    }
    
    @Test
    void 유효하지_않은_이메일_형식_시_예외_발생() {
        // given
        UserCreateRequest request = new UserCreateRequest(
            "김개발", 
            "invalid-email",  // 잘못된 형식
            "USER"
        );
        
        // when & then
        assertThatThrownBy(() -> userService.createUser(request))
            .isInstanceOf(ValidationException.class);
    }
}

4. SDD vs TDD vs BDD 비교 📊

구분TDDBDDSDD
초점테스트행동/기능명세/스펙
문서테스트 코드Given-When-Thenspec.md
AI 활용어려움보통최적화
작성 시점코드 전코드 전코드 전
가독성개발자용비개발자 가능모두 가능
구조 정의

함께 사용하면 최강!

1
2
3
4
5
6
7
// 조합: SDD + TDD
1. spec.md 작성 (전체 구조 정의)
2. AI로 코드 생성
3. TDD로 테스트 작성  검증
4. 리팩토링

 "명세 → 구현 → 검증" 완벽한 흐름!

5. SDD의 실전 활용 💡

Use Case 1: 팀 협업

1
2
3
4
5
6
7
8
9
10
# Before SDD
PM: "사용자 관리 기능 만들어주세요"
개발자: "알겠습니다" (막연함)
→ 2주 후 "이거 아닌데..." (요구사항 불일치)

# After SDD
PM: [spec.md 작성]
개발자: [spec.md 검토 후 승인]
개발자: AI에게 spec.md 전달 → 코드 생성
→ 1일 후 "완벽하게 맞아요!" (정확한 구현)

Use Case 2: 레거시 리팩토링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 레거시 코드 (스파게티 코드)
public class LegacyUserService {
    public void doSomething(String a, String b, int c) {
        // 1000줄의 복잡한 로직
        // 주석도 없고, 구조도 애매...
    }
}

// SDD 프로세스
1. 레거시 코드 분석  spec.md 작성 (역공학)
2. spec.md 검토  개선
3. AI에게 spec.md 전달  깔끔한 코드 생성
4. TDD로 동일한 동작 검증

 안전한 리팩토링!

Use Case 3: API 버전 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
# v1/spec.md
## POST /api/v1/users
- name: String
- email: String

# v2/spec.md
## POST /api/v2/users
- name: String
- email: String
- phone: String (추가됨)
- address: Object (추가됨)

→ 스펙 문서로 버전별 차이 명확히 관리!

6. SDD 실전 팁 🎓

Tip 1: 스펙 문서는 ‘살아있는 문서’로 관리

1
2
3
4
5
6
7
8
# Git으로 스펙 버전 관리
git add spec.md
git commit -m "feat: User API 스펙 추가"

# 코드와 함께 리뷰
- spec.md: 명세 검토
- implementation: 코드 검토
→ 명세와 코드가 일치하는지 확인!

Tip 2: OpenAPI/Swagger 형식 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# swagger.yaml (더 구조화된 스펙)
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /api/users:
    post:
      summary: 사용자 생성
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreateRequest'
      responses:
        '201':
          description: 생성 성공
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'

AI는 이런 구조화된 형식을 더 잘 이해해요!

Tip 3: 도메인별로 스펙 파일 분리

1
2
3
4
5
specs/
  ├── user-spec.md        # 사용자 도메인
  ├── product-spec.md     # 상품 도메인
  ├── order-spec.md       # 주문 도메인
  └── common-spec.md      # 공통 사양 (에러 응답, 페이징 등)

Tip 4: 예제를 풍부하게

1
2
3
4
5
6
7
8
9
## 좋은 스펙 예시

### POST /api/users
- Request 예시:
  ```json
  {
    "name": "김개발",
    "email": "dev@example.com"
  }
  • Response 예시:
    1
    2
    3
    4
    
    {
      "userId": 1,
      "createdAt": "2025-12-13T19:30:00+09:00"
    }
    
  • 에러 예시:
    1
    2
    3
    4
    
    {
      "error": "VALIDATION_ERROR",
      "message": "이메일 형식이 올바르지 않습니다"
    }
    

→ AI는 예시를 보고 패턴을 파악해요! (Few-Shot Learning)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
------

### 7. SDD의 한계와 주의사항 ⚠️

#### 한계 1: 스펙 작성이 어려울 수 있어요

```java
// 해결책: 점진적 스펙 작성
1. 초안 작성 (30% 완성도)
2. AI에게 "이 스펙을 개선해줘" 요청
3. 검토 후 확정
4. 코드 생성

→ AI가 스펙 작성도 도와줄 수 있어요!

한계 2: 복잡한 비즈니스 로직은 스펙으로 표현하기 어려워요

1
2
3
4
5
6
7
8
9
10
11
12
13
# Bad: 너무 복잡
"사용자의 최근 3개월 주문 내역을 분석하여,
구매 패턴이 유사한 다른 사용자들의 관심 상품 중,
현재 사용자가 보지 않은 상품을 추천 점수 순으로 정렬하되,
재고가 있는 상품만 최대 10개까지..."

# Good: 단계별로 분리
## 1단계: 사용자 주문 내역 조회
## 2단계: 유사 사용자 찾기 (별도 스펙)
## 3단계: 추천 상품 계산 (별도 스펙)
## 4단계: 정렬 및 필터링

→ 복잡한 로직은 작은 단위로 분해!

주의사항: 스펙과 코드의 불일치

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 문제 상황
1. spec.md 작성
2. AI로 코드 생성
3. 코드 수정 (스펙 업데이트  )
4. 시간이 지나면... 스펙과 코드가 달라짐

// 해결책
public class SpecValidator {
    @Test
    void 스펙과_코드_일치_검증() {
        // 1. spec.md 파싱
        Spec spec = SpecParser.parse("spec.md");
        
        // 2. 실제 API 테스트
        ResponseEntity<String> response = testRestTemplate.postForEntity(
            "/api/users",
            request,
            String.class
        );
        
        // 3. 스펙과 일치하는지 검증
        assertThat(response.getStatusCode()).isEqualTo(spec.getExpectedStatus());
        assertThat(response.getBody()).matchesJsonSchema(spec.getResponseSchema());
    }
}

8. 마치며: 개발자의 역할은 ‘설계자’가 되는 것 🎨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * AI 시대의 개발자
 */
public class ModernDeveloper {
    
    // Before: 코드 작성자
    public void beforeAI() {
        // 모든 코드를 직접 타이핑
        writeCode();
        writeMoreCode();
        debugCode();
        refactorCode();
    }
    
    // After: 아키텍처 설계자
    public void withAI() {
        // 1. 무엇을 만들지 정의 (SDD)
        Spec spec = defineSpecification();
        
        // 2. AI에게 구현 위임
        Code code = ai.generate(spec);
        
        // 3. 검증 및 개선 (TDD)
        verify(code);
        
        // 4. 아키텍처 최적화
        optimize(code);
    }
}

SDD의 핵심 철학:

  1. “기획서가 곧 코드다” - 명확한 스펙은 그 자체로 구현 가능
  2. “AI는 실행자, 개발자는 설계자” - 역할의 변화
  3. “스펙 작성이 곧 개발 역량” - 무엇을 만들지 정의하는 능력
  4. “생산성 10배” - 단순 작업은 AI에게, 창의적 설계는 개발자에게

핵심 정리:

기존 방식SDD + AI
코드 작성에 60분스펙 작성 10분 + AI 생성 5분
요구사항 불일치 자주 발생스펙 검토로 사전 방지
AI와 10번 왕복 대화한 번에 정확한 코드 생성
레거시 코드 파악 어려움스펙 문서로 명확히 이해

이제는 “코딩을 잘하는 개발자”보다 “스펙을 잘 쓰는 개발자”가 더 생산적인 시대가 되었어요!

다음번에 프로젝트를 시작할 때, 코드를 바로 작성하기 전에 spec.md를 먼저 작성해보세요. 🚀


참고 자료

  • OpenAPI Specification: https://swagger.io/specification/
  • REST API Design Best Practices: https://restfulapi.net/
  • Specification by Example (Book): https://www.manning.com/books/specification-by-example
This post is licensed under CC BY 4.0 by the author.

[AI 실전] 나만의 AI 사수 만들기: System Prompting과 .cursorrules 자동화

[AI 기초] MCP(Model Context Protocol): AI에게 '손과 발'을 달아주는 표준 인터페이스 (feat. JDBC)