Android개발자가 본 DI(Dependency Injection) 란??

이종현(JongHyunLee)
8 min readDec 16, 2020

--

해당 문서는 안드로이드 공식 문서를 기반으로 만들어졌습니다.
공식문서:
https://developer.android.com/training/dependency-injection?hl=ko

주변에 많은 사람들이 DI에 대해 의문을 갖는 편입니다.
주된 질문은 DI가 무엇인가보다는 DI를 써야하는가에 대해서 의문을 많이 가지는 것 같습니다.

DI란 무었이며, 왜 써야하는가를 실무자의 입장에서 풀어나갈 예정이고, 간단히 수동 의존성 주입에 대해 설명하고, 이후에 자동 의존성 주입 라이브러리에 대해서 설명할 예정입니다.

해당 포스팅은 레퍼런스 문서들을 기반으로한 필자의 주관적인 해석과 견해가 포함되어 있기 때문에 참고하고 봐주셨으면 좋겠습니다.

바쁘신 분들을 위해 결론은 하단에 기술해놓겠습니다.

DI(Dependency Injection)란?

사실 이 개념을 모르는 개발자는 많이 없을걸로 예상되지만 한번 집고 넘어가도록 하겠습니다.

우리는 단일클래스로 개발을 할 수 없기때문에 클래스간의 참조는 필수적으로 일어나며 이때 의존성이 발생합니다. 예를들어 Car라는 클래스(이하 Car)는 Engine이라는 클래스(이하 Engine)를 참조합니다.(엔진이 없는 차는 없기때문에) 이때 Engine을 얻는 방법은 3가지정도로 구분됩니다. 다른말로 Engine과 Car간의 Dependency를 생성하는 방법은 3가지정도로 구분된다라고 할 수 있습니다.

  1. Car 내부에서 Engine을 생성합니다.
  2. 다른 곳에 저장되어 있는 객체를 가져옵니다.
  3. 외부에서 생성된 객체를 매개변수로 제공받습니다.(DI)

그림과 코드로 살펴보도록 하겠습니다.

1.Car 내부에서 Engine을 생성하는 경우

어떤 단점이 있을까요?? Car는 Engine을 항상 가지고 있습니다. 즉 Car는 Engine에 강하게 종속되어 있으며 Car만 따로 분리하여 테스트가 어렵게 만듭니다.(Engine을 다른 가상의 값으로 넣어가며 테스트가 불가능합니다.) 우리는 Car와 Engine간의 결합도를 낮춰 테스트 더블이 가능하도록 만들고 싶습니다.

테스트 더블에 대해서는 하단에 작성하도록 하겠습니다.

2.Car 내부에서 getEngine통해 Engine를 가져오는 경우

우리는 context객체에서 getSystemService(서비스명)을 호출하여 특정 서비스 객체를 가져오는 코드에 익숙합니다. 이것의 단점은 뭘까요?? 일반적인 네이밍 규칙에 따르면 get(ClassName)은 SingleTone Pattern을 뜻합니다. 즉 getSystemService로 생성된 SystemService클래스는 사용하지 않더라도 계속 메모리에 남게되며 메모리 누수가 발생할 가능성이 있습니다. 편하고 빠를수도 있다는 장점이 있지만 “1.Car 내부에서 Engine을 생성하는 경우”와 같은 단점도 가지고 있습니다.

다들 아시겠지만 참고로 onClick, onCreate 등등 앞에 on이 붙으면 콜백함수를 뜻합니다.

3.Engine를 외부에서 선언한 후 Car에 넘겨주는 경우

Car는 Engine을 외부에서 받아옴으로써 Engine에서 자유롭습니다. 즉 Car의 Engine값에 다양한 값을 전달할 수 있으며(Electro Engine, Bio Engine등) 내가 원하는 Mock Engine들이 Car에 전달하여 테스트가 가능합니다. 즉 테스트 더블이 가능해졌다라고 할 수 있습니다.

테스트 더블(Test double)이란?

테스트 더블은 한국말로 ‘대역 가능 테스트’ 정도로 해석 할 수 있습니다. 좀 더 쉽게 ‘아무거나 대신 다른걸로 넣어볼게 테스트’ 정도로 해석 할 수 있습니다.
Car내부에서 Engine을 생성한 경우 우리는 항상 Engine을 이용해서만 테스트를 해야합니다.(다른 엔진을 받아올 수 없습니다.) 이로인해 Car를 테스트 할 수 있는 Engine의 범위는 Engine단 하나로 제한됩니다.
하지만 우리가 테스트할때는 실제 Engine이 아닌 가상의 값을 넣어 Car를 테스트 하고싶습니다. 이때 Engine을 매개변수로 받는다면 여러 다른 하위 Engine을 테스트 할 수 있으며 Engine을 대신할 대역(대역 배우같은)을 넣어 테스트가 가능해집니다.
Car에 전달되는 대역 인자값을 Stub/Mock/Fake 등으로 표현되며, 이러한 방식을 테스트 더블이라고 합니다.

