프리코스 때부터 현재까지, 대다수(라고 하지만 사실상 100%) 지원자분들과 크루들은 MVC 패턴을 채택하여 우아한테크코스의 미션을 진행했고, 진행해오고 있습니다. 이 글을 읽고 계신 다음 기수에서 활동하게 되실 예비 크루분들도 그러실 것이라 생각합니다.
그런데, 현재 미션을 진행하면서 많은 크루들이 코드 리뷰를 받을 때, 공통적으로 리뷰어 분들로부터 받고 있는 피드백이 있습니다.
"정말 Controller에서 해야 할 일인가요?"
"Controller의 책임을 Model로 옮겨보세요."


저 역시 자동차 경주와 로또 미션을 제출했을 때, 두 번 모두 이러한 요지의 피드백을 받았습니다.
이 피드백들은 MVC 중 Controller가 어떤 역할을 해야 하는지에 대해 제대로 알지 못하고 미션을 구현해왔음을 깨닫는 계기가 되었습니다. 또한 MVC 패턴을 채택하여 프로그램을 작성할 때는 이상적인 Controller의 역할이 무엇인지, Controller의 로직을 최소화해야 할 이유와 그 방법은 무엇인지 고민하며 코드를 작성해야 한다는 사실을 알게 되었습니다.
View와 Model을 연결해주는 역할 그 이상 이하도 아니다.
Controller는 역할은 View와 Model 간 연결 수단에 그쳐야 합니다.
연결로 그친다라는 것은, Controller를 View와 Domain 측 객체의 함수를 트리거하는 코드로만 구성하는 것입니다.
이는 곧, Domain 측에서 수행할 수 있는 작업들에 대해서는 최대한 Domain의 객체 측에 많은 책임을 부여해주어야 한다는 사실로도 이어질 수 있습니다.
Controller에서는 Controller로부터 요청을 받은 Model 측이 어떤 로직을 진행할지에 대한 관심을 가지면 안됩니다.
MVC를 사용하는 가장 큰 목적은 비즈니스 로직과 UI 로직의 분리이다.
비즈니스 로직은 Model 측에서 구현해야 하고, UI 로직은 View에서 구현해야 합니다. 이러한 분리를 통해, UI 로직 혹은 비즈니스 로직 상 변화가 일어나더라도 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다는 효과를 볼 수 있게 됩니다. 이렇게 두 요소에 명확하게 역할을 나눠놓은 상태에서, Controller는 이 둘 사이를 연결하는 용도로만 활용하는 것이 MVC의 핵심 가치라고 할 수 있습니다.
그림과 같이, MVC 패턴에서는 Controller가 View와 Model에 의존하고 있습니다.
Controller가 두 가지 구성 요소에 의존을 하다 보니, 규모 증대 측면에서 위험이 크다고 볼 수 있습니다. 따라서 Model과 View로 역할을 제대로 분리하지 못한다면, Controller의 로직이 비대해지고 유지보수가 힘들어지는 상황을 마주하게 됩니다.
엄격하게 책임을 할당하지 않고 두 요소의 일부 로직이 Controller에 남아있게 된다면, 그에 따라 파생되는 로직들 역시 Controller에 추가될 수 있으며 이는 Controller의 지나친 비대화로 빠지게 되는 지름길이 될 것입니다.
미션을 진행하면서, 단위 테스트가 힘들었던 대표적인 요인들로 다음과 같은 것들이 있었습니다.
랜덤적인 요소가 들어간 객체에 대한 테스트사용자로부터의 입력이 포함된 로직에 대한 테스트 등이러한 요소들로 인하여, 아래 두 가지 요소로는 단위 테스트를 진행하기가 불가능에 가깝다는 제약사항을 안고 가야 하는 상황이었습니다.
View
Controller
이것들을 제외하면 남은 것은 랜덤적인 요소를 포함하고 있지 않는 Domain 객체들입니다. 그렇다면, 최소한 이 객체들에 대해서는 단위 테스트를 꼼꼼하게 작성해야 테스팅의 이점을 최대한으로 얻을 수 있을 것입니다. 그런데 만약 이러한 로직들이 Controller에 들어가게 된다면, 테스트를 충분히 할 수 있는 요소임에도 하지 못하게 되는 상황이 발생하게 됩니다.
따라서 Controller의 책임에 대해서 정확히 이해하고 개발을 하는 것이 테스팅 가능성을 높이는 데 중요한 것이라고 볼 수 있습니다.
작성한 Controller의 코드가 정말 나머지 두 요소를 연결하는데 그치는지는 여러 기준을 통해 판단할 수 있을 것입니다. 저 같은 경우, 미션 중 받았던 피드백을 통해서 다음과 같은 판단 기준이자 행동 요령을 가져갈 수 있었습니다.
데이터를 연산 혹은 변경하고, 다른 Domain Model 혹은 View 측에서 사용하기 좋게 정제하는 로직은 Model 측에서 해야 할 일입니다. Controller에서 조건문이나 반복문을 작성했다면, 이러한 역할을 수행하는 코드를 Controller에서 작성했을 가능성도 높다고 볼 수 있습니다.
예를 들어, 리스트의 요소를 하나하나 순회해가며 특정 조건을 만족했을 때 값을 변경하는 일은 정말 컨트롤러에서 해도 되는 것인가?
컨트롤러는 도메인 측에서 정제해준 객체를 그대로 가져다가 사용하는 것이 좋지 않을까 하는 의문을 가져볼 수 있을 것이고, 도메인 측으로 옮겨보는 시도를 할 수 있을 것입니다.
우아한테크코스의 미션에는 항상 아래의 요구사항들이 등장합니다.
함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.
한 메서드에 오직 한 단계의 들여쓰기만 한다.
함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
제가 느낀 Controller에서 메소드를 잘게 분리하는 것의 가장 큰 이점은, 생각지 못한 부분에서 공통된 로직을 발견할 수 있다는 것입니다. 공통된 로직이 있다는 것은 곧 인터페이스 혹은 독립된 Domain 객체로 분리할 수 있다는 것과 같습니다.
이것으로 Domain 측에 객체를 추가함으로써 더욱 고도화된 객체지향 프로그래밍을 달성할 수 있고, Controller에 존재하던 로직의 양을 획기적으로 줄일 수 있습니다. 또한 테스트 가능한 로직이 추가됨으로써 테스팅으로 인한 이점을 얻을 수 있게 됩니다.
우아한테크코스에서 교육을 받은지 벌써 3주가 되어가고 있습니다. 자동차 경주 미션을 마치고, 레벨 1의 두 번째 미션으로 프리코스 3주차에서 구현하였던 로또를 재구현해보게 되었습니다. 이번 미션을 하면서 새로 만나게 된 페어는 제가 만나본 안드로이드 개발을 하는 사람들 중에서 Kotlin의 API와 객체지향에 대한 이해가 가장 높았던 크루였고, 그 덕에 미션 과정에서 정말 많은 것들을 배울 수 있었습니다. 함께 미션을 진행하는 과정에서, 객체 지향에 대한 잘못된 생각을 바로잡을 수 있었을 뿐만 아니라, value class, static factory method, Map 관련 함수들을 처음으로 접하고 학습해볼 수 있었습니다. 이번 미션을 통해 많은 것들을 배우고 도움을 받았던 것처럼, 나중에 개발에 어려움을 겪는 사람들에게 도움을 주는 사람이 되고 싶다는 생각을 하게 되었습니다.
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%8D%B8-%EB%B7%B0-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC