페이지 목차
이 페이지에서 검색
AI 드리븐
이 페이지에 묻기
AI 드리븐 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.
생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.
AI · DEVELOPMENT
AI 드리븐
생성 수정
요구사항과 역할 정의부터 구현·검증·배포·운영까지, AI와 함께 개발하며 사람이 판단할 기준을 정리합니다.
26개 문서 · 2일차 · 14 / 26
13차시 · 검증 ① 코드·동작 점검
지도 위치 : 2일차 · 구현과 검증 · 검증 | 이전: 12차시 데이터 관리 역할 | 다음: 14차시 검증 ② 보안 점검과 차단 키워드 : 코드 검토와 동작 검증의 분리, 경계·실패 상황, 동시 요청 문제
이 차시를 다 읽으면
- 정적 검증(코드 검토) 과 동적 검증(동작 검증) 을 구별하고, 각각이 잘 찾는 문제와 놓치는 문제를 말할 수 있다.
- 검증 역할 두 개(리뷰어 · QA)의 권한 차이 를 만드는 자 ↔ 검증하는 자 축으로 설명할 수 있다.
- 인원 · 날짜 · 남은 자리에 대한 경계값 목록 을 직접 쓸 수 있다.
- 경쟁 상태 를 유와 종차로 정의하고, "조회 → 판단 → 저장" 코드가 동시 요청에서 실패하는 과정을 그림으로 설명할 수 있다.
- 경쟁 상태의 해결책 세 가지를 주 방어 / 마지막 방어 로 나누고, 동시성 시험 결과를 숫자 두 개 로 판정할 수 있다.
0. 이 차시의 판별 축
| # | 축 | 왼쪽 (0) | 오른쪽 (1) | 이 축으로 갈리는 것 | 큰 축 (README 5장) |
|---|---|---|---|---|---|
| 1 | 읽는가, 실행하는가 | 정적 검증 — 코드를 읽고 판단 | 동적 검증 — 실행하고 결과 확인 | 코드 검토 ↔ 동작 검증 | 정적 ↔ 동적 검증 (⑧의 파생) |
| 2 | 누가 검증하는가 | 만든 역할 자신 | 다른 맥락 · 제한된 권한의 역할 | 같은 맹점을 공유하는가 | 만드는 자 ↔ 검증하는 자 (⑤의 파생) |
| 3 | 어떤 경우를 시험하는가 | 정상 경로 — 적당한 값 하나 | 경계 · 실패 — 끝자락 값, 끊김, 오류 | AI 코드의 버그가 숨는 곳 | 이 차시의 축 |
| 4 | 요청이 하나씩인가, 동시에인가 | 순차 — 한 명씩 | 동시 — 같은 순간 여러 명 | 혼자 시험하면 안 보이는 경쟁 상태 | 이 차시의 축 |
| 5 | 완료를 무엇으로 판정하는가 | "테스트 다 통과했습니다" | 재현 명령 + 출력 + DB의 숫자 | 검증 보고를 믿을 수 있는가 | 증거 ↔ 주장 |
이 차시가 답하는 질문은 "AI가 만든 코드의 버그는 어디에 숨는가, 그리고 누가 어떻게 찾는가" 입니다. AI가 만든 코드는 정상 경로에서는 거의 항상 동작 합니다. 데모에서는 문제가 보이지 않습니다. 실제 사고는 0명, 최댓값, 네트워크 끊김, 두 사람이 동시에 누르는 순간(축 3 · 4의 오른쪽)에 납니다. 앞에서 배운 "만드는 자와 검증하는 자의 분리"(7차시)를 이번에는 화면이 아니라 코드에 적용합니다.
1. 정적 검증과 동적 검증
1.1 정의와 경계
정적 검증 (코드 검토) — [검증 방법]인데, [ 코드를 실행하지 않고 읽어서 판단한다 ]는 점에서 실제로 돌려 보는 동적 검증과 구별된다.
동적 검증 (동작 검증) — [검증 방법]인데, [ 실제로 실행해 나온 결과로 판단한다 ]는 점에서 읽어서 판단하는 정적 검증과 구별된다.
| 코드 검토 (정적) | 동작 검증 (동적) | |
|---|---|---|
| 방법 | 코드를 읽고 판단 | 실행하고 결과 확인 |
| 잘 찾는 것 | 인가 누락, 명세 불일치, 에러 처리 빠짐, "조회 → 판단 → 저장" 패턴, 구조 문제 | 실제 응답 오류, 연동 문제, 동시성 문제 |
| 한계 | 실제로 도는지는 모름 | 시험한 경우만 확인됨 |
| 담당 | 리뷰어 (code-reviewer, 읽기 전용) | QA (qa, 실행 권한 있음, 앱 코드 수정 불가) |
둘 다 필요합니다. 코드 검토만 하면 "논리상 맞아 보이는" 버그를 놓치고, 동작 검증만 하면 시험하지 않은 경로를 놓칩니다.
1.2 반례 — 이건 검증이 아닙니다
| 대상 | 왜 아닌가 |
|---|---|
| 구현 에이전트의 "테스트 다 통과했습니다" | 만든 자의 주장 . 0절 축 2의 왼쪽 + 축 5의 왼쪽 |
| 정상 예약 한 번 눌러 보고 "잘 됩니다" | 동적 검증이긴 하지만 축 3의 왼쪽만 시험 |
| 같은 대화 안에서 "방금 만든 코드 검토해줘" | 만든 맥락을 그대로 가진 채 검토 — 같은 맹점 |
💡 코드를 읽지 않는 사람에게 이 표의 의미 — 두 검증 중 동작 검증은 내가 직접 할 수 있습니다. 코드 검토는 리뷰 역할에게 맡기고, 내 시간은 동작 검증과 그 결과 숫자를 보는 데 씁니다.
2. 검증하는 자를 분리하기
앞에서 배운 하위 에이전트(2차시)와 역할 정의(5차시)로 검증 역할 둘을 만듭니다. 핵심은 권한(tools) 입니다.
# .claude/agents/code-reviewer.md
tools: Read, Grep, Glob
점검: 명세 일치, 입력 검증, 인가(본인 데이터만), 트랜잭션 범위, 에러 형식,
조회-판단-저장 패턴
보고: [심각도 | 파일:줄 | 문제 | 사용자에게 생기는 피해 한 줄]
# .claude/agents/qa.md
tools: Read, Bash, Grep, Glob (앱 코드 수정 불가, tests/ 폴더만 작성)
시험: 정상 1 + 경계값 + 실패 상황 + 동시 요청 20개
보고: [시험한 경우 | 기대 결과 | 실제 결과 | 재현 명령]
🔍 읽는 포인트 : tools: 줄에 Write · Edit가 없다 = 코드를 고칠 수 없다. 리뷰어는 읽기만, QA는 실행까지만 합니다.
| 원칙 | 이유 |
|---|---|
| 검증자는 발견하면 보고만 하고 고치지 않는다 | 고치는 순간 검증자가 만드는 자가 되어 다시 같은 맹점을 공유 |
| 고치는 일은 구현 역할이 한다 | 검증자의 보고 → 구현 역할 수정 → 검증자 재시험 |
QA는 tests/에만 쓸 수 있다 | 시험이 실패할 때 앱 코드 대신 시험을 고치는 일을 막음 (17차시) |
3. 경계와 실패
3.1 경계값 — 값의 끝자락
| 입력 | 시험할 값 |
|---|---|
인원 headcount (1~4) | 0, 1, 4, 5, -1, 1.5, "abc", 빈 값 |
| 남은 자리 | 정확히 1 남았을 때, 0일 때 |
| 날짜 | 오늘, 지난 날짜, 취소 기한 당일 자정 직전 · 직후 |
| 문자열 | 빈 문자열, 아주 긴 문자열, 이모지, 특수문자 |
| 목록 | 0개, 1개, 한 페이지를 딱 채우는 개수 |
⭐ 이 표는 코드 지식이 아니라 서비스에 대한 이해 에서 나옵니다. "인원은 몇 명까지?", "취소는 언제까지?"를 정한 사람은 나(3차시의 인수 기준)입니다. 그래서 경계값 표는 내가 직접 쓰고, 시험은 AI에게 시킵니다.
3.2 실패 상황 — "이게 실패하면?"
| 실패 | 기대하는 모습 |
|---|---|
| DB 연결이 끊김 | 500과 일반 메시지. 내부 정보(SQL, 파일 경로) 노출 없음 (14차시) |
| 결제 대행사가 응답하지 않음 | 예약은 결제 대기 상태로 남고 다시 시도 가능 (16차시) |
| 예약 생성 다음 단계에서 오류 | 자리만 줄고 예약이 없는 상태가 남지 않음 — 트랜잭션 (12차시 4절) |
4. ⭐ 경쟁 상태 — 동시 요청 문제
4.1 정의와 그림
경쟁 상태 (Race Condition) — [동시성 버그]인데, [ 여러 요청이 같은 데이터를 동시에 읽고 바꿀 때, 실행 순서가 겹치는 방식에 따라 결과가 틀어진다 ]는 점에서 요청 하나만으로 재현되는 일반 버그와 구별된다.
남은 자리: 1
사용자 A 사용자 B
① 남은 자리 조회 → 1 ① 남은 자리 조회 → 1
② 1 > 0 이니 예약 가능 ② 1 > 0 이니 예약 가능
③ 남은 자리 = 0 저장, 예약 생성 ③ 남은 자리 = 0 저장, 예약 생성
결과: 자리 1개에 예약 2개 (초과 예약)
🔍 읽는 포인트 : A의 ①과 ③ 사이에 B의 ①이 끼어든 것이 전부입니다. 각자의 코드는 혼자서는 완벽합니다.
AI가 흔히 만드는 "조회 → 판단 → 저장" 코드는 혼자 시험할 때 완벽하게 동작합니다. 그래서 일부러 동시에 요청을 보내 봐야 드러납니다(0절 축 4).
4.2 반례 — 이건 경쟁 상태가 아닙니다
| 대상 | 왜 아닌가 |
|---|---|
| 같은 사용자가 결제 버튼을 두 번 눌러 예약이 두 개 | 중복 요청 문제 — 같은 의도가 두 번 들어옴. 해결은 멱등성 (16차시) |
| 인원 5명 예약이 통과됨 | 요청 하나로 재현되는 입력 검증 누락 (10차시) |
| "드물게 일어나는 일" | ⚠️ 인기 클래스 오픈 시각에는 매번 일어납니다 |
4.3 해결책 — 주 방어와 마지막 방어
| # | 방법 | 하는 일 | 역할 |
|---|---|---|---|
| 1 | 조건부 갱신을 한 문장으로 | 조회와 차감을 DB가 한 번에 처리 — 사이에 끼어들 틈이 없음 | 주 방어 |
| 2 | 행 잠금 | 트랜잭션 안에서 SELECT ... FOR UPDATE로 그 회차를 잠그고 판단. 다른 요청은 기다림 | 주 방어 (1의 대안) |
| 3 | DB 제약조건 | CHECK (remaining >= 0) — 1 · 2에 버그가 있어도 음수를 거부 | 마지막 방어선 (12차시 3.2) |
UPDATE sessions SET remaining = remaining - 1
WHERE id = $1 AND remaining >= 1;
-- 바뀐 행이 0개면 "자리 없음" → 409 SOLD_OUT
🔍 읽는 포인트 : WHERE ... remaining >= 1 — "자리가 있으면 줄여라"를 한 문장으로 말합니다. 조회와 저장 사이의 틈(4.1의 그림)이 사라집니다.
💡 비유는 마지막에 — 공연장 매표소 두 곳이 같은 좌석표를 각자 장부로 확인하고 팔면 한 좌석이 두 번 팔립니다. 해결은 중앙 전산 한 곳에서 "비어 있으면 팔림 처리"를 한 번에 하는 것입니다.
5. 동시성 시험
# 남은 자리 1인 회차에 20개 요청을 동시에 보냄
for i in $(seq 1 20); do
curl -s -o /dev/null -w "%{http_code}\n" -X POST localhost:4000/reservations ... &
done; wait
# 기대: 201이 정확히 1개, 나머지는 409
# 그리고 DB에서 해당 회차 예약 수가 정확히 1인지 확인
🔍 읽는 포인트 : 끝의 &가 "기다리지 않고 동시에" 보내는 표시입니다. 기대 줄의 숫자 두 개 만 보면 됩니다.
⭐ 판정은 숫자 두 개 로 합니다.
| 숫자 | 정상 |
|---|---|
| ① 성공(201) 응답 개수 | 1 |
| ② DB에 실제로 생긴 예약 개수 | 1 |
둘 중 하나라도 2 이상이면 경쟁 상태가 있는 것입니다. ①은 1인데 ②가 2라면 응답과 실제 데이터가 어긋난 더 나쁜 경우입니다.
앞으로 다시 만납니다 — 이 동시성 시험은 17차시에서 자동 테스트로 옮겨져 변경 때마다 다시 돌고, 19차시의 자동 검사 관문에서 통과 조건이 됩니다.
6. 클래스온에 적용하면
| 시점 | 리뷰어에게 | QA에게 |
|---|---|---|
POST /reservations 구현 직후 | 조회-판단-저장 패턴이 있는가, 트랜잭션 범위 | 경계값 표(3.1) + 동시 요청 20개 |
| 예약 취소 구현 직후 | 인가(본인 예약만) | 취소 기한 자정 직전 · 직후, 자리 복구 확인 |
| 리뷰 지적이 너무 많거나 어려울 때 | "사용자에게 실제 피해가 생기는 것 / 나중에 고쳐도 되는 것 으로 나눠줘" | — |
7. 실습 — 맡기고 검수하기
지시
qa 역할로 진행해줘. 앱 코드는 수정하지 마.
1. 남은 자리 1인 회차를 만들고, 예약 요청 20개를 동시에 보내.
결과로 ① 201 응답 개수 ② DB에 실제로 생긴 그 회차의 예약 개수, 이 두 숫자를 먼저 보여줘.
2. 아래 경계값 표대로 시험하고 [입력 | 기대 | 실제 응답 | 통과?] 표로 보고해줘.
(내가 쓴 경계값 표를 여기 붙임)
3. DB를 멈춘 상태에서 예약 요청을 보내고 응답 원문을 보여줘.
모든 시험의 재현 명령과 실행 출력 원문을 붙여줘. 실패한 것이 있으면 고치지 말고 보고만 해.
(별도로) code-reviewer 역할로 POST /reservations를 검토하고,
지적을 [사용자에게 실제 피해 | 나중에 고쳐도 됨]으로 나눠 보고해줘.
받을 증거
- 동시성 시험의 숫자 두 개 와 실행 출력 원문
- 경계값 결과 표와 재현 명령
- DB 중단 시 응답 원문
- 리뷰 지적을 피해 기준으로 나눈 표
내가 직접 확인할 것
| 볼 것 | 이걸로 아는 것 / 왜 내가 보는가 |
|---|---|
| 동시성 시험의 숫자 두 개가 둘 다 1 인가 | 경쟁 상태 여부. 이 차시에서 가장 중요한 숫자 |
| 12차시의 DB 조회 도구로 그 회차의 예약 행을 내가 직접 센다 | AI가 보고한 ②번 숫자를 내 눈으로 확인 (README 9.1의 행동 시험) |
| 경계값 표를 내가 썼는가 | 서비스 규칙을 아는 사람은 나. AI가 쓰면 규칙과 다른 경계를 시험할 수 있음 |
| DB 중단 시 응답에 SQL · 파일 경로가 보이는가 | 보이면 14차시의 정보 노출 |
| QA가 앱 코드를 바꿨는가 (변경 파일 목록) | 바꿨다면 검증자가 만드는 자가 된 것 (2절) |
| 리뷰 지적 중 "실제 피해" 칸 | 이것부터 구현 역할에게 넘김 |
부록 A. 스스로 확인하기
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 정적 검증과 동적 검증을 유와 종차로 정의하면? | 1.1 |
| 2 | 코드 검토와 동작 검증이 각각 잘 찾는 문제와 한계는? | 1.1 |
| 3 | "테스트 다 통과했습니다"가 검증이 아닌 이유를 두 축으로 말하면? | 1.2 |
| 4 | 리뷰어와 QA의 권한은 어떻게 다르고, 왜 검증자는 고치지 않는가? | 2 |
| 5 | 인원(1~4) 입력의 경계값 목록은? 이 표를 누가 써야 하는가? | 3.1 |
| 6 | 경쟁 상태를 정의하고 "조회 → 판단 → 저장"이 실패하는 과정을 설명하면? | 4.1 |
| 7 | 경쟁 상태와 중복 요청은 어떻게 다른가? | 4.2 |
| 8 | 해결책 세 가지와 각각의 역할(주 방어 / 마지막 방어)은? | 4.3 |
| 9 | 동시성 시험을 판정하는 숫자 두 개는? | 5 |
부록 B. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| 구현 에이전트의 "테스트 통과" 보고를 그대로 믿는다 | ❌ 검증은 다른 역할이, 출력 원문으로 (1.2) |
| 경계값을 "적당한 값 하나"로 시험 | ❌ 끝자락 값 목록 전부 (3.1) |
| 동시 요청 문제를 "드물다"며 넘긴다 | ⚠️ 인기 클래스 오픈 시각에는 매번 일어납니다 (4.2) |
| 검증자가 발견하자마자 직접 고친다 | ❌ 보고하고, 고치는 건 구현 역할 (2) |
CHECK 제약조건 하나로 경쟁 상태를 해결했다고 본다 | ⚠️ 그건 마지막 방어선. 주 방어(조건부 갱신 · 잠금)가 함께 (4.3) |
| 201 개수만 보고 끝낸다 | ⭕ DB의 실제 예약 수까지 두 숫자 (5) |
부록 C. NotebookLM 복습 프롬프트
이 13차시 노트만 근거로 6문제를 내줘. 비전공자 기준으로.
"정적 검증과 동적 검증", "검증자를 분리하는 권한 설계", "경쟁 상태와 중복 요청의 차이", "동시성 시험의 숫자 두 개"는 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.
LangTailor