Android 아키텍처 비교–MVP, MVVM, SVC–2

Bansook Nam
22 min readApr 19, 2019

이전 ”Android 아키텍처 비교–MVP, MVVM, SVC–1" 글을 이어갑니다.

3. 아키텍처 패턴의 비교

Android 현업 개발자라면 간단한 TODO List를 작성하고 목록을 확인하고, 수정할 수 있는 스펙을 가진 Google Sample GitHub의 TODO 앱 저장소를 본 적이 있을 것이다. 저장소에 있는 다양한 branch들을 살펴보면 MVP 패턴에도 mvp-clean, mvp-dagger 등등 다양한 버전이 존재하고, MVVM 패턴도 mvvm-live, mvvm-databinding 이 존재한다. 이어서 Google Sample 저장소 안에 todo-mvp-kotlin, todo-mvvm-live-kotlin 브랜치 2개와 SVC가 적용된 GitHub 브랜치 1개를 비교해보려고 한다.

1) Code Line Count

코드 생산성을 정성적인 수치로 가장 간단하게 확인할 수 있는 방법은, 같은 스펙을 놓고 코드 라인 숫자를 비교해보는 것이다. 실제로 프로그래머가 코드를 작성하는 양을 알 수 있고, 코드가 적을 수록 문제가 생긴 위치를 찾는 데 더 유리하다. 다음 도표는 cloc을 이용하여 라인 카운트를 한 자료이다.

비교를 위해 각 브랜치에서 app/src/main 폴더에 들어있는 kotlin, xml코드만 카운트 하였다.

그림 7 todo-mvp-kotlin
그림 8 todo–mvvm-live-kotlin(databinding 사용)
그림 9 todo-svc-kotlin

MVP와 MVVM을 비교해보자. MVVM이 MVP보다 kotlin 파일의 코드 라인수 26줄이 늘었고, xml의 코드 라인수가 150줄이 늘어났다. xml에 로직이 들어가면 그만큼 kotlin파일의 코드 라인이 줄어들어야 하는데 오히려 늘었다는건 참 아쉬운 부분이다.
코드가 늘어난 원인을 분석해보면 첫째, MVVM 브랜치에서는 databinding 라이브러리를 사용하는데 xml 파일 안에 관련 import로직과, View에 들어가는 로직이 추가되었기 때문이다. 둘째, ViewModel 안에 View에 보여줄 Observing data와 명령을 수행하는 SingleLiveEvent등이 과하게 추가되었고, Listener 객체들도 사용하면서 Observing과 객체 참조 연결만 해주는 의미없는 로직들이 추가되었기 때문이다. 셋째, 재활용되지 않는 BindingAdapter관련 로직이 추가되었기 때문이다. (BindingAdapter는 재활용 될수록 유용하다. 한곳에서만 쓰인다면 만드는 노력이 그만큼 아쉽다.)
Google에서 보여준 샘플 MVVM은 아쉬운 부분이 많다. 오히려 코드 라인수가 늘었으며, ViewModel 내부도 MVP와 비교해서 가독성이 많이 떨어진다.
한편 MVP와 SVC를 비교해보면 kotlin파일수는 4개가 늘었지만, 코드 라인수는 86라인가량 줄었다. SVC에서는 화면을 Screen, ControlTower, Views로 나누기 때문에 파일 수가 늘어났기 때문이고, Boilerplate되는 코드를 줄였기 때문이다. Xml숫자도 3개가 줄어들었다. SVC로 구현을 하면 Activity와 Fragment는 같은 Screen의 개념으로 접근하기 때문에 불필요한 Activity-Fragment 뎁스를 줄여서 설계가 되면서 기존에 껍데기 역할만 하던 xml도 생략이 가능해진 것이다. 코드 라인수만 놓고 비교하면 SVC로 설계한 것이 코드 라인 수가 가장 적다.

2) 디자인 요소 배치와 정보 및 이벤트 전달

MVP, MVVM, SVC 모두 View의 기초 설계는 Xml로 구성한다. Android Native에서 기본적으로 화면 렌더링은 View 객체를 통해서 이루어진다. View는 Java 또는 Kotlin 언어로 만들어져있고 결국에는 JVM 객체로 만들어진다. 그래서 화면을 설계할때 꼭 Xml이 아니더라도 100% 코드 기반으로 만들 수도 있다.

