--- id: wiki-2026-0508-sprout-wrap-techniques-스프라우트-랩-기 title: "Sprout & Wrap Techniques (스프라우트 & 랩 기법)" category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Sprout and Wrap, Wrap Method, Wrap Class, 스프라우트 랩] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [refactoring, legacy-code, michael-feathers] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: java framework: junit --- # Sprout & Wrap Techniques (스프라우트 & 랩 기법) ## 매 한 줄 > **"매 legacy code 의 직접 수정 X — 매 sprout 의 옆에 새 method/class 의 추가, 매 wrap 의 기존 caller 의 둘러쌈"**. Michael Feathers, *Working Effectively with Legacy Code* (2004) 의 4 paired 기법: Sprout Method, Sprout Class, Wrap Method, Wrap Class. ## 매 핵심 ### 매 4 techniques - **Sprout Method**: 매 새 method 의 옆에 grow, 매 caller 의 1줄 add. - **Sprout Class**: 매 새 class 의 grow, 매 caller 의 instantiate + delegate. - **Wrap Method**: 매 기존 method rename → 매 same name 의 new method 의 둘러쌈 (before/after logic). - **Wrap Class**: 매 decorator/proxy 의 wrap (cross-cutting: logging, auth). ### 매 Sprout vs Wrap - **Sprout**: 매 add new logic to existing operation. - **Wrap**: 매 add cross-cutting (before/after) to existing operation 의 callers. ### 매 응용 1. Add validation before legacy save. 2. Add audit logging across all calls. 3. Add caching layer transparently. 4. Add feature flag toggle. ## 💻 패턴 ### Sprout Method ```java public void process(Order o) { // legacy untouched repo.save(o); notifyCustomer(o); // sprouted } void notifyCustomer(Order o) { // tested email.send(o.customer(), template(o)); } ``` ### Sprout Class — when sprout method 의 not enough ```java // Original legacy class — touch minimally public class OrderProcessor { public void process(Order o) { repo.save(o); new OrderNotifier(email, sms).notifyAll(o); // sprouted class } } // New, fully tested public class OrderNotifier { private final EmailService email; private final SmsService sms; public OrderNotifier(EmailService e, SmsService s) { this.email = e; this.sms = s; } public void notifyAll(Order o) { email.send(o.customer().email(), "thanks"); if (o.customer().smsOptIn()) sms.send(o.customer().phone(), "order placed"); } } ``` ### Wrap Method — rename + intercept ```java public class Pay { // Step 1: rename original to *Internal private void payInternal(Order o) { gateway.charge(o.amount()); } // Step 2: new method with same original name public void pay(Order o) { audit.log("pay.start", o.id()); // before payInternal(o); // delegate audit.log("pay.end", o.id()); // after } } ``` ### Wrap Class — Decorator ```java public interface PaymentGateway { void charge(long amount); } public class StripeGateway implements PaymentGateway { /* legacy untouched */ } public class LoggingGateway implements PaymentGateway { private final PaymentGateway inner; private final Logger log; public LoggingGateway(PaymentGateway inner, Logger log) { this.inner = inner; this.log = log; } @Override public void charge(long amount) { log.info("charge.start amount={}", amount); try { inner.charge(amount); } finally { log.info("charge.end"); } } } // Wire-up: clients see PaymentGateway, get wrapped version PaymentGateway gw = new LoggingGateway(new StripeGateway(), log); ``` ### Wrap Class for caching ```java public class CachingRepo implements OrderRepository { private final OrderRepository inner; private final Map cache = new ConcurrentHashMap<>(); public CachingRepo(OrderRepository inner) { this.inner = inner; } public Order findById(long id) { return cache.computeIfAbsent(id, inner::findById); } public Order save(Order o) { var saved = inner.save(o); cache.put(saved.id(), saved); return saved; } } ``` ### Python — wrap with closure ```python def wrap_with_audit(fn, audit): def wrapped(*a, **kw): audit.log(f"{fn.__name__}.start") try: return fn(*a, **kw) finally: audit.log(f"{fn.__name__}.end") return wrapped pay = wrap_with_audit(legacy_pay, audit) ``` ### Sprout 의 test (TDD first) ```java @Test void notifyCustomer_priorityOrder_sendsPriorityTemplate() { var o = new Order(customer, true); new OrderProcessor(repo, emailMock, smsMock).notifyCustomer(o); verify(emailMock).send(customer.email(), "priority-thanks"); } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Add small new logic to legacy method | **Sprout Method** | | New logic 의 large/independent | **Sprout Class** | | Add before/after to specific method | **Wrap Method** | | Cross-cutting (logging, cache, auth) across many callers | **Wrap Class** (decorator) | | Legacy call sites 의 모두 update 의 가능 | Direct refactor 의 OK | **기본값**: 매 legacy add → Sprout. 매 cross-cutting → Wrap Class. ## 🔗 Graph - 부모: [[Refactoring_Best_Practices|Refactoring]] - 변형: [[Sprout Method (스프라우트 메서드)]] - Adjacent: [[Aspect-Oriented Programming (AOP)|Aspect-Oriented Programming]] ## 🤖 LLM 활용 **언제**: 매 untested legacy code 의 modification 필요; 매 cross-cutting concern 의 add. **언제 X**: 매 codebase 의 well-tested — 매 직접 refactor 의 cleaner. ## ❌ 안티패턴 - **Wrap without rename**: 매 infinite recursion. - **Sprout class 의 1-method-only forever**: 매 결국 dead weight — 매 collapse 의 검토. - **Wrap class 의 not used by all callers**: 매 inconsistent behavior — DI 의 enforce. - **No tests on sprouted/wrapped code**: 매 entire technique 의 point 의 lost. ## 🧪 검증 / 중복 - Verified (Feathers, *WELC* ch.6–7, 2004; Fowler refactoring catalog). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — full Sprout & Wrap quartet with Java/Python patterns |