5 年続く 「はてなブックマーク」 アプリを継続開発する技術
Post on 19-Jan-2017
12.917 Views
Preview:
TRANSCRIPT
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
開発の流れ
● 企画・タスク管理 (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
ViewHodleronCreateViewHolder
onBindViewHolderRetrieve item / item view type
Create
Component Component
Pass view holder and item
Domain model
https://github.com/nobuoka/ComponentsRecyclerAdapter
メンテナンスしやすいコードを書こう
● アノテーションと lint を活用● ドキュメントコメント● 設計を考える
Actvity/Fragment を肥大化させない 複雑な処理はライブラリにする などなど
まとめ
まとめ
● 継続して開発を続けるには?● コード変更を行いやすく・コード品質を高める
ソフトウェアテスト (自動化・フィードバック重要) リファクタリング (機能開発とは分ける) コードレビュー アプリケーションの設計・実装
● さらに、自動化 Gradle のタスクを書くところから始めよう
top related