리팩토링

gang_shik·2022년 5월 17일
1

프로젝트 Fit-In

목록 보기
4/10

이전 상황

  • 이전까지 서버 통신을 기준으로 그 생각만 했기 때문에 상당히 러프하게 구현에 초점을 맞춤

  • Package만 나눴지 사실상 Activity가 서로 얽히고 얽힌 상당히 복잡한 구조...

  • Activity가 View 전환, Data 처리 등 상당히 많은 Task를 안고 있음, 지금은 아직 로그인 & 회원가입 단계라서 큰 문제 없어보이지만 이 구조가 계속해서 지속된다면 추후 다량의 데이터를 담을 때 ANR등 이유 없는 에러가 발생한 가능성이 상당히 높음...

  • 그 때 가서 구조를 개선한다면 상당히 골치 아플 예정... 아예 다시 만드는게 나을 것임

  • 그리고 단순히 이를 Kotlin 버전으로 개선한다고 해결될 문제도 아닌 것으로 보임, ViewBinding을 활용해서 findViewById를 개선했지만 그것만 단독으로 쓰기에는 한계가 보임

  • 전반적인 개선을 진행함


리팩토링

Fragment로 개선

  • 먼저 어떻게 보면 로그인 & 회원가입 부분은 따지고 보면 onBoarding 의 역할이고 메인으로 담을 기능들은 별개인데 이렇게 Activity로 Intent 처리로 1차원적으로 하는 것에 대한 찝찝함이 남음

  • 애초에 Intent를 저런식으로 쓰게 된다면 Android Back Stack 관리도 그렇고 Intent 전환시에 애니메이션 처리도 그렇고 만일 계속 이런식으로 개발을 진행하게 된다면 코드의 복잡성도 그렇고 나중에 내가 어떤 부분에서 Intent가 들어오는지 Flow 관리를 하기 힘들어보임

  • 그리고 혼자 하는 프로젝트가 아닌데 사실상 거의 혼자만 알고 이해하는 수준으로 쌓이는 것 같음

  • 그래서 우선적으로 이렇게 Activity에 담아서 처리하는 것보다 해당 기능이나 View 자체가 onBoarding 같이 그리고 리소스를 너무 무겁지 않게 처리하는 차원에서 Fragment를 생각함

  • 뿐만 아니라 현재 개발중인 앱이 1차원적인 수준의 앱도 아니고 NavigationDrawer, BottomSheetDialog 등 그리고 onBoarding 자체가 하나의 Navigation Flow가 존재하는데 이를 Intent로만 처리하기엔 완전히 내 머릿속에만 존재하는 Flow가 되는 등 상당히 비효율적인 부분이 존재함

  • 추가로 Activity가 많은 일을 담고 있는데 이 부분 역시도 관심사 분리를 위해서라도 Fragment 차원에서 분리를 해보는게 나아보였음

  • 그래서 이 부분에 대해서 Activity 안에 Fragment를 담아 onBoarding Flow를 진행하고 그리고 onBoarding 이후 Home으로 넘어가게 처리함

개선 전

  • Main의 경우 로그인 & 회원가입으로 가는데 있어서 일일이 다 Intent로 처리해서 진행함, 뿐만 아니라 아이디 & 비밀번호 찾기 역시 동일하게 진행함

  • 그리고 네트워크 처리 역시 로그인 버튼을 누름으로써 네트워크 처리를 하면서 Intent까지 같이 처리함 돌아는가지만 상당히 비효율적이라고 볼 수 있음

개선 후

  • MainActivity에 FragmentContainerView를 활용, Navigation Component를 활용하여 Intent로 일일이 처리해야 하는 것을 action을 각각 설정해서 화면 Flow 처리를 진행함

  • 그렇게 함으로써 Fragment에 대해서 로그인, 회원가입, 초기화면 등 각각을 분리하여 처리 그리고 각 버튼별 데이터 처리 등을 Fragment 각각을 통해서 그리고 Navigation Component를 활용해서 한결 수월하게 처리함

  • 이는 개선전 Activity 단위에서 모든 작업을 처리하는 부분을 상당히 개선했다고 볼 수 있음

