RxJava 1 과 비교해서 정리한 RxJava 2

RxJava 1 적용한지 얼마 안 되었는데, 벌써 RxJava 2 라니, 말도 안 돼! 심지어 3.X 에 대한 정보도 심심찮게 들리는데…

서론

RxJava1과 RxJava2 를 먼저 비교하기에 앞서, 왜 이 진입 장벽 높은 라이브러리가 버전업을 했는지부터 알아야 할 필요가 있다. Reactive Extension (Rx)은 옵져버 패턴을 이용해서 이벤트 기반 비동기식 프로그래밍을 할 수 있게 만들어준 라이브러리다. 다양한 언어에서 이 Rx의 프로토콜을 이용할 수 있게 기준이 되는 것이 Reactive Stream Specification인데, RxJava 1은 이 기준에 맞지 않는 것들이 많아서, 버전업을 시키면서 완전히 처음부터 다시 작성하기로 결정된 것이다.

다른점

RxJava 2 에서 변경된 것들은 많지만, 일단 대분류로 그 중 인상적인 것들만 모아봤다. 이들을 제외하고도 달라진 점들은 꽤 많으니, 직접 진행하는 프로젝트에 일단 넣어보고 고민하는 것도 괜찮은 방법이다. 목록이 긴 관계로 관심 있는 대분류를 클릭해서 보면 편할 것이다.
Observable.create
Null
Functional Interfaces
Testing
Observable vs Flowable
Single, Completable and Maybe

결론

RxJava1 을 사용하면서 이미 Backpressure 에 대한 이해가 있었다면, 빠른 시일 내에 업데이트하는 게 좋을 것 같다. Dagger 1 에서 Dagger 2로 넘어가는 것만큼 사고의 변환이 크지 않고, Technical Debt 기간이 길어질수록 이자가 쌓여서, 어차피 나중에 전환할 때 더 힘들어지기 때문이다. 이 블로그와 같이 90% 이상이 RxJava 1 베이스인 경우라면, PR을 여러 기능별로 끊어서 넣고 배포 때 플레이스토어의 Staged Rollout 기능을 통해서 예상치 못하는 이슈들을 잡아나가면서 진행하는 걸 추천한다.

Observable.create()

RxJava 1의 가장 큰 진입 장벽의 하나던 .create() 함수가 전면적으로 개편되었다. API를 처음 배울 때 가장 처음 확인하게 되는 .create() 함수를 올바르게 쓰기가 너무 어려운 관계로 .just().defer() 을 함께 많이 이용했는데, RxJava 2에서부터는 Flowable 의 경우 .create() 의 경우 BackpressureStrategy 를 미리 지정해줘야 하게 된다. Back pressure 을 책임져야 하는 존재는 source이어야 하며, chain이 아니라는 철학이 반영되었다.

Null

RxJava 1 에서 자주 보이던 Observable.just(null), 특히 RxBinding 을 같이 쓰는 프로젝트에서 아주 자주 보게 되는데, RxJava 2 에서는 더는 null을 정상적인 값으로 받거나 넘기지 않게 된다.

위와 같이 null을 값으로 받거나 넘기게 될 경우, NullPointerException 으로 취급해서 에러로 핸들 하게 된다. Observable 의 경우 오로지 완료되거나 오류를 던지는 것 외에는 값을 넘길 수 없게 되며, API 제작자들은 enum 타입으로 분리하던 객체를 이용해서 Observable 처럼 내려줄 객체를 정의해야 한다.

Functional Interfaces

RxJava 1.X 와 2.X 모두 Java 6의 호환을 고려하고 설계되어서 Java 8의 java.util.function.Function 에서 제공하는 Functional Interface 들을 사용하지 못해서, RxJava 내부에서 별도로 정의했었다. 단지, 이제 RxJava 2.X부터는 Exception을 핸들 하게 되어서, 더는 .map() 안에서 try-catch 코드를 보지 않아도 된다! 예를 들면, 아래와 같다:

Testing

RxJava 1.X의 TestSubject 가 드디어 없어졌다! TestSchedulerPublishSubject 를 통해서 손쉽게 테스팅을 할 수 있게 되었다. 개인적으로는 유닛 테스트 작성할 때 PublishSubjectRxJavaPlugins로 구현했었는데, 이제 injection을 통해서 스케쥴러를 주입할 수 있게 짜는 방향으로 가게 될 것 같다.

Observable vs Flowable

상용 프로젝트 환경에서 Rx 스트림 내에서 MissingBackpressureException 혹은 OutOfMemoryError 을 겪게 되면 매우 대응하기가 난감하다. 특히 RxJava 1 에서는 백프레셔를 대응할 수 있는 오퍼레이터인지 아닌지, 그리고 어떻게 대응을 할지에 대해서 알기 위해선 일단 한번 당하고(?) 문서를 확인하며 배우는 방식이다. 근본적으로 Hot Observable은 올바르게 백프레셔를 처리하기 힘들며, 애초에 RxJava 1.X에서는 Hot Observable과 Cold Observable으로 명확하게 나뉘어있지 않기 때문이다.

RxJava 2.X에서는 이 둘을 명확하게 나누기 위해서 백프레셔가 일어나지 않는 Observable과 백프레셔를 주의해야 하는 Flowable로 나누게 되었다. 아래는 RxJava 위키에서 정의하는 Observable과 Flowable 을 사용해야 할 때를 정리한 것이다:

Observable을 사용 해야 할 때

  1. 흐르는 객체에 따라 다르겠지만, OOM이 일어날 수 없을 정도의 객체의 개수를 관리할 때
  2. 터치이벤트나 클릭 좌표 이동 등 sampling 또는 debouncing으로 쉽게 핸들 할 수 있는 GUI 이벤트 사용할 때
  3. 이때 항상 유의해야 하는 것은, Observable이 Flowable 보다 적은 자원을 필요로 하다는 점이다

Flowable 을 사용 해야 할 때

  1. 수많은 객체들이 생성되며 흘러내려 올 때 chain이 생성하는 소스의 수를 제한할 수 있을 때
  2. 디스크에서 읽기 혹은 파싱 해서 객체를 내려줄 때
  3. JDBC를 통해서 데이터베이스에서 읽고 내려줄 때
  4. 네트워크 IO를 통해서 값을 내려줄 때

Single, Completable and Maybe

Single

싱글 타입은 하나의 onSuccess 혹은 onError 만을 내려줄 수 있는 타입이다. 소비자 역할이 되는 SingleObserver은 3개의 메소드만 가지고있는 인터페이스이며 API는 아래와 같다:

Completable

Completable 타입은 onComplete 혹은 onError 만을 내려줄 수 있는 타입이다. 소비자 역할이 되는 CompletableObserver은 3개의 메소드만 가지고 있으며 API 는 아래와 같다:

Maybe

RxJava 2.0.0-RC2 에서 Maybe 라는 새로운 베이스 타입을 소개했다. 이론적으로는 위의 Single 과 Completable 을 합친것과 같다. API 는 아래와 같다:

결론

결론다시보기

Reference

  • https://msdn.microsoft.com/en-us/library/hh242985(v=vs.103).aspx
  • http://www.reactive-streams.org/
  • https://github.com/ReactiveX/RxJava/wiki/What’s-different-in-2.0
  • https://artemzin.com/blog/reply-to-kaushik-gopals-aricle-rxjava-1-rxjava-2-understanding-the-changes/
  • http://blog.kaush.co/2017/06/21/rxjava1-rxjava2-migration-understanding-changes/
  • https://blog.mindorks.com/migrating-from-rxjava1-to-rxjava2-5dac0a94b4aa

300만 다운로드를 통해서 배운 교훈들

2011년 1월 여름방학에 인턴십을 구하거나 친구들을 만나서 시간을 보내지 않고, 방구석에서 Class Timetable의 첫 번째 버전을 만들었다. 2011년으로부터 딱 1년 전부터 사용하기가 쉽고 단순한 시간표 관리 앱을 찾았으나, 기존의 앱들은 너무 복잡하고 사용하기가 힘들었다. 그날로부터 나는 종이로 만든 시간표보다 더 쉽고 편리한 솔루션을 만들기 위해서 약 500시간을 넘게 사용한 거 같다.

