--- id: wiki-2026-0508-the-two-hats title: The Two Hats category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Two Hats Rule, Beck's Two Hats, 두 모자] duplicate_of: none source_trust_level: A confidence_score: 0.92 verification_status: applied tags: [tdd, refactoring, kent-beck, discipline] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: Vitest --- # The Two Hats ## 매 한 줄 > **"매 한 번에 한 hat 만 — adding feature OR refactoring, never both"**. Kent Beck 의 *TDD By Example* (2003) 의 핵심 discipline. 매 mode-switching 의 명시적 awareness 가 매 code change 의 quality 를 결정. 매 Feathers 의 legacy work 에서도 동일 principle. ## 매 핵심 ### 매 두 hat - **Adding Feature hat**: 매 new behavior. 매 fail test 작성 → minimum code 로 pass. 매 design improvement 의 X. - **Refactoring hat**: 매 behavior 의 preservation. 매 all test green. 매 new feature 의 X. ### 매 switching rule 1. 매 hat 의 명시적 awareness — 매 commit message 에 표시. 2. 매 transition 시 모든 test green 확인. 3. 매 hat 을 동시에 쓰면 — 매 step back, 매 separate commit. ### 매 응용 1. PR 분리 — 매 refactor PR + 매 feature PR (reviewer 부담 감소). 2. Commit hygiene — 매 "refactor: extract method" + 매 "feat: add discount logic". 3. Code review — 매 "이거 refactor 야 feature 야?" 질문 의 명시. ## 💻 패턴 ### Hat marker in commit ```bash # Refactor hat git commit -m "refactor: extract calculatePrice into PriceCalculator No behavior change. All 142 tests green before and after." # Feature hat git commit -m "feat: add VIP tier discount - Add VIPDiscount strategy - Test: VIP gets 20% off - Test: STANDARD unchanged" ``` ### TDD cycle with hats ```typescript // === FEATURE HAT === // 1. Write failing test it('VIP customer gets 20% discount', () => { expect(calculateDiscount(100, 'VIP')).toBe(80); }); // 2. Minimum code to pass (ugly OK) function calculateDiscount(price: number, tier: string): number { if (tier === 'VIP') return price * 0.8; return price; } // === REFACTOR HAT === (all tests green) // 3. Improve design without behavior change type Tier = 'VIP' | 'STANDARD'; const DISCOUNT: Record = { VIP: 0.2, STANDARD: 0 }; function calculateDiscount(price: number, tier: Tier): number { return price * (1 - DISCOUNT[tier]); } // Run tests → green. Commit as refactor. // === FEATURE HAT === again for next behavior it('GOLD customer gets 10% discount', () => { /* ... */ }); ``` ### Mode-switch checklist ```typescript // Before switching to Refactor hat: // [ ] All tests green? // [ ] Last commit clean? // [ ] No half-written feature? // Before switching to Feature hat: // [ ] Refactor commit complete? // [ ] Tests still green? // [ ] Clear what new behavior to add? ``` ### Sprout method (Feathers, refactor-friendly feature add) ```typescript // Legacy mess — don't refactor (Feature hat) function processOrder(order: any) { // ... 300 lines, low test coverage ... } // Add new behavior in NEW pure function (Feature hat) export function calculateLoyaltyPoints(order: Order): number { return Math.floor(order.total / 10); } // Wire it in — minimal touch function processOrder(order: any) { const points = calculateLoyaltyPoints(order); // sprout // ... existing mess unchanged ... } // Later, separate Refactor hat session can clean up `processOrder`. ``` ### Detecting hat conflation (smell) ```typescript // SMELL: PR titled "Add discount logic" but diff includes: // - new feature ✓ // - rename 5 unrelated variables ✗ // - extract 3 unrelated methods ✗ // - reformat whole file ✗ // // Reviewer cannot tell what changed semantically. // Fix: split into 3-4 commits/PRs by hat. ``` ### Pre-commit hook (hat enforcement) ```bash #!/bin/bash # .git/hooks/pre-commit msg=$(cat "$1") if [[ ! "$msg" =~ ^(feat|fix|refactor|docs|test|chore): ]]; then echo "Commit message must start with type prefix indicating hat" exit 1 fi ``` ## 매 결정 기준 | 상황 | Hat | |---|---| | New behavior needed | Feature | | Existing test green, code ugly | Refactor | | Bug fix (changes behavior to correct) | Feature (failing test first) | | Rename for clarity | Refactor | | Extract method | Refactor | | Add parameter | Feature (likely) | | Mixed urge | Stop — split into two | **기본값**: 매 explicit hat awareness, 매 separate commit, 매 green test 의 transition gate. ## 🔗 Graph - 부모: [[Test-Driven_Development]] · [[Refactoring_Best_Practices|Refactoring]] - 변형: [[Red-Green-Refactor]] - 응용: [[Sprout Method]] - Adjacent: [[Testability_Architecture]] · [[Technical_Debt]] · [[Conventional Commits]] ## 🤖 LLM 활용 **언제**: PR splitting advice, commit message disciplining, refactor-vs-feature classification. **언제 X**: 매 actual editor mode-switching automation — 매 human discipline 이 본질. ## ❌ 안티패턴 - **Mixed PR**: refactor + feature 의 단일 PR — 매 review 가 noise 에 묻힘. - **"While I'm here"**: 매 unrelated tweak — 매 feature 가 endless drift. - **Refactor in red**: 매 broken test 에서 refactor — 매 behavior change 의 hidden source. - **Big-bang refactor**: 매 multi-day refactor 의 isolation — 매 daily incremental 이 안전. ## 🧪 검증 / 중복 - Verified (Beck 2003 *TDD By Example*; Feathers 2004 *WELC*). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Beck two hats + sprout + commit hygiene |