
요구사항 1줄에서 브라우저 테스트 완성까지: 삼비판 사이클(Three-Critique Cycle) 실전 로그
요약
단 한 줄의 요구사항으로부터 설계 비판 하네스를 통해 구체적인 수락 조건, 역 시나리오, DbC 계약을 도출하는 '삼비판 사이클' 프로세스를 소개합니다. Claude Code를 활용하여 설계 단계에서 예외 상황을 국소화함으로써 구현의 안정성을 높이는 실전 사례를 다룹니다.
핵심 포인트
- 삼비판 사이클을 통해 1줄의 요구사항을 14개의 수락 조건으로 구체화
- 포퍼의 반증주의를 적용한 역 시나리오 생성으로 설계 결함 사전 방지
- DbC(Design by Contract)를 통한 인터페이스의 사전/사후 조건 정의
- 설계 단계의 국소화를 통해 요구사항 변경 시 수정 비용 최소화
요구사항 1줄에서 브라우저 테스트 완성까지: 삼비판 사이클(Three-Critique Cycle) 실전 로그
TL;DR
- 출발점은 1줄의 일본어 요구사항이었다
- 설계 비판 하네스(Design Critique Harness)를 통과하자, 그 1줄로부터 14개의 수락 조건(Acceptance Criteria), 18개의 역 시나리오(Reverse Scenarios), 5개의 DbC 계약(Design by Contract)이 생성되었다
- 구현 중에 "가독성 높은 문자만 사용해 주세요"라는 추가 요구사항이 들어왔다. 1개 파일 수정만으로 흡수할 수 있었다 (설계 단계에서 국소화해 두었기 때문에)
- 최종적으로 Vitest 34개 테스트 + Playwright 12개 테스트까지 도달하여, Chromium 브라우저에서 PW(Password) 생성 및 복사를 자동 확인하고 있다
1. 출발점: 요구사항은 1줄이었다
실제로 받은 요구사항을 그대로 인용한다.
"PW 생성 앱으로 할까요, 글자 수와 출력할 개수, 대문자/소문자/숫자/기호 체크박스가 있어서 그 요구사항에 따라 랜덤한 PW를 정해진 개수만큼 출력한다. 각각 복사하기 쉽도록 클립보드에 복사하는 버튼을 붙인다."
이것이 전부다. "글자 수", "출력할 개수", "4종류의 체크박스", "복사 버튼". 요구사항으로서는 심플하며, 읽어보면 무엇을 만들고 싶은지는 알 수 있다.
하지만 여기에는 아무것도 적혀 있지 않은 사항이 산더미 같다. "랜덤"이란 어느 정도의 난수 품질인가. 복사에 실패했을 때는 어떤 일이 일어나는가. 연타하면 어떻게 되는가. 생성 중에 체크박스를 바꾸면 어떻게 되는가.
이 1줄을 그대로 구현자(Claude Code)에게 넘기면 어떻게 될까. 아마 "작동하는" 것은 완성될 것이다. 다만 위에 언급한 의문들에 대한 답은 구현자의 판단에 맡겨지며, 설계상의 의도로 기록되지 않는다.
2. 설계 비판 하네스를 통하면 어떤 일이 일어나는가
/design-critique를 실행하면, 4단계가 순차적으로 실행된다.
Phase 1: 소크라테스 문답 — 요구사항의 암묵지를 5개 질문으로 심층 탐구
Phase 2: 포퍼 역 시나리오 — 6개 패턴으로 "설계가 무너지는 케이스"를 생성
Phase 3: DbC 계약화 — 사전/사후/불변 조건을 각 인터페이스에 정의
...
페이즈가 끝나면 design-spec.md가 생성된다. 생성된 스펙의 내용을 살펴보자.
수락 조건(AC): 14개가 정의되었다
1줄의 요구사항으로부터, 다음과 같은 수락 조건이 14개 구체화되었다. 대표적인 것을 꼽는다.
| AC-ID | Given / When / Then |
|---|---|
| AC-01 | 여러 문자 종류 선택 시, 생성된 각 PW에 선택한 모든 문자 종류가 최소 1자 이상 포함됨 |
| ... |
AC-11과 AC-14는 원래의 1줄에는 전혀 적혀 있지 않았다. 하지만 소크라테스 문답과 포퍼 역 시나리오를 거치며, "설계상에서 답을 내놓아야 할 질문"으로 부상했다.
역 시나리오(RS): 18개 (그 중 확장 3건)
포퍼의 반증주의(Falsificationism)를 소프트웨어 설계에 전용하여 "설계가 무너지는 케이스"를 열거한다.
| RS-ID | 패턴 | 시나리오 | 대처 |
|---|---|---|---|
| RS-B03 | 부적절한 Bogus | crypto 미정의 환경 | 기동 시 Fail-Fast · 에러 배너 표시 |
| ... | constants.js 1개 파일 수정으로 완결되도록 국소화 |
RS-C01(연타 순서 역전)과 RS-C03(생성 중 체크박스 변경)은 "단순한 버튼과 체크박스 앱"에서는 놓치기 쉬운 병행성(Concurrency) 문제다.
DbC 계약: 5개의 인터페이스에 사전/사후 조건
가장 읽을거리가 있는 것은 DbC(Design by Contract) 계약 정의다. 인터페이스가 5개 정의되었으며, 각각에 사전 조건·사후 조건·불변 조건이 붙어 있다.
initApp() (기동 시 환경 검증)의 사후 조건(발췌):
Postcondition:
app.capabilities = Object.freeze({ crypto, clipboard }) 가 설정됨
crypto: typeof window.crypto?.getRandomValues === "function"
...
"crypto를 사용할 수 없는 브라우저에서 어떻게 할 것인가"를 구현 전에 문구 수준에서 확정 짓고 있다.
requestGenerate() (생성 오케스트레이션)의 절차(발췌):
내부 상태: currentGenerationToken: number (초기값 0, 단조 증가)
절차 (확정 · 5단계):
1. currentGenerationToken += 1
...
「연타되어도 마지막 클릭 결과만 표시한다」라는 동작이 코드를 작성하기 전 알고리즘 레벨에서 정의되어 있다.
MVP 분류: 5개 파일 구성 · 6종의 확장 포인트
Gall's Law 단계에서 "이번에 구현할 것"과 "나중에 해도 될 것"을 분리했다.
pw-gen-app/
├── index.html
├── styles.css
...
빌드 도구 없음. ES 모듈 (ES Modules)을 직접 읽어 브라우저에서 여는 것만으로 동작하는 구성이다.
확장 포인트도 6종 정의되었으나, 이번에는 "접점만 준비하고 구현하지 않는다 (YAGNI)"라고 명시되었다. 풀(Pool) 확장에 대해서는 "constants.js 파일 1개 수정으로 완결되도록 설계한다"라는 접점이 MVP 설계에 포함되었다. 이것이 나중에 추가 요구사항이 왔을 때 효과를 발휘한다.
3. 구현 중 추가 요구사항이 발생함
설계 비판 하네스(Design Critique Harness)를 통과하여 구현 단계에 들어갔을 때, 다음과 같은 추가 요구사항이 왔다.
"PW에 사용할 문자열은 가독성이 높은 것만 사용해 주세요. 1과 !, 0과 o는 비슷하니까 사용하지 말아 주세요."
요구사항 추가 타이밍으로는 늦다. 설계가 끝나고 구현이 시작된 단계다.
하지만 영향 범위는 constants.js 파일 1개로 끝났다.
왜일까. 설계 사양서에 "풀 상수 정의는 constants.js 한 곳뿐이다"라는 불변 조건(Invariant)이 정의되어 있었기 때문이다. 문자 풀이 한 곳에 국소화(Localization)되어 있는 설계에서는 "제외 문자를 바꾸는" 변경은 constants.js를 고치면 끝난다. generator.js도 validator.js도 app.js도 건드릴 필요가 없다.
실제로 제외한 문자는 다음과 같다.
// constants.js (발췌)
// 가독성 필터: 시각적으로 혼동하기 쉬운 문자를 제외
// 0/O/o (제로와 오), 1/l/I (일과 엘·아이), 기호 중 혼동하기 쉬운 것
...
"나중에 들어온 요구사항 변경을 파일 1개의 수정으로 흡수할 수 있었던 것"은 우연이 아니다. 설계 단계에서 풀 정의의 국소화를 불변 조건으로 명시한 결과다.
4. 구현의 전체 모습
최종적인 구현 구성을 보여준다.
테스트 구성
| 테스트 레이어 | 프레임워크 | 건수 | 주요 커버 대상 |
|---|---|---|---|
| 유닛 (Unit) | Vitest | 22건 | validator · generator · shuffle · clipboard 각 모듈 |
| ... |
Playwright E2E에서는 실제로 Chromium 브라우저를 기동하여 다음을 확인하고 있다.
// E2E 테스트의 한 예 (개요)
test('PW 생성 버튼을 누르면 N개의 PW가 표시된다', async ({ page }) => {
// 문자 수 · 개수를 입력하고 체크박스를 켬
...
"가독성 필터 확인" 테스트는 추가 요구사항(제외 문자 지정)에 대응하여 추가한 것이다. 50개를 생성하여 E2E로 전수 스캔하기 때문에, 구현 실수가 있다면 반드시 검출할 수 있다.
코드 구조 방침
설계 사양서가 정의한 "퓨어 코어(Pure Core)와 얇은 쉘(Thin Shell)" 방침이 구현에 반영되어 있다.
부작용 제로 (유닛 테스트로 망라하기 쉬움):
validator.js, generator.js, shuffle.js
DOM 조작 있음 (E2E로 확인 필요):
...
의존 방향은 일방향으로 고정되어 있다. app.js → {validator, generator, clipboard} → {shuffle, constants} 방향뿐이다. 역참조는 없다.
5. 회고: 하네스가 있으면 무엇이 바뀌는가
"설계 하네스 없이 1줄의 요구사항을 그대로 전달하는" 경우와 비교하여 무엇이 바뀌었는지 정리한다.
엣지 케이스(Edge Case)가 "설계 단계"에서 정의되었다
| 상황 | 하네스 없음 | 하네스 있음 |
|---|---|---|
| 연타했을 때 | 구현자의 판단에 맡겨짐 | AC-13 · RS-C01에서 "마지막 클릭 결과가 최종 DOM"이라고 사전 정의됨 |
| ... |
하네스가 없는 경우, 이러한 동작은 "코드를 작성해 보고 깨닫거나" "사용자가 보고하여 깨닫는" 타이밍까지 정의되지 않는다.
하네스가 있는 경우, 이것들은 구현 전에 수락 조건(Acceptance Criteria), 역 시나리오(Inverse Scenario), DbC(Design by Contract) 계약으로서 기록되어 있다. 구현 시에 생각할 필요가 없으며, 테스트 시에 "어떻게 테스트할 것인가"가 자명해진다.
추가 요구사항을 파일 1개로 흡수할 수 있었다
구현 단계에서의 "가독성 필터 추가"는 테스트 하네스 (Test Harness) 없는 설계에서도 구현할 수 있다. 하지만 "영향 범위가 어디인가"에 대한 답은 설계에 의존한다.
설계 단계에서 풀 (Pool) 정의의 국소화를 불변 조건 (Invariant)으로 명시했기 때문에, "constants.js를 수정하면 된다"라고 즉각적으로 판단할 수 있었다. 테스트 또한 constants.js의 변경을 확인하는 기존 유닛 테스트 (Unit Test)가 통과하면 충분했으며, 다른 모듈의 테스트는 재실행하는 것만으로 충분했다.
변하지 않은 것: 코드를 작성하는 것은 인간(과 Claude)이다
설계 비판 하네스 (Design Critique Harness)는 "설계 단계에서 에지 케이스 (Edge Case)를 정의하는 메커니즘"이다. 코드를 자동으로 생성하거나 버그를 자동으로 수정하지는 않는다.
"하네스를 통과하면 완벽한 코드가 나온다"가 아니라, "하네스를 통과하면 구현 시에 판단해야 하는 모호함이 줄어든다"가 정확한 설명이다.
요약
한 줄의 요구사항이 설계 비판 하네스를 통과하여 무엇이 되었는지 되돌아본다.
| 산출물 | 수 |
|---|---|
| 수락 조건 (AC) | 14개 |
| ... |
"요구사항 1줄"에서 시작하여 "브라우저에서 PW 생성·복사를 자동 확인하는 E2E 테스트"까지 이른 프로세스는, 설계 $\rightarrow$ 구현 $\rightarrow$ E2E의 각 계층에서 "비판적인 질문"이 기능한 결과다.
"연타 순서 역전"이나 "생성 중 체크박스 변경"과 같은 문제는, 단순히 동작하는 데모를 만드는 것이 목적이라면 놓치고 지나갈 법한 문제들이다. 그것들이 구현 전에 RS-C01·RS-C03로서 기록되었고, 테스트 코드로 구체화되었다. 삼비판 사이클 (Three-Critique Cycle)의 "실전 로그"로서 참고가 되기를 바란다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기