현재 Class Timetable 앱은 300만 이상의 다운로드와 많은 긍정적인 앱 스토어 평가를 받았으며, 한때에는 나의 가장 큰 수입원이기도 했다. 아직 다양한 국가에서 홍보 및 관리를 활발하게 하지 않아서 못 들어 봤을 수도 있지만, 호주, 뉴질랜드, 영국의 학생들에게는 꽤 인기가 있다.

Class Timetable App

앱 스토어의 추천을 받고 하루에 10만 다운로드 이상을 기록하며 대박을 친 앱들에 대한 정보를 여러 블로그에서 접했다. 그 앱들과 비교하면 나의 앱은 그저 적당한 성공일 것이다. Class Timetable은 한 번도 앱 스토어 1등 자리까지 간 적이 없으며, 나 또한 하루아침 사이에 떵떵거리는 부자가 되지 않았고, 실패한 횟수가 성공한 횟수보다 훨씬 많았다. 심지어 주말 사이에 만들어서 대박을 친 앱들과 비교하면 나는 훨씬 더 많은 시간을 소모했으며, 비록 300만 다운로드는 축하해야 하는 수치이긴 하지만, 이것은 하루아침이 아닌 6년 이상의 시간이 걸렸다.

Class Timetable 다운로드 통계

대신, 나의 “적당한 성공” 이야기는 느리고 꾸준한 성장 이야기에 더 가깝다. 신화처럼 접하게 되는 다른 성공 사례들보다 가장 현실에 가까운 이야기일 것이다. Class Timetable은 6년째 꾸준히 성장 하고 있으며, 그 자체만으로 이미 조금 특별하다. 많은 1위의 앱들은 수명이 짧은 경우가 허다하기 때문이다. 나는 지금부터 지난 몇 년 동안 배운 몇 가지 사실을 알려 드리고자 한다. 아무리 많은 성공을 경험했든, 아직 경험하지 못한 사람이든 유용하게 활용할 수 있는 곳이 있기를 바란다.

하나의 성공한 앱을 만들기에 위에 수많은 실패한 앱들이 존재했다

나는 여전히 무수히 많은 실패한 앱들 중 일부는 훌륭한 아이디어라고 생각한다. 단지 시기적절한 마케팅이 아니여서, 혹은 운이 없어서 일 수도 있다는 미련을 아직도 가지고 있다. 그중 ‘Ginge-O-Meter’ 이라 불리는 앱이 있었는데 정말 많은 시간과 노력을 투자했었다. 이 앱은, 사람의 사진을 찍으면 그 사람의 머리가 얼마나 빨간지 측정해주는 앱이었다 (의역: 19세기부터 서양권에서는 빨간 머리의 사람들을 Ginger 이라고 불러왔으며, 현재는 영혼이 없다는 둥 많은 농의 대상이 되기도 한다.). 유머성 앱이 아닌, 실제 이미지 인식 기술을 사용해서 색상 분석 기술을 사용해서 작동하는 앱이었다.

아쉽게도 별로 인기는 없었으며, 금액으로는 50만 원도 수익 내지 못했다. 솔직한 심정으로는 정말 많은 노력을 부었기에, 실패했을 때는 어마어마하게 실망스러웠다. 하지만 거기서 멈추지 않아서 지금의 Class Timetable이 존재하게 되었다. 어쨌든, 여기서 내 요점은 야심에 찬 아이디어가 실패하더라도 좌절하지 않고, 다시 도전하고.. 다시 도전하고.. 다시 도전하는 것이 중요하다는 것이다: 다음 아이디어는 성공할 것이라는 걸 믿으며.

최초 사용자로서 모든 디자인을 설계해라

앱의 기능을 못 사용하겠다고 소리를 지르며 고치라고 항의하는 이메일을 받는다고 상상해보라. 꽤 짜증이 날 것이다. 이런 유사한 이메일을 여러 번 받다 보면, 더 사용하기 쉽고 간단한 제품을 만들어야겠다고 생각이 든다. 내가 이제껏 느낀 것은, 제품을 기획하고 디자인 할 때는 컴맹을 사용자 대상으로 지정하는 것이다. 간단하게 만들기 위해서는, 잘 못 사용하는 경로를 최소화 하는 것이 매우 중요하다. 모든 기능이 명확하고 간단한 흐름을 가지게 설계를 하면, 고객지원에 소모하는 시간이 줄 것이며, 전반적으로 사용자들 또한 행복해할 것이다.

Class Timetable이 최초로 일 평균 1000 다운로드를 기록할 때에, 나는 평균적으로 주당 20통 가까이 이메일을 받았다. 사용자들이 항의하는 기능 위주로 앱을 개선해 나가다 보니, 현재에는 주당 한두 통 받으며, 대부분은 항의 이메일이 아니고 새로운 기능에 관한 제안들이다. 그리고 정말 가끔은 팬레터도 받는다 (진짜로!)

비평가들의 말은 듣되, 그들의 의견을 맹목적으로 믿지는 말아라

나는 여태껏 고객들에게서부터 수백 개가 넘는 새로운 기능에 대한 요청을 받았다. 이 모든 기능을 다 넣었다면, Class Timetable은 매우 방향성이 모호한 앱이 되었을 것이다. 만약 새로운 기능 요청 중에서 현실적인 요청들만 포함 시켰더라도, 결과는 크게 다르지 않을 것이다. 사용자들이 마음에서 우러나온 제품에 대한 방향을 제시하더라도, 이 해결책이 항상 정답이지는 않기 때문이다. 그렇다면 우리는 무엇을 해야 할까?

사용자들의 의견을 들어라. 그들이 표현하는 불만과 경험하는 문제들을 자세히 관찰하다 보면, 더 깊은 곳에 근본적인 원인이 존재할 것이다. 그리곤 그 원인을 제거 / 개선하며 제품의 방향 앞으로 발전해 나가는 것이다. 때론 매우 좋은 기능 요청도 제품의 전체적 완성도에 해가 될 경우가 있으며, 이럴 경우 과감하게 쳐내야 한다.

Class Timetable의 가장 큰 장점은 간단함과 좋은 사용성이었다. 제안받은 몇몇 기능들은 기간에 걸쳐 추가되었으면 좋았겠지만, 대부분은 앱의 복잡성만 늘렸을 것이다. 나는 간결함을 중점으로 개발을 진행했으며, 이 간결미는 Class Timetable을 매력적으로 만들었다.

많은 다운로드 수를 통한 눈속임보다, 하나의 좋은 제품이 더 월등하다

Class Timetable은 단 한 번도 앱 스토어 매인 화면에 올라가지 못했다. 하루에 10만 다운로드도 이루지 못했지만, 이것은 나한테 아무런 의미가 없었다. 많은 앱은 차트에서 1위를 해도, 일 년 뒤엔 버려지는 경우가 허다하다. 기발한 이름이나 바이럴 마케팅을 통해서 사용자들을 끌었어도, 사용자들의 실제 문제를 해결하지 않기 때문이다.

진정으로 좋은 제품을 만드는 것은 사용자들이 계속해서 재사용하는 제품을 기획하는 것이다. 다수의 사용자는 눈치조차 못 채는 기능에 시간과 노력을 투자해라. 사용자들이 가지고 있는 진짜 문제를 해결하는 것에 집중해서, 그 사용자들이 다른 사용자들을 끌어 오게끔 하여라. 재방문하는 사용자들이 많은 것은 제품에 대한 좋은 청신호이다.

인자해져라

Class Timetable이 처음으로 앱 스토어에 올라갔을 때는 1달러짜리 유료 앱이었다. 나는 내가 500시간 이상을 투자한 제품을 사용자들이 1달러로 사용하는 것은 거저 가져가는 거나 마찬가지라고 생각했다. 첫 주에 단 네 명의 사람이 다운로드를 했다. 심지어 매주 지날수록 신규 다운로드 수는 계속 떨어졌다. 잭팟을 터트리는게 어떤 느낌인지는 몰랐지만, 적어도 이건 아니라는걸 알았다. 500시간을 변기 속으로 버리는 기분을 느꼈다!

이때 나에겐 두 가지 결정권이 있었다:
1. 이대로 내버려둬서 매우 천천히 앱을 죽이거나
2. 앱을 무료로 풀어서 많은 사용자가 계획표를 작성할 때 겪는 문제들을 해소한다