그러나 디자인 설계인 만큼 작업을 하면서 “미리보기” 기능이 지원되어야 수월하게 작업이 가능하다. 그러려면 현재 IDE에서 제공하는 Xml 미리보기 기능을 사용할 수밖에 없다. Xml로 View 트리를 설계하면, html을 보는 것처럼 구조가 쉽게 파악이 되고, 각각의 디자인 Attribute들을 쉽게 확인, 수정이 가능하다. 이렇게 만들어진 Xml은 실제 빌드된 후 Runtime에서 Java객체로 생성되며 정의된 View 트리에 맞게 Inflating되어 Java 객체로 변한다. 정리하면 Xml은 디자인을 돕기 위한 수단이고, 결국에는 Java 객체가 된다.

MVP

Xml로 View를 구성한 뒤에는 각 View에 대한 접근을 Activity와 Fragment에서 하게된다. 이 두 객체를 View라고 보는 것이다. 그렇게 하기 위해서 View interface를 정의하게되고 구현은 Activity와 Fragment에다가 한다. Presenter에서는 Activity와 Fragment를 직접적으로 알지는 못하고 View로 알기 때문에 View로서의 역할을 수행시킨다.

View에서 클릭과 같은 Action이 일어나면 Presenter의 presenter.doSomthing()과 같은 형태로 함수를 직접 호출해준다. 바로 이부분에서 MVP구조로 개발할때 로직이 복잡해질 수 있는 여지가 있다. View는 본래 역할은 정보를 보여주는 역할과, 사용자가 View에 준 Action을 전달하는 것이 주요 역할이어야 한다. 그러나 Presenter를 직접 참조하게 되므로 Action전달만 하는 것이 아닌 비즈니스 로직을 직접 호출까지 한다. 다시 말하면 View에서 비즈니스 로직에 관여할 가능성이 커진다는 이야기다. MVP로 앱을 만들고 있다면 View와 비즈니스 로직이 결합되지 않도록 노력하는 것이 꼭 필요하다.

MVVM

MVVM에서 databinding 라이브러리를 사용하게되면 Xml이 디자인 요소 배치를 담당할 뿐 아니라, ViewModel을 통해 정보(data) 연결(binding)과 이벤트 전달의 역할을 한다. 특정 View에 어떤 데이터가 매핑되는지 xml상에서 바로 입력이 가능하고 observe함수를 사용하지 않아도 빌드시 알아서 observe함수를 자동 생성한다. MVP나 기존의 방법으로 매핑하려면 각각의 View에 id를 부여하고, 해당 View를 findViewById 등으로 참조를 가져온 후에, 데이터를 세팅해야하는데, databinding 라이브러리는 아이디 없이도 알아서 View와 데이터를 매핑시킨다.

장점만 있는 것은 아니다. 개발자에게 편의성을 주는 반면 불편한 점이 있다. 첫째는 Xml내부에서 중괄호({}) 안에 선언하는 구문을 별도의 문법을 사용하는 불편함이다. 특정 observing 필드를 여러 View에서 참조하는 경우에 이 필드를 참조하는 View들을 찾기 위해서는 텍스트 기반 검색을 수동으로 해야한다.

그림 10 Find Usage로 찾아지지 않음 (안드 3.5기준)

또 IDE에서 제공해주는 자동완성이 불완전하고, Refactoring, Call Hierarchy나 Find Usages 등의 코딩 기능을 현재는 제공해주지 않는다. IDE가 Java나 Kotlin코드로 인식을 하지 않고 별도의 문법으로 인식하여 상태 관리시 유지보수가 어려워지는 것이다. 둘째로 불편한 점은 Xml escaping이다. Generic 관련 코드를 xml에서 작성하는 경우 “<, >”(홑화살괄호)를 써야할 수 있는데, Xml이기 때문에 이 문자가 허용되지 않는다. “&lt; &gt;” 형식으로 escaping 시켜야한다. 이를 피하기 위해서는 관련 로직들을 별도의 메서드로 빼는 방법을 추천한다.
Xml에 정의한 View에서 발생한 이벤트도 일반적으로 ViewModel을 매개로 이루어진다. 방법은 크게 3가지가 있다.

