본문 바로가기
Dev/고민과 삽질의 기록들🤔

DIP의 본질? (Testable한 객체를 위한 여정 중..)

by Mintta 2023. 11. 27.

의존성 역전...!

 

정말 오랜만에 다시 아티클을 쓰네요. 최근에는 여러 기업 코딩테스트도 있었고, 알고리즘 공부, 리팩토링 당장 해야할 것들로 벅차서 계속 미뤄지고 있었네여 다시 열심히 시간을 내서 써보겠습니다..!

Combine에 대해서 공부하면서 깨달았던 점들도 정리할게 아직 산더미지만 그래도 기록해두고 싶은 주제가 있어서 조금은 쉽게 글이 써지는 주제라 이 글을 먼저 작성합니다. 


Testable한 객체란 뭘까 

 

최근 MVC프로젝트를 MVVM-C으로 리팩토링을 진행하면서 "Testable한 객체"를 중점으로 설계를 개선해나가고 있습니다.

 

제 블로그에서 관련 주제가 나올 때마다 언급했지만 Testable한 객체에 대해서 한번 짚고 넘어가겠습니다.

 

제가 생각했을 때 Unit test의 목적, 본질은 ‘특정 Input이 들어왔을 때 우리의 단일 로직 또는 객체가 의도한 Ouput을 내는가?’를 검증하는 것이라고 생각합니다.

만약 test하려는 객체와 의존성 객체들이 서로 강하게 결합되어 있다면, 문제가 발생했을 때 원인을 파악하기가 까다롭습니다. 이는 객체가 다수의 책임을 가지게 되면서 발생하는 문제입니다. 다수의 책임을 가진 객체는 여러 작업을 수행하므로 응집도 또한 낮습니다.

즉, 객체간의 결합도가 높고 응집도가 낮은, 다수의 책임을 가진 객체는 문제 발생시 원인을 찾기 어려워 Testable하지 않습니다.

 

따라서 Testable한 객체란, 객체간 결합도는 낮고 응집도는 높으며 단일 책임을 갖는 객체를 말한다고 생각합니다.

 

이런 이유로 객체간의 결합도를 낮추는 데에 매우 열정적이였고, MVC의 모든 책임이 집약된 ViewController의 책임을 분리시키는 과정을 지나 다다르게 된 MVVM으로의 리팩토링 과정에서 당연하단듯이 ViewController에 필요한 ViewModel 또한 DIP를 통해 결합도를 낮춰주었습니다. 그리고 객체간의 결합도를 낮추었다는 사실에 뿌듯해하고 있었죠.

불필요하거나 과한 추상화의 가능성..? 🤔

그리고 스터디를 진행하면서 리팩토링 과정을 공유하다가 이 지점에서 다른 인사이트를 듣게 되었습니다.

 

모든 객체 간에 결합도를 낮추는 것은 정답이 아닐 수 있다. 불필요하거나 과한 추상화일 수도 있고, protocol의 본질과 맞지 않을 수 있다.

 

이 인사이트를 듣고 이에 대해 고민하는 시간을 가졌습니다. 지금까지 결합도를 낮추는데에 집중하다보니, 다른 것은 놓치지 않았나를 다시 되짚어보았습니다.

처음부터 돌아가서 생각해보겠습니다.

저는 결합도를 낮추기 위해 ViewModel을 DIP를 활용해 결합도를 낮춰주었습니다. 객체간 결합도를 낮추기 위해서 수단으로써 DIP를 활용한 것 입니다.

 

DIP의 본질이 뭘까 ?

그렇다면 DIP의 본질은 무엇일까요 ?

 

제가 생각 했을 때 DIP의 본질은 "추상화된 것에 의존하게 만들고, 실제 구현체에 의존하지 않게 만드는 것"입니다.

이는 곧, 변화가 많이 생길 수 있는 구현자체에 의존하는 것이 상대적으로 변경이 잘되지 않는 본질적인 것에 의존하게 만들어야한다는 의미입니다.

 

예를 들어, 제 프로젝트의 온보딩 과정 중에서 "임신 주차"에 대한 정보를 받아야한다고 하는 기능이 존재할 때 이 기능 자체는 변하지 않는 본질적인 기능에 가깝습니다. 서비스에 꼭 필요한 기능입니다.

 

임신 주차를 몇주차부터 몇주차까지 받을지에 대한 것은 세부적인 구현 디테일입니다. 임신 1주차부터 대략 40주차까지라고 했을 때 말도 안되는 입력을 막기 위해 기획에서 정의한 주차 범위 안에 들어오는 입력만 받을 수 있게끔 합니다. 이는 기획자의 의도가 반영된 것이고, 이는 변경이 생길 수 있는 부분입니다. 이런 디테일을 포함하고 있는 구체타입에 의존하게 된다면 변경에 유연하지 않겠죠.

 

