d8a80f6272
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해 끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은 과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업. 도구: Datacollect/scripts/link_reconcile_apply.mjs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.4 KiB
8.4 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-breaking-dependencies | Breaking Dependencies | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
Breaking Dependencies
매 한 줄
"매 untestable code 를 매 test 가능하게 만드는 첫 단계 = 매 hard dependency 를 매 break.". Michael Feathers 의 Working Effectively with Legacy Code (2004) 의 24 가지 dependency-breaking technique 가 매 canonical reference. 2026 현재 modern DI / 매 hexagonal architecture 가 매 prevention layer, but 매 legacy 에는 여전히 매 Sprout Method / Extract Interface / Adapter 가 핵심.
매 핵심
매 dependency types
- Construction dependency: 매
new SomeService()hardcoded. - Static dependency: 매
Date.now(),Math.random(),Singleton.getInstance(). - Hidden dependency: 매 method 안에서 file / DB / network 직접 호출.
- Temporal coupling: 매 method A → B 순서 강요.
- Inheritance dependency: 매 base class 가 매 hard-to-test 행위 가짐.
매 Feathers 의 핵심 24 technique 중 top 8
- Sprout Method: 매 new behavior 를 매 새 method 로 분리해 매 test 가능하게.
- Sprout Class: 매 new behavior 를 매 new class 로.
- Extract Interface: 매 concrete dep → 매 interface → mock.
- Extract and Override Call: 매 hard call 을 매 protected method 로 → subclass 에서 override.
- Subclass and Override Method: 매 testing subclass.
- Adapt Parameter: 매 untestable arg → 매 wrapper interface.
- Break Out Method Object: 매 거대 method → 매 클래스.
- Introduce Instance Delegator: 매 static → 매 instance method 로.
매 응용
- Legacy Java/C# enterprise codebase 의 unit test 도입.
- Module 간 cyclic dependency 끊기.
- 매 third-party SDK 의 hard wiring 제거.
- Microservice extraction 전 prep.
- 매 monolith 의 plugin 화.
💻 패턴
Extract Interface (Java)
// BEFORE — hard wired
public class OrderService {
public Receipt placeOrder(Order o) {
EmailSender sender = new EmailSender(); // hard dep
sender.send(o.customerEmail, "...");
return Receipt.from(o);
}
}
// AFTER — Extract Interface + DI
public interface Notifier {
void send(String to, String body);
}
public class EmailSender implements Notifier { /* impl */ }
public class OrderService {
private final Notifier notifier;
public OrderService(Notifier notifier) { this.notifier = notifier; }
public Receipt placeOrder(Order o) {
notifier.send(o.customerEmail, "...");
return Receipt.from(o);
}
}
// Test
@Test void placesOrderAndNotifies() {
var fake = mock(Notifier.class);
var svc = new OrderService(fake);
svc.placeOrder(testOrder());
verify(fake).send(eq("a@b.com"), anyString());
}
Sprout Method (TypeScript)
// BEFORE — 매 huge method, 매 add new logic 못 testable
class Invoice {
process(): void {
// 200 lines 의 legacy
// 매 new logic 추가하면 매 테스트 X
}
}
// AFTER — sprout 로 새 행동만 분리
class Invoice {
process(): void {
// 200 lines 의 legacy (그대로)
this.applyLoyaltyDiscount(); // 매 sprouted
}
applyLoyaltyDiscount(): number { // 매 testable in isolation
if (this.customer.tier === 'gold') return this.total * 0.1;
if (this.customer.tier === 'silver') return this.total * 0.05;
return 0;
}
}
Extract and Override Call (Python)
# BEFORE
class Job:
def run(self):
now = datetime.utcnow() # 매 hard
if now.weekday() >= 5: return
self.do_work()
# AFTER
class Job:
def run(self):
if self._now().weekday() >= 5: return
self.do_work()
def _now(self) -> datetime: # 매 protected — override in test
return datetime.utcnow()
class TestJob(Job):
def __init__(self, fixed_dt): self.fixed_dt = fixed_dt
def _now(self): return self.fixed_dt
# Test
def test_skips_weekend():
j = TestJob(datetime(2026, 5, 9)) # 매 Saturday
j.do_work = MagicMock()
j.run()
j.do_work.assert_not_called()
Adapt Parameter (Java)
// BEFORE — HttpServletRequest hard to construct in test
public Result handle(HttpServletRequest req) {
String userId = req.getHeader("X-User-Id");
return repo.find(userId);
}
// AFTER — adapter interface
public interface RequestAdapter {
String header(String name);
}
public Result handle(RequestAdapter req) {
return repo.find(req.header("X-User-Id"));
}
// Production wraps; test = trivial map
class ServletRequestAdapter implements RequestAdapter {
private final HttpServletRequest delegate;
public String header(String n) { return delegate.getHeader(n); }
}
Subclass and Override (Python — break DB)
class ReportGenerator:
def fetch(self) -> list[dict]:
return db.query("SELECT * FROM orders") # 매 hard
def render(self) -> str:
rows = self.fetch()
return "\n".join(f"{r['id']}: {r['total']}" for r in rows)
class TestableReport(ReportGenerator):
def __init__(self, fake_rows): self.fake_rows = fake_rows
def fetch(self): return self.fake_rows
def test_renders():
r = TestableReport([{"id": 1, "total": 100}])
assert r.render() == "1: 100"
Wrap Method (legacy 호환 유지)
// 매 old API 깨면 안 됨 → 매 wrap
public void payInvoice(Invoice i) {
audit.log(i); // 매 new behavior
payInvoiceLegacy(i); // 매 unchanged
}
private void payInvoiceLegacy(Invoice i) { /* 매 untouched */ }
Seam mapping checklist
1. List 매 untestable thing in target method
- Static call? → Introduce Instance Delegator / Replace Function with Function Object
- new? → Extract Interface + inject
- Time/random? → Extract & Override Call
- File/DB/HTTP? → Repository / Adapter
2. Pick 1 dep / day. 매 small step + commit.
3. 매 Test 먼저 (characterization test)
- 매 current behavior 를 lock down
4. Refactor under green tests.
Hexagonal post-state (prevention)
[Domain Core] ← [Port (interface)] ← [Adapter (DB / HTTP / FS)]
매 NO direct import 매 from core to adapter
매 Test = stub adapter against port
매 결정 기준
| 상황 | Technique |
|---|---|
| 매 add new feature 매 large method | Sprout Method |
| 매 hard dep on concrete class | Extract Interface + DI |
| 매 static / time / random | Extract and Override Call |
| 매 framework type 의 arg | Adapt Parameter |
| 매 file / DB / network | Repository / Port-Adapter |
| 매 huge method 의 internal logic | Break Out Method Object |
| 매 base class behavior bad | Subclass and Override |
기본값: characterization test 먼저 → 매 minimal mechanical refactor (compiler-driven) → 매 small commit.
🔗 Graph
- 부모: Refactoring_Best_Practices
- 변형: Sprout-Method
- 응용: Test-Driven Development · Hexagonal Architecture
- Adjacent: Dependency_Injection_(DI) · SOLID Principles · Mocking
🤖 LLM 활용
언제: legacy code unit test 도입, untestable method 마주침, monolith 분해 직전 prep, third-party SDK lock-in 제거. 언제 X: 매 already clean architecture (over-refactor 금지), 매 throwaway prototype.
❌ 안티패턴
- Big-bang rewrite: 매 일부 break → 매 전체 unstable.
- Test 없이 refactor: 매 behavior 변경 모름 → 매 silent regression.
- 너무 많은 mock: 매 test 가 매 implementation 에 결합.
- Interface 의 1 implementation 영구: 매 YAGNI — 매 두 번째 user 생기면 그때 추출.
- God adapter: 매 1 interface 에 30 method.
- Refactor + feature 동시 commit: 매 review 불가.
🧪 검증 / 중복
- Verified (Feathers "Working Effectively with Legacy Code" 2004, Fowler "Refactoring" 2nd ed).
- 신뢰도 A.
🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Breaking Dependencies with Feathers techniques |