회사에서 서비스하고 있는 앱의 내부 광고 구조를 리팩토링할 수 있는 기회가 생겼다.
글을 쓰는 시점에서 리팩토링은 아직 진행 중이지만, 여러가지 기록해놓으면 좋을 이야기가 있는 것 같아 적는다.
광고 구조를 리팩토링하고 싶다고 생각이 들게 된 발단은,
앱 내에 다른 광고들을 더 추가하는 작업을 하면서 하게 되었다.
새로운 곳에 광고가 추가될 때마다 해야하는 코드 작업이 필요 이상으로 많으며,
온갖 곳에 분기처리 코드가 있어서 혹시라도 실수할까봐 이미 한 작업을 몇번이나 확인해야만 했다.
그때부터 남는 시간에 조금씩 광고 구조를 어떻게 개선하면 좋을지 생각하게 되었다.
우리의 앱은 실행 시 처음에 서버로부터 광고 정보를 받아온다.
1. 어떤 화면에서 2. 광고 업체들을 어떤 순서로 표시할지에 대한 정보이다.
광고를 표시하는 화면에 진입하면 저장되어있는 화면 별 광고 순서를 가져와서,
resume 때마다 광고를 순서에 맞게 변경하고 있다.
그리고... 문제가 많다.
1. 별도의 통신 라이브러리를 사용하지 않음
서버에서 받은 광고 표시 정보의 응답을 바로 데이터 클래스로 파싱하지 못하고,
일일히 응답 xml의 태그들을 하드코딩된 String들로 값을 가져와서 변환해주고 있다.
2. xml을 파싱해서 얻은 데이터를 SharedPreference
에 저장함
응답으로 오는 광고 정보만 20개 이상인데, 각각 다른 key 값을 사용해서 SharedPreference
에 저장하고 있으니 광고의 종류만큼 key로 사용하는 상수들이 생겨난다.
3. 여러 광고 업체의 SDK를 사용하며, 하나의 클래스에서 모든 경우를 다 처리함.
앱 내에서 여러 광고 업체를 통해 광고를 표시하고 있는데, 그래서 광고 업체마다의 SDK가 제공하는 인터페이스와 사용 방법이 다 다르다.
그런데 이걸 하나의 클래스 안에서 지옥의 분기처리를 통해 업체 별로 달라지는 광고 View들을 다루고 있다.
코드의 양이 너무나 많고, 심지어 클래스 내에서 Activity
의 참조도 가지고있다.
4. 무조건 static String 상수를 사용함
광고 표시 위치 구분, 어떤 업체의 광고 View를 표시해야 할지 구분하는 요소를 String 상수로만 정의해놓았다.
그리고 코드에는 equals
와 하드코딩된 String 문자열들이 가득하다.
세세하게 따지면 더 많은 문제가 있지만, 대표적으로 이정도인 것 같다.
대표적인 문제점들을 바탕으로 리팩토링의 컨셉을 정해봤다.
1. Retrofit 적용하기
이건 전체적으로 적용해나갈 것이라 해당 리팩토링에만 국한된 내용은 아니다.
적용하게 되면 또 다른 긴 파싱 코드를 추가하지 않고 원하는 데이터 클래스로 변환이 가능할 것이다.
2. SharedPreference
제거하기
SharedPreference
를 이런 용으로 사용하는 것 자체가 문제지만, 수많은 각각의 상수 key들이 보기 안좋은 분기처리 코드를 유발하는 원인이 되었다.
그래서 저장하고자 하는 데이터들을 하나의 Entity로 디자인하고 Room을 사용하여 DB에 저장하려 한다.
3. 광고 업체 별 분기처리 제거
업체마다 광고 View를 다루는 방법이 다르기 때문에 분기처리를 없애기 위해서는 인터페이스의 통일이 필요하다.
그리고 광고 업체 별 광고 처리 코드를 인터페이스를 구현하는 개별 클래스들로 분산시킬 것이다.
4. String 상수 제거
단순한 String 상수는 문자열이 주는 의미를 제외하고 코드상에서 어떠한 의미나 맥락을 가지기 어렵다는 것을 코드를 보며 깨달았다.
복잡한 의미가 필요한 상황에서는 부적절하기에 다른 요소로 대체하고자 한다.
사실은... 위의 항목들을 달성하여 이루고자 하는 진짜 목표는 따로 있다.
"광고 추가 작업할 때 최소한의 코드 작업만 하도록 만들기"가 진짜 목표이다.
지금은 광고를 추가하기 위해 거의 열 군데 쯤 되는 곳을 건드려야 한다.
진짜 목표를 이루고자 하면 가독성이나 유지보수성은 자연스레 좋아질 것이라고 생각한다.
커다란 컨셉을 정한 뒤 세부적인 요소들을 결정하는데 있어서 팀원분들과 많은 이야기를 나누었다.
앱 내에서 광고는 삽입될 화면과 현재 표시해야할 광고 업체로 구분해주어야 한다.
광고를 표시하는데 필요한 광고 코드가 화면과 업체 별로 달라지기 때문이다.
광고 구분을 위해 코드 상에 산재되어있는 화면과 업체 관련 하드코딩 String들과 상수들을 완벽히 걷어내고,
앞으로는 그 어디에서도 해당 String들을 사용하지 않도록 만들자고 결정했다.
그래서 앞으로 사용할 구분 요소들을 sealed class
로 정의해야 할지, enum
으로 정의해야할지 결정해야 했다.
sealed class
와 enum
둘 다 무언가를 구분하기 위한 용도로 사용하기 적합하며,
단순한 String 상수보다 코드상에서 더 많은 의미를 가질 수 있기 때문에 고민되었다.
하지만 이야기를 통해 다음과 같이 정리할 수 있었다.
sealed class
는 다형성을 활용하기에 적합하다.
-> 서브클래스 별로 어떤 메서드의 동작을 다르게 구현해야한다던가 하는 처리가 필요 없었다.
enum
은 요소들을 entries
를 통해 Array로 묶어줄 수 있다.
-> 특정 요소를 Array의 find
로 빠르게 찾을 수 있다.
서버 응답 광고 정보를 DB의 데이터 클래스로 변환할 때 필요했다.
Room
은 enum
변환을 자동으로 처리해준다.
-> 코드 상에서 String들을 없애기로 했기 때문에 Room
에 저장하고 가져오는 타입을 enum
으로 정의하는 것이 좋을 것이다.
결론적으로 enum
을 사용하는 것이 적합하다고 판단했다.
(참고자료)
다이어그램에 있는 AdView
는 업체 별 광고 View들의 인터페이스를 통일하기 위한 interface이다.
그리고 AdView
를 사용하는 클래스인 AdUtil
는 내부적으로 순서에 맞게 광고를 load 해주는 등의 처리를 한다.
AdUtil
의 설계에 대해 이야기를 하다가 팀원 분이 의문을 제기해주셨다.
"AdUtil
클래스가 처리하는 내용의 범위는 어떻게 되나요?"
나는 광고와 관련된 처리를 외부에서 전혀 알지 못했으면 좋겠다고 생각했다.
Activity
는 전혀 모르고, 그저 AdUtil
을 사용하여 광고를 표시한다.AdUtil
은 LifecycleObserver
같은걸 구현하여 Activity
에 스스로를 observer로 등록한다.Activity
마다 lifecycle에 맞춰 광고 View를 resume, pause하는 코드를 일일히 작성 할 필요가 없어진다.Activity
와 광고 관련 코드를 분리할 수 있다.반면에 팀원 분은 다음과 같이 생각하고 계셨다.
Activity
에서 load, resume, pause 등의 처리를 직접 해주어야 한다고 생각한다.RecyclerView
사이사이에 끼어있는 경우도 있으니 Activity
의 lifecycle에만 맞추어서 광고 View를 처리하지 못하는 상황이 분명히 있다.두 생각 다 납득할만 하다고 생각한다.
다만 어떤 것이 우리의 프로젝트 코드 상황에 더 적합할지 가려내고 결정해야 한다.
사실 이 사항에 대해서 명확하게 결정난 것은 없다.
우리는 아직 많고 복잡한 기존 코드에 대해 빠삭하게 알고있지도 못할 뿐더러,
기획팀의 변화무쌍한 개발요청은 언제나 불확실하기 때문이다.
추구하고자 하는 코드의 기준과 제약사항들을 다시 점검하고 지금 순간에서의 최선의 결정을 내릴 수 있게 해야하지 싶다.
두서에도 적어놨지만 이 리팩토링은 진행 중이기 때문에 내용이 더 추가될 수도 있다.