(해당 글은 실제 SDD 기반 개발 경험을 정리한 뒤, Claude AI 를 통해 초안을 작성하고 수정한 글입니다.)
들어가며
최근 몇 달 동안 레거시 시스템의 SP(Stored Procedure) 로직을 Java 로 전환하는 작업을 하고 있습니다.
처음엔 Claude Code 같은 AI 에이전트를 붙여서 흔히 말하는 “바이브 코딩” 스타일로 작업했습니다.
"이 SP Java 로 옮겨줘"
"이 분기 로직 서비스로 분리해줘"
"기존 응답 포맷 유지해서 리팩토링해줘"
위와 같은 방식의 개발은 기존 코딩방식에 비교해 놀라운 경험이었습니다.
몇 천 줄짜리 SP 를 던져주면:
- repository
- service
- transaction 처리
- DTO
- 테스트 코드
까지 한 번에 꽤 그럴듯하게 만들어줬습니다.
특히 CRUD 수준에서는 생산성이 정말 좋았습니다.
근데 SP 이관 규모가 커질수록 묘한 피로감이 쌓이기 시작했습니다.
분명 처음엔:
“오 이거 진짜 되네?”
싶었는데,
시간이 지날수록 점점 이런 상황이 반복됐습니다.
"아니 그 분기 빠졌는데..."
"그 상태값 예외처리 왜 없어졌지?"
"rollback 범위가 원래랑 다른데?"
"null 은 0 처리해야 하는데?"
처음엔 단순히 모델 성능 문제라고 생각했습니다.
근데 계속 작업하다 보니 느낀 건 조금 달랐습니다.
문제는 AI 가 코드를 못 짠다기보다,
요구사항과 맥락을 계속 잊어버린다
에 가까웠습니다.
특히 SP → Java 이관 같은 작업은 생각보다 암묵적인 규칙이 많았습니다.
- 특정 상태값에서는 계산 제외
- null 은 0으로 간주
- 특정 배치 시간에는 다른 분기 처리
- transaction 범위 유지
- rollback 동작 유지
- legacy side effect 유지
문제는 LLM 이 happy path 구현에는 강하지만,
이런 레거시성 규칙을 지속적으로 유지하는 데는 꽤 취약했다는 점이었습니다.
그리고 어느 순간부터는:
AI 가 짠 코드 위에
또 AI 가 코드를 덧칠하는 느낌
이 강하게 들기 시작했습니다.
분명 처음엔 요구사항을 설명했는데,
중간부터는:
- 무엇을 원했는지
- 어떤 규칙이 중요했는지
- 왜 그렇게 구현해야 했는지
자체가 점점 흐릿해졌습니다.
그래서 요즘 자주 보이는 SDD(Spec-Driven Development) 쪽을 본격적으로 적용해보기 시작했습니다.
왜 SDD 를 적용했나
기존 바이브 코딩 흐름은 대략 이런 느낌이었습니다.
요구사항 설명
→ 구현
→ 수정
→ 다시 구현
→ 또 수정
작은 기능에서는 괜찮았습니다.
근데 SP 이관처럼:
- 레거시 규칙 많음
- 엣지 케이스 많음
- 하위 호환 중요함
- transaction 민감함
같은 작업에서는 점점 불안해졌습니다.
특히 가장 큰 문제는 이거였습니다.
"그럴듯하게 동작하는데,
중요한 요구사항 하나가 빠져있다"
예를 들면:
- 메인 로직은 정상 동작
- 테스트도 일부 통과
- 코드도 꽤 깔끔함
근데:
- 특정 상태값 예외처리 누락
- rollback 범위 달라짐
- null 처리 규칙 사라짐
같은 문제가 계속 발생했습니다.
이런 경험이 반복되다 보니,
결국 문제는:
더 좋은 모델을 쓰는 것
보다,
요구사항과 맥락을
어떻게 구조적으로 유지할 것인가
에 더 가까웠습니다.
그래서 spec-kit + Claude Code 기반 SDD workflow 를 적용하기 시작했습니다.
기존 Workflow 와 달라진 점
기존에는 거의 이런 흐름이었습니다.
프롬프트
→ 구현
→ 수정
→ 구현
반면 SDD 적용 후에는:
/spec
/tasks
/implement
형태로 단계가 분리됐습니다.
처음엔 이것만으로도 꽤 좋아졌습니다.
특히:
- 구현 범위가 명확해지고
- task 단위가 작아지고
- context drift 가 줄어드는 느낌
이 있었습니다.
근데 프로젝트가 커지자,
여전히 반복적으로 발생하는 문제가 있었습니다.
바로:
요구사항 누락
이었습니다.
예를 들어 spec 에는:
- 특정 상태값에서는 누적 계산 제외
- null 데이터는 0 처리
- 기존 SP 와 동일한 transaction 범위 유지
라고 적혀 있는데,
실제 구현에서는:
- 메인 계산 로직은 있음
- 서비스 구조도 괜찮음
근데:
- 특정 상태값 분기 누락
- transaction 범위 일부 변경
같은 문제가 계속 발생했습니다.
즉:
"그럴듯하게 구현됐지만
중요한 요구사항 하나가 빠진 상태"
가 반복됐습니다.
그래서 AI 하네스를 붙이기 시작했다
처음에는:
“더 좋은 모델 쓰면 해결될까?”
싶었습니다.
근데 실제로 효과가 있었던 건:
더 똑똑한 모델
보다,
실수를 검증하는 구조
였습니다.
그래서 기존 workflow 에 검증 단계를 추가하기 시작했습니다.
기존 Workflow:
/spec
/tasks
/implement
개선 후 Workflow:
/spec
/extract-requirements
/tasks
/implement
/coverage-check
/review
핵심은:
Spec 를
AI 가 검증 가능한 형태로 변환하는 것
이었습니다.
Requirement Extraction
가장 먼저 추가한 건:
/extract-requirements
단계였습니다.
이 단계는 spec 문서를 읽고,
atomic requirement 를 추출합니다.
예를 들어 이런 spec 이 있으면:
- 특정 상태값에서는 누적 계산 제외
- null 데이터는 0 처리
이를 아래처럼 구조화합니다.
[
{
"id": "REQ-001",
"text": "특정 상태값에서는 누적 계산 제외"
},
{
"id": "REQ-002",
"text": "null 데이터는 0 처리"
}
]
이제부터는:requirements.json
이 실제 검증 기준이 됩니다.
Coverage Validator
그 다음 추가한 건 Coverage Validator 였습니다.
역할은 단순합니다.
각 requirement 가
실제로 구현됐는지 검사
하는 겁니다.
입력은:
- requirements.json
- git diff
- changed files
출력은 이런 형태입니다.
[
{
"requirement": "REQ-001",
"status": "implemented",
"evidence": [
"SettlementService.java:144"
]
},
{
"requirement": "REQ-002",
"status": "missing"
}
]
여기서 가장 중요했던 건:
"구현됐다고 주장하지 말고
증거를 제시하라"
를 강제한 점이었습니다.
이 구조를 넣고 나서부터:
“분명 말했는데 왜 안 만들었지?”
문제가 꽤 줄기 시작했습니다.
직접 해보면서 느낀 점
처음엔 AI 코딩의 핵심이:
더 좋은 모델
이라고 생각했습니다.
근데 실제로는:
요구사항과 맥락을
얼마나 구조적으로 유지할 수 있는가
가 훨씬 중요했습니다.
특히 SP → Java 이관처럼:
- 레거시 규칙 많음
- 엣지 케이스 많음
- 기존 동작 유지 중요함
- 맥락 손실 치명적임
같은 작업에서는 더더욱 그랬습니다.
LLM 은 앞으로도 계속:
- 일부 요구사항을 놓치고
- 맥락을 잊고
- 엣지 케이스를 생략할 가능성이 높습니다.
그래서 중요한 건 LLM을 얼마나 믿을 것인가 보다, LLM의 실수를 어떻게 검증할 것인가에 더 가까운 것 같습니다.
최근에는 spec-kit workflow 에 아래 단계를 추가하는 방향으로 계속 개선 중입니다.
/spec
/extract-requirements
/tasks
/implement
/coverage-check
/review
아직 완벽하진 않지만,
적어도 “그럴듯하지만 중요한 요구사항이 빠진 코드”는 꽤 줄어들고 있습니다.
'AI' 카테고리의 다른 글
| [AI] SDD를 이용한 에이전틱 코딩 경험기(할 일 목록 만들기) (0) | 2026.05.18 |
|---|