2016-02-19 / DroidKaigi 2016
5 年続く「はてなブックマーク」アプリ
を継続開発する技術
信岡 裕也
NOBUOKA Yuya
株式会社はてな (Hatena Co., Ltd.)
自己紹介
● はてな id:nobuoka
● Twitter @nobuoka
● ソフトウェアエンジニア
 モバイルアプリ (Android アプリ / UWP アプリ)
 Web サービス (Java / Scala / Perl / TypeScript)
● 経歴
 2012 – 2014 年 : 主に 「B!」 サーバーサイド
 2014 年 : 主に 「少年ジャンプルーキー」
 2015 年 : 「B!」 Android アプリ開発
背景
「はてなブックマーク」 アプリの開発
● 「はてなブックマーク」
 自社の web サービス
 ソーシャルブックマーク
 サービス自体は 10 年以上続いている
● 今月 2/4 に Android アプリ 「はてなブック
マーク」 が 5 周年!
「はてなブックマーク」 で web ページをブックマーク
他の人がブックマークした web ページ一覧を閲覧できる
現在の B! Android アプリ開発チーム
● 企画 (マネージャも) 2 人
● デザイナー 1 人
● エンジニア 2 (+1) 人
Cashlytics
B! Android アプリの歩み
● 2011-02-04 最初のリリース
 API level 4+ 対応
 最新の Android は 2.3 という時代
● 継続的に開発 (エンジニアが兼任で 1 〜 2 人程度)
● 2015 年からエンジニア 2 (+1) 人体制
 大きな機能追加や変更ができるように
● 5 年続いてきて、この 1 年ほど特に開発が盛ん
B! Android アプリの歩み
2015-01-01
長く開発しているアプリで遭遇する問題
● コードの変更が意図せぬ影響を起こして期待す
る動作をしないようになる
● 機能追加、変更時に既存の設計では対応できな
い
● Android プラットフォームの変化への追従
● などなど
この発表の目指すところ
● 継続的にアプリ開発を行うには?
 チームでの取り組み、方針を紹介
● 話題としては
 自動テスト、CI サーバー、ビルドシステム
 チーム体制、開発フロー
 アプリ設計、ライブラリ化
第 1 部
テスト・CI・自動化
ソフトウェアテストの話
ソフトウェアテスト
● テストを書いてますか?
 Instrumented tests
 Local unit tests (Gradle plugin 1.1 より)
● テストは我々の開発を助けてくれる
 バグの早期発見 (新規開発時もコード変更時も)
 テストしやすく → より良い設計
● Gradle によりテストが書きやすくなった
 → Gradle 導入後からテストを書くように
Getting Started with Testing | Android Developers
B! アプリ開発とテスト
● コード品質を高める一つの手段としてテストを
利用
● 標準のテストツールをベースに
 Testing Concepts (Android Developers)
 AndroidJUnitRunner、Espresso
● テスト用のビルドタイプ (“ttest”) を用意
 Build config / ProGuard / テスト用コード
目的を意識してテストを書く
● あらゆる状況・動作をテストすることは困難
● 何をテストするのか不明確なままテストを書い
ても効果は薄い
● 目的に応じて手段も変わってくる
目的を意識してテストを書く
● 各 API level で問題なく動作するか?
● 手動での動作確認では発見しづらい項目の検査
 スクリーンビューの記録とか
● 将来変更するときに見逃しそうな部分
● 複雑な処理が期待通りに動くか?
● 外部とのやりとり
● など
テストを自動で実行する
● 手動ではなかなかテストを実行しない
 テスト実行には時間がかかる・面倒
 テストに落ちるのに気付くのが遅れる
● → 自動で実行されるように
● Jenkins
● Android SDK のエミュレータを利用
● SDK セットアップ : sdk-manager-plugin
テスト結果のフィードバック
● 自動で動いていても気づかなければ意味がない
● 目に入る場所にフィードバックする
 Slack のチャンネル
 GHE の pull request
テスト結果のフィードバック
● Jenkins → Slack
 Jenkins Slack plugin
● Jenkins → GitHub
 curl コマンドで GitHub の API を叩く
● プラグイン等なくても Web API 等が提供され
ていれば戦える
最近進めていること
● Jenkins の Pipeline plugin の利用
 旧称 Workflow plugin
 ジョブの処理を Groovy の DSL で記述
→ 柔軟に書きやすく・管理しやすく
 Android エミュレータの起動・終了を Gradle タ
スクに
→ ジョブを柔軟に・Jenkins への依存を小さく
● スクリーンショットによる表示のテスト
テストを書いて変更しやすくし、品質を高めよう
● 欠陥検出や品質を高めるための一つの手段
 コードレビューなどと組み合わせて
● 必要ならテスト用の build type を用意
● 何を目的としてテストを書くのか意識する
● 動かさないと腐っていくので
 まずは自動化
 目につくところにフィードバック
リリースフローと自動化
リリースの流れ
● 週初めにリリース内容相談・決定
 基本的にリリース可能なものからどんどん出す