무료로 앱을 공개하는 순간 다운로드 수는 하루에 50건, 100건, 1,000건 … 으로 상승하기 시작했다. 얼마 지나지 않아 나는 추가 기능을 풀 수 있는 결제 기능을 배포했으며, 꽤 많은 사용자가 이 기능을 사용한다. 뭔들 주당 1달러 보다는 나았을 것이다! 결론은, 쪼잔하게 굴지 마라! 아무도 안 쓰는 비싼 제품보다는 많은 사람이 쓰는 무료 제품이 더 좋다! 기존의 사용자에게 업셀링을 하는 것이, 새로운 사용자에게 결제를 유도하기보다 더 쉽기 때문이다.

한보 물러서서 생각해라, 매우 자주

가끔은 좋은 해결책이 없어서 한 문제에 막혀서 오래 고민하는 때도 있을 것이다. 이 고민은 작성하고 있는 코드에 관한 것일수도 있고, 앱의 마케팅을 어떻게 해야 할지에 관한 고민일 수도 있다. 그럴 땐 한보 물러서서 더 큰 그림을 보도록 노력해라. 설계를 올바르게 하면 코드에 관한 문제는 해결되는 것처럼, 마케팅 문제 또한 더 능력과 경험이 있는 사람이 쉽게 해결할 수 있는 문제일 수도 있다.

나의 전체 소프트웨어 개발 커리어중에서 단 한 번도 물러서서 생각하는 것을 후회한 적이 없다. Class Timetable을 만들면서 그 필요성에 대해서 더 강하게 느끼게 되었다. 예를 들어 Class Timetable 1.0 버전을 만들면서 나는 완전히 몰입하며 코딩만 하며 시간을 많이 사용했었다. 코드 관련 문제가 있을 때마다, 한보 물러서서 생각하지 않고, 구현하는 것에 더 집중해서 문제를 해결했었다. 몇 년 뒤에 나는 너무나 많은 코드 품질 문제로 코드베이스를 통째로 다시 짜야 하는 노고를 겪게 되었다.

막혔을 때는 한 걸음 물러서서 생각해라!

결론

오늘날까지 Class Timetable은 잘 유지되고 있다. 나는 iOS 업데이트 혹은 Class Timetable의 추후 방향과 같은 고민을 하며 계속해서 미래를 준비하고 있다. 혹시 이 글을 읽고 있는 당신이 학교에서 공부를 하고 있다면 언제든지 유용하게 사용 하길 바란다.

참조

이 문서는 외부 블로그 글을 번역한 내용입니다.

AsyncTask와 비교해서 당신이 RxJava를 당장 써야하는 이유

AsyncTask를 이용해서 네트워크 코드 혹은 데이터베이스 읽고 쓰는 코드 등을 잘 개발해왔는데, 최근들어 RxJava 가 새로운 트렌드 같습니다. 그냥 단순한 트렌드 인가요? 혹은 이 러닝 커브를 극복해야할 만큼 장점이 있는지 잘 모르겠습니다.

Intro

AsyncTask 는 설계상 수 많은 문제점들이 존재한다. 제대로 된 의존성 관리가 안되어서 테스트를 하기가 매우 어렵고, IO 관련 로직을 UI 레이어에서 처리해야 하는 점, 메인 스레드 외에서는 시작을 할 수 없는 것, 메모리 누수가 일어나기 매우 쉬운점, 등 수많은 불만과 이슈들이 존재한다.

먼저 확실히 하자면, RxJava는 AsyncTask의 문제를 해결하지 않는다. 단지 AsyncTask 가 설계적으로 가지고있는 문제점들이 극복하는데 사용되는 노력과 코드가 훨씬 적을 뿐이다. 더군다나, 훨씬 더 유연하고 강력한 도구가 생겼는데, 굳이 오래되고 녹이 다 슬어 버린 도구를 지향 할 이유는 하나도 없다. 새로운 변화와 배움을 받아들이는 자세가 중요한 것이 아닌가 생각이 든다.

Case Study: RxJava & AsyncTask

연쇄 API 호출, 평행 API 호출, 테스팅, UI 로직 handling, 등 수 많은 예제들이 있지만, 가장 많이 사용되는 연쇄 API 호출에 대해서 설명하겠다.

연쇄 API 호출

AsyncTask 에 비교해서 RxJava의 유연함을 가장 쉽게 보여 줄 수 있는 예제는 아마 연쇄된 네트워크 통신일 것이다. 아래의 코드는 가상의 서버에 User API 를 호출한 뒤에, 그 사용자의 ID 값을 다시 서버에 보내서 유효한 사용자인지 아닌지 알아야하는 상황이라고 가정하자. AsyncTask 로 구현하면 아래와 같을 것이다.

AsyncTask

이에 반해, RxJava 에서의 구현은 훨씬 더 간결하다. (RxJava 외에 Retrofit, Retrolambda 처럼 시너지가 좋은 라이브러리들을 사용했다고 가정했다)

RxJava

여러 API 는 꼭 네트워크 호출이 아니여도 상관이 없다. 사용자의 버튼입력에 따른 로직 추가도 RxBinding 을 이용하면 몇줄의 코드로 구현이 가능하고, 코드 리뷰를 해야하거나 예전에 짠 코드를 다시 읽어야 할때도 보다 편하게 흐름을 파악 할 수 있다.

RxJava 에서 수 많은 Operator 들의 조합으로 수만가지의 상황을 해결하는 도구상자가 완비되는 것이다. Operator, 즉 연산자를 연마하는데 걸리는 시간만 극복하고 나면 모든 곳에서 적용을 하고싶을 정도로 즐거워진다. 비교하자면, 어릴적 구구단을 외울때는 고통스러웠으나, 시간이 흘러 응용 문제들을 풀며 기쁨을 느낀 시절과 비슷하다.

Conclusion

개발은 공예다. 비록 다른 공예에 비해 역사적으로 기간이 길지는 않지만, 개발 또한 인간의 생활주변에서 주로 사용되며, 미적 효과를 가진 도구를 제작하는 과정이기 때문이다. 통일신라 시절 도자기를 빚던 도예가들이 자연 시유의 기술을 시도 하지 않았었다면, 우리가 흔히 높은 품질로 자랑하는 고려청자는 세상에 존재하지 못했을 것이다. AsyncTask의 망령에 잡혀 사는 당신, 도예가들의 정신을 이어 받아, 새로운 장을 받아들이는건 어떨까

I am an ordinary developer. If this blog post contains any wrong information or improvements, please feel free to add comments! I would love to hear it, to improve and learn together. 😀

Reference

  • https://realm.io/news/gotocph-jake-wharton-exploring-rxjava2-android/
  • https://www.reddit.com/r/androiddev/comments/63f78o/can_someone_explain_why_is_rxjava_is_better_than/
  • https://stackoverflow.com/questions/2735102/ideal-way-to-cancel-an-executing-asynctask
  • http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html
  • http://blog.daum.net/_blog/BlogTypeView.do?blogid=0Tc6T&articleno=25&categoryId=4&regdt=20101227214814

RxRealcase 2장: 네트워크가 불안정한 존을 위한 Exponential Backoff

About

RxRealcase 시리즈는 가상의 개발자와 가상의 앱을 이용해서 RxJava Operator 들의 실제 적용 사례를 정리한 글이다. 가볍게 읽을 수 있으며, 필요할때 적용 할 수 있는 의미로 작성하고 있다. 이번 장은 RxJava를 적용하며 불안정한 네트워크에서 비교적 우아하게 서버에 재호출을 하는 방법에 대해서 고민을 하는 글이다. https://github.com/wotomas/Findeed 에서 전체 코드가 언젠가 공개될 예정이다. #experienceMatters

시나리오

이상

존은 성공한 사업가로서 필수적으로 설치를 해야한다는 Findeed 라는 앱을 자주 사용한다. 사업차 해외로 출장을 자주 가는 존은 간혹 네트워크가 불안정한 나라에서도 Findeed 를 아무 문제없이 사용한다. 네트워크가 불안정한 곳에서 접속 할때에는 존은 향긋한 헤이즐넛 라떼를 마시며 기분 좋게 기다린다.

하지만…

현실

존은 가끔 출장 중에 Findeed 라는 앱을 사용하지만 네트워크가 불안정한 곳에서는 실행이 정상적으로 안되는 앱을 보면서 한숨만 푹푹 쉰다. 투자자의 연락을 확인해야하는 존은 수동으로 재시도를 몇번이나 시도한 끝에 겨우 접속에 성공하였다. 답변하기 버튼을 클릭하는 순간 네트워크가 불안정하다며 앱이 종료되어버린다.