아래는 ViewModel직접 호출 방식의 예다.

그림 11. MVVM ViewModel 직접호출

직접적으로 ViewModel의 함수를 호출 하는 방법이 있다. ViewModel은옵저빙 데이터를 보관하는 용도가 주요 역할이기 때문에 이렇게 메서드를 ViewModel에 넣는 방식은 지양하는게 좋다. ViewModel에 비즈니스 로직이 직접적으로 들어가는 경우는 각 화면이 해당 비즈니스 로직에 특화되고, 그 비즈니스 로직이 다른곳에서 쓰이지 않아야 설계적으로 의미가 있다.

다음은 Observing Command Field를 통한 방법이다.

그림 12. MVVM Observing Command 사용

Observing Command를 사용하는 경우 위의 그림 처럼 3가지 Step을 늘 작성해주어야한다. (A) 관찰 필드를 선언해주고, (B) 로직 수행부에서 관찰을 하고, (C) 필드를 invoke시켜 Observer들에게 알려준다.
기존 MVP에서는 함수를 만들고, 그 함수를 실행만 하면 됐었는데, 이 방식은 함수와 함수 실행 사이에 3가지 일이 추가되는 모습이다. (마지막 invoke도 실제 함수 invoke가 아닌 필드 invoke이므로), 코드가 늘어날수록 가독성이 떨어지고 유지보수가 힘들다.
꼭 그렇다고 안좋기만 한것은 아니다. 명령 관찰하는 지점이 3개가 넘는다면
오히려 이렇게 관찰 포인트를 두는 것이 깔끔하다. 하지만 99% 1곳에서만 필요로 하는 경우가 대부분이다.

다음은 별도의 Listener를 통한 방법이다.

그림 13. MVVM Listener를 통한 Call 방법

위와 같이 별도의 Listener를 두게되면 ViewModel에서 비지니스 로직을 떼어낼 수 있어서 좋은점이 있다. Listener의 실제 구현은 Binding이 일어나는 곳에서 할 수 있게 되는데, 그 곳이 만약 Activity나 Fragment라면 해당 클래스에 비지니스를 작성하기 보다는, 별도의 분리된 클래스로 위임해주는 것이 좋다.

“그림13”의 경우 Google Sample에 있는 예제인데, Listener를 세팅해주는 방법을 자세히 들여다보면 결국에 taskDetailViewModel.setCompleted(checked) 함수를 호출 하는 것을 볼 수 있다. 어차피 ViewModel 함수를 수행할거라면, Listener를 만들지 않고, 바로 수행하면 된다. 이 부분이 앞에서 코드 라인수를 비교할때 언급했던 의미 없는 로직들이다.

뷰 이벤트를 처리하는 구조가 다양하기 때문에, 프로젝트 내에서 혼재해서 사용한다면 작업하는 팀원들은 혼란을 가질 수밖에 없다. 그렇기 때문에 설계할 때 규칙을 미리 정하거나, 팀원들과 사전에 구조에 대해 이야기 하는 것이 좋다.

SVC

MVP와 큰 틀은 유사하기 때문에 MVP를 써본 개발자는 구조를 어렵지 않게 파악 가능하다. Xml은 View 배치를 위한 수단으로만 쓰고, 데이터 매핑이나 View 이벤트 전달은 Views 클래스에서 담당한다. 이미 Activity나 Fragment로부터 분리가 되어있기 때문에 MVP에서 View 인터페이스를 따로 만드는 수고가 MVP보다 줄어들었다. Presenter에 해당하는 ControlTower도 마찬가지로 인터페이스가 불필요하다. 또 MVP에서 부수 작업이었던 인터페이스 없이도 유닛 테스트 작성에는 문제가 없다.
View에서 발생하는 이벤트는 ViewsAction이라는 객체를 통해 전달한다. 버튼 클릭이 일어났다면 viewsAction.onClickClose() 이런 형식으로 인터페이스에 함수를 추가하면 된다. 이 이벤트는 ControlTower에게 직접 전달된다. (실제 구조는 ControlTower에서 ViewsAction을 구현하고 있고, viewsAction이 ControlTower가 된다. 그러나 의미상 전달한다고 이해 하는 것이 플로우를 이해하기 쉽다.)
개발 스펙에 따라서 Google의 Android Architecture Component인 ViewModel 객체를 함께 쓰기도 한다. 첫 번째 예로 Fragment의 Depth가 깊은 경우이다. A Activity에 A,B,C Fragment가 존재할 때, 각각 RecyclerView 가 있고, 그 안에 ViewPager가 있는데, 그 안에 페이지는 Fragment로 구성되어 있다면 벌써 View Depth는 3단계로 들어간다. 그런데 Fragment에 있는 데이터들을 A,B,C Fragment끼리 공유해야된다면, AAC가 없던 시절에는 static한 캐시 클래스에서 관리를 하거나 모델 클래스를 최하단 View Depth까지 넘겨줬어야 한다. AAC ViewModel을 쓰면 activity context 참조만 있으면, Activity 단위에서 모델 쉐어링이 가능하다. 두 번째로 모드나 상태 관리를 위해서도 사용하면 구조가 단순해지는 경우도 있다. 편집기에서 읽는 모드, 편집 모드, 각종 모드들이 있을때 떨어져있는 View들에게 모드가 변경되었음을 값 할당만으로 알려 줄 수 있고, View들은 그 값을 Observing만하면 된다.

3) 화면 전환 로직의 배치

하나의 Activity에서 다른 Actiivty를 시작하거나, 화면 내부에 Fragment들을 교체해서 다른 화면을 보여주는 것을 화면 전환이라고 한다. 화면에서 다른 화면으로 전환할때는 새로운 화면을 위한 정보를 전달해야 되는 경우도 있고, 또 새로 열린 화면이 끝나면서, 새로운 정보를 처음 열었던 화면에 돌려줘야하는 상황도 생긴다. MVP나 MVVM에서는 화면 자체도 View의 개념으로 보고 있고, SVC에서는 Android 특성상 화면은 View와는 다른 성격을 가진다고 생각해 Screen이라는 인터페이스를 새로 만들게 되었다.

MVP

MVP에서는 화면이 바뀌는 로직을 보통 View가 되는 Activity나 Fragment에다가 구현을 한다. 그러나 강제적인 부분은 아니기 때문에 Presenter에서 구현하는 사람도 더러 있기도 하다. 하지만 View에서 하는 것이 나중에 결과를 받을때 자연스럽기 때문에 Activity나 Fragment에서 구현하는게 좋다.

이렇게 개발을 하면 Presenter의 로직 내부에서 화면 전환이 필요한 시점에는 view.start**Activity(), view.showWriteFragment() 이런 형식으로 view 함수를 호출하게된다 이렇게 되면 View의 “정보 렌더링 로직”과 “화면전환 로직”을 둘 다 포함하는 객체가 된다. 2가지 다른 성격의 로직이 같이 있기 때문에 화면 이동에 대한 흐름이 한눈에 보이지 않아, 코드 분석시 시간이 더 걸린다.

MVVM

MVVM에서도 마찬가지로 Activity나 Fragment에서 화면 전환이 이루어진다. TODO앱 MVVM브랜치를 보면 **Navigator 라는 인터페이스를 정의하고 Activity나 Fragment에 화면 전환 함수들을 구현했다. 그렇게 화면 전환에 함수들을 관리를 하겠다는 의미이다. 그런데 코드를 자세히보면 이 함수들을 호출하는 것이 자기 자신인 Activity나 Fragment이다. 굳이 인터페이스를 만들지 않고 함수를 만들어도 호출이 가능한데, 개발자들이 화면 이동임을 명시하기 위해 단순히 마킹한 형태라고 볼 수 있다. 인터페이스는 다른 객체 참조 파일에서, 해당 인터페이스를 이용하지 않는 이런 마킹용 인터페이스는 실용적이지도 않고, 강제적이지도 않기 때문에 지속되기 어렵다.

SVC

