본문으로 건너뛰기

Usecase: 팀이 결제 시스템 프로덕션 버그를 디버깅하기

⚠️ 참고: 이것은 사용 방법을 설명하기 위한 가상의 usecase입니다. 실제 AI 응답은 아래 예시와 다를 수 있습니다.

결제 버그가 프로덕션에 올라갔을 때, 자연스러운 반응은 가능한 빨리 수정하는 것입니다. Superpowers는 올바른 순간에 속도를 늦추고 제대로 수정하는 방법을 가르쳐줍니다 -- 체계적으로, 증거를 기반으로, 리뷰를 거쳐서.


배경

2-3명의 개발자로 구성된 소규모 팀에서 e-commerce 시스템을 관리하고 있다고 상상해보세요. 어느 날, 팀이 고객으로부터 보고를 받습니다:

"20% 할인 상품을 구매하고 10% 바우처를 사용했는데 가격이 맞지 않습니다. 기대했던 가격보다 더 많이 청구되었습니다."

확인 결과:

  • 상품: 티셔츠 정가 500,000원, 20% 할인
  • 바우처: SAVE10, 10% 추가 할인
  • 기대값: 500,000 x 0.8 = 400,000 -> 400,000 x 0.9 = 360,000원
  • 실제값: 시스템이 계산한 금액 350,000원

10,000원 차이. 고객이 적게 청구되었지만, 로직 오류는 고가 상품이나 더 큰 바우처에서 훨씬 큰 차이를 만들 수 있습니다.

기술 스택: Node.js, TypeScript, calculateFinalPrice() 함수


배울 수 있는 것

이 usecase를 통해 다음을 실습합니다:

  1. Systematic Debugging 4 Phases -- 추측이 아닌 체계적인 디버깅 프로세스 (자세히 보기: 디버깅 & 검증)
  2. TDD Fix -- 수정 전에 버그를 재현하는 테스트를 먼저 작성 (자세히 보기: TDD)
  3. 양방향 Code Review -- 보내는 사람은 컨텍스트를 준비하고, 리뷰하는 사람은 꼼꼼히 읽은 후 피드백 (자세히 보기: Code Review)
  4. Verification Gate -- 명확한 증거가 있을 때만 "수정 완료" 선언 (자세히 보기: 디버깅 & 검증)

단계별 실습

1단계: Phase 1 -- 근본 원인 조사

코드를 수정하기 전에, 문제를 완전히 이해해야 합니다. 추측은 허용되지 않습니다.

1.1. 고객의 버그 보고서 읽기

고객 피드백:

"티셔츠를 500,000원에 구매했는데, 상품 페이지에 20% 할인이라고 되어 있었습니다. SAVE10 바우처로 추가 10% 할인을 적용했습니다. 360,000원을 낼 것으로 기대했지만 시스템이 350,000원을 계산했습니다."

🧑 사용자가 AI에게 고객 버그 보고서 내용을 제공합니다. 정확한 숫자를 기록합니다 — 재해석하지 않고, 원본 데이터로 작업합니다. 예시 프롬프트:

"고객이 결제 버그를 보고했습니다: 티셔츠 500,000원 20% 할인 구매, SAVE10 바우처 10% 할인 사용, 360,000원 기대했지만 시스템이 350,000원을 계산했습니다. 체계적인 프로세스로 디버깅을 도와주세요."

🤖 AI가 보고서를 받고, 구체적인 숫자 (정가, 할인 %, 바우처, 기대값 vs 실제값)를 기록합니다. AI는 원인을 바로 추측하지 않고 체계적인 조사 프로세스를 시작합니다.

1.2. 오류 재현

🤖 AIsrc/utils/pricing.ts 파일을 읽습니다 — calculateFinalPrice() 함수는 Product (discountPercent 포함)와 Voucher (선택적, discountPercent 포함)를 받아 할인 후 가격을 반환합니다. 언뜻 보면 로직이 맞아 보입니다: 상품 할인을 먼저 적용하고, 그 다음 바우처를 적용합니다. 하지만 이것은 AI가 실행되고 있다고 생각하는 코드입니다. AI는 프로덕션의 실제 코드를 계속 확인합니다.