해결

개발도상국에서 Findeed 의 접속자가 많아 지기 시작하자 앱 개발자는 네트워크 에러 핸들링을 개편하기로 결정한다. 기존에 실패시에 1초마다 3번 재시도를 하는 로직을 수정하기로 결정 한 것이다. 재시도 횟수에 따라 호출의 간격이 길어지면 좋겠다고 생각하며 개발자는 다시 한번 Rx의 심해로 빠져보기로 한다.

1. repeatWhen()

개발자는 repeatWhen() 을 사용하니 네트워크 호출이 정상적으로 돌아와도 다시한번 호출을 하게 되는것을 확인하였다. 네트워크 에러가 생길시에만 재호출을 하고 싶지만, repeatWhen()onComplete()이 호출될때 다시 Subscription 이 생성되는걸 확인했다. 이번 구현에는 부적합하다고 판단한 개발자는 다른 걸 찾아 보기로 했다.

2. retryWhen() + timer()

조금만 더 찾아보다 보니 개발자는retryWhen()을 발견했다! onError() 이 발생할때 다시 Subscription 을 생성할 수 있게 되니 개발자는 써보기로 결정했다. timer() 과 합쳐서 구현하면 간단한 재시도는 구현할 수 있겠다고 판단했다. 하지만 더 우아한 해결책을 원한 개발자는 더 찾아 보기로 결정한다.

3. retryWhen() + zipWith() + range() + timer()

개발자는 여러개의 operator들을 묶으면 구현 할 수 있겠다고 판단하였다. retryWhen()range()zipWith() 시켜서 재시도 횟수를 구현하고, timer() 을 이용해서 인터벌이 늘어 날 수록 다음 시도 간격을 증가 시키는 방법으로 결정하였다.

구현

코드가 조금 긴 관계로 Gist 를 확인하면 완성된 코드를 확인 할 수 있다.

Unit Test

RetryWithExpoentialBackOff 의 경우 testSubscriber 을 이용하여 다양한 상황을 테스트 한다.

Reference

  • 글에서 사용된 Findeed 라는 앱은 아직까지 공개 / 개발 되지 않은 가상의 앱이다.
  • https://en.wikipedia.org/wiki/Exponential_backoff
  • http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/
  • https://gist.github.com/sddamico/c45d7cdabc41e663bea1
  • https://stackoverflow.com/questions/22066481/rxjava-can-i-use-retry-but-with-delay/25292833#25292833
  • http://leandrofavarin.com/exponential-backoff-rxjava-operator-with-jitter

RxRealcase 1장: 성격이 급한 존을 위한 끊김 없는 데이터 전송

About

RxRealcase 시리즈는 가상의 개발자와 가상의 앱을 이용해서 RxJava Operator 들의 실제 적용 사례를 정리한 글이다. 가볍게 읽을 수 있으며, 필요할때 적용 할 수 있는 의미로 작성하고 있다. 이번 장은 RxJava를 적용하며 비교적 빠른 디스크 캐시와 비교적 느린 네트워크 호출을 끊김 없이 사용자들에게 가공해서 보여주는 방법에 대해서 고민을 하는 글이다. https://github.com/wotomas/Findeed 에서 전체 코드가 언젠가 공개될 예정이다. 당당하게 말하라, 불필요한 로딩 화면은 게으른 기획이다. #experienceMatters

시나리오

이상

존은 아침에 일어나며 Findeed 라는 앱을 킨다. 로딩 화면을 보면서 존은 느긋하게 아침 커피를 내리며 로딩이 끝나기를 기다린다. 로딩이 마치면 존은 향긋한 아침 커피를 마시며 피드를 읽으며 기분좋게 하루를 시작한다.

하지만…

현실

존은 아침에 일어나며 가끔 Findeed 라는 앱을 키지만, 로딩 화면을 보면서 존은 앱을 종료하고싶다는 충동을 느끼며 기다린다. 심지어 인터넷이 느린곳을 가거나 로딩중에 인터넷이 불안정하면 로딩이 정상적으로 끝나지도 않는다. 대부분의 사람들은 존 처럼 로딩이 끝날때까지 기다릴 정도로 자애롭지 않을 것이다.

해결

성격이 급한 존을 위해서 앱 개발자는 안드로이드 로컬 데이터베이스에 캐싱 하기로 한다. 존의 핸드폰이 피드를 요청 할 시에, 상대적으로 빠른 경우가 많은 캐쉬를 먼져 보여준 다음에 네트워크 피드가 들어올 경우에 마술처럼 업데이트를 시키는 방법을 택하기로 했다.

1. Concat

.concat() 을 사용하자니 첫번째 옵져버블이 끝나기 전에는 두번째 옵져버블은 시작도 안하는것을 확인하였다. 개발자는 네트워크 요청과 캐쉬 요청이 동시에 시작했으면 좋겠기 때문에 다른 걸 찾아 보기로 했다.

2. Merge

Operator 의 심해에서 뒤적거리다보니 개발자는.merge()을 발견했다! 개발자는 동시에 요청을 보내고 여러개의 스트림을 종합해서 받을 수 있으니 최고라고 생각했다. 10년이 넘은 먼지 쌓인 핸드폰으로 테스트 도중 로컬 캐쉬 결과가 더 늦게 오는 경우에 네트워크을 통해 새로 들어온 데이터가 덮어 씌여지는 경우를 확인했다! 불안정하다고 생각한 개발자는 다른 방법이 있을꺼라고 생각하고 더 찾아 보기로 했다.

3. Publish & Merge & TakeUntil

구석에 쳐다보고 관심 주지 않았던 .publish()가 관심을 가져달라고 얼쩡거렸다. 개발자는 selector 을 받을 수 있는 .publish().merge().takeUntil() 을 조합하면 뭔가 나올 것 같다는 느낌을 받았다. 상대적으로 느릴 확률이 높은 네트워크 호출을 publish() 하여 캐쉬 호출을 selector로 지정하지만, takeUntil() 을 이용해서 네트워크가 오기 전까지만 캐쉬를 쓰게끔 구현하는 방식으로 결정하였다.

구현

Unit Test

PublishSubject를 이용해서 시간차로 호출이 돌아오는 경우를 5가지 케이스로 나눈다.
1. 네트워크 데이터가 캐쉬 데이터보다 빠르게 올 경우
2. 캐쉬 데이터가 네트워크 데이터보다 빠르게 올 경우
3. 네트워크 데이터가 유실 되었고, 캐쉬 데이터만 있을 경우
4. 캐쉬 데이터가 유실 되었고, 네트워크 데이터만 있을 경우
5. 둘다 없을 경우

Reference

  • 글에서 사용된 Findeed 라는 앱은 아직까지 공개 / 개발 되지 않은 가상의 앱이다.
  • concat(), concatEager(), merge() 모두다 많이 사용되는 개념이지만, 대부분 네트워크가 무조건 더 느리다는 전재가 필요하기때문에 이것이 충족이 안될시 잘못된 state에 빠지게된다. (merge() out-of-order problem)
  • https://github.com/kaushikgopal/RxJava-Android-Samples
  • http://blog.kaush.co/2015/01/21/rxjava-tip-for-the-day-share-publish-refcount-and-all-that-jazz/

안드로이드에서 Clean Architecture의 흔적과 장점

불필요한 코드 삭제할때 흔한 경우

Intro

위의 GIF 파일을 보고 실소를 한 개발자들이 많을 것이다. 그 만큼 불필요한 코드라고 판단하고 리펙토링 했는데 전혀 뜬금없는 곳에서의 에러를 겪은 상황이 만연하게 일어난다는 증거이기도 하다.

As the number of unintended defects rose … they feared the changes would do more harm than good. Their production code began to rot.
-Robert C. Martin (Clean Code)

클린코드 에서 Robert C. Martin (a.k.a Uncle Bob) 이 코드가 썩어가는 과정을 비약적으로 표현한 예제이다. 예상치 못한 고장이 많아지기 시작하며, 개발자들은 수정을 두려워 하기 시작하며, 코드는 점차 썩어버리는 것이다. 왜 프로젝트가 커질수록 저런 경우가 많아지는것일까? 책의 제목이 설명하듯 Clean Code 이지 않아서이며, 더 나아가 Clean Architecture 이 아니기 때문이다.


Architecture

