기획서가 곧 코드가 되는 세상이 왔어요 📋
“이제 개발자는 코딩하지 않아도 되는 걸까요?”
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 + AI | 10분 | 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
사용자 생성 로직
- Request Body 유효성 검증
- name: 2-50자 체크
- email: 이메일 형식 체크
- role: Enum 값 체크
- 이메일 중복 확인
- User Entity 생성 및 저장
- UserResponse로 변환하여 반환
사용자 조회 로직
- userId로 DB 조회
- 없으면 404 에러 반환
- 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 비교 📊
| 구분 | TDD | BDD | SDD |
|---|
| 초점 | 테스트 | 행동/기능 | 명세/스펙 |
| 문서 | 테스트 코드 | Given-When-Then | spec.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의 핵심 철학:
- “기획서가 곧 코드다” - 명확한 스펙은 그 자체로 구현 가능
- “AI는 실행자, 개발자는 설계자” - 역할의 변화
- “스펙 작성이 곧 개발 역량” - 무엇을 만들지 정의하는 능력
- “생산성 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