회사에서 자바로 된 웹앱을 코틀린으로 마이그레이션을 하는 업무를 맡았다.
자바에서 코틀린으로 마이그레이션하는건 어려운 일은 아니였지만, 문제는 앱이 오래전에 그것도 외주로 만들어져서 코드가 엉망이였다.
간단하게 설명하자면
메인액티비티에 웹뷰를 6개나 둔 다음, 그 메인액티비티의 인스턴스를 static으로 만들고 여기저기서 참조에 쓰는 구조였다. 자바스크립트 인터페이스를 모아둔 클래스인 JsHandler 와 뷰에 대한 참조를 모아둔 클래스 ViewHandler(당시에는 뷰바인딩이나 데이터바인딩이 없었다), 명령어를 int 타입으로 입력받아 명령어에 따른 처리를 하는 actionHandler 클래스 등이 있었는데
이 모든 클래스는 메인액티비티에 멤버로 있고, 다른 모든 클래스는 메인액티비티의 인스턴스를 통해 각각의 클래스에 접근했다.
그러다보니 상호참조도 엄청나게 일어나고, static 인스턴스를 쓰다보니 어디서 어떤걸 쓰는지 가늠하기가 어려웠다.
제일 시급한 일은 이 더러운 static 인스턴스와 상호참조를 없애는 것이였다.
나는 먼저 의존성이 적은 말단 부분부터 마이그레이션 및 리팩토링을 시작했다.
말단 부분이란, 적어도 다른 클래스에 의존하지 않고 사용될 뿐인 헬퍼 클래스 같은 것이다.
테스트코드도 전혀 없어서 오로지 꼼꼼함을 십분 발휘해서 기능손실이 없도록 작업을 해야했다.
직접 테스트코드를 써가며 작업하는 것도 생각해봤지만 그렇게하면 시간이 너무 오래걸릴 것 같아서 포기했다.
또 구조도 구조지만 코드 자체도 엉망이라 필요없는 변수나 조건문이 많았다.
기획서도, 이 앱에 대해 물어볼 사람도 없었기에 나는 그 냄새나는 코드만 보고 기능을 유추해야했다.
그렇게 살얼음판을 걷듯이 코드를 해체하고 재조립해나가다보니 굉장히 스트레스를 받았다.
결국 100% 기능을 살려가며 작업하는 것은 포기했다.
지금 속도라면 한세월이 걸릴 것이였기 때문이다.
일단 돌아가게 해놓고, 안되는 부분을 하나씩 찾아가며 수정해나가는 방법을 택했다.
지금 단계에서는 코드의 퀄리티보다는 돌아가는 것이 중요했다.
일단 내가 생각한 구조로 바꾼뒤에 다시 한번 리팩토링하면 된다.
이 과정에서 나는 속도를 중요시했다. 나는 원래 코드를 쓰는 속도가 꽤 느린편이다.
더 완벽한 코드를 쓰기 위해 머릿 속에 수많은 생각을 하면서 코드를 쓰다보니 그런 것이다.
그런게 중요한 국면이 있기는 하겠지만 지금은 그렇지 않았다.
일단 나는 최대한 빠르게 초록막대를 보고 싶었다.
그런 테스트주도개발이라는 책에서 본 문구를 떠올리곤 빠른 속도로 작업하기 시작했다.
IDE가 지원하는 마이그레이션 기능도 적극 활용했다.(물론 나중에 꼼꼼하게 다듬었다)
그렇게 메서드 스텁까지 동원해가며 내가 생각한 구조를 짰다.
내가 생각한 구조는 메인액티비티의 static 인스턴스를 없애고, 프래그먼트와 뷰모델을 적극활용하여 의존성이 최대한 안쪽으로 향하게끔 했다.
즉, 글로벌하게 써야할 기능들은 액티비티에 두고 개별적인 기능들은 프래그먼트에 두었다. 프래그먼트에서 액티비티에 정의해둔 기능이 필요하다면 뷰모델 프로바이더를 이용해 액티비티의 뷰모델을 얻어 사용했다.
나도 뷰모델을 공유하는 짓은 하고 싶지 않았지만 어쩔 수가 없었다.
기획서도 뭣도 없으니 기존 구조를 크게 벗어날 수는 없었다.
그래도 봐줄만한 정도로 되는것을 목표로 삼았다.
또한 ViewHandler를 없애고 데이터바인딩으로 대체했다.
모든 뷰에 대한 명령을 처리하고 있던 거대한 actionHandler라는 클래스를 각각의 프래그먼트로 분리했다.
문제는 JsHandler였다. 이 클래스는 앱에서 필요한 모든 자바스크립트 인터페이스를 모아둔 클래스다.
각각의 자바스크립트 인터페이스는 static 메인액티비티 인스턴스를 참조하고 있었다
이 참조를 어떻게 끊을까 생각하다가 rx의 PublishSubject 라는것을 쓰기로했다.
LiveData와 비슷하게 데이터를 발행할 수 있는 주제(Subject)였다.
나는 자바스크립트 인터페이스에서 대부분의 코드를 걷어내고, 자바스크립트 인터페이스가 불리면 PublishSubject에 데이터를 넣어서 이 PublishSubject를 구독하고 있는 옵저버들에게 이벤트를 보냈다.
이렇게하면 JsHandler는 메인액티비티를 알지 않아도 된다.
한마디로 JsHandler를 그냥 자바스크립로부터 입력을 받아 이벤트를 발생시키는 클래스로 변신시킨 것이다.
이렇게 하면 JsHandler는 어디에도 의존하지 않는 독립적인 클래스가 된다.
그리고 메인액티비티와 프래그먼트의 뷰모델에서 각각 필요한 PublishSubject를 구독하여 이벤트를 전달받았다.
그렇게 마음에 드는 방법은 아니였지만 어쩔 수 없었다.
JsHandler를 각각 웹뷰에 맞게 분리하고 싶어도 할 수 없었다.
모든 웹뷰가 똑같이 JsHandler를 자바스크립트 인터페이스로써 사용하고 있었기에
각각의 자바스크립트 인터페이스가 어느 웹뷰에서 불려야하는지 알길이 없었기 때문이다.
이 jsHandler는 자바스크립트 이벤트를 viewModel에 전달하고, viewModel은 그 이벤트를 처리했다. 추가로 UI 조작이 필요한 경우에는 viewModel에서 liveData를 이용해 액티비티, 프래그먼트로 이벤트를 전달했다. 그러면 액티비티, 프래그먼트에서는 liveData를 구독해 적절한 처리를했다.
또한 메인액티비티의 static 인스턴스로 접근하고 있던 이 jsHandler와 PreferenceService 같은 유틸성 클래스들은 Dagger를 통해 주입함으로써 쓸데없는 메인액티비티에 대한 참조를 끊을 수 있었다.
그렇게 메인액티비티의 static 인스턴스를 완전히 없앨 수가 있었고 코드도 훨씬 보기 편해졌다.
메인액티비티에 몰려있던 6개의 웹뷰도 각 프래그먼트로 분리해서 UI도 더 보기 쉬워졌다. (전에는 6개의 웹뷰를 껐다 켰다하면서 사용했기에 언제 어느 웹뷰가 화면에 보여지는지, 어떻게 생겨먹었는지 알기가 힘들었다)
이렇게 구조를 개선했지만 문제는 산더미처럼 남아있었다.
일단 코드 자체도 상당히 난해했고, 구조도 기형적이였기에 몇몇 코드는 완전히 새로써야했는데
제대로 돌아가는지 확신할 수 없었다.
부분부분을 리팩토링해서 작은 범위부터 다져나가고 싶어도, 부분을 바꾸면 곧 전체를 바꿔야하는 스파게티 코드라 그러기도 힘들었다.
가장 문제는 웹이 앱에 너무 많은 개입을 하고 있다는 것이였다.
앱의 UI를 열고 닫고 하는 걸 자바스크립트 인터페이스를 통해 웹에서 결정하는건 기본이고 웹뷰의 clearHistory, url 로드, 자바스크립트 실행, 유저에이전트 변경, 이미지 리소스 변경까지도 웹에서 결정하고 있었다.
앱에서 특정 웹뷰의 현재 url을 전달받아서 그에 따른 처리를 한 뒤 알 수 없는 과정을 거쳐 또 다른 자바스크립트를 호출하는 식의, 웹쪽 코드를 모른다면 앱의 동작을 예상하기 굉장히 힘든 구조였다.
나를 가장 힘들게 했던 건 불려야할 자바스크립트 인터페이스가 안불리는데, 도저히 원인을 모르겠는 경우였다.
그런 경우 대부분 답은 앱에서 웹으로 제대로된 정보를 넘겨주지 않았거나 구조를 변경하면서 특정 자바스크립트 인터페이스가 안불리는 것이였다.
기존에는 한번에 6개의 웹뷰가 동시에 존재했지만 그걸 분리하기 위해 네비게이션을 도입하면서 한번에 한 두개의 웹뷰만 존재하게 바꿨기 때문에 비활성화된 웹뷰에서는 자바스크립트 인터페이스가 안불렸던 것이다.
결국 나는 네비게이션 도입을 포기하고, 대신 웹뷰들을 프래그먼트로 묶어서 모두 메인액티비티에 넣었다.
그렇게 타협을 하면서 어찌저찌 앱을 동작시키는데 성공했다.
하지만 역시 잘 돌아가지 않아서 디버깅으로 대부분의 시간을 보내야했다.
앱은 물론이고 웹쪽에도 문의할 사람이 없었던 나는 그저 모든 자바스크립트 인터페이스와 이벤트를 처리하는 옵저버들에서 로그를 출력하게 해놓고 어느 시점에 어떤 메서드가 불리는지 유심히 살펴보았다.
텍스트뷰를 화면에 띄워서 url 이동이나 UI를 보여주고 숨기는 등의 중요한 이벤트를 한눈에 볼 수 있도록 만들기도 했다. (물론 중단점도 십분 활용했다)
그렇게 수없이 많은 디버깅과 리팩토링 끝에 대부분의 기능이 정상작동하게 만들었다.
리팩토링에서 가장 힘들었던 부분은 기획서도 없는 상태에서 쓸모없는 부분과 있어야할 부분을 구분하는 것이였다.
이 점은 마켓에 출시되어있는 릴리즈버전과 동작을 직접 비교해가면서 구분 할 수 밖에 없었다. 덕분에 시간은 오래 걸렸지만 어떻게든 됬다.
모든 자바스크립트 인터페이스에서 로그를 출력했던 것이 크게 도움이 되었다. 대부분의 힌트는 로그에서 찾을 수 있었다.
아무튼 거의 한달이 넘는 시간동안 코틀린 마이그레이션 & 리팩토링에만 매달려서 현재 작업은 막바지다.
중간중간에 몇번이고 상사에게 이건 못하겠습니다, 라고 말하고 포기하고 싶었지만 어떻게든 고민하고 타개책을 찾다보니 되긴 됬다.
나도 처음에는 이게 불가능하다고 생각했다. 그 정도로 원본의 코드는 심각했다. 게다가 문서도 없고 이 앱을 아는 사람도 없으니까. 다시 만드는게 빠를 정도였지만 문서도 없는데 어떻게 다시 만들겠나, 하는 생각에 어쩔 수 없지 하고 마음을 다잡아가며 작업했다.
나에게 이런 폭탄을 준 상사에게 원망의 감정도 있었으나 결과적으로 되긴 됬으니 뭐.
이 경험으로 웬만한 코드는 코틀린 마이그레이션, 리팩토링 할 수 있겠다는 자신감도 생겼다.
물론 이렇게나 심각한 코드는 다시는 만지기 싫지만.
'안드로이드 앱개발' 카테고리의 다른 글
안드로이드 라이브러리 만들때 유의사항 (0) | 2021.12.31 |
---|---|
사내 카페 주문앱 개발기(1): 클린아키텍쳐 (0) | 2021.11.27 |
Service (0) | 2021.09.04 |
Permission (0) | 2021.09.04 |
Dagger2 Scope (0) | 2021.09.04 |