View가 Screen(Activity나 Fragment)으로부터 객체 분리가 되면서 Activity나 Fragment는 Screen의 기능만 최대한 담당할 수 있게 되었다. SVC에서는 화면 전환하는 로직은 Activity나 Fragment 즉 Screen에게 전적으로 위임하고 있기 때문에 ControlTower내부에서 화면 흐름을 보고 싶으면 screen 필드로 검색을 해서 화면 흐름을 쉽게 찾아볼 수 있다. screen.start**Activity(), screen.showWriteFragment() 이런 형태로 작성이 된다. 그러면 해당 Screen인 Activity와 Fragment 클래스 파일에는 화면의 이동에 대한 함수들만 거의 남게된다. 처음 인수인계 받은 프로그래머도 Activity 파일을 열면, “아 이 화면에서는 어떤 화면이 시작될 수 있고, 어떤 화면으로부터 결과를 받는구나” 하고 화면 전환의 흐름을 읽을 수 있다. 직접 눈으로 보고 싶다면 SVC-TODO앱 GitHub 링크에서 확인 가능하다.

4) 스펙 변경 대응 능력

처음 계획했던 대로 서비스가 출시되는 일은 절대로 없다. 서비스를 오픈하기 전에도 수시로 스펙이 바뀌고, 오픈 후에는 더더욱 바뀐다. 처음 설계하고 코드를 만들어나가는 과정보다 스펙이 바뀔때 대응하는것이 어려울때가 더 많다. 추가적으로 코드를 분석해야되고, 불필요한 코드는 제거하고, 함수나 필드 코드를 다른 클래스 등으로 추출하고, 기존 코드와 함께 문제없이 동작하는 코드를 새로 만들어야한다. 패턴마다 이런 작업들을 할때 특징들이 조금씩 다르다.

MVP

MVP에서는 View와 Presenter를 위한 인터페이스를 1:1로 매번 생성한다. 파일이 늘 2개씩 추가로 늘어나는 불편함이 있다. 이 인터페이스는 어떤 함수가 정의되어 있는지 살펴 보기에는 좋을지 몰라도, 유지보수 측면에서는 그리 좋지 않다. 함수가 추가/제거될때마다 인터페이스쪽도 수정을 해줘야되며, 파라미터가 바뀔때, 메서드 정의도 늘 같이 바꿔줘야한다. 이 작업을 30개가 넘는 화면에서 하다보면 생산성이 떨어지게 된다.

MVVM

MVVM으로 작업 중 스펙변경이 xml을 수정해야하는 상황이 올때 가장 난감하다. 해당 화면을 만든 작업자면 그래도 스펙을 어느정도 기억하기 때문에 무리가 없지만, 담당이 다른 개발자가 그 화면을 열어보면 우선 해야할 것이 2가지가 있다. Xml에 정의된 ViewModel 필드들의 파악 그리고 Xml안에 있는 로직들의 파악이다.

Xml안에 있는 로직들을 옮겨야하는 상황이되면, 개발자의 특기 잘라내기 붙여넣기 신공을 잘 발휘해야한다. 이 로직들이 Java나 Kotlin에 있었다면 IDE의 refactoring 기능 도움을 받을 수 있겠지만, Xml안에 있는 로직들은 전부 수동이다. 복잡한 Xml의 스펙 변경을 맡았다면 기존의 로직들에는 영향이 없는지, 사이드 이펙트는 어떨지 감이 안 올 수도 있다. IDE 컴파일 에러에서 잡히면 좋은데, 런타임시 에러는 정말 원인 찾기가 너무 힘들다.

SVC

MVP에서 불편했던 인터페이스 중복 생성은 없어졌다. Views, ControlTower는 화면별로 하나씩 매핑되고 인터페이스가 아닌 그냥 클래스 객체이다. MVP와 MVVM에는 없던 ViewsAction을 정의하고, View에서 일어나는 이벤트를 정의하는것이 추가적인 일로 느껴질 수는 있겠다. 하지만 ViewsAction을 잘 정의해놓으면 나중에 그 화면을 이해하는데 많은 도움이된다. 해당 화면 View에서 어떤 Action들이 있는지 바로 확인이 가능하기 때문이다.

5) 디버깅

디버깅은 문제상황이 생겼을때 원인 파악에 큰 도움이 되는 IDE의 기능이다. 로직의 흐름을 파악할 수 있고, 심지어 해당 로직의 시점에 데이터들을 확인하고 수정까지 가능하다. 처음부터 개발할때도 디버깅을 많이 쓰지만, 서비스 운영중에 발생하는 문제를 파악할때도 꼭 필요하다.

