Vibe Coding vs Spec Coding: 동일한 환불 기능을 두 번 구축하기
요약
AI를 활용한 'Vibe Coding' 방식이 가진 위험성을 실제 환불 기능 구현 사례를 통해 경고합니다. 명확한 설계(Spec) 없이 프롬프트만으로 코드를 작성할 경우 멱등성, 레이스 컨디션, 트랜잭션 관리 등 심각한 논리적 결함이 발생할 수 있음을 보여줍니다.
핵심 포인트
- Vibe Coding은 빠른 프로토타이핑에는 유리하나 복잡한 비즈니스 로직 구현에는 위험함
- 멱등성(Idempotency) 보장이 없는 API는 중복 결제/환불 문제를 야기함
- 단순 프롬프트 수정만으로는 트랜잭션 격리 수준이나 레이스 컨디션 문제를 해결하기 어려움
- 안정적인 시스템 구축을 위해서는 AI 활용 전 명확한 기술 명세(Spec) 작성이 필수적임
Vibe coding (바이브 코딩)은 매혹적입니다. 원하는 것을 평범한 언어로 설명하면 AI가 코드를 작성하고, 10분 후면 작동하는 엔드포인트(endpoint)를 갖게 됩니다.
저도 그 매력에 빠졌었습니다 — 하지만 그런 방식으로 환불 기능을 출시했다가, 90분짜리 spec (명세) 작성만 했어도 완전히 방지할 수 있었던 버그들을 수정하느라 이후 2주를 허비하기 전까지는 말이죠.
이 글은 정확히 동일한 요구사항을 사용하여 두 방식의 차이를 나란히 비교함으로써, 그 격차가 어디에서 발생하는지 보여줍니다.
요구사항 (The requirement)
이커머스 플랫폼에 주문 환불 기능이 필요합니다. PM의 브리프(brief)는 다음과 같습니다:
- 전체 및 부분 환불 지원
- 결제 게이트웨이 (Stripe 스타일)를 호출하여 결제 취소
- 환불 상태 추적: 대기 중(pending), 처리 중(processing), 성공(succeeded), 실패(failed)
- 상담원이 내부 도구를 통해 환불을 실행할 수 있도록 지원
충분히 간단합니다. 두 경로 모두 여기서 시작합니다.
경로 A: vibe coding (바이브 코딩)
프롬프트(prompt):
"Node.js로 주문 환불 API를 만들어줘. 전체 및 부분 환불을 지원해야 해. 결제 게이트웨이를 호출해서 결제를 취소하고, 환불 상태를 추적해줘. Express와 Postgres를 사용해."
60초 후: createRefund와 getRefundStatus가 포함된 깔끔한 RefundController가 생성되었습니다. 주문 존재 여부를 확인하고, 주문 총액과 금액을 대조하며, paymentGateway.refund()를 호출하고 결과를 저장합니다. 코드는 전문적으로 보입니다. 해피 패스 (happy path)는 잘 작동합니다.
출시합니다.
버그 #1: 중복 환불 (the double refund)
상담원이 환불 버튼을 클릭했는데 페이지가 잠시 멈추자, 다시 한번 클릭합니다. 두 번의 환불이 실행됩니다. 멱등성 (idempotency) 체크가 없습니다.
수정 프롬프트: "동일한 주문에 대해 중복 환불이 발생하는 것을 방지하는 체크 로직을 추가해줘." AI는 쿼리를 추가합니다: 만약 이 주문에 대해 이미 존재하는 환불이 있다면 거절합니다. 작동합니다 — 하지만 영원히 작동하지는 않습니다.
버그 #2: 부분 환불 초과 (partial refund overflow)
200달러짜리 주문입니다. 상담원이 50달러, 그다음 80달러, 그다음 100달러를 환불합니다. 총 환불 금액: 230달러. 중복 체크는 정확히 동일한 중복만 잡아낼 뿐, 누적 금액은 잡아내지 못합니다.
수정 프롬프트: "누적 환불 금액을 추적하고, 주문 총액을 초과하는 환불은 거부하십시오." AI는 SUM(amount) 쿼리를 추가합니다. 하지만 이 쿼리는 삽입(insert) 작업과 하나의 트랜잭션(transaction) 내에서 이루어지지 않으므로, 두 개의 동시 부분 환불이 모두 체크를 통과할 수 있습니다.
버그 #3: 게이트웨이 타임아웃 (gateway timeout)
게이트웨이가 타임아웃됩니다. 환불 행(row)은 영원히 processing 상태에 머뭅니다. 고객 지원팀은 재시도할 수 없습니다. 중복 체크가 그들을 차단하기 때문입니다. 돈이 실제로 빠져나갔을까요? 아무도 모릅니다.
수정 프롬프트: "게이트웨이 타임아웃에 대한 재시도 로직을 추가하십시오." AI는 재시도 루프를 추가합니다. 지수 백오프(exponential backoff)도 없고, 게이트웨이 호출 시 멱등성 키(idempotency key)도 없으며, 제한(cap)도 없습니다. 이제 재시도가 게이트웨이 측에서 중복 결제를 생성할 수 있습니다.
버그 #4: 레이스 컨디션 (race condition)
두 개의 에이전트가 동일한 주문에 대해 동시에 환불을 처리합니다. 둘 다 누적 체크를 통과하고(두 환불 모두 아직 커밋되지 않음), 둘 다 게이트웨이에 도달하며, 둘 다 성공합니다. 고객은 두 번 환불됩니다.
_수정 프롬프트: "잠금(locking)을 추가하십시오..."
네 개의 패치(patch)가 적용되었고, 각각은 개별적으로는 합리적이지만, 아키텍처는 누더기 상태입니다. 상태 머신(state machine)도 없고, 문서화된 불변량(invariants)도 없으며, 패치들이 어떻게 상호작용하는지에 대한 테스트도 없습니다.
실제 비용
첫 번째 버전은 10분이 걸렸습니다. 네 개의 패치는 2주가 걸렸습니다. 조사, 테스트, 고객 지원 에스컬레이션, 그리고 게이트웨이 기록과 대조하는 한 번의 수동 조정(reconciliation) 작업이 포함되었습니다.
"빠른" 방식은 빠르지 않았습니다. 도파민을 앞당겨 쓰고 고통을 뒤로 미룬 것뿐입니다.
경로 B: 스펙 코딩 (spec coding)
동일한 요구사항. 동일한 AI. 하지만 시작점이 다릅니다. 코드를 작성하기 전, 이 내용을 작성하는 데 90분을 사용합니다:
# 기능: 주문 환불 처리
## 목표
...
그다음 프롬프트:
"이 스펙(spec)에 기술된 환불 기능을 구현하십시오. 상태 머신(state machine)을 정확히 따르십시오. 동시성 제어를 위해 SELECT FOR UPDATE를 사용하십시오. 모든 게이트웨이 호출에 멱등성 키(idempotency key)를 포함하십시오. 모든 금액은 센트(cents) 단위로 합니다." [스펙 붙여넣기]
결과물의 구조적 차이
AI는 첫 번째 버전에서 다음과 같이 생성합니다:
SELECT FOR UPDATE를 포함한 트랜잭션(transaction) 내에 래핑된processRefund- 트랜잭션
_내부_에서 수행되는 누적 잔액 확인 (cumulative balance check) — 레이스 윈도우(race window) 없음 - 생성 시 발행되어 모든 게이트웨이 호출에 전달되는 멱등성(Idempotency) UUID
- 최대 3회로 제한된 지수 백오프(exponential backoff)를 적용한 백그라운드 재시도(background retry)
- 스펙의 상태 머신(machine)과 정확히 일치하는 상태 전이(state transitions)
경로 A(Path A)에서 발생할 수 있는 모든 버그가 사전에 처리되었습니다. 중복 환불? 잠금(lock)과 멱등성 키(idempotency key)가 해결합니다. 오버플로(Overflow)? 삽입(insert)과 동일한 트랜잭션 내의 잔액 확인이 해결합니다. 타임아웃(Timeout)? 게이트웨이가 안전하게 처리하는 동일 키 재시도가 해결합니다.
동일한 AI. 동일한 능력. 하지만 결과물은 극적으로 다릅니다 — 입력값이 극적으로 달랐기 때문입니다. AI가 더 똑똑해진 것이 아니라, 더 나은 제약 조건(constraints)을 부여받은 것입니다.
솔직한 계산
| Vibe coding | Spec coding | |
|---|---|---|
| 첫 번째 버전까지 걸리는 시간 | 10분 | ~2.5시간 |
| ... |
Vibe coding은 프로토타입, 내부 도구, 그리고 버그가 발생해도 그냥 넘어가도 되는 모든 상황에 훌륭합니다. 하지만 돈, 상태 머신(state machines), 또는 동시성(concurrency)이 개입되는 순간, 스펙을 건너뜀으로써 "절약"한 90분은 사채 이자처럼 되돌아올 것입니다.
Spec Coding의 전체 사례 연구를 바탕으로 수정되었습니다. 해당 사이트에서는 이 워크플로우를 위해 무료 스펙 템플릿과 브라우저 기반의 spec packet generator를 제공합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기