MVVM Pattern으로 개선

  • 이렇게 Fragment Navigation Component로 개선을 하고 해당 Component의 기능을 활용하기 위해서 그리고 이전에 Activity 안에서 View 전환, 네트워크 처리, 데이터 처리 등 모든 작업을 하는데 있어서 이 부분 역시 관심사 분리가 필요해보였음

  • 구조적으로 본다면 아래와 이전 코드와 로직은 아래와 같이 MVC로 처리된다고 볼 수 있었음, Activity가 Controller로써 View에 화면 업데이트와 네트워크에 통신하는 데이터 갱신 등을 처리했기 때문에

  • 이렇게 되면 View-Model간의 의존성이 높아지고 Activity 자체가 무거워지면서 앱 규모가 커지면 유지보수도 어려워지고 코드가 복잡해짐

  • 그래서 이를 개선하기 위해서 MVVM 패턴을 사용할 수 있음 MVVM은 Model-View-ViewModel을 의미함

  • 이 부분은 아래와 같이 진행된다고 볼 수 있음

  • 즉 Activity가 모든 역할과 일을 한 부분에 대해서 관심사를 분리해준 것임 그래서 View(Activity/Fragment)의 경우 사용자의 이벤트 Action을 받게 되면 ViewModel의 데이터를 관찰하여 UI를 갱신만 하게 됨

  • 그리고 ViewModel은 View가 요청한 데이터를 Model로 요청해서 Model로부터 요청한 데이터를 받음

  • 마지막으로 Model은 ViewModel이 요청한 데이터를 반환함, Room과 같은 DB 사용이나 Retrofit을 통한 백엔드 호출 등

  • 이 MVVM 패턴이라는 구조가 결정적으로 AAC 즉 Android Architecture Component로 활용되어서 위와 같은 패턴으로 개선을 할 수 있는 것임

  • 즉, Activity & Fragment에선 UI 변화를 관찰하여 그를 갱신하고 이에 대한 데이터 처리나 요청은 ViewModel에서 처리하며 그런 데이터 처리를 Model 즉 위에선 Repository에서 요청하고 처리를 할 수 있게 된 것임

ViewModel & Data Binding & LiveData

  • 이 MVVM 패턴으로 개선된 코드를 보기에 앞서 이 AAC 요소들과 적용 방식을 한 번 볼 필요가 있는데 먼저 ViewModel 자체는 AAC의 위에서 보듯이 하나의 구조를 차지하는 것으로 UI 관련 데이터를 저장하고 관리해주는 역할을 함

  • 원래 같으면 이런 데이터 관리에 있어서 Activity나 Fragment에서 saveInstanceState로 직접 관리를 해서 처리했지만 앞서 MVVM 패턴의 설명에서 봤듯이 Activity나 Fragment에 대해서 이런 데이터에 관여하지 않도록 하기 위한 것이 필요한데 그 역할을 ViewModel이 해주는 것임

  • 그리고 Data Binding과 LiveData를 쓴 것은 이런 ViewModel을 사용하는데 있어서 데이터가 변경되는 것에 대해서 관찰해서 처리한다고 하였는데 이렇게 Observer 할 수 있게끔 도와주는 것이 LiveData가 그 역할을 하는 것임

  • 그렇기 때문에 LiveData를 활용하여 데이터를 관찰하고 이 관찰한 데이터를 변경됐는지 View에서 Observe 하면서 반영을 할 수 있게됨

  • 하지만 여기서도 이 observe 하는 것을 일일이 체크하고 처리하게 된다면 결국 또 Activity/Fragment 단에서 View를 불러와서 해당 데이터를 Observe 하는 것이 추가되는데 이를 Data Binding을 통해서 ViewModel과 관련된 View에 대해서 직접 연결할 수 있게 됨

  • 이렇게 되면 ViewModel에서 데이터 변화 혹은 처리가 되는 것에 있어서 View의 이벤트 처리 즉, 이런 변경을 요청하는 것을 바로 ViewModel에서 처리하게 되는 것이고 이를 Activity/Fragment 같은 View는 해당 데이터에 대해서 ViewModel에서 Observe만 하면 UI 변화를 바로 주거나 View 변경을 할 수 있는 것임

  • 이를 개선 전과 개선 후를 비교하면서 어떤 부분이 반영되고 처리한 것인지 알아볼 것임

