구조적 사고를 위한 실용 가이드
DI는 구조 문제를 가리는 화장술이다
의존성 주입(DI)은 강력한 도구지만, 현실에서는 구조적 문제를 해결하는 대신 보이지 않게 만드는 화장술로 남용되는 경우가 많다. 복잡하게 얽힌 의존성을 컨테이너 안으로 밀어넣어서 "깔끔해 보이게" 만들지만, 근본적인 설계 문제는 그대로 남아있게 된다.
진정한 해결책은 DI 컨테이너에 손을 뻗기 전에 구조적 개선 가능성부터 탐색하는 것이다.
5가지 필수 질문
DI를 사용하기 전에 반드시 다음 질문들에 답해보라:
1. "이 의존성이 왜 이렇게 복잡한가?"
PlayerController가 InputManager, AnimationController, SoundManager, UIManager에 모두 의존한다면, DI로 주입하기 전에 먼저 물어봐야 한다:
- Player가 정말 이 모든 것을 직접 알아야 하나?
- 이벤트 시스템으로 더 자연스럽게 분리할 수 있지 않나?
- 컴포넌트 기반 설계로 책임을 나눌 수 있지 않나?
대부분의 경우 복잡한 의존성은 잘못된 책임 분리의 신호다.
2. "이것이 진짜 외부에서 결정되어야 할 요소인가?"
진정한 외부 컨텍스트:
- 환경별 설정 (개발/테스트/프로덕션)
- 플랫폼별 구현 (모바일/PC 입력 시스템)
- 런타임 교체 (AI 전략, 렌더링 파이프라인)
가짜 외부 컨텍스트:
- 단순히 new 키워드를 피하기 위한 목적
- 클래스가 "깔끔해 보이게" 하려는 의도
- 응집도 높은 로직을 억지로 분리하는 경우
80%의 DI 사용은 가짜 외부 컨텍스트에 해당한다.
3. "설계에 얼마나 많은 시간을 투자하고 있는가?"
느슨한 결합을 위해 작업 속도가 현저히 떨어진다면, 그것은 설계 부채를 쌓고 있는 것이다.
간단한 UI 컨트롤러 하나 만드는데:
- 인터페이스 설계: 30분
- DI 컨테이너 설정: 20분
- 실제 기능 구현: 10분
이런 식이라면 빠르게 구현하고 나중에 리팩토링하는 것이 더 경제적이다.
4. "이 추상화가 실제 변경 요구사항에 대응하는가?"
미래를 위한 유연성은 구체적 변경 시나리오가 있을 때만 의미있다.
막연한 "나중에 바뀔 수도 있어서"보다는:
- 언제, 왜, 어떻게 바뀔 것인가?
- 변경 빈도는 어느 정도인가?
- 변경 비용과 유연성 확보 비용 중 어느 것이 큰가?
대부분의 "미래 대비" 설계는 실제로는 일어나지 않는 시나리오를 위한 것이다.
5. "팀원들이 이 코드를 디버깅할 수 있는가?"
DI로 인한 간접성은 다음과 같은 인지적 부하를 만든다:
- 런타임에 뭐가 주입되는지 모호함
- 콜스택 추적의 복잡화
- "이 코드가 실제로 뭘 하는지" 파악 어려움
코드를 작성하는 시간보다 읽고 디버깅하는 시간이 더 많다는 것을 잊지 마라.
구체적 판단 기준
DI를 써야 하는 명확한 신호들:
- 테스트를 위해 Mock 객체가 반드시 필요
- 플랫폼별로 다른 구현체 필요 (iOS/Android)
- 런타임에 전략을 교체해야 함 (AI 난이도)
- 설정 파일로 동작을 제어해야 함
DI를 피해야 하는 경고 신호들:
- "깔끔한 코드"를 위해서라는 이유
- 인터페이스 설계에 과도한 시간 소모
- 간단한 기능인데 복잡한 의존성 그래프
- 팀원들이 코드 흐름을 이해하기 어려워함
대안적 접근법
DI 대신 고려할 수 있는 패턴들:
1. 이벤트 시스템
복잡한 의존성 대신 느슨하게 결합된 이벤트 통신
2. 컴포넌트 기반 설계
Unity의 GameObject-Component 패턴처럼 조합 기반 구조
3. 서비스 로케이터
DI보다 단순하지만 여전히 유연성 제공
4. 팩토리 패턴
객체 생성 로직만 분리하는 가벼운 접근
빠른 구현 → 리팩토링 전략
완벽한 초기 설계보다 효과적인 경우가 많은 접근법:
1단계: 빠른 프로토타이핑
- 직접 호출, 구체 클래스 사용
- 동작하는 버전 우선 구현
- 실제 사용 패턴 파악
2단계: 문제점 식별
- 자주 변경되는 부분 찾기
- 테스트하기 어려운 부분 발견
- 재사용이 필요한 로직 구분
3단계: 선택적 리팩토링
- 진짜 문제가 있는 부분만 DI 적용
- 나머지는 그대로 유지
- 점진적 개선
결론: 건전한 회의주의를 가져라
DI는 만능 해결책이 아니다. "DI를 써야 좋은 코드"라는 교조적 사고에서 벗어나라.
중요한 것은 기술적 완결성이 아니라 문제를 효과적으로 해결하는 것이다. 때로는 "더러운" 직접 호출이 "깔끔한" DI보다 더 정직하고 효율적일 수 있다.
DI 컨테이너에 손을 뻗기 전에 한 번 더 생각해보라. 정말로 외부에서 주입되어야 할 컨텍스트인가? 아니면 단지 코드가 깔끔해 보이기를 원하는 것인가?
진정한 엔지니어링은 복잡성을 숨기는 것이 아니라 적절히 관리하는 것이다.
'★철학으로 보는 코딩' 카테고리의 다른 글
| AI 코딩 시대, 개발자와 개발사가 가야할 길 (0) | 2025.10.09 |
|---|---|
| 상태와 사이드이펙트는 사실 같다? (0) | 2025.06.09 |
| 탑다운과 바텀업: 프로그래밍에서의 계층별 접근 전략 (0) | 2025.06.09 |
| 마틴 파울러와 도메인 주도 개발(DDD)의 함정 (0) | 2025.06.08 |
| 프로그래밍 패러다임의 재고찰 (1) | 2025.06.08 |