Spec-first-dev (명세 우선 개발)
DDD로 이름을 짓고, BDD로 명세를 정의하고, 이를 충족하는 최소 변경으로 구현하고, 증거로 검증합니다. 스택에 구애받지 않습니다.
1. 코딩 전에 생각하기
- 가정을 명확히 합니다. 요청이 불명확하거나 여러 해석이 가능하다면 이를 명시하고 질문합니다. 절대 조용히 넘어가지 않습니다.
- 더 간단한 접근 방식이 있다면 말하고 우회를 권장합니다.
- 작업이 변경해야 할 코드를 읽습니다. 변경이 영향을 미치는 모든 항목(호출자, 테스트, 픽스처, 설정, 익스포트)과 손상시킬 수 있는 항목(파괴되거나 노출되는 데이터, 작동을 멈추는 호출자)을 목록화합니다. 이것이 범위(scope)이며, 추가 기능은 범위에 포함되지 않습니다. 건너뛴 추가 기능은 한 줄로 명시합니다.
- 첫 번째 코드 작성 전에, 작업에 적합한 이름으로 main 브랜치에서 새 브랜치를 생성합니다 (예:
feat/<feature>,fix/<bug>). 읽기 전용 작업은 브랜치가 필요 없습니다.
2. 도메인 모델링 (DDD)
코드 작성 전에 몇 줄로 다음을 기록합니다:
- 경계 컨텍스트(Bounded context) 및 보편 언어(Ubiquitous language): 도메인 전문가가 사용하는 용어입니다. 코드베이스의 기존 용어를 재사용하고, 필요할 때만 새 용어를 추가합니다.
- 집합체(Aggregate) 및 불변 조건(Invariants): 항상 참이어야 하는 조건입니다.
- 코드, 테스트, 메시지의 이름은 이 용어를 사용합니다. 일반적인 이름(
handle,process,manager,utils)은 거부합니다. - 도메인 로직은 순수 데이터에 대한 순수 함수입니다. I/O(DB, 네트워크, 시계, 무작위성)는 경계의 포트 뒤에 위치하며, 의존성 주입됩니다.
이를 docs/specs/<feature>.md에 저장합니다.
도메인 개념을 추가하지 않는 사소한 변경은 이 단계를 건너뜁니다.
3. 행동 명세 (BDD)
도메인 언어로 Given/When/Then 시나리오를 작성합니다. 각 행동(behavior)당 하나의 시나리오이며, 엣지 케이스와 실패 케이스를 포함합니다. 각 시나리오는 구현이 아닌 관찰 가능한 행동을 설명합니다. 명확하지 않은 시나리오는 사용자의 승인을 받습니다. 이 시나리오가 계약(contract)입니다. docs/features/<feature>.feature에 저장합니다.
4. 계획
여러 단계로 구성된 작업의 경우, docs/plans/<feature>.md에 짧은 계획을 작성합니다: 각 단계와 검증 확인 사항을 포함합니다. 기존 명세(spec)에서 작업을 계속하는 경우에도 먼저 계획을 작성합니다. 그런 다음 사용자에게 검토를 요청하고 승인을 받기 전까지 절대 실행하지 않습니다. 완전히 작동하는 첫 번째 옵션을 선택합니다:
- 존재할 필요가 있는가? 아니라면 건너뜁니다.
- 이미 이 코드베이스에 있는가? 주변 코드와 동일한 방식으로 사용합니다.
- 표준 라이브러리 또는 플랫폼 기능인가? 사용합니다.
- 설치된 의존성인가? 사용합니다. 몇 줄 때문에 새 의존성을 추가하지 않습니다.
- 한눈에 이해되는 한 줄짜리 코드인가? 사용합니다.
- 그 외에는, 동작하는 최소 코드를 작성합니다.
5. 실행
오케스트레이션
작업을 실행하기 전에 (명세나 계획 유무와 관계없이) 작업이 다음 조건에 맞는지 확인합니다: 여러 분리 가능한 항목(예: 3개 이상의 수정), 독립적인 단계, 또는 명확히 난이도가 다른 단계로 구성되어 있는 경우. 그렇다면, orchestration.md(이 파일과 같은 위치)를 읽고 두 가지 옵션을 AskUserQuestion으로 먼저 질문합니다: 계획을 작성하는 경우 계획 검토와 같은 턴에, 그렇지 않으면 즉시 질문합니다. 항상 질문해야 합니다; 다른 승인(계획, 명세, "ok")은 선택으로 간주되지 않습니다. 작업이 조건에 맞지 않으면 인라인으로 처리합니다.
시나리오별: 레드, 그린, 리팩터
- 시나리오를 실패하는 테스트로 전환합니다. 실행하고 올바른 이유로 실패하는지 확인합니다.
- 통과하는 최소 코드를 작성합니다. 실행합니다.
- 이 변경으로 인해 지저분해진 부분만 리팩터링합니다.
- 버그 수정: 먼저 실패하는 시나리오로 재현하고, 해당 함수의 모든 호출자를 grep한 후, 공유 코드에서 근본 원인을 한 번에 수정합니다.
6. 차이(diff)를 최소화
- 반드시 필요한 부분만 수정합니다. 기존 스타일을 따르고 인접한 코드를 개선하지 않습니다. 모든 변경된 줄은 요청에 근거해야 합니다.
- 변경으로 인해 생성된 고아 코드만 제거합니다; 다른 죽은 코드는 언급만 하고 삭제하지 않습니다.
- 아무도 요청하지 않은 추상화, 래퍼, 옵션, 설정, "나중을 위한" 코드는 추가하지 않습니다. 추가보다는 삭제가 우선입니다.
- 이동하는 코드는 기존의 에러 처리와 검증을 유지합니다.
- 주석은 "왜"에 대해서만 한 줄로 답니다. 알려진 제한사항은
shortcut: <제한사항>, <업그레이드 시점>형식으로 표시합니다. - 절대 생략하지 않음: 신뢰 경계에서의 검증, 데이터 손실을 방지하는 오류 처리, 보안, 접근성, 사용자가 요청한 모든 것.
7. 완료를 선언하기 전에 검증
테스트와 빌드 또는 타입 검사를 실행합니다. 출력을 읽습니다. 실제 결과를 보고합니다; 실패했거나 실행되지 않은 것이 있으면 그렇게 말합니다. "잘 동작할 겁니다"는 안 됩니다.
8. 응답
건너뛰거나 확인하지 않은 사항과 사용자가 반드시 알아야 할 위험을 한두 줄로 마무리합니다.