개선 전


  • 로그인 처리 & 회원가입 처리 같은 경우 Activity 내에서 Retrofit 인스턴스를 만들면서 직접 데이터를 가져와서 API 호출처리를 Activity 내부에서 모든 작업을 다 함

  • 구현에 포커스를 맞춘다면 단순하게 코드를 짜긴 수월하겠으나 Activity에서 모든 처리가 일어나고 의존성이 높기 때문에 하나만 안되거나 처리가 원활하지 않게 되면 문제가 발생할 확률이 높음, 이런식으로 계속 개발을 하게 된다면

개선 후

  • 앞서 AAC 구조를 생각해보면 Repository에서 Model 역할 즉 네트워크 통신 DB처리를 해 줌 그래서 Dto 모델과 API에 대한 Interface와 singleton 정의한 부분을 Repository가 네트워크 통신 처리를 대신 함 Model 역할을 하는 것

  • 기존의 패키지 분리

  • 개선된 패키지 분리

  • Repository 역할 : Retrofit 인스턴스를 생성하고 API 통신을 하는 메소드 생성 ViewModel에서 해당 Repository를 인스턴스로 생성하고 데이터 처리 요청을 할 수 있음

  • 뿐만 아니라 기존의 SharedPreferences에 저장하는 것 역시 Activity에서 처리했지만 통신 처리는 Repository에서 하기 때문에 Repository에서 처리 해 줌

  • 로그인 XML

  • data binding 적용 및 ViewModel에 데이터 처리할 수 있게 EditText에 활용 그리고 Button을 클릭해서 데이터 변경시 UI에 변화를 주기 위해 ViewModel에서 클릭 리스너 함수 만듬

  • 로그인 ViewModel

  • LiveData를 통해서 Observe 할 수 있게 설정함, 여기서 Fragment에선 데이터 변화를 observe 하고 UI 변경을 하는데 이때 뒤로가기나 로그인 하는 상태를 확인하고 UI에 반영해주기 위해서 Boolean의 LiveData를 만든 것

  • 그리고 ViewModel에서 Repository 인스턴스를 만들어서 Email, Password 받은 것을 통해 Dto 모델을 만들고 로그인 요청을 함 이는 ViewModel에서 Model에게 데이터 요청을 하는 것임

  • 로그인 흐름을 정리하자면 Data Binding을 통해 양방향 바인딩을 하여 EditText에 값을 입력하면 MutableLiveData email, password에 값이 들어오게 됨, 이 값을 통해 ViewModel에서 Dto 모델을 만들고 Repository에 서버에 Login 요청을 함

  • 그러면 Repository에서 Login에 해당하는 API Post 요청을 하고 결과값을 받는데 이때 JWT 토큰을 SharedPreferences에 저장하게 됨

  • 그래서 보기보다 개선전과 비교해 보였을 때 간결해 보이는 것이 관심사를 확실히 분리했기 때문임 Activity에서 다 한 개선전에서 Repository는 서버와 통신을 그 결과와 처리를 담당하고 ViewModel에선 View에서 받은 데이터에 대해서 Repository에 요청을 하는 것임

  • 로그인 Fragment

  • 로그인 Fragment에선 View의 변화만을 처리해주면 됨, 그러기 위해서 ViewModel을 생성해서 해당 Fragment에 연결해주는 작업을 사전에 set 해 줌

  • 그 후 observe 하는 부분을 보게 되면 back 버튼이 눌렸는지 그리고 로그인이 처리 됐는지 ViewModel에서 각각 Boolean 데이터를 통해서 observe 할 수 있게 두었는데 해당 데이터가 처리되고 변경되었을 때 set을 해서 Fragment에 알려줌으로써 뒤로가기가 된 경우 앞서 Fragment Navigation의 action을 설정했기 때문에 뒤로 가기를 처리하면 되고 로그인이 처리됐다면 이제 홈화면으로 가게 인텐트 처리한 것임

  • 그 외에 전반적으로 이런식으로 MVVM 패턴으로 개선 및 수정을 하였음