● 機能が揃う → リリース用ブランチを切る
 リリース用ブランチでバージョン更新
 自動テスト、パッケージ作成、チーム内への配
布・動作確認
● Play Store へのアップロード・公開
いつもの手順
● リリース用パッケージのビルド
● チーム内に配布 (Beta by Crashlytics)
● GitHub Enterprise に “リリース” 作成
● Google Play Store にアップロード・公開
いつもの手順
リリースフローが手動だと?
● 面倒
 APK パッケージを作って Play Store にアッ
プロードするぐらいならいいけど
● ミスが起こりがち
 手順漏れ (clean、GHE のリリース作成等)
 アップロードすべきパッケージを間違う
 など
自動化しよう!
Gradle のタスク作成 & Jenkins 上で実行
● Gradle のタスク
 リリース用の APK パッケージ作成
 GHE の “リリース” を作成 & アップロード
 将来的には Google Play Store へも
● Jenkins 上で実行
 将来的に Pipeline plugin を予定
● リリースフローの自動化がしやすそう
リリースフローの自動化をしよう
● 面倒だと感じたら / ミスしそうだと感じたら
● とりあえず Gradle のタスクを書こう
 わりと手軽にいろいろできる
 がっつり時間を取るのは難しいので少しずつ
● さらに CI サーバー上で実行
 手元にビルド環境が整ってない状況でもリ
リースできる
テストや CI 関連のトーク
● Android CI: 2016 edition
● 僕がテスト書け書けおじさんになった経緯とそ
の過程でやったこと
● Advanced Android Espresso
● 生まれ変わった UI Automator を使いこなす
● Android Lint で正しさを学ぼう
● 怖くない gradle でのビルド環境設定と Bazel
第 2 部
開発フロー
開発の流れ
● 企画・タスク管理 (Trello)
● デザイン・設計・コーディング
 やりとりは口頭・チャットツール (Slack)
● 東京・京都の遠隔、非同期コミュニケーション
● コードレビュー (GHE)
● 自動テスト (Jenkins)
● チーム内へのパッケージ配布 (Beta by Crashlytics)
B! アプリ開発と Git ブランチ
● Git flow
dev
master
release
feature
B! アプリ開発とコードレビュー
● 社内の文化としてコードレビューは定着
● 継続して開発するためにも重要
 他人が見て意図がわかるか?
 将来、変更が難しくなってしまわないか?
● レビューされやすいように依頼側も気を付ける
 適切な規模 (数百行程度)
 変更の目的を明確に (複数の変更を混ぜない)
dev
Dev ブランチにマージする段階で
レビュー完了しているように
dev
規模が大きくなるなら分ける
リファクタリング
● 長く開発を続けていると & 続けていくにはリファクタ
リングが必要
 機能追加・変更時に設計を変える必要
 レガシーコード改善
● リファクタリングする際の問題
 機能追加・変更前に必要なリファクタリングを見極
めづらい
 他の変更とコンフリクトする
B! アプリ開発とリファクタリング
● リファクタリングと機能追加は分ける
 混ざるとレビューできない
● 機能開発時に必要になった箇所をリファクタリングとして切り
だす
 不必要なリファクタリングは行わない
 これはヤバい、という場所はリファクタリングする
● 小さな単位でリファクタリング
 レビューしやすい
 早く dev ブランチに取り込みコンフリクトを避ける
dev
開発中にリファクタリングが必要になったら
ブランチを新たに切って先にレビュー
Refactoring
feature
Preview 版と大規模な新機能開発
● 大規模な新機能開発
 dev ブランチでリファクタリングすると feature ブランチと
コンフリクトしがち
● どんどん dev ブランチにマージ?
 開発中の状態でリリースされると困る!!!
● Preview 版 (product flavor) でのみ機能を有効に
 これ自体がバグの原因になりうるので注意
 規模が大きい場合は有効
● 開発中の状態で動作確認してもらうのもやりやすい
Preview 版専用画面
メンテナンスしやすいコードを生み出す体制
● コードレビューしよう
 依頼側はレビューしやすいように気を付ける
● 必要なリファクタリングはどんどんやる
 リファクタリングと機能開発は分ける
● 開発用にプレビュー版などを作るといいかも
 Product flavor
第 3 部
設計・実装
継続して開発するために必要なこと
● 古い API level の対応
 Android Support Library
 自前で実装
● 将来にわたってメンテナンスしやすい設計・実装
 クラスの役割を明確に、クラス間の結合度を小さく
 処理は共通化する
 Activity、Fragment を肥大化させない
 複雑な処理はライブラリを作るなどして隠す
 意図がわかるように (コメントやアノテーションなど)
Android Support Library の互換機能
● B! アプリは現状 API level 10+ 対応
● v7 appcompat library などが重宝
● 誰もが使っている機能
 Fragment、AppCompatActivity、など
● あまり使われてなさそうな便利機能
 Drawable tinting など
