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

12,275 views
13,213 views

Published on

DroidKaigi 2016 での発表資料です。

Published in: Software
1 Comment
25 Likes
Statistics
Notes
No Downloads
Views
Total views
12,275
On SlideShare
0
From Embeds
0
Number of Embeds
3,650
Actions
Shares
0
Downloads
25
Comments
1
Likes
25
Embeds 0
No embeds

No notes for slide

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

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

×