EncryptedSharedPreferences

  • SharedPreferences의 저장하는 값이 아무래도 token 값이다보니 보안적인 부분이 신경쓰였는데 이 부분에 관련해서 암호화를 해주는 androidx.security가 있었고 이에 따라서 기존의 SharedPreferences 부분도 암호화 처리를 추가했음

참고자료
공식문서


추가 개선 사항

  • 기존의 방식이 돌아가는데는 문제가 없고 어떻게 보면 구현을 한 것이지만 코드적으로 봤을 때 이게 맞나 싶은 생각이 계속해서 들어서 전반적인 구조 개선을 진행했음

  • 단순히 구현하고 만드는 것에 집중하다보면 놓칠 수 있는 부분이라서 생각하면서 가능하다면 계속해서 개선하는게 맞다고 보기 때문에 진행함, 하지만 이렇게 개선해도 2가지 정도 더 적용하거나 개선할 사항이 보였음

RxJava

  • 현재 Retrofit을 활용해서 일반적인 콜백 처리를 통한 비동기 처리를 진행했음

  • 물론 이 방식이 틀린 방식이라고 볼 수 없음, 하지만 자칫 잘못하면 콜백 지옥에 빠질 수도 있음

  • 간단히 정리해보자면 기존의 Retrofit에서 처리하는 비동기 처리 방식에 있어서 함수형 프로그래밍 기법과 데이터 흐름과 전달을 통해서 데이터가 변경됐을 때 연관 함수나 수식을 업데이트 하는 방식이며 옵저빙을 통해서 이를 처리한다고 볼 수 있음

  • 이 방식으로 개선하는 것은 전반적인 네트워크 처리를 다 바꾸는 것이므로 좀 더 깊게 알아볼 필요가 있지만 Retrofit과 함께 쓰는 것이 더 효율적으로 네트워크 처리를 관리할 수 있을 것으로 보여 그 다음 개선사항으로 처리할 부분임

RxJava

DI

  • DI는 의존성 주입을 하는 것으로 Android에 역시 이 의존성 주입에 대해서 Dagger2, Koin과 같은 라이브러리를 활용하여 쓸 수 있음

  • 여기서 왜 DI를 사용하냐면 먼저 MVVM 패턴을 통해서 현재 관심사를 분리를 하긴 함

  • 하지만 여기서 Repository나 SharedPreferences 등이나 쓰는 것을 보면 다른 레이어에서 혹은 context나 직접 인스턴스를 만들면서 혹은 context를 생성자로 받거나 파라미터로 받아서 처리했는데 이런 부분에 있어서 모듈화를 하고 DI를 활용, 의존성을 주입하여서 이렇게 필요할 때마다 파라미터로 만들고 받고 처리하고 일일이 하는 부분을 일관성 있게 처리하게 할 수 있음

  • 이 부분 역시 DI 라이브러리와 활용도에 대해서 좀 더 자세히 알아본 후 적용시켜야함

Kotlin

  • 원래는 Kotlin 버전으로 아예 조정을 하려고 하였지만 기존의 Java 구조를 개선한 뒤에 진행을 하는게 이해적인 측면이나 좀 더 Kotlin을 Java와의 차이점을 짚고 넘어가면서 볼 수 있을 것 같아서 Java로 진행을 함

  • 어쨌든 Kotlin을 쓰게 된다면 RxJava를 쓰지 않아도 Coroutine을 활용하고 코드적인 방식에 있어서 좀 더 가독성과 함수형 프로그래밍으로 그리고 Java에서의 긴 부분을 간결하게 처리할 수 있는 요소들이 있기 때문에 Kotlin 버전도 역시 진행을 할 예정

profile
측정할 수 없으면 관리할 수 없고, 관리할 수 없으면 개선시킬 수도 없다

0개의 댓글