🧑 사용자가 AI가 코드를 읽는 것을 관찰하고, 확인해야 할 올바른 파일/함수인지 확인합니다. AI가 잘못된 파일을 읽고 있다면, 방향을 수정합니다.

1.3. 최근 변경사항 확인

🤖 AIgit log를 실행하여 최근 커밋을 확인합니다:

git log --oneline -10

결과:

a1b2c3d feat: add loyalty points calculation
f4e5d6a refactor: simplify discount calculation    <-- 바로 이것
b7c8d9e fix: update shipping fee for remote areas
...

🤖 AI가 "refactor: simplify discount calculation"이라는 메시지의 커밋 f4e5d6a가 매우 의심스럽다고 인식하고, diff를 확인합니다:

git diff f4e5d6a~1..f4e5d6a -- src/utils/pricing.ts

🧑 사용자가 AI와 함께 git log 결과를 봅니다. 추가 컨텍스트가 있다면 (예: "그 커밋은 새 개발자가 지난주에 merge한 것입니다"), AI에게 제공하여 조사 범위를 좁힙니다.

1.4. 실제 코드 (오류 코드) 발견

🤖 AI가 커밋 f4e5d6a의 diff를 확인하고 프로덕션의 실제 코드가 변경되었음을 발견합니다: 할인을 순차적으로 적용하는 대신 (가격 x 0.8 후 x 0.9), 리팩토링한 사람이 퍼센트를 합산 (20% + 10% = 30%)한 후 한 번에 적용 (가격 x 0.7)했습니다. AI가 발견 내용을 사용자에게 보고합니다.

🧑 사용자가 AI의 분석을 읽고 확인합니다: "맞습니다, 올바른 비즈니스 로직은 순차적 적용입니다." 비즈니스 규칙이 다르다면 (예: 합산이 맞는 경우), 명확하게 말하여 AI가 조사 방향을 조정하도록 합니다.

문제: 이것은 로직 오류입니다 — "단순화"가 계산 결과를 변경했습니다.

1.5. 데이터 흐름 추적

🤖 AI가 두 가지 방법으로 계산 흐름을 추적하여 사용자에게 명확하게 제시합니다:

오류 코드 (합산):

totalDiscount = 20 + 10 = 30
price = 500,000 * (1 - 30/100)
price = 500,000 * 0.7
price = 350,000원

올바른 로직 (순차):

price = 500,000 * (1 - 20/100) = 500,000 * 0.8 = 400,000
price = 400,000 * (1 - 10/100) = 400,000 * 0.9 = 360,000원

차이: 360,000 - 350,000 = 10,000원. 🤖 AI가 경고합니다: 더 비싼 상품이나 더 큰 바우처의 경우 차이는 훨씬 커질 것입니다.

🧑 사용자가 AI의 계산이 맞는지 확인하고 결론에 동의합니다. 이것은 중요한 체크포인트입니다 — 다음 단계로 진행하기 전에 양쪽이 원인에 대해 일치해야 합니다.

참고: 이 단계에서는 조사만 합니다, 아직 아무것도 수정하지 않습니다. 이론보다 증거가 먼저입니다.


2단계: Phase 2 -- 패턴 분석

버그를 재현한 후, 패턴을 찾아봅니다.

2.1. 정상 작동하는 사례 찾기

🤖 AI가 여러 다른 경우를 자동으로 확인하여 버그가 언제 나타나고 언제 나타나지 않는지 파악합니다:

경우 1: 상품 할인만 (바우처 없음)

Input:  product = { price: 500000, discountPercent: 20 }, no voucher
Code:   totalDiscount = 20, price = 500000 * 0.8 = 400,000
Result: 400,000원 -- 정확

경우 2: 바우처만 (상품 할인 없음)

Input:  product = { price: 300000, discountPercent: 0 }, voucher = { discountPercent: 10 }
Code:   totalDiscount = 0 + 10 = 10, price = 300000 * 0.9 = 270,000
Result: 270,000원 -- 정확

경우 3: 두 할인 동시 적용

