Effective Java Item 5: 의존성 주입으로 유연하고 테스트 가능한 코드 만들기
서론
자바 개발자라면 한 번쯤은 들어봤을 Effective Java는 조슈아 블로흐(Joshua Bloch)가 제안하는 자바 프로그래밍의 모범 사례들을 담고 있습니다. 그 중 아이템 5는 “자원을 직접 연결하는 대신 의존성 주입을 선호하라”는 강력한 메시지를 전달합니다. 이 아이템은 많은 클래스가 하나 이상의 기본 자원에 의존하며, 이러한 자원을 클래스 내부에 직접 생성하거나 “하드와이어링”하는 방식이 코드의 유연성, 테스트 용이성, 재사용성을 저해할 수 있음을 강조합니다.
이 글을 통해 Effective Java Item 5의 핵심 개념인 의존성 주입이 무엇인지, 왜 중요한지, 그리고 어떻게 적용할 수 있는지 자세히 알아보겠습니다.
본론
1. 하드와이어링의 문제점
클래스가 자신이 사용할 자원을 직접 생성(예: new
키워드 사용)하거나, 정적 유틸리티 클래스 또는 싱글턴에 의존하는 것을 하드와이어링(Hardwiring)이라고 합니다. 이러한 방식은 다음과 같은 문제점을 야기합니다.
- 유연성 부족: 자원의 구현을 변경하려면 해당 자원을 사용하는 모든 클래스를 수정해야 합니다. 이는 코드 변경에 대한 비용을 증가시키고, 새로운 요구사항에 대한 적응력을 떨어뜨립니다.
- 테스트의 어려움: 단위 테스트 시 실제 자원(예: 데이터베이스 연결, 네트워크 서비스)에 의존하게 되어 테스트 환경을 구축하기 어렵고, 테스트 실행 시간이 길어지며, 테스트 결과의 일관성을 보장하기 어렵습니다.
- 재사용성 저하: 특정 자원에 강하게 결합된 클래스는 다른 환경이나 다른 자원 구현이 필요한 곳에서 재사용하기 어렵습니다.
2. 의존성 주입(Dependency Injection)이란?
의존성 주입은 클래스가 자신이 의존하는 자원을 직접 생성하는 대신, 외부에서 주입받는 설계 패턴입니다. 즉, 의존 객체를 사용하는 객체 내부에서 생성하는 것이 아니라, 외부에서 생성하여 넣어주는 방식입니다.
의존성 주입은 주로 다음 세 가지 방법으로 이루어집니다.
- 생성자 주입 (Constructor Injection): 가장 선호되는 방식입니다. 클래스의 생성자를 통해 필요한 의존성을 전달받습니다.
public class SpellChecker { private final Dictionary dictionary; public SpellChecker(Dictionary dictionary) { // 의존성 주입 this.dictionary = dictionary; } public boolean isValid(String word) { return dictionary.contains(word); } }
[이미지: 생성자 주입 예시 코드 다이어그램, Alt-text: SpellChecker 클래스가 생성자를 통해 Dictionary 인터페이스를 주입받는 UML 다이어그램]
- 세터 주입 (Setter Injection): 세터 메서드를 통해 의존성을 주입받습니다. 선택적인 의존성에 유용할 수 있습니다.
- 인터페이스 주입 (Interface Injection): 특정 인터페이스를 구현함으로써 의존성을 주입받습니다. (자바에서는 잘 사용되지 않음)
3. 의존성 주입의 장점
의존성 주입을 사용하면 하드와이어링의 문제점을 해결하고 다음과 같은 이점을 얻을 수 있습니다.
- 유연성 증대:
- 클래스는 특정 구현체가 아닌 인터페이스에 의존하게 됩니다.
- 런타임에 다른 구현체를 쉽게 교체할 수 있어, 코드 변경 없이 다양한 환경에 적응할 수 있습니다.
- [내부 링크: 자바 인터페이스의 중요성]
- 테스트 용이성:
- 단위 테스트 시 실제 자원 대신 Mock 객체나 Stub 객체를 주입하여 테스트를 격리하고 빠르게 실행할 수 있습니다.
- 이는 테스트 코드의 복잡성을 줄이고, 테스트의 신뢰성을 높입니다.
- [외부 링크: Mockito 공식 문서]
- 재사용성 향상:
- 클래스가 특정 자원에 대한 강한 결합 없이 더 일반적인 형태로 설계되므로, 다양한 프로젝트나 모듈에서 재사용하기 용이합니다.
- 결합도 감소 및 응집도 증가:
- 클래스 간의 의존성이 느슨해져 결합도가 낮아지고, 각 클래스의 책임이 명확해져 응집도가 높아집니다. 이는 유지보수성을 향상시킵니다.
4. 의존성 주입 프레임워크
스프링(Spring)이나 구글의 구스(Guice)와 같은 의존성 주입 프레임워크는 대규모 시스템에서 의존성 관리를 자동화하고 단순화하는 데 도움을 줍니다. 하지만 Effective Java Item 5의 핵심 원칙은 프레임워크 없이도 생성자 주입과 같은 간단한 방법으로 적용할 수 있습니다. 중요한 것은 의존성을 외부에서 주입받는다는 개념 자체입니다.
결론
Effective Java Item 5는 견고하고 유연하며 테스트하기 쉬운 자바 애플리케이션을 구축하기 위한 필수적인 지침입니다. 자원을 직접 연결하는 하드와이어링 방식의 단점을 이해하고, 대신 의존성 주입을 적극적으로 활용함으로써 우리는 더 나은 소프트웨어 설계를 할 수 있습니다.
지금 바로 여러분의 코드에서 자원을 직접 생성하는 부분을 찾아보고, 의존성 주입을 통해 더 유연하고 테스트 가능한 코드로 개선해보세요!