MVP

xml을 제외한 로직들이 Java, Kotlin 파일로 되어있기 때문에 원하는 위치에 브레이크 포인트를 걸어서, 확인이 가능하다. 변수값들도 확인이 가능하고, 임의 값 할당 등 IDE에서 제공하는 디버깅 기능들이 모두 가능하다.

MVVM

databinding을 쓰는 경우 xml안에 있는 코드 로직들은 브레이크 포인트가 걸리지 않는다. 그 이유는 xml자체에 로직이 포함되어있지만, 실제 돌아가는 로직은 Java파일로 자동생성 되기 때문이다. activity_home.xml 이라는 layout파일로 databinding로직을 넣었다면 ActivityHomeBinding이라는 클래스가 자동생성된다. 이 파일 안에 xml에 넣었던 로직들이 자동생성이 되어있을 것이다. 자동생성된 파일에 브레이킹 포인트를 걸어놔야 개발자가 중간 동작들을 확인 가능하다.

그림 14 databinding의 BindingAdapter

그리고 BindAdapter와 InverseBindAdapter에서 오류가 있는 경우에는 (크래시가 아닌 UI 오류) Static 함수를 통해 찾아야 되기 때문에 해당 xml과 직접적인 연결이 되어있지 않아, 구조를 잘 모르면 해메기가 쉽다.
연결된 부분을 찾으려면 텍스트 검색으로 app:items를 찾아 쓰거나, 자동생성된 BindingImpl 클래스와 내부의 BindingAdapter 연결고리를 찾아야 한다.
본인이 만든 코드가 아니기 때문에 찾는 것부터 시간이 소요되며, xml코드가 바뀌게 되면 브레이킹 포인트도 다시 잡아야한다. 이부분은 databinding에서 꽤 아쉬운 부분이다. 나중에 지원이 되리라 믿는다.

SVC

MVP와 동일하게 모든 디버깅 기능들을 코드에서 쓸 수 있다.

6) 유닛 테스트

일반적으로 잘 알려진 유닛테스트의 기본 형태는 Given, When, Then 3가지 단계로 나뉜다.

• Given: 주어진 어떤 상황과 값에서

• When: 어떤 로직이 수행되었을때

• Then: 내가 원하는 결과가 맞는지를 확인하는 것

패턴으로 개발하는 가장 큰 이유중에 하나가 바로 유닛 테스트를 작성 가능한 구조를 만들기 위함이다. 패턴없이 Activity나 Fragment에 모두 구현을 하게되면, 특히 Android의 View 디펜던시 때문에 유닛 테스트가 돌지 않는다. Roboletric이나 Espresso와 같은 테스트 라이브러리를 이용하여 테스트를 해야하는데, 둘 다 굉장히 테스트 시간이 오래 걸린다. 대부분의 개발자는 가장 빠르고 가벼운 유닛 테스트가 돌아가는 형태를 원한다.

MVP

주어진 값에 대해 Presenter의 비즈니스 로직에 따라서 원하는 View 함수가 제대로 불리는지를 확인한다. View는 Mocking이 되기 때문에 Android 디펜던시가 분리된다. 테스트 코드의 가독성이 꽤 좋은 편이다.

MVVM

MVP와 다른 점은 View 객체를 ViewModel이 직접 알지 못하기 때문에 ViewModel이 들고 있는 값이 예상한 값인지를 검증한다. Xml에 선언한 값, 로직은 테스트 대상에서 제외된다. 여기서 큰 차이점이 있다. ViewModel와 Xml에 선언한 View와의 databinding 관계는 “신뢰”를 두고, 테스트 영역에서 제외한다. 오로지 ViewModel에 정의한 데이터가 정확한지를 테스트 한다.