Input:  product = { price: 500000, discountPercent: 20 }, voucher = { discountPercent: 10 }
Code:   totalDiscount = 20 + 10 = 30, price = 500000 * 0.7 = 350,000
Result: 350,000원 -- 오류 (기대값 360,000원)

🧑 사용자가 AI가 나열한 경우들을 검토합니다. AI가 아직 고려하지 않은 특별한 경우가 있다면 (예: "시스템에는 VIP 고객을 위한 세 번째 할인 유형도 있습니다"), 추가 분석을 위해 보충합니다.

2.2. 비교 및 패턴 결론

🤖 AI가 결과를 종합하고 패턴을 도출합니다: 버그는 두 할인이 동시에 적용될 때만 발생합니다. AI가 수학적 이유를 설명합니다:

  • 합산: price * (1 - (a + b) / 100)
  • 순차: price * (1 - a/100) * (1 - b/100)

이 두 수식은 수학적으로 동등하지 않습니다 (a 또는 b가 0인 경우를 제외).

🧑 사용자가 AI가 제시한 패턴이 시스템의 실제 동작과 일치하는지 확인합니다. 다른 고객의 추가 버그 보고서가 있다면, AI에게 제공하여 교차 검증합니다.


3단계: Phase 3 -- 가설 수립 및 검증

3.1. 명확한 가설 진술

🤖 AI가 명확하고 구조화된 가설을 진술합니다:

가설: calculateFinalPrice() 함수가 할인 퍼센트를 합산 (20% + 10% = 30%)하는 대신 순차적으로 적용 (가격 x 0.8 x 0.9)해야 하기 때문에 버그가 발생합니다. 커밋 f4e5d6a ("refactor: simplify discount calculation")가 순차 적용에서 합산으로 로직을 리팩토링했습니다.

🧑 사용자가 가설을 읽고 합리적인지 평가합니다. 동의하면 AI가 검증을 진행하도록 확인합니다. 동의하지 않거나 다른 가설이 있다면, 제시합니다. 가설을 종이에 적거나 코멘트에 기록합니다 — 가설이 머릿속에만 존재하지 않도록 합니다.

3.2. 한 번에 하나의 변수 확인

🤖 AIcalculateFinalPrice() 함수에 console.log를 추가하여 각 계산 단계를 추적한 후, 고객 보고서의 입력값으로 실행합니다:

[DEBUG] Initial price: 500000
[DEBUG] Product discount: 20
[DEBUG] Voucher discount: 10
[DEBUG] Total discount (합산): 30
[DEBUG] Final price: 350000

🤖 AI가 디버그 결과를 사용자에게 제시하고, 어떤 라인이 가설을 증명하는지 명확히 가리킵니다.

🧑 사용자가 디버그 출력을 보고 고객 보고서의 데이터와 대조합니다. 결과가 보고된 오류 동작과 일치하는지 확인합니다.

3.3. 구체적인 숫자로 가설 확인

🤖 AI가 여러 입력 세트로 비교 표를 만들어 버그의 영향 범위를 증명합니다:

경우합산 (버그)순차 (정확)차이
500k, 20% + 10% 바우처350,000360,00010,000
1,000k, 30% + 15% 바우처550,000595,00045,000
2,000k, 50% + 20% 바우처600,000800,000200,000

차이는 상품 가격과 할인율에 따라 증가합니다. 🤖 AI가 결론을 내립니다: 가설이 확인되었습니다.

🧑 사용자가 비교 표를 리뷰하고, 실제 데이터를 기반으로 버그의 심각도를 평가합니다 (예: "우리의 가장 비싼 상품은 500만 원이니, 최대 차이는..."). 수정 우선순위를 결정합니다.


4단계: Phase 4 -- TDD Fix

이제 정확한 원인을 알고 있습니다. 코드 수정에 뛰어드는 대신, 올바른 TDD 프로세스를 따릅니다: 테스트를 먼저 작성하고, 코드를 그 다음에 수정합니다.

