[번역] DroidKaigi 2016 ~ 5년간 「Hatena Bookmark」앱을 계속 개발하는 기술
Aug 28, 2016. | By:
pluulove
본 포스팅은 5 年続く 「はてなブックマーク」 アプリを継続開発する技術 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
실제 슬라이드의 일본어
부분을 번역했다는 점 양해바랍니다.
1p
5년간 「Hatena Bookmark」 앱을 계속 개발하는 기술
NOBUOKA Yuya
주식회사 Hatena (Hatena Co., Ltd.)
2p
자기소개
- Hatena id:nobuoka
- Twitter @nobuoka
- 소프트웨어 엔지니어
- 모바일 앱 (
Android 앱
/ UWP 앱)
- 웹 서비스 (Java / Scala / Perl / TypeScript)
- 경력
- 2012-2014년 : 주로「B!」서버 사이드
- 2014년 :「소년 점프 루키」
- 20215년 :
「B!」 Android 앱 개발
3p
배경
4p
「Hatena Bookmark」 앱 개발
- 「Hatena Bookmark」
- 자사의 web 서비스
- 소셜 북마크
- 서비스 자체는
10년 이상
계속되고 있다
- 이번 달 2/4에 Android 앱「Hatena Bookmark」가
5주년
!
5p
「Hatena Bookmark」에서 web 페이지를 북마크
6p
다른 사람이 북마크한 web 페이지 리스트를 관람할 수 있다
7p
현재 B! Android 앱 개발팀
- 기획 (매니저도) 2명
- 디자이너 1명
- 엔지니어 2(+1)명
8p ~ 9p
B! Android 앱의 흐름
- 2011-02-04 최초 릴리즈
- API level 4+ 대응
- 최신 Android는 2.3이던 시대
- 계속해서 개발 (엔지니어가 겸임하여 1~2명 정도)
- 2015년부터 엔지니어 2(+1)명 체재
5년간 이어와서, 근 1년간 특별히 개발이 한창
10p
오래 개발하고 있는 앱에서 만나는 문제
- 코드 변경이 의도치 않게 영향을 일으켜 기대하는 동작을 하지 않게 된다
- 기능추가, 변경 시에 기존 설계에는 대응되지 않는다
- Android 플랫폼의 변경에 따라가게 된다
- 등등
11p
이 개발이 향하는 곳
계속 앱 개발
하기 위해서는?
- 주제로는
- 자동 테스트, CI 서버, 빌드 시스템
- 팀 체재, 개발 플로우
- 앱 설계, 라이브러리화
12p
제 1 부
테스트 · CI · 자동화
13p
소프트웨어 테스트 이야기
14p
소프트웨어 테스트
- 테스트를 작성하고 있습니까?
- Instrumented tests
- Local unit tests (Gradle plugin 1.1 부터)
- 테스트는 우리 개발을 도와준다
- 버그의 조기 발견 (신규개발 시에도 코드 변경 시에도)
- 테스트하기 쉽게 → 보다 좋은 설계
Gradle로 테스트가 작성하기 쉽게
되었다
Getting Started with Testing, Android Developers
15p
16p
B! 앱 개발과 테스트
17p ~ 18p
목적을 의식하여 테스트를 작성
- 여러 가지 상황 · 동작을 테스트하는 것은 어렵다
- 무엇을 테스트할것 인가 불명확한 채 테스트를 작성해도 효과는 엹다
- 목적에 따라서 방법도 바뀐다
- 각 API level에서 문제없이 동작하는가?
수동으로 동작 확인으로는 발견하기 어려운 항목
의 검사
- 이후에 변경 시에 놓치기 쉬운 부분
- 복잡한 처리가 기대하는 대로 움직이는가?
- 외부와의 상호작용
- 등등
19p
테스트를 자동으로 실행한다
- 수동으로는 좀처럼 테스트를 실행 하지 않는다
- 테스트 실행에는 시간이 걸리고 귀찮다
- 테스트
실패를 알게 되는 것이 늦다
- → 자동으로 실행되도록
- Jenkins
- Android SDK 에뮬레이터를 이용
- SDK 셋업 :
sdk-manager-plugin
20p ~ 21p
테스트 결과 피드백
- 자동으로 움직여도 알지 못하면 의미 없다
눈에 보이는 곳에 피드백
한다
- Slack 채널
- GHE의 pull request
- Jenkins → Slack
- Jenkins → GitHub
- curl 명령어로 GitHub의 API를 부른다
- 플러그인 등이 없어도 Web API 등이 제공된다면 호출 가능 하다
22p
- Jenkins의 Pipeline plugin 이용
- 이전 이름 Workflow plugin
- Job 처리를 Groovy의 DSL로 기술 → 유연하게 적기 쉽게 · 관리하기 쉽게
- Android 에뮬레이터 기동 · 종료를
Gradle Task
로 → Job을 유연하게 · Jenkins에 적게 의존하도록
- 스크린샷으로 표시 테스트
23p
테스트를 적고 바꾸기 쉽게 하고, 품질을 높이자
- 결함검출이나 품질을 높이기 위해서
하나의 방법
- 필요하면 테스트용 build type을 준비
- 무엇을 모적으로 테스트를 적는가 의식한다
- 움직이지 않으면 썩으므로
24p
릴리즈 흐름과 자동회
25p
릴리즈의 흐름
- 주초에 릴리즈 내용 상담 · 결정
- 기능이 갖추어진다 → 릴리즈용 브런치를 자른다
- 릴리즈용 브런츠에 버전을 갱신
- 자동 테스트, 패키지 작성, 팀내 배포 · 동작 확인
- Play Store에 업로드 · 공개
26p ~ 27p
매번하는 순서
- 릴리즈용 패키지 빌드
- 팀내에 배포 (Beta by Crashlytics)
- GitHub Enterprise에 “릴리즈” 작성
- Google Play Store에 업로드 · 공개
28p
릴리즈 플로우가 수동이면?
- 귀찮다
- APK 패키지를 만들고 Play Store에 업로드하는 정도라면 괜찮아도
- 실수하기 쉽다
- 순서 빠짐 (clean, GHE의 릴리즈 작성 등)
업로드해야할 패키지가 엇갈리다
- 등
29p
자동화하자!
30p
Android 앱을 Jenkins로 빌드해서 GitHub에 “릴리즈”를 만들자
31p
Gradle Task 작성 & Jenkins상에서 실행
- Gradle Task
- 릴리즈용 APK 패키지 작성
- GHE의 “릴리즈”를 작성 & 업로드
- 장래적으로는 Google Play Store에도
- Jenkins 상에서 실행
- 장래적으로 Pipeline plugin을 예정
32p
릴리즈 플로우 자동화를 하자
- 귀찮다고 느낀다면 / 실수할 것 같다고 느낀다면
- 우선 Gradle Task를 적자
- 의외로 간단하게 여러 가지가 된다
- 충분히 시간을 들이기에는 어려우므로 조금씩
- CI 서버상에서 실행
- 수중에 빌드 환경이 준비안된 상황에서도 릴리즈 가능하다
33p
테스트나 CI 관련 이야기
34p
제 2 부
개발 플로우
35p
개발 흐름
- 기획 · Task 관리 (Trello)
- 디자인 · 설꼐 · 코딩
- 커뮤니케이션은 구두 · 채팅 도구 (Slack)
- 코드 리뷰 (GHE)
- 자동 테스트 (Jenkins)
- 팀내에의 패키지 배포 (Beta by Crashlytics)
36p
B! 앱개발과 Git 브런치
37p
B! 앱 개발과 코드 리뷰
- 사내 문화로 코드리뷰는 정착
- 계속해서 개발하기 위해서라도 중요
- 다른 사람이 봐도 의도를 알 수 있는가?
- 장래, 변경이 어렵게 되어있지 않은가?
- 리뷰하기 쉽게 의뢰측도 주의를 기울이자
- 적절한 규모 (수백 줄 정도)
- 변경 목적을 명확하게 (복수 변경을 엮지 않는다)
38p
Dev 브런치에 머지되는 단계에 리뷰
완료한 상태가 되도록
39p
규모가 크다면 분리한다
40p
Refactoring
- 오랫동한 계속 개발하고 있고, 해나가기 위해서는 Refactoring이 필요
- 기능추가 · 변경시에 설계를 변경할 필요
- 레거시 코드 개선
- Refactoring시의 문제
- 기능추가 · 변경전에 필요한 Refactoring을 가리기 어렵다
- 다른 변경과 충돌(Conflict)한다
41p
B! 앱개발과 Refactoring
Refactoring
과 기능 추가는 나눈다
- 기능개발 시에 필요한 항목을 Refactoring으로 분리한다
- 불필요한 Refactoring은 하지 않는다
- 이것은 안된다, 라는 곳은 Refactoring한다
- 작은 단위로 Refactoring
- 리뷰하기 쉽다
- 빠르게 dev 브런지에 포함되어 충돌을 피한다
42p
개발 중에 Refactoring이 필요하게 되었다면 브런치를 새롭게 나누어 먼저 리뷰
43p
Preview 버전과 대규모 새로운 기능개발
- 대규모 새로운 기능개발
- dev 브런치에서 Refactoring하면 feature 브런치와 충돌(Conflict)하기 쉽다
- 계속해서 dev 브런치에 머지?
Preview 버전(product flavor)에서만 기능을 유효하게
- 이 자체가 버그의 원인이 될 수 있으므로 주의
규모가 큰 경우
에는 유효
- 개발 중의 상태에 동작 확인하기에도 쉽다
44p
Preview 버전 전용 화면
45p
유지 보수하기 쉬운 코드를 만드는 체제
- 코드 리뷰하자
- 의뢰측은 리뷰하기 쉽게 하도록 주의를 기울인다
- 필요한 Refactoring 계속해서 한다
- 개발용 Preview 버전 등을 만들면 좋을지도
46p
제 3 부
설계 · 구현
47p
계속해서 개발하는 데 필요한 것
- 오래된 API level 대응
- Android Support Library
- 스스로 구현
- 장래에 걸쳐 유지 보수하기 쉬운 설계 · 구현
- Class의 역할을 명확하게, Class간의 결합을 적게
- 처리는 공통화한다
Activity, Fragment를 비대화시키지 않는다
- 복잡한 처리는 라이브러리를 만드는 등으로 숨긴다
- 의도가 알기 쉽게 (주석이나 Annotation 등)
48p
Android Support Library 상호기능
- B! 앱은 현재 API level 10+ 대응
v7 appcompat library등이 매우 중요하다
- 여러 사람이 사용하고 있는 기능
- Fragment, AppCompatActivity, 등
- 그다지 쓰이지 않는 편리 기능
49p
오래된 API level을 지원하는 구현
Support Library의 구현을 참고
로
- 예를 들면 ProgressBar의 색 변경
- “android:progressTint”같은 것
- ProgressBar의 서브 클래스를 만들어 대응
- 예를 들면 그림자 구현: “Build.VERSION.SDK_INT”로 분기
- API level 21 이후는 elevation
- 그 미만에는 Drawable
- 오래된 API level에서는 포기하고 아무것도 하지 않는 경우도
50p
Annotation으로 지원
Annotation Support Library을 쓰자!
- @Nullable / @NonNull
- 각종 리소스 (@StringRes 등)
- @MainThread / @WorkerThread
51p
Activity · Fragment를 비대화시키지 않는다
52p
Activity나 Fragment는 비대화되기 쉽다
여러 Callback 메소드로 여러가지를 처리
- View 조작을 직접 하게된다
- setContentView 메소드나 findViewById 메소드 등의 존재
- View 조작을 위한 Class 준비가 불편
- 어디에 적으면 좋을지 망설이는 것
- 특정 Activity/Fragment에만 필요한 처리
53p
게다가 동일한 처리를 분산
- Activity/Fragment내에 작성된 처리가 다른 곳에서도 필요하게 된다면?
- 분리하기 어려우면 그대로 복사/붙여넣기 등
동일한 처리가 복수 Activity/Fragment에 존재
!!!!
- 유지 보수하기 어렵다
54p ~ 55p
구체적인 예
- Web 페이지의 리스트 항목의 Context Menu
- 「나중에 읽기」
- → 비로그인 상태의 경우, 로그인 화면으로 이동시 로그인해서 돌아온 경우는 「나중에 읽기」의 HTTP Request를 송신
- Activity/Fragment의 은밀한 부분
- Context Menu 선택시의 처리 (onContextMenuItemSelected)
- onActivityResult 메소드
56p
Activity나 Fragment에서 처리를 분리
- Activity/Fragment
- 유저 입력
- View/UI 처리
- Lifecycle에 따른 상태관리
- Callback 메소드
- 그 외 어떤 처리
→ 어떤 처리를 하는 Class
57p
Activity나 Fragment에서 처리를 분리
- Activity/Fragment
- 그 외 어떤 처리
- 유저 입력
- View/UI 처리
- Lifecycle에 따른 상태관리
(밖으로 분리한 부분의 설계 수정도 중요)
58p
Callback 메소드에서의 메소드 호출
- onCreate, onStart, onStop 등
- 메소드 호출을 요구되는 곳이 많다
- 문제
- 명시적으로 호출할 필요가 있는 것이 불편
- 호출을 잊기 쉽다
59p
CallbacksAppComponents
- 자작 라이브러리
- Activity나 Fragment의 기본 Class를 제공
- 각 Callback 메소드의 호출을 받는 인터페이스를 구현한 객체를 등록할 수 있다
- 등록해두면 기본 Class에서 마음대로 부른다
- 각 Callback 메소드에서 여러 처리를 할 필요가 있으므로 Activity/Fragment에서 처리를 분리하기 어려운 문제를 해결
60p
기본 Class가 Callback 메소드를 호출
61p
https://github.com/nobuoka/CallbacksAppComponents
62p
복잡한 처리를 라이브러리화 한다
63p ~ 64p
복잡한 사양에는 복잡한 처리를 적을 필요
- 점점 복잡하게 되는 페이지 일람의 리스트 처리
- 리스트의 Section을 분류한다
- 리스트 항목을 동적으로 추가한다
- 리스트 마지막에 무엇을 표시 (광고나 다음 페이지 읽기 버튼이나)
- 기사 일람의 리스트 도중(10번째나)에 「추천 유저」 등을 표시 →
심하다!!!!
65p
복잡한 처리를 라이브러리화한다
- 복잡한 기능은 2개로 나누어 생각한다
- 기능에 특화된 부분 (다루는 Class · 데이터 내용 등)
- 범용적으로 복잡한 부분 → 라이브러리화
- 복잡한 처리는 라이브러리에 밀어 넣는다
- 어플리케이션 코드에서는 단순하게 라이브러리를 사용할 뿐
66p ~ 67p
ComponentsRecyclerAdapter
- 앞선 복잡한 리스트를 실현하기위해 만든 라이브러리
- 복수의 item view type의 사용을 용이하게
- 표시를 위한 데이터 구조를 컴포넌트의 Mock 구조로
- 컴포넌트에서 Section 분리를 하거나
- 기본 리스트의 도중에 다른 컴포넌트를 담은 컴포넌트를
68p
https://github.com/nobuoka/ComponentsRecyclerAdapter
69p
유지 보수하기 쉬운 코드를 적자
- Annotation과 lint를 활용
- 문서 주석
- 설계를 생각
- Activity/Fragment를 비대화시키지 않는다
- 복잡한 처리는 라이브러리화한다
- 등등
70p ~ 71p
정리
- 계속해서 개발을 하기 위해서는?
- 코드 변경을 하기 쉽게 · 코드 품질을 높인다
- 소프트웨어 테스트 (자동화 · 피드백 중요)
- Refactoring (기능개발과는 분리)
- 코드 리뷰
- Annotation의 설계 · 구현
- 더욱이, 자동화
comments powered by