가독성은 MVP보다 떨어진다. 그 이유는 ViewModel에 정의한 field들이 어떤 역할을 하는지 알아야 명확한 테스트 코드가 작성 가능하기 때문이다. 예를 들면 통계 에러 메시지가 잘 나타나는지 테스트 코드를 작성할때, MVP 테스트코드에서는 명확하게 view.showLoadingStatisticsError()이 호출되는지 확인하는 구문이 MVVM에서는 statisticsViewModel.empty.get()값이 true인지 값으로 확인을 한다. 그렇기 때문에 이 테스트코드를 인수 받는 프로그래머는 각 state값이 view에게 어떻게 영향을 미치는지까지 인수인계를 받아야 하는 것이다.

SVC

MVP의 장점과, MVVM에서 사용하는 Observing 필드의 장점을 둘 다 사용해서 테스트 코드를 짤 수 있고, Screen이 분리되어있기 때문에 각 Screen의 LifeCycle을 임의로 변경해가며 테스트 코드 작성이 가능하다(TasksFragmentTest-lifeCycle.handleLifecycleEvent 함수 사용). 예로 네트워크 통신 요청을 보냈는데, 홈버튼 등을 눌러 Screen이 Pause상태가 되었을때, Views에는 어떤 함수가 호출될지 등을 테스트 코드로 작성 가능하다는 이야기이다. 또 Observing필드의 값이 변경되었을때 실제로 Views에서 원하는 함수가 실행되는지도 테스트 가능하다.

4. 마무리

지금까지 다양한 관점으로 Android 아키텍처를 비교해보았다. 각자 장단점이 분명히 존재하고, 개발 스펙에 따라서 알맞는 패턴이 다르다.

패턴에 관심이 있는 개발자라면 “켄트 백의 구현 패턴”(에이콘출판, 2008)을 추천한다. 책을 읽어보면 패턴에 대한 전반적인 지식과 설계의 기초를 쌓을 수 있다. 이 책에서 2개의 문단만 소개하려고 한다.

• 아무리 다양한 패턴을 소개한다고 하더라도 패턴만으로 프로그래밍 과정에서 발생하는 모든 문제를 해결할 수는 없다. 하지만 다른 사람의 스타일을 무조건 따라해 사용하는 것보다는 자신만의 스타일을 개발해 사용하고, 팀원들과 스타일에 관해 토론하고 공유 하는 것이 더 효과적이다. …(생략)… 패턴은 절대적인 진리가 아니므로, 사람의 의사 결정을 돕는 도구 정도로 생각하는 것이 좋다. 어떤 구현 패턴은 다른 방식으로 진화하기도 한다. 그러므로 패턴이 절대적이지 않다는 사실을 인식하고 상황에 따라 패턴을 적절히 변화시켜 사용해야 한다. …(생략)… 패턴은 반복적으로 일어나는 문제에 대한 합리적인 해결책을 제공해서 프로그래머가 남는 시간과 에너지, 창의력을 진정 독창적인문제 해결에 사용할 수 있게 해준다.(p. 31)

• 커뮤니케이션을 중시하면 유연성도 좋아진다. 더욱 많은 사람들이 코드를 짧은 시간 안에 읽고 이해하고 수정할 수 있다면, 여러분의 조직이 미래에 취할 수 있는 전략의 폭도 넓어진다.(p. 38)

서론에서 이야기한 것처럼 아무 패턴 없이 Activity와 Fragment만으로도 원하는 스펙을 구현하는데는 전혀 무리가 없다. 그러나 여러 명이 협업하는 프로젝트, 또 테스트를 필수로 작성해야 하는 환경의 프로젝트는 분명 아키텍처를 설계하는 것이 장기적으로 유리하다.

세상에 어떤 아키텍처도 완벽하다고 하거나 최고라고 할 수는 없다. 다 각자만의 장단점이 존재하고, 러닝 커브가 존재한다. 제일 좋은 아키텍처는 그 프로젝트에 참여하는 모든 팀원들이 함께 선택하고, 팀원들의 개발 시간을 줄여주고, 팀원들끼리 커뮤니케이션하기 쉽고, 서로 담당 영역이 바뀌어도 쉽게 적응할 수 있을만큼 구조화를 잘해놓은 아키텍처라고 생각한다. 여러분이 속한 팀의 팀워크를 최상으로 만드는 아키텍처를 서로 토의하면서 발전시켜서 보다 나은 형태로 앱을 완성해가고, 가치있는 좋은 앱들이 많이 생겨나기를 바란다.

--

--