Clean Architecture 에서 사용되는 Architecture의 정의를 한번 살펴보자.

The process and product of planning, designing and constructing concepts

잘 만들어진 건물의 도면도를 보면 추상적으로 공간이 어떻게 사용되는지 상상이 가능하다. 건축학도가 도면도만 보아도 그 공간이 학교인지 가정집인지 머리속에서 상상 할 수 있듯이, 개발자들도 서비스의 도면도를 보고 어떤 기능을 제공하는 서비스인지 상상 할 수 있어야 한다. 마찬가지로, 깨끗하게 구현된 서비스의 코드를 보고, 도면도로 그려 낼 수 있어야 한다. 도면도로 도저히 분리시켜 낼 수 없을때, Dirty Architecture로 구현하고 있는지 확인해야 할 때다.

즉, 새로운 개발자 (건축가)가 프로젝트에 참여 할 경우, 도면도만 보고 어떤 서비스인지 머리속에서 그려진다면 Clean Architecture 인 것이다.
건물의 평면도


Clean Architecture

소프트웨어 개발이 발전하면서 코드가 썩는것을 방지하면서 유지보수가 가능한 프로젝트를 만들기 위해 수많은 Architecture 들이 정의 됬으며, 이들은 모두 Separation of Concerns 라는 공통된 목표를 가지게 되었다. 유지보수를 위해 레이러로 나눌때의 최적화된 규칙을 찾는것인데, Uncle Bob의 Clean Architecture은 하나의 아우르는 규칙을 제시하는데, 이것이 The Dependency Rule 이다.
그래서 이게 무슨뜻이야!


The Dependency Rule

Dependency Rule이란 위의 그림을 기준으로 코드의 의존성은 오로지 안으로만 향하는 것이다. 단적으로 예를들면, 밖의 레이어에서 정의된 모든 객체는 안의 레이어에서 언급조차 되지 않는것이다. 즉, 함수, 객체, 변수의 이름에서 조차 다른 레이어와의 연결고리를 끊는것이다. 간단하게 안드로이드의 예로는 Framework Layer에 포함되는 Activity, Fragment, Context 등이 Interface Adapter Layer에 포함되는 Controller, Presenter, Repository 등에서 찾을 수 없어야 한다는 뜻이다.

그렇다면 거꾸로 Presenter 에서 Activity로 무언가를 알려줘야 할 경우에는 어떻게 해야할까? 여기서 사용되는 개념이 Dependency Inversion Principle이다. 인터페이스를 이용해서 의존관계와 반대로 흐름을 제어 할 수 있게 하는 방법인데, Presenter 에서 View Interface 를 이용해서 동작을 제어하는것, Repository Interface 를 이용해서 데이터베이스의 데이터를 가공하는것 등이 있다. 더 자세하게 알고싶다면 Uncle Bob’s Clean Architecture 을 꼭 읽어보길 추천한다!


Example

플리토의 앱을 부분적으로 분석해보았다. 화살표의 방향이 의존관계를 나타내며, 플리토 앱은 현재 Clean Architecture 에 의거하여 분리시키며 리펙토링을 진행하는 중이다.
Flitto App 평면도 나열


Framework & Drivers

가장 상위층인 4층에는 가능한 최소한의 로직들이 담긴 클래스들이 포함되어있다. 오로지 아래층으로 데이터를 넘겨 줄 수 있는 접착제 같은 역할만 분담하고 있으며, 안드로이드의 경우 Context 가 필요한 모든 존재들을 이 레이어에서 해결한다. 그 외에도 외부 모듈이나 제어가 불가능한 존재들은 모두 이곳으로 포함시켰다.
Flitto Example: Activities, Fragments, Services, Network, Analytics, Database, etc
4층 평면도


Interface Adapter

3층에서는 아래층과 윗층이 소통을 할 수 있게 데이터를 변환시키는 레이어다. 4층에 대해서 전혀 몰라야 하며, 이는 특정 화면을 Activity에서 Fragment로 변환시킨다고 3층 아래의 코드가 수정 될 일이 전혀 없어야 하는 것이다. 같은 맥락으로 데이터베이스를 다른걸로 봐꾸던, 센서를 다른걸 이용하더라도 수정될 일이 없어야 한다.
Flitto Example: Presenters, Views, MediaWrapper, Repositories, OrientationManager, RetrofitController, etc
3층 평면도


Application Business Rules

2층에서는 구축하는 시스템의 Use Case 가 모두 정의 되어있는 층이다. 3층과 마찬가지로, 3층의 코드가 수정이 된다고 해도, Use Case의 정의가 수정되는 일은 없어야한다. 즉, Repository 에서 기존에 데이터베이스에서 바로 데이터를 주던, 메모리 케쉬를 통해서 최적화를 해서 주던, Use Case의 로직은 수정이 되지 않아야 하는 것이다.
Flitto Example: 플리토의 경우 Presenter 가 들고있는 subscription 을 각각의 Use Case로 정의했다. 코드 리펙토링을 거쳐 재사용성을 위해 추후에 Use Case 로 분리 할 예정이다.
2층 평면도


Enterprise Business Rules

1층의 경우 서비스의 비즈니스 객체가 경우가 많다. 안드로이드의 경우 Plain Old Java Object (POJO) 가 되는 경우가 많으며, 기획 단계에서 비즈니스 로직의 수정이 있지 않는 이상, 서비스의 다른 층의 수정으로 이 계층이 수정 될 일은 없어야 한다.
Flitto Example: Model POJOs
1층 평면도


Flitto App Blueprint

Conclusion

위의 그림은 여러 층의 평면도가 합쳐서 만들어진 정리된 플리토 앱의 도면도이다. 마치 왼쪽의 주택의 도면도 처럼 층별로 분리가 되어있으며, Dependency RuleDependency Inversion Principle 을 이용하여 객체간 책임감 분리는 넘어서 계층간의 책임감도 분리시켜냈다.

Clean Architecture을 이용하여 새로운 개발자가 들어왔을때 시스템에 적응 기간을 효과적으로 단축 시킬 수 있으며, 리펙토링의 방향을 올바르게 정할 수 있게 되는 것이다. 시간이 지나며 코드가 썩는것을 방지하는 방부제가 되는 것이다. 레이어간의 책임감을 분리 시킴으로서 새로운 기능을 추가하거나 기존의 기능을 수정 해야 할때 예상치 못한 곳에서 오류가 생기는 것을 미연에 방지하는 예방책인 셈이다. 위에서 언급했듯이 더 자세하게 알고싶다면 Uncle Bob’s Clean Architecture 을 꼭 읽어보길 추천한다!

플리토의 앱을 발전 방향을 잡는 나침판이 되었으면 좋겠고, 모두 클린 코드에 대한 전반적 정보 공유를 위해 글을 썼으며, 틀린 정보가 있다면 같이 수정하며 배워 나가고 싶다.
I am an ordinary developer. If this blog post contains any wrong information or improvements, please feel free to give feedbacks! I would love to hear, improve and learn together. 😀


Extra

여담으로 플리토에서 함께 안드로이드 앱을 개발 하실 개발자를 찾습니다. 함께 좋은 서비스를 위해 고민하고 안드로이드 개발을 좋아하시는 분이라면 환영합니다! 같이 토론하고 지식을 공유하고 지속적으로 실력이 늘고싶습니다!

많이 지원해주세요!


Reference

  • https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
  • https://fernandocejas.com/2015/07/18/architecting-android-the-evolution/
  • https://github.com/android10/Android-CleanArchitecture
  • https://github.com/googlesamples/android-architecture/issues/190
  • Clean Architecture Getting Clean, Keeping Lean by Joe Birch from Boston DroidCon 2017

Android Debug Tools: 디버깅 도구 총정리

안드로이드 개발에 사용되는 수많은 디버깅 툴 중에서 편리한 도구 몇 가지를 정리하기 위함. 아래에 사용된 앱은 DebugTools 참조 가능.

디버깅 툴 List Up

Stetho

