--- name: incremental-implementation description: "변경을 얇고 검증 가능한 조각으로 점진적으로 전달합니다. 파일을 둘 이상 건드리는 기능이나 변경을 구현할 때, 또는 계획에서 다음 작업을 집어 들 때 사용하세요. 피처 플래그 뒤에 변경을 롤아웃할 때, 한 번에 많은 코드를 쓰려 할 때, 또는 작업이 한 단계에 넣기에는 너무 커 보일 때 사용하세요." --- # Incremental Implementation ## 개요 얇은 수직 슬라이스로 만드세요. 한 조각을 구현하고, 테스트하고, 검증한 다음 확장하세요. 기능 전체를 한 번에 구현하지 마세요. 각 증분은 시스템을 동작하고 테스트 가능한 상태로 남겨야 합니다. 이것이 큰 기능을 감당할 수 있게 하는 실행 규율입니다. ## 언제 사용하나 - 여러 파일에 걸친 변경을 구현할 때 - 작업 분해에서 새 기능을 만들 때 - 기존 코드를 리팩터할 때 - 테스트하기 전에 약 100줄을 넘게 쓰고 싶어질 때 **사용하지 말아야 할 때:** 범위가 이미 최소인, 파일 하나와 함수 하나의 변경. ## 증분 주기 ``` ┌──────────────────────────────────────┐ │ │ │ Implement ──→ Test ──→ Verify ──┐ │ │ ▲ │ │ │ └───── Commit ◄─────────────┘ │ │ │ │ │ ▼ │ │ Next slice │ │ │ └──────────────────────────────────────┘ ``` 각 조각마다: 1. **구현.** 기능의 가장 작은 완전한 조각을 구현하세요 2. **테스트.** 테스트 스위트를 실행하세요 (없으면 테스트를 작성하세요) 3. **검증.** 조각이 기대한 대로 동작하는지 확인하세요 (테스트 통과, 빌드 성공, 수동 확인) 4. **커밋.** 설명적인 메시지와 함께 진행을 저장하세요 (원자적 커밋 지침은 `git-workflow-and-versioning`을 보세요) 5. **다음 조각으로 이동.** 이어서 진행하세요. 처음부터 다시 시작하지 마세요 ## 자르는 전략 ### 수직 슬라이스 (선호) 스택을 관통하는 완전한 경로를 하나 만드세요. ``` 조각 1: 작업 생성 (DB + API + 기본 UI) → 테스트 통과, 사용자가 UI로 작업을 만들 수 있음 조각 2: 작업 목록 (쿼리 + API + UI) → 테스트 통과, 사용자가 자신의 작업을 볼 수 있음 조각 3: 작업 수정 (업데이트 + API + UI) → 테스트 통과, 사용자가 작업을 수정할 수 있음 조각 4: 작업 삭제 (삭제 + API + UI + 확인) → 테스트 통과, 전체 CRUD 완료 ``` 각 조각은 끝까지 동작하는 기능을 전달합니다. ### 계약 우선 슬라이스 백엔드와 프론트엔드가 병렬로 개발해야 할 때: ``` 조각 0: API 계약을 정의 (타입, 인터페이스, OpenAPI 스펙) 조각 1a: 계약에 맞춰 백엔드 구현 + API 테스트 조각 1b: 계약과 일치하는 목 데이터에 맞춰 프론트엔드 구현 조각 2: 통합하고 엔드투엔드로 테스트 ``` ### 위험 우선 슬라이스 가장 위험하거나 가장 불확실한 조각을 먼저 다루세요. ``` 조각 1: WebSocket 연결이 동작함을 증명 (위험이 가장 큼) 조각 2: 증명된 연결 위에 실시간 작업 업데이트를 만듦 조각 3: 오프라인 지원과 재연결을 추가 ``` 조각 1이 실패하면, 조각 2와 3에 투자하기 전에 알게 됩니다. ## 구현 규칙 ### 규칙 0: 단순함 우선 코드를 쓰기 전에 물으세요. "동작할 수 있는 가장 단순한 것은 무엇인가?" 코드를 쓴 뒤, 다음 점검과 대조하세요. - 더 적은 줄로 할 수 있는가? - 이 추상화가 그 복잡성을 감수할 만큼 가치가 있는가? - 스태프 엔지니어가 이것을 보고 "왜 그냥 ...하지 않았지?"라고 말할 것인가? - 가상의 미래 요구사항을 위해 만드는가, 아니면 현재 작업을 위해 만드는가? ``` 단순함 점검: ✗ 알림 하나만을 위한 미들웨어 파이프라인이 있는 범용 EventBus ✓ 단순한 함수 호출 ✗ 비슷한 컴포넌트 두 개를 위한 추상 팩토리 패턴 ✓ 공유 유틸리티를 둔 직관적인 컴포넌트 두 개 ✗ 폼 세 개를 위한 설정 기반 폼 빌더 ✓ 폼 컴포넌트 세 개 ``` 비슷한 코드 세 줄이 때 이른 추상화보다 낫습니다. 먼저 순진하고 명백히 올바른 버전을 구현하세요. 테스트로 정확성이 증명된 뒤에만 최적화하세요. ### 규칙 0.5: 범위 규율 작업이 요구하는 것만 건드리세요. 하지 마세요: - 변경 옆의 코드를 "정리"하기 - 수정하지 않는 파일의 import를 리팩터하기 - 완전히 이해하지 못한 주석을 제거하기 - "쓸모 있어 보인다"는 이유로 스펙에 없는 기능을 추가하기 - 읽기만 하는 파일의 문법을 현대화하기 작업 범위 밖에서 개선할 만한 것을 알아채면 적어 두세요. 고치지는 마세요. ``` 보이지만 건드리지 않음: - src/utils/format.ts에 이 작업과 무관한 미사용 import가 있음 - 인증 미들웨어의 오류 메시지를 개선할 수 있음 (별도 작업) → 이것들에 대한 작업을 만들까요? ``` ### 규칙 1: 한 번에 하나만 각 증분은 논리적인 것 하나를 바꿉니다. 관심사를 섞지 마세요. **나쁨:** 새 컴포넌트를 추가하고, 기존 컴포넌트를 리팩터하고, 빌드 설정을 업데이트하는 커밋 하나. **좋음:** 변경마다 하나씩, 커밋을 세 개로 나누기. ### 규칙 2: 컴파일 가능한 상태 유지 각 증분 뒤에 프로젝트는 빌드되어야 하고 기존 테스트는 통과해야 합니다. 조각 사이에 코드베이스를 깨진 상태로 두지 마세요. ### 규칙 3: 미완성 기능에는 피처 플래그 기능이 사용자에게 준비되지 않았는데 증분을 머지해야 하면: ```typescript // Feature flag for work-in-progress const ENABLE_TASK_SHARING = process.env.FEATURE_TASK_SHARING === 'true'; if (ENABLE_TASK_SHARING) { // New sharing UI } ``` 이렇게 하면 미완성 작업을 드러내지 않고 작은 증분을 메인 브랜치에 머지할 수 있습니다. ### 규칙 4: 안전한 기본값 새 코드는 안전하고 보수적인 동작을 기본으로 해야 합니다. ```typescript // Safe: disabled by default, opt-in export function createTask(data: TaskInput, options?: { notify?: boolean }) { const shouldNotify = options?.notify ?? false; // ... } ``` ### 규칙 5: 롤백하기 쉽게 각 증분은 독립적으로 되돌릴 수 있어야 합니다. - 추가 변경(새 파일, 새 함수)은 되돌리기 쉽습니다 - 기존 코드의 수정은 최소이고 집중되어야 합니다 - 데이터베이스 마이그레이션에는 대응하는 롤백 마이그레이션이 있어야 합니다 - 한 커밋에서 무언가를 지우고 같은 커밋에서 대체하지 마세요. 분리하세요 ## 에이전트와 함께 일하기 에이전트에게 점진적으로 구현하도록 지시할 때: ``` "계획의 작업 3을 구현합시다. 데이터베이스 스키마 변경과 API 엔드포인트만 먼저 하세요. UI는 아직 건드리지 마세요. 다음 증분에서 하겠습니다. 구현한 뒤, 저장소의 테스트와 빌드 명령을 실행해 아무것도 깨지지 않았는지 확인하세요." ``` 각 증분에서 범위 안인 것과 범위 밖인 것을 명시하세요. ## 증분 체크리스트 각 증분 뒤에 저장소 자체의 명령으로 검증하세요 (`test-driven-development` 스킬의 Discover the Stack First 절을 보세요). - [ ] 변경이 한 가지를 하고, 그것을 완전히 한다 - [ ] 기존 테스트가 모두 아직 통과한다 (저장소의 테스트 명령: `npm test`, `./gradlew test`, `pytest`, ...) - [ ] 빌드가 성공한다 (저장소의 빌드 명령) - [ ] 스택에 타입 검사가 있으면 통과한다 (`npx tsc --noEmit`, `mypy`, ...) - [ ] 린트가 통과한다 (저장소의 린트 명령) - [ ] 새 기능이 기대한 대로 동작한다 - [ ] 변경이 설명적인 메시지와 함께 커밋되었다 **참고:** 영향을 줄 수 있는 변경 뒤에 각 검증 명령을 실행하세요. 성공한 뒤에는, 그 이후 코드가 바뀌지 않았다면 같은 명령을 반복하지 마세요. 바뀌지 않은 코드에 다시 실행해도 정보가 늘지 않습니다. ## 흔한 합리화 | 합리화 | 현실 | |---|---| | "전부 마지막에 테스트하겠다" | 버그는 쌓입니다. 조각 1의 버그가 조각 2-5를 틀리게 만듭니다. 조각마다 테스트하세요. | | "한 번에 하는 편이 더 빠르다" | 무언가 깨져서 바뀐 500줄 중 어디가 원인인지 못 찾을 때까지는 더 빠른 *것처럼 느껴집니다*. | | "이 변경은 너무 작아서 커밋을 나눌 필요가 없다" | 작은 커밋은 공짜입니다. 큰 커밋은 버그를 숨기고 롤백을 고통스럽게 만듭니다. | | "피처 플래그는 나중에 넣겠다" | 기능이 완성되지 않았다면 사용자에게 보이면 안 됩니다. 지금 플래그를 넣으세요. | | "이 리팩터는 포함해도 될 만큼 작다" | 기능과 섞인 리팩터는 둘 다 리뷰하고 디버깅하기 어렵게 만듭니다. 분리하세요. | | "확인차 빌드 명령을 한 번 더 실행하자" | 성공한 뒤에 같은 명령을 반복해도, 그 이후 코드가 바뀌지 않았다면 아무 것도 늘지 않습니다. 이후 편집 뒤에 다시 실행하세요. 안심용으로 실행하지 마세요. | ## 레드 플래그 - 테스트를 실행하지 않고 코드를 100줄 넘게 작성함 - 한 증분에 관련 없는 변경이 여러 개 있음 - "이것도 빨리 추가하자"는 범위 확장 - 더 빨리 가려고 테스트/검증 단계를 건너뜀 - 증분 사이에 빌드나 테스트가 깨져 있음 - 커밋되지 않은 큰 변경이 쌓임 - 세 번째 사용 사례가 요구하기 전에 추상화를 만듦 - "여기 있는 김에" 작업 범위 밖의 파일을 건드림 - 한 번만 쓰는 작업을 위해 새 유틸리티 파일을 만듦 - 사이에 코드 변경 없이 같은 빌드/테스트 명령을 연속으로 두 번 실행함 ## 검증 한 작업의 모든 증분을 끝낸 뒤: - [ ] 각 증분을 개별적으로 테스트하고 커밋했다 - [ ] 전체 테스트 스위트가 통과한다 - [ ] 빌드가 깨끗하다 - [ ] 기능이 스펙대로 끝까지 동작한다 - [ ] 커밋되지 않은 변경이 남아 있지 않다 ## 참고 증분마다의 검증은 국소 확인입니다. 작업을 완료로 선언하기 전에, 작업과 관계없이 모든 증분이 넘어야 하는 상시 기준인 프로젝트 전체의 완료 정의를 최종 관문으로 적용하세요. `../../references/definition-of-done.md`를 보세요.