즉, 가상의 다양한 Engine값을 넣어 Car를 테스트 할 수 있다면 테스트 더블이 가능하다라고 할 수 있겠습니다.

참고: 스턴트 대역배우를 영어로 stunt double이라고 합니다.

여태까지 우리는 수동으로 의존성 주입을 하는 방법에 대해서 알아봤습니다. 그런데 아직 우리는 뭔가 좀 부족합니다. Car같은경우 여러가지 객체를 매개변수로 받을 수 있습니다. 엔진, 타이어, 보닛, 핸들등등 너무나 많은 외부 객체가 있습니다. 이런경우 수동으로 저런 클래스들을 생성해서 넣어준다면 개발자는 아래와 같은 코드를 생산합니다. 외부 생성 객체가 100개라면 어떻게 될까요???

var engine = Engine()같은 상용구 코드( boilerplate code와 같은말입니다)가 많아지며, 많아진 상용구 코드는 개발자를 피로하게 하며(계속 쳐야되니까) 수정 관리가 용이하지 않게 만듭니다.(찾아가며 바꿔줘야되니까)

반복되고 강조되는 코드는 개발자를 불안하게 해요.(feat. 개통령 아저씨)

자동으로 DI하는 방법은 없을까?

DI라이브러리는 수동으로 DI하는 것을 자동으로 DI를 가능하게 하며, 이를 제어역전이라고 합니다.(사실 제어역전중의 일부가 DI입니다.) 자동으로 DI를 하는 라이브러리는 Dagger, Dagger2, Koin, Hilt등이 있으며 상기 개념에 대해 이해도가 낮은상태에서 접근시 굉장히 어려움을 겪습니다. 저 또한 그랬습니다. 이후 포스팅에서 자동 DI 라이브러리에 대해서 천천히 알아보도록 하겠습니다.

제어역전(IoC)이란?

항상 개발에서는 용어가 발목을 잡습니다. Inversion of control이란 뭘까요??
역전이라고 하는걸 이해하기전에 일반적인 제어가 무엇인지 알 필요가 있습니다.
일반적인 제어란, 개발자가 원할때 원하는 코드를 명시적으로 생성, 호출, 생명주기 통제등을 하는 행위를 이야기 합니다. 원할때 원하는 코드를 사용하려면 다량의 제어문(if, when, try-catch)이 들어갈 수 있으며 이또한 오류 가능성을 높이고, 개발자의 피로도를 증가시킵니다.
제어 역전이란 개발자가 클래스의 생성, 호출, 생명주기 통제등을 제어하는게 아니라 위임받은 라이브러리(ex:Dagger, Koin, Spring 등)가 코드의 생성, 호출, 생명주기 통제등의 행위를 의미합니다.
즉, 개발자가 할일을 위임하면 라이브러리가 대신 해준다. 정도로 이해하시면 될것같습니다.

안드로이드 개발자는 나도모르게 IoC에 익숙해져 있습니다. startActivity(Intent(Activity))를 호출하면 언제 어디서 Activity의 onCreate가 호출되는지 모르지만 우리는 사용할 수 있습니다.

결론

장점

  1. 수동 DI는 테스트의 품질을 높여준다.
  2. 수동 DI는 코드 리팩토링이 수월하게 한다.
  3. 수동 DI는 Unit Test가 가능하게 한다.
  4. 수동 DI는 객체간의 의존성을 줄이거나 없애줄 수 있다.
  5. 자동 DI는 수동 DI에서 발생하는 단점을 없애줄 수 있다.(Boilerplate code, 흐름제어 등)

단점

  1. 간단한 수동 DI는 상용구 코드를 제거하지 못한다.
  2. 자동 DI라이브러리의 가장 큰 단점은 러닝커브가 높다.(나만 이렇게 생각하는거 아니죠..?)
  3. 자동 DI는 코드 추적이 불가능할정도로 어렵다.
  4. 공동개발이라면 팀원들 모두 어느정도 수준의 이해도가 있어야 한다.

개인 견해

  1. 단위 테스트 위주의 프로그래밍을 할 경우에 필수적으로 DI를 사용해야 합니다.
  2. 개발하는 프로그램 규모가 작을경우 DI는 오히려 안좋은 선택이 될 수 있으나, 개발하는 프로그램 규모가 일정규모 이상이라면 사용하는게 매우 좋습니다.
  3. DI 라이브러리를 사용할 경우 팀원들도 반드시 학습이 되어있어야 합니다.

마무리

구태여 수동 DI와 자동 DI 라이브러리를 구분한 이유는 일반적으로 검색에서 나오는 DI는 자동 DI라이브러리 관련된 장단점이 많으며 DI라는 큰 개념과는 약간 상이한 부분이 있어 구분을 지어 나눠봤습니다.

다음 포스팅에서는 자동 DI 라이브러리를 하나씩 알아보도록 하겠습니다.

Koin

이상 안드로이드 개발자 이종현이었습니다.
재밌게 읽으셨다면 👏🏻눌러 주시는 것도 잊지말아주세요~ 저에게 큰 힘이됩니다. :)

읽어주셔서 감사합니다.

--

--