ViewModel protocol 예시 1)

예시와 함께 실제 프로젝트에서는 어떨지 생각해보겠습니다.

아래처럼 ViewModel의 protocol이 기능들을 추상화하고 있다면 기획의 변동으로 인해 받고자 하는 데이터가 바뀔 때 마다 함께 변화되기 쉽상인 protocol입니다. 특히 아래처럼 protocol을 구성했을 때 기획이 계속해서 변동되는 MVP단계의 프로젝트라면 이 protocol은 변화에 너무 민감합니다.

protocol OnboardingViewModel {
	func checkIsVaildFetalNickname() {}
    func checkIsValidPregnancy() {}
}

 

그렇다했을 때 이 protocol은 DIP의 본질에 맞는 것일까요..? 🤔 저는 아니라고 생각했습니다.

DIP의 본질인 "변화가 적은 추상화된 것에 의존하게 만든다"에 경험적으로 생각했을 때 ViewModel의 기능을 추가, 수정하기 위해서 protocol까지 같이 바꿔줬어야하는 경우들이 많았기에 DIP를 사용하기에 적절하지 않다고 생각한 것 입니다.

 

ViewModel protocol 예시 2)

다른 예시도 살펴보겠습니다.

아래는 실제 저희 프로젝트에서 사용하고 있는 ViewModel protocol입니다.

protocol ViewModel where Self: AnyObject {
    associatedtype Input
    associatedtype Output
    
    func transform(input: Input) -> Output
}


protocol ArticleDetailViewModel: ViewModel where Input == ..., Output == ... {}

Combine을 사용하면서 팀내에서 획일화된 구조로 Input Output구조를 채택하고 있습니다.

그리고 해당 Input Output 구조체 타입과 transform이라는 메서드로 추상화된 이 ViewModel protocol은 기획이 변하더라도 Input과 Output 구조체 내에 프로퍼티가 추가될 뿐이지, 이 자체는 "변하지 않는 것"이라고 생각했습니다.

따라서 저희 구조상에서는 DIP의 본질을 지킨 채 객체간 결합도를 낮추며 유의미하게 사용하고 있다고 판단했습니다.


설계는 균형의 예술이다.

결론은 처음과 그대로 어떤 변화도 일어나지 않았지만 명확한 근거가 늘어나 더 탄탄한 기반이 생긴 느낌이 듭니다.

 

사실 이 고민을 하는 과정에서 문득 오브젝트에서 본 문구도 생각이 나더라고요.

 

설계는 균형의 예술이다.
- 오브젝트

 

오브젝트라는 책에서는 위에처럼 말하며 "훌륭한 설계는 적절한 트레이드 오프의 결과물"이라고 말합니다.

훌륭한 설계는 적절한 트레이드오프의 결과물이라고 말하며 좋은 설계로 만들기 위한 노력들 (인터페이스 추가)이 과하다는 생각이 들 수도 있다는 것입니다. 그리고 마지막으로 말합니다.

구현과 관련된 모든 것들이 트레이드 오프의 대상이 될 수 있다는 사실이다. 여러분이 작성하는 모든 코드에는 합당한 이유가 있어야한다.
고민하고 트레이드 오프하라.
- 오브젝트

 

어떤 것과의 트레이드 오프일까요 ? 제가 생각했을 때 "과하다" 라는 것, 즉 사람이 느끼는 피로도입니다. 휴먼 리소스라고 부르는 것이죠.

MVC를 MVVM-C로 리팩토링을 하는 과정에서 유연한 설계를 위해 수많은 인터페이스를 추가하게 되고, 이로 인해 확실히 코드 점핑이 급격하게 늘었습니다. 이에 따른 피로도도 점점 늘어나고 있는 것을 몸으로도 느끼고 있었습니다.

 

  • 유연한 설계를 위해 이런 리팩토링 과정을 거치면서, 휴먼 리소스에 대한 것은 아예 고려를 하지 않는 것이 맞는가 ?
  • 현업에서는 가끔 오히려 이런 휴먼 리소스가 더욱 중요한 가치로 여겨지며, 많은 리소스가 들 경우 추상화를 안하는 경우도 있다고 하는데....(확실치는 않습니다만..)

하는 생각도 드네요. 그래도 사실 '지금 하고있는 이 정도의 프로젝트 수준이라면 이런 것은 고려안해도 될 것이다'라고 생각은 하지만서도 어느 지점까지 가야 이런 트레이드 오프까지 고민하게 되는 것이고, 그 중요도가 교차하는 순간이 언제쯤일지가 궁금해지네요.

 

 

댓글