🤖 AI가 행동하기 전에 질문합니다: "가설이 확인되었습니다: 퍼센트 합산 대신 순차 적용으로 인한 버그입니다. TDD 프로세스에 따라 수정을 진행할까요 — 버그를 재현하는 테스트를 먼저 작성하고, 그 다음 코드를 수정하는 방식으로?"

🧑 사용자가 확인합니다: "동의합니다, 진행하세요."

RED: 버그를 재현하는 실패 테스트 작성

🤖 AIsrc/utils/__tests__/pricing.test.ts 파일을 5개 테스트 케이스로 생성합니다:

  • 상품 할인과 바우처를 순차적으로 적용 (기대값 360,000)
  • 상품 할인만 (기대값 400,000)
  • 할인 없는 상품에 바우처만 (기대값 270,000)
  • 할인 없음 (기대값 200,000)
  • 소수점이 있는 가격의 반올림 (기대값 263,500)

🤖 AInpm test -- --grep "calculateFinalPrice" 실행 → 2 FAIL, 3 PASS. 테스트 1이 예상대로 실패합니다: 기대값 360,000이지만 실제값 350,000. 테스트 5도 합산 로직의 잘못된 계산으로 실패합니다.

테스트 2, 3, 4는 하나의 할인 유형만 있어서 합산과 순차가 같은 결과를 내기 때문에 통과합니다.

🧑 사용자가 테스트 케이스 목록을 리뷰합니다 — 시스템의 특수한 경우가 빠져 있다면 보충합니다 (예: "VIP 고객이 5% 추가 할인을 받는 경우도 있습니다").

GREEN: 모든 테스트가 통과하도록 함수 수정

🤖 AIsrc/utils/pricing.ts를 수정합니다 — 할인을 순차적으로 적용하는 로직을 복원합니다: 상품 할인 먼저, 그 다음 바우처 할인 (이미 할인된 가격에 적용). 테스트 재실행 → 5/5 PASS.

🧑 사용자가 수정된 코드의 diff를 읽고, 로직이 시스템의 비즈니스 규칙과 일치하는지 확인합니다.

REFACTOR: Guard Clause 추가

🤖 AIsrc/utils/pricing.ts에 edge case 검증을 추가합니다: 가격이 음수가 아닌지, 할인이 0-100 범위인지. 테스트 파일에 guard clause용 테스트 케이스 2개를 추가합니다. 전체 테스트 스위트 재실행 → 모두 통과, regression 없음.

🧑 사용자가 guard clause를 리뷰합니다 — 검증 한도가 비즈니스 규정에 적합한지 확인합니다 (예: 할인이 100%까지 가능한지, 최대 80%인지?).


5단계: 양방향 Code Review

버그가 수정되고 테스트가 통과했습니다. 하지만 merge 전에 code review가 필요합니다.

Dev A (수정한 사람)가 review request 전송

🧑 사용자가 AI에게 PR 생성을 요청합니다. 🤖 AI가 완전한 구조화된 정보로 pull request를 생성합니다:

## WHAT
상품 할인과 바우처 할인을 동시에 적용할 때 가격이 잘못 계산되는 버그 수정.

## ROOT CAUSE
커밋 f4e5d6a ("refactor: simplify discount calculation")가 순차 적용에서
퍼센트 합산으로 로직을 변경.

합산: 500000 * (1 - (20+10)/100) = 350,000 (오류)
순차: 500000 * 0.8 * 0.9 = 360,000 (정확)

## PLAN
- 할인을 순차적으로 적용하는 로직 복원
- 5개 테스트 케이스 추가: 양쪽 할인, 상품만, 바우처만,
  할인 없음, 반올림
- input validation을 위한 guard clause 추가

## CHANGES
- src/utils/pricing.ts: calculateFinalPrice() 함수 수정
- src/utils/__tests__/pricing.test.ts: 7개 테스트 케이스 추가

## BASE_SHA -> HEAD_SHA
f4e5d6a -> g7h8i9j

참고: Dev A가 충분한 컨텍스트를 제공하여 Dev B가 효율적으로 리뷰할 수 있게 합니다. 보내는 사람에게는 리뷰하는 사람이 문제를 이해할 수 있도록 도울 책임이 있습니다.