Facebook 에서 만든 안드로이드 개발용 디버깅 플랫폼. Chrome inspect tools (chrome://inspect/) 를 사용해서 앱의 UI, database, SharedPreference, network status 등을 디버그할 수 있게 함.

사용법

앱 실행 후 크롬에서 chrome://inspect/ 확인 시 하단 inspect button 클릭 후 디버깅 화면으로 진입 가능

Chrome Inspect Button

네트워크 디버깅

Network Call Example

Stetho는 네트워크 호출이 있을 때마다 intercept를 통해 사용자에게 정보를 제공 할 수 있다. Response 정보뿐만 아니라 Header 정보, latency, size, 이미지 미리 보기 까지 한눈에 볼 수 있어서 네트워크 관련 디버깅 툴로 매우 효율적이다.

Network Header

데이터베이스 디버깅

Stetho 의 경우 Database inspection 또한 매우 간편하게 이뤄진다.
Resources → Web SQL → 해당 DB를 찾아서 클릭할 경우 직접 쿼리문 작성 후 실시간 값 변화 확인 가능합니다.

Database Query Example

Shared Preference도 database와 마찬가지로 매우 간편하게 확인 가능. Resources → Local Storage → 해당 pref name 찾아서 클릭 시 수치 확인 후 수정 가능 (더블클릭 후 값 수정)

SharedPreference Example

DebugDrawer

이름 그대로 Debug module 들을 모아놓은 drawer이다. 다양한 디버깅 모듈을 포함해서 자신에게 필요한 모듈을 장착한 customized drawer 을 가질 수 있게 되며 필요한 기능이 있을 시 언제든지 확장이 가능하다. 피카소, 글라이드 등 이미지, OkHttp, Location, 네트워크 모듈 등이 자주 사용되며, custom 모듈도 제작 가능하다.

Debug Drawer Example

사용법

Custom Module 추가

Debug Drawer의 가장 큰 장점은 custom module을 추가할 때 나타난다. 앱 디버깅 중에 특정 안드로이드 버전에서만 특정 fragment가 생성될 때 focus가 뺏기는 문제가 있었던 적이 있는데, 어떤 뷰가 도대체 왜 포커스를 뺏어가는지 상상이 안 되어서 해결하는 데 사용했었던 모듈이다.

Custom Focus Module Example

StrictMode

개발자가 실수하는 것을 감지하고 해결 할 수 있도록 돕는 모드. 일종의 경고 알람 같은 면에서는 메모리 리크를 감지해주는 Leak Canary와 비슷하다.
– 디스크 쓰기
– 디스크 읽기
– 네트워크 사용
등을 감지 할 수 있는데, StrictMode를 사용하면 java.io.*, android.database.sqlite.*, java.net.* 클래스들에게 후크를 걸게 되어서 감지가 가능하게 된다. 플래시 메모리를 사용하는 안드로이드라서 디스크 쓰기/읽기의 속도가 빠를것이라고 예상하지만, 용량이 채워질수록 큰 폭으로 느려진다.

사용법

TinyDancer

FPS tracking 유틸이다. 인간이 연속된 동작으로 인식할수있는 FPS 는 24 FPS부터다. 일반적인 사람은 30 부터 60 사이의 FPS는 차이를 못 느끼며, 매우 부드러운 동작으로 인식한다. 즉, Measure과 draw를 16.66 ms 안에 그려내야 하는 안드로이드 경우 dropped frame per second 수에 따라서 FPS가 결정이 된다. 부드러운 사용자 경험을 위해서 디버그 빌드와 UI Test를 연동 시켜 FPS를 프로파일링 하는 식으로 많이 사용된다.

Tiny Dancer Demo

사용법

LeakCanary

Leak Canary 경우 메모리 누수를 감지해주는 디버깅 툴이다. 액티비티에서 메모리 누수가 발견될경우 사용자에게 알람을 띄워주며, 별도의 프래그먼트에서 누수를 감지하고싶으면 refWatcher로 집어넣어준다음에 onDestory 가 호출될때 refWatcher도 별도로 호출해야한다.

사용법

Chuck

Chuck 는 안드로이드 앱 내에서 사용할 수 있는 HTTP Inspector 이다. Stetho 를 이용해서 네트워크를 확인할시, 크롬 인스펙터가 구동되기 전에 생긴 네트워크 호출일 경우 확인하기 불편하다는 점을 보안할 수 있다. 디버그 빌드에서 앱이 네트워크 호출을 할 시, Chuck 라는 앱이 호출되면서 앱 내에서 전반적인 네트워크 호출에 관한 정보들을 확인할 수 있다.

Chuck Example

사용법

Telescope

Telescope 은 앱의 루트 레이아웃에 TelescopeLayout 이라는 뷰를 추가함으로써, 테스트 사용자들 (개발자 혹은 회사 내부 사람들)이 언제든지 쉽게 스크린샷과 함께 버그 리포트를 손쉽게 보내게 해주는 툴이다. 지정된 손가락 갯수를 특정시간동안 누르고 있으면 dev 이메일로 리포트가 전송되게 만들어져있는 라이브러리다.

Telescope Example

사용법

Process Phoenix

Process Phoenix 는 앱의 특정 액티비티를 완전히 깔끔한 상태로 시작하고싶을때 손쉽게 사용 할 수 있는 디버깅 라이브러리다. 불사조가 다시 태어나듯이, context 만 쥐어주면 깨끗한 상태로 액티비티가 만들어지는데, 주로 staging 이나 dev build 에서 release build로 설정을 봐꾸거나 할때 사용한다. 앱을 완전히 삭제하고 다시 설치할 필요없이 필요 할 시에 호출해서 사용할 수 있는 것이다.

사용법

Hugo

Hugo 는 annotation-based 로깅 툴이다. 사용하기도 매우 간편하다. Log.d(TAG, message) 를 호출하기보다, @DebugLog annotation 하나만 추가하면, 메소드가 실행되는데 걸린 시간부터 파레메터로 받은 변수들 까지 다 손쉽게 볼 수 있는 것이다.

사용법

ClassyShark

ClassyShark는 안드로이드 라이브러리도 별도로 추가해야하는 것이 아니고, 외부로 존재하는 .jar 프로그램이다. Apk 파일을 업로드 하면 자신의 apk파일이 어떤 라이브러리들로 이뤄져있으며, 용량 차지, 메소드 카운트 부터 proguard 적용은 잘 되었는지 등을 확인 할수있는 바이너리 inspection 툴이다. .dex, .aar, .so, .apk, .jar, .class 파일들 을 다 열어서 확인 해볼 수 있으며, 안드로이드 xml 파일들까지 다 확인 할 수있다.

Classy Shark 화면

사용법

java -jar ClassyShark.jar실행 후 확인 하고 싶은 파일 open 후 확인

좋은 개발자? 뛰어난 안드로이드 개발자가 되는 방법!

지나친 비약으로 들릴 순 있겠지만, 무언가에 뛰어나다는 것은 매우 상대적인 개념이다. MMORPG 게임을 하다 보면 상위 랭커가 되기 위해서는 남들보다 많은 시간 투자와 효율적인 사냥 동선이 최고임을 알 것이다. 개발에도 마찬가지로 적용된다. 남들보다 더 많은 시간과 노력을 어떻게 더 효율적으로 사용함에 따라 뛰어난 안드로이드 개발자와 평균적인 안드로이드 개발자가 나뉜다. 구체적으로 어떻게 효율적으로 사용해야 하는 걸까?

효율적인 사냥 동선

서론

누구나 할 수 있는 말이겠지만, 뛰어난 개발자가 되기 위해서는, 학습연습을 남들보다 많이 무한 반복하면 된다. 그렇다면 뛰어난 안드로이드 개발자가 되기 위해서 어떤 것들을 학습해야 하고 연습해야 할까?

학습

뉴스레터

뉴스레터

뛰어난 안드로이드 개발자가 되기 위해선 안드로이드에 대해서 많이 알아야 한다. 유기적인 플랫폼을 잘 이해하기 위해서는 정보를 많이 접해야한다. 신기술이 무엇인지, 커뮤니티에서 화두가 되는 주제가 무엇인지, 기술이 어떤 방향으로 발전하고 진화하고있는지를 알기 위해서는 주기적으로 발행되는 뉴스레터가 매우 효율적이다. 이메일 주소만 기입하면 주 단위로 새로운 정보를 발송해주며, 나아가 더 빠르게 정보를 접하고 싶다면, androiddev subreddit 혹은 활발하게 활동하는 안드로이드 개발자들 트위터를 팔로우 하는것을 추천한다.
* Action Items
* Kotlin weekly 구독하기
* Android Weekly 구독하기
* Android DevDigest 구독하기
* /r/androiddev/ 시작 홈페이지로 설정하기
* 활발한 안드로이드 개발자 팔로우 하기

팟캐스트

팟캐스트

한국에는 상대적으로 덜 발전한 미디어 매체인 팟캐스트는 사실 안드로이드 정보로 가득 차 있다. 능동적으로 시간을 투자하고 정보를 접해야하는 글이나 블로그 포스트와 다르게, 팟캐스트 같은 경우는 수동적으로 노래 듣는것 처럼 정보를 접할 수 있다. 운동, 운전, 산책 등 노래를 들을 수 있는 상황에서는 팟캐스트 한편 듣는 것을 추천한다.
* Action Items
* Fragmented 구독하기
* Android Intelligence 구독하기
* Test Talks 구독하기
* Android Developer Backstage 구독하기

온라인 수업

빠르게 변화하고 발전하는 안드로이드 코드베이스에 맞춰서 지속적으로 업데이트되는 온라인 수업들도 존재한다. Udacity 와 CodePath 인데, 자기만의 페이스에 맞춰서 공부를 할 수 있으며, 같은 기능을 다양하게 개발 방법을 볼 수 있다.
* Action Items
* Udacity
* Advanced Android App Development 수업 듣기
* Gradle for Android and Java 수업 듣기
* Material Design for Android Developers 수업 듣기
* Android Development for Beginners 수업 듣기
* CodePath

세미나 및 컨퍼런스

세미나 및 컨퍼런스 참가 하는 것 또한 매우 중요하다. 분야의 전문가들이 제공하는 정보를 접할수 있게 되며, 새로운 기술의 응용에 대해서 배울 수 있다. 잘 준비된 세미나 한번 보는것은 그 주제에 관해서 농축된 한약을 한 사발 마시는것과 같다고 생각한다. 국내/국외 세미나가 정리되어있는 사이트들은 이러하다:
* Action Items
* 국외 컨퍼런스 참가하기
* 국내 컨퍼런스 참가하기

실행

오픈소스

실제 팀 단위 개발 환경과 가장 유사한 경험은 오픈소스 프로젝트에 참여 하는 것이다. 프로젝트가 유지 되는 방식이나 개발이 진행되는 방식, 등 혼자 개발하는 것 보다 훨씬 많은 경험을 할 수 있으며, 토론과 협동을 통해 유기적으로 발전하는 프로젝트를 지켜보는 것 또한 매우 좋은 경험이다. 참여를 하지 않더라도, 다양한 오픈 소스 프로젝트를 뜯어보는 것 만으로도 디자인 패턴이 안드로이드에서는 어떻게 적용되는지 등을 배울 수 있다.
* Action Items
* 오픈소스 프로젝트 PR 넣기
* 오픈소스 프로젝트 이슈 만들기
* 안드로이드 오픈소스 프로젝트 뜯어보기
* MovieGuide
* Archi
* MVP u2020

코드리뷰

개발은 팀 스포츠라고 봐도 무방하기 때문에, 코드리뷰를 잘 하는것 또한 개발 실력에 큰 영향을 끼친다. 코드리뷰 실력이 늘게 되면, 코드의 문제점을 더욱 더 빠른 시점에서 파악할 수 있고, 다른 개발자들과의 소통을 통해 다양한 아이디어를 접하기가 쉬워진다. 또한, 타인의 시선에서 코드를 보는 실력이 늘어서 일관성있는 설계와 유지보수 하기 쉬운 코드를 짜게 된다.
* Action Items
* 오픈소스 프로젝트 코드리뷰에 참가하기
* 본인 코드도 코드리뷰 하기

테스팅

테스팅은 자연스레 개발자들이 “시간이 남으면 하는 것”이라는 인식이 강하지만, 아무리 버그가 없는 코드를 썼어도, 테스팅이 없다면 불안정한 코드이다. 마치 수술실의 의사가 시간이 없다고 검증도 하지않고 환자의 환부를 봉합하는 것과 마찬가지로 책임감이 걸여되는 행동이다. 테스트 코드를 작성하는 기간도 개발 기간의 일부로 잡아야 하며, 테스팅을 습관화 하면 개발을 하며 자연스레 더 깨끗한 코드를 짜기 시작하는 본인을 발견할 것이다.
* Action Items
* Unit Test 작성하기
* UI Test 작성하기
* Pref Test 작성하기
* CI 연동하기

데이터 수집

사용자 행동 데이터를 분석해서 본인의 앱을 지속적으로 발전시켜 나가야 개발 결정을 하는 실력 또한 발전한다. 비록 초반에는 직관적으로 기능 개발이 가능 하더라도, 성숙해져가는 제품의 방향을 직관에 맡기기에는 직관이 틀리는 경우가 너무나 많다.
* Action Items
* 개인 프로젝트에 Fabric 연동하기
* Classy Shark로 apk 분석해보기

결론

뛰어난 개발자가 되는 방법에는 많은 길들이 존재할 것이고, 위에 제시한 방법은 그 수많은 길들중 일부분일 것이다. 나 또한 뛰어난 개발자가 되고싶은 평범한 개발자 중 한명이며, 같은 방향을 추구하는 다른 개발자들과 공유 하고싶은 마음에 쓰는 글이니, 틀린 정보가 있다면 같이 수정하며 배워 나가고 싶다. 많은 내용은 Droidcon Boston 2017의 First Do No Harm 을 참고 + 영감 받아 쓴 글이다.

I am an ordinary developer. If this blog post contains any wrong information or improvements, please feel free to add comments! I would love to hear it, to improve and learn together. 😀

Android Testing 정리

안드로이드의 테스팅의 역사는 짧고 역동적이다. 수많은 라이브러리나 툴들이 개발되었으며 (MonkeyRunner, UiAutomator, Espresso, Robotium, Calabash, Appium, Android Testing Framework, Spoon, Robolectric, etc) 그 짧은 시간 안에 이미 역사 속으로 사라진 툴들도 수없이 많다. 진입장벽이 높아 자연스레 개발자들이 “시간이 남으면 하는 것”이라는 인식이 강하지만, 알고 볼수록 더 즐거운 테스팅에 대해서 배워보자.

개념

Testing은 여러 종류로 나뉘며, 심지어 이름도 부르는 사람에 따라 다양하다. 명칭을 외우기 보다는 왜 Testing 이 여러 종류로 나뉘었는지 이해하면 Test Code 작성이 쉬워질 것이다.

이 글에서는 Unit Tests, Integration Tests 그리고 Functional Tests 에 대해서 이야기 할 것이다.

아래에서 더 자세하게 설명하겠지만, 일단 이 3종류의 Test를 정의하고 시작하겠다.

  1. Unit Test: JVM 상에서 구동되는 로컬 Test. 실행 시간이 매우 짧으며, 안드로이드 혹은 다른 라이브러리 코드에 의존성이 없어야 한다. OOP 언어에서는 가장 작은 단위가 interface 인 경우가 많다. 즉, 특정 클래스가 가지고있는 모든 public method가 테스트 단위가 되는 경우가 많다.

  2. Integration Tests: 단위 테스트가 완료된 모듈들을 합쳐서 하는 테스트를 Integration Test라고 칭한다. 이 시점에는 비즈니스 로직을 테스트하는 경우가 많은데, 이곳에서 비즈니스 로직은 돈을 버는 수단이 아니라, 앱에서 데이터가 가공되는 로직을 칭한다.

흔한 Integration Test 시나리오:
ApiManager.class가 데이터를 서버에서 받아서 UserController.class에 넘겨서 데이터를 업데이트 하고 UserInfoPresenter.class가 보여주는것을 관리하고 최종적으로 Fragment에 넘긴다.

Business Logic Flow

ApiManager, UserController, UserInfoPresenter 모두 인터페이스로 Unit Test가 가능하기 때문에 최종적으로 모든것이 연결되어서 Integration Test를 진행할 수 있게 되는것이다.

Integration Test 시점에서는 주로 UI Test를 많이 사용하는데, 이 시점에서는 Mock Server과 Mock Data를 사용해야한다. 왜 그러한지는 아래에서 Functional Test 부분에서 더 설명 할 것이다.

  1. Functional Tests: 기능에 관해서 하는 테스트이다. 사용성 테스팅, 성능 테스팅, 한계 테스팅, 보안 테스팅, 등 실제 사용자가 사용하는 환경과 똑같은 조건에서 진행되는 테스트는 다 이곳으로 들어간다.

실제 서버를 이용한 UI Test 는 이곳에 들어가게된다. 이 시점에서 사용되는 UI Test 는 다른 테스팅과 합쳐져서 사용되는 경우가 많은데, 성능 테스팅과 한계 테스팅이 자주 같이 사용된다. 한계 테스팅 (Stress Testing) 은 MonekyTest 등으로 자주 사용되는데, UI 이벤트를 쏘면서 앱에 행동을 자동으로 리포트화 시킨다. 성능 테스팅에 자주 사용되는 접근법은 빌드별로 FPS Profiling을 포함시켜서 리포트화 시키는 것이다.

테스팅의 피라미드

이런 여러 종류의 Testing을 어떻게 최적화해서 배치하는 것이 좋을까. Test의 안정성과 실제 사용 경험과 유사성은 반비례 관계이지만, 그렇다고 모든 Test를 Functional Test로 작성 한다면, 과도한 False Alarm에 테스트로서 가치가 떨어질 것이다.

고로, 최적화된 Testing Suite를 유지하기 위해선 아래의 규칙을 따르는 것을 권장한다:
70-80% Test는 Unit Test로 이뤄 코드의 안정성을 테스트한다
20-30% Test는 Integration Test로 이뤄 앱의 실제 구동 환경을 테스트한다
10-20% Test는 Functional Test로 이뤄 사용자 경험을 최대화시킬 수 있는 요소들을 테스트한다

이것들을 숙지하고 이제 각 Testing의 구체적인 정보들을 익혀보자.

Always write a test to reproduce a bug before you fix it – Robert C. Martin

Unit Test

안드로이드에서의 Unit Test 는 JUnit 4 로 이뤄져있기를 권장한다. JUnit은 자바에서 가장 널리 사용되는 유닛 테스팅 프레임워크며, 버젼 3과 4의 차이는 성능적인 면에서도 어마어마하며, 불필요한 양식을 따를 필요가 없기때문이다. JUnit 3 에서는 테스트 클래스가 junit.framework.TestCase 클래스의 자식으로 생성되었어야하지만, JUnit4 부터는 그럴필요가 없어졌다. (고로 TestCase class가 사용되는 예제를 보면 Maintain 이 되지 않은 프로젝트임을 인지하고 읽는것을 추천한다) JUnit 4 에서 부터는 @Test annotation 을 이용해서 테스트 케이스를 작성한다.

Unit test는 코드의 가장 작은 단위의 코드를 테스트하는 코드이다. 이 단위는 메소드 혹은 클래스가 되는 경우가 대부분이며, 특정 클래스/메소드가 예상한것처럼 구동하는지를 테스트 하는 것이다. 다시 말해, 특정 클래스의 모든 Public 메소드가 구현한대로 작동하는지, 그리고 특정 클래스의 state가 구현한대로 올바른 state에 존재하는지, 등을 테스트 하게 된다.

런타임에 Unit Test는 모든 final 한정자가 삭제된 android.jar 파일에 대해서 test가 실행되며, Unit Test에 사용되는 jar 파일에는 실제 로직이 들어간 코드가 전혀 없으며, 호출될시 exception을 던지게 되어있다 (즉, android.text.TextUtil.isEmpty() 등 이 존재하는 로직이 test에서 깨지는 이유).

테스트를 하는 클래스나 메소드의 dependencies들을 Mockito 라는 라이브러리로 mock을 해서 unit과 dependencies 들을 분리 시킬 수 있는 것이다. 테스트 코드 내부에는 테스트 하는 Unit만 Concrete class로 구현하고, 그 외에 클래스들은 dummy, stub, mock, spy, fake 등을 이용해서 구현하는 것이 좋다.

그 외에, 가독성을 위해 한 테스트 케이스 안에서는 하나의 assertion (mockito를 사용한다면 verify) 이 존재하는것을 권장하며, 모든 테스트는 트리플A (Arrange, Act, Assert) 의 원칙을 지켜야한다. (세팅을 하고 → 실행을 시키고 → 확인을 한다)
그렇다면 언제 Unit Test를 짜야하나? 유닛 테스트는 항상 두려움에 의해서 작성되어야한다. 두려움이 없으신 분이라면, 안전하게 모든 public 메소드를 테스트 하는것을 목표로 잡는것도 무방하다.

Integration Test

Functional Test는 하드웨어나 에뮬레이터를 통해서 구동되는 테스트를 칭한다. 안드로이드의 Instrumentation API / UIAutomator 를 통해서 구동되며, 대표적인 라이브러리로는 Espresso, Robotium, Appium 등이 있다. Context에 대한 접근이 가능하며, 사용자들의 행동 또한 테스트 할 수 있다. 테스트 중에 서버의 값을 받아서 화면에 정상적으로 출력이 되는지 확인 하기 위해서, 서버에서 돌려받는 값을 mock해서 테스트를 바로 할 수 있는 것이다. Instrumented Test를 통해서 만들어진 별도의 APK로 테스트가 만들어지만, 실제 앱 APK에는 영향을 주지 않는다.

Functional Test의 경우 많은 라이브러리들이 존재하며, 라이브러리들 마다 개별적인 버그가 존재한다. 구글이 espresso 를 공식 지원하기로 한 이상, 메인 스트림은 espresso이 될 가능성이 가장 크다. CI와 연동시킨다면 서버에서 에뮬레이터를 통해서 UI 자동화 테스트가 돌아가게 만들 수 있기때문에, Unit Test와 함꼐 자동화 툴과 연동이 가능하다.

Functional Test

Functional Test의 공식적인 뜻은 소프트웨어에서부터 하드웨어까지 모든 컴포넌트간의 상호작용을 테스트 하는것을 말한다. 이 글에서 Functional Test 는 추상적이고 포괄적인 개념으로 사용했으며, 대표적인 예로는 Performance, Stress, Usability Test 등이 있다.

Performance Test (Traceview, profiling, dumpsys etc..)

Performance Test는 앱의 성능을 테스트 하는 기능으로, traceview 혹은 profiling 을 통해서 앱의 성능을 수치화해서 확인을 하는 방식이다. 주로 Cold start time, 평균 FPS, 메모리 / CPU 소모량 등을 테스트를 통해서 수치화 시켜서 리포트화 시키는 경우가 많다. Functional Test 경우 주로 많이 성숙해진 프로젝트에 사용된다. 왜냐하면:

premature optimization is the root of all evil

Stress Test (Moneky Test)

안드로이드에서 stress test는 monkey test를 주로 사용하며, 마치 원숭이에게 핸드폰을 던져줬을때의 상황을 묘사한다고 해서 지어진 이름이다. 핸드폰에 무작위의 수많은 사용자 이벤트들을 발생시키며 앱이 터지는 경우 그 케이스를 개발자들에게 전달 해주는 방식으로 이뤄져있다.

결론

테스팅에는 명확한 규칙은 없다. 하지만 장려하는 분포도는 70 | 20 | 10이다. 가장 중요 한것은 개발 팀원들이 다같이 테스트에 대한 자주적 최적화, 그리고 코드 품질과 제품 개발 비용 최소화에 대한 고민을 지속적으로 하며 유기적으로 변해가는 테스팅 문화를 만들어가는것이 중요 할 것이다.

I am an ordinary developer. If this blog post contains any wrong information or improvements, please feel free to add comments! I would love to hear it, to improve and learn together. 😀

Reference:

https://developer.android.com/studio/test/index.html
https://developer.android.com/training/testing/index.html
https://developer.android.com/training/testing/start/index.html
http://www.vogella.com/tutorials/AndroidTesting/article.html
https://www.tutorialspoint.com/android/android_testing.htm
http://www.guru99.com/why-android-testing.html
https://www.toptal.com/android/testing-like-a-true-green-droid
https://codelabs.developers.google.com/codelabs/android-testing/index.html?index=..%2F..%2Findex
https://github.com/hotchemi/awesome-android-testing
http://blog.danlew.net/2014/01/23/testing-on-android-part-3-other-testing-tools/
https://docs.google.com/spreadsheets/d/1IPvDArF5o7pNZ81SYealqVNc5Pwi3_iV_x0e65HKRpg/edit#gid=0
DroidCon 2016 Berlin: Testing Why? When? How?
DroidCon 2016 Berlin: Elegant?? Unit Testing
DroidCon 2016 Berlin: Testing made sweet with a Mockito
DroidCon 2016 Berlin: #Perfmatters for Android