古い API level をサポートする自前の実装
● Support Library の実装を参考に
● 例えば ProgressBar の色変更
 “android:progressTint” 的なもの
 ProgressBar のサブクラスを作って対応
● 例えば影の実装 : “Build.VERSION.SDK_INT” で分岐
 API level 21 以降で elevation
 それ未満では Drawable
● 古い API level では諦めて何もしない場合も
アノテーションによるサポート
● Annotations Support Library を使おう!
● @Nullable/@NonNull
● 各種リソース (@StringRes など)
● @MainThread / @WorkerThread
 整理されてない古いコードで便利
Activity・Fragment を肥大化させない
Activity や Fragment は肥大化しがち
● 各コールバックメソッドでいろいろな処理
● View 操作を直接行いがち
 setContentView メソッドや findViewById
メソッドなどの存在
 View 操作のためのクラスの準備が面倒
● どこに書けばいいか迷うもの
● 特定の Activity/Fragment でのみ必要な処理
さらには同じような処理が分散
● Activity/Fragment 内に書かれた処理が別の
場所でも必要になったら?
● 分離しづらいとそのままコピペされるなど
● 同じような処理が複数の Activity/Fragment
に存在!!!!
● メンテナンスしづらい
具体例
● Web ページ一覧のリスト項目のコンテキストメニュー
 「あとで読む」
 → 非ログイン状態の場合、ログイン画面に遷移し、ロ
グインして戻ってきた場合は 「あとで読む」 の HTTP
リクエストを送信
● Activity/Fragment と密な部分
 コンテキストメニュータップ時の処理
(onContextMenuItemSelected)
 onActivityResult メソッド
Activity や Fragment から処理を分離
Activity/Fragment
View / UI の処理
コールバックメソッド
ライフサイクルに応じた状態管理
その他何らかの処理
何らかの処理をするクラス
ユーザー入力
Activity や Fragment から処理を分離
Activity/Fragment
View / UI の処理
コールバックメソッド
ライフサイクルに応じた状態管理
その他何らかの処理
ユーザー入力
(外に出した部分の設計しなおしも大事)
コールバックメソッドからのメソッド呼び出し
● onCreate、onStart、onStop など
● メソッド呼び出しを求められることが多い
 広告とか
● 問題
 明示的に呼ぶ必要があるのは面倒
 呼び忘れしやすい
CallbacksAppComponents
● 自作ライブラリ
 Activity や Fragment のベースクラスを提供
 各コールバックメソッドの呼び出しを受け付けるイン
ターフェイスを実装したオブジェクトを登録できる
 登録しておけばベースクラスで勝手に呼んでくれる
● 各コールバックメソッドで種々の処理をする必要がある
ために Activity/Fragment から処理を分離しにくい問
題を解決
ベースクラスがコールバックメソッドを呼ぶ
Activity/Fragment
View / UI の処理
コールバックメソッド
ライフサイクルに応じた状態管理
その他何らかの処理
ユーザー入力
ベースクラス
https://github.com/nobuoka/CallbacksAppComponents
複雑な処理をライブラリにする
複雑な仕様には複雑な処理を書く必要
● どんどん複雑になるページ一覧のリストの処理
 リストをセクション分けする
 リストの項目を動的に追加していく
 リストの最後に何かを表示 (広告とか次ページ読
み込みボタンとか)
 記事一覧のリストの途中 (10 件目とか) に 「お
すすめユーザー」 などを表示 → 厳しい!!!!
複雑な処理をライブラリにする
● 複雑な機能は 2 つに分けて考える
 機能に特化した部分 (扱うクラス・データ内
容等)
 汎用的で複雑な部分 → ライブラリ化
● 複雑な処理はライブラリに押し込んでしまう
● アプリケーションコードでは単にライブラリを
使うだけ
ComponentsRecyclerAdapter
● 先の複雑なリストを実現するために作ったライブラリ
● 複数の item view type の使用を容易に
● 表示のためのデータ構造をコンポーネントの木構造に
 コンポーネントでセクション分けしたり
 ベースのリストの途中に別のコンポーネントを指
し込むコンポーネント
Components-
RecyclerAdapter
ViewHolderFactory
Binder
Component
ViewHodler
onCreateViewHolder
onBindViewHolder
Retrieve item / item view type
Create
Component Component
Pass view holder and item
Domain model
https://github.com/nobuoka/ComponentsRecyclerAdapter
メンテナンスしやすいコードを書こう
● アノテーションと lint を活用
● ドキュメントコメント
● 設計を考える
 Actvity/Fragment を肥大化させない
 複雑な処理はライブラリにする
 などなど
まとめ
まとめ
● 継続して開発を続けるには?
● コード変更を行いやすく・コード品質を高める
 ソフトウェアテスト (自動化・フィードバック重要)
 リファクタリング (機能開発とは分ける)
 コードレビュー
 アプリケーションの設計・実装
● さらに、自動化
 Gradle のタスクを書くところから始めよう

5 年続く 「はてなブックマーク」 アプリを継続開発する技術