Dev B (리뷰어)가 프로세스에 따라 리뷰

Dev B (또는 Dev B의 AI)는 서둘러 피드백하지 않습니다. 대신 5단계 프로세스를 수행합니다:

1. READ -- 전체 PR description과 diff를 읽기

2. UNDERSTAND -- 문제와 수정 방법 이해

Dev B가 직접 다시 계산합니다:

합산: 500000 * (1 - 30/100) = 500000 * 0.7 = 350,000
순차: 500000 * 0.8 * 0.9 = 400000 * 0.9 = 360,000

확인: Dev A의 계산이 맞습니다.

3. VERIFY -- 코드와 테스트 확인

Dev B가 수정된 코드를 읽고, 각 테스트 케이스를 읽고, 각 테스트 케이스가 의미 있고 정확한지 확인합니다.

4. EVALUATE -- Edge case 평가

Dev B가 질문합니다:

  • 총 할인이 100%를 초과하면? (guard clause로 처리됨)
  • price = 0이면? (0 * 무엇이든 = 0, ok)
  • voucher.discountPercent = 0이면? (price * 1 = price, ok)
  • 다른 함수에 영향이 있는가? (calculateFinalPrice의 caller 확인)

5. RESPOND -- 구체적이고 건설적인 피드백

Dev B는 다음과 같이 작성하지 않습니다:

"Looks good! LGTM"

Dev B는 다음과 같이 작성합니다:

## Review: Fix calculateFinalPrice

### 확인 완료
- 순차 적용 로직이 맞습니다. 3개의 다른 입력 세트로 직접 계산을 확인했습니다.
- 테스트 케이스가 기본적인 경우를 충분히 커버합니다.
- input validation을 위한 guard clause가 적절합니다.

### 제안
1. voucher.discountPercent = 0인 경우의 테스트 케이스를 추가하여
   가격에 영향을 주지 않는 것을 확인해야 합니다.

2. promotion.ts 파일의 `applyPromotion()` 함수를 확인해야 합니다 --
   이 함수도 calculateFinalPrice()를 호출하고 있으며, 유사한
   로직이 수정이 필요할 수 있습니다.

3. 왜 합산이 아닌 순차 적용인지 설명하는 코멘트를 추가하여,
   다른 사람이 다시 "simplify"하는 것을 방지해야 합니다.

### 결론
위 3가지 제안과 함께 Approve. 제안 2는 merge 전에 확인해야 합니다.

Dev A 응답

Dev A가 피드백을 읽고 처리합니다:

  1. voucher.discountPercent = 0 테스트 케이스 추가 -- 완료
  2. applyPromotion() 함수 확인 -- 이 함수는 calculateFinalPrice()만 호출하고 별도의 할인 로직이 없음을 확인 -- 수정 불필요
  3. 설명 코멘트 추가 -- 완료

Dev A가 calculateFinalPrice() 함수에 설명 코멘트를 추가합니다 — 합산이 아닌 순차 적용의 이유를 명확히 기재하여, 향후 다른 사람이 다시 "simplify"하는 것을 방지합니다. 새 커밋을 push하고 3가지 피드백 모두 처리했음을 reply로 확인합니다.


6단계: Verification Gate

버그가 수정되었다고 선언하기 전에, 5단계 검증 프로세스를 적용합니다.

🧑 사용자가 요청합니다: "merge 전에 전체 검증을 실행해주세요."

1. 결정 -- 어떤 테스트를 실행할 것인가

🤖 AI가 실행해야 할 테스트 목록을 나열합니다:

- calculateFinalPrice 유닛 테스트: 7개 테스트 케이스
- checkout 흐름 통합 테스트: 3개 테스트 케이스
- Regression 테스트 스위트: 전체

🧑 사용자가 필요한 경우 추가합니다 (예: "checkout 페이지 e2e 테스트도 실행해주세요").

2. 실행 -- 테스트 수행

🤖 AI가 전체 테스트를 실행합니다:

# Unit tests
npm test -- --grep "calculateFinalPrice"

# Integration tests
npm test -- --grep "checkout"

# Full regression
npm test

3. 읽기 -- 결과를 한 줄씩 확인

🤖 AI가 결과를 읽고 제시합니다:

Test Suites: 24 passed, 24 total
Tests:       187 passed, 187 total
Snapshots:   0 total
Time:        12.34s

187개 테스트 모두 통과. Regression 없음.

4. 확인 -- 결과 대조

🤖 AI가 확인 체크리스트를 종합합니다:

  • calculateFinalPrice 7개 테스트 케이스: PASS
  • checkout 흐름 통합 테스트 3개: PASS
  • 총 187개 테스트: PASS
  • skip되거나 pending된 테스트 없음

🧑 사용자가 스테이징 환경에서 고객 보고서의 정확한 입력값으로 수동 확인합니다:

  • 상품 500,000원, 20% 할인, 바우처 10%
  • 결과: 360,000원 -- 정확

5. 선언 -- 증거가 있는 결론

🤖 AI가 증거를 기반으로 결론을 내립니다:

버그가 수정되었습니다. calculateFinalPrice() 함수가 할인 합산에서 순차 적용으로 수정되었습니다. 7개 유닛 테스트, 3개 통합 테스트, 스테이징에서의 수동 확인으로 검증되었습니다. Regression 없음. 프로덕션 배포 준비 완료.

🧑 사용자가 최종 확인하고 merge/배포를 승인합니다.


결과

Superpowers 프로세스를 적용한 후, 팀은 다음을 달성했습니다:

  1. 버그가 제대로 수정됨 -- 임시 핫픽스가 아닌 체계적이고 증거가 있는 수정
  2. 7개 테스트 케이스 추가 -- 이 버그가 향후 재발하는 것을 방지
  3. 팀이 디버깅 프로세스를 학습 -- 반응 모드 ("최대한 빨리 수정")에서 체계적 모드 ("최대한 정확하게 수정")로
  4. 코드에 코멘트 추가 -- 다음 사람이 읽으면 왜 순차 로직이어야 하는지 이해할 수 있으며, 아무도 다시 "simplify"할 수 없음

총 소요 시간: 전체 프로세스에 약 2-3시간. "빠른 수정"보다 오래 걸린 것처럼 보이지만:

  • 버그가 재발할 가능성 없음
  • 급한 수정으로 인한 추가 버그 발생 가능성 없음
  • 향후 자신 있게 리팩토링할 수 있는 테스트 확보

핵심 포인트

1. 디버깅할 때 절대 추측하지 마세요

4단계 체계적 프로세스를 따르세요: 조사 -> 패턴 분석 -> 가설 -> TDD Fix. 각 단계는 다음 단계로 넘어가기 전에 구체적인 산출물이 있습니다.

2. git log는 여러분의 친구입니다

커밋 "refactor: simplify discount calculation"이 바로 원인이었습니다. 원인을 추측하기 전에 git log --oneline -10git diff를 실행하는 습관을 기르면 잘못된 방향으로 디버깅하는 시간을 절약할 수 있습니다.

3. 수정 전에 버그를 재현하는 테스트를 작성하세요

테스트는 버그 존재의 증거입니다. 테스트가 실패하면 (RED), 증거가 있습니다. 수정 후 테스트가 통과하면 (GREEN), 버그가 수정되었다는 증거가 있습니다. 테스트 없음 = 증거 없음.

4. 양방향 Code review

  • 보내는 사람: 충분한 컨텍스트를 준비 (WHAT, ROOT CAUSE, PLAN, CHANGES). 리뷰하는 사람이 문제를 빠르게 이해할 수 있도록 돕습니다.
  • 리뷰하는 사람: 피드백하기 전에 꼼꼼히 읽습니다. 실제로 확인하지 않았으면 "LGTM"을 쓰지 않습니다. 구체적이고 건설적인 피드백을 합니다.

5. Evidence before claims

"수정 완료"는 명확한 검증 증거가 있을 때만 선언합니다: 테스트 통과, regression 없음, 수동 확인 성공. 증거 없는 선언은 보장이 없는 약속입니다.


참고