読者です 読者をやめる 読者になる 読者になる

Android開発でRxJavaをチームに導入した話

買物情報事業部の八木(@sys1yagi)です。

Android界隈でRxJavaが話題になっていますね。クックパッドアプリ(以後、「本体」と表現します)でも先日ついにRxJavaの導入を果たしました。本エントリではRxJavaをチームに導入する為に行ったいくつかの取り組みを紹介します。

目次

  • RxJava導入の失敗
  • どのような課題を解決するのか
  • 導入の為に機能を分解し、学習コストを考える
  • ブログを書く
  • 低コスト、低リスクに導入する
  • 勉強会を開く

RxJava導入の失敗

2014年11月にRxJavaの1.0.0がリリースされました。遂に実用段階かという事で個人的にあれこれ触り、本体に導入する機会を伺っていました。ある日、bug fixの為にRxJavaを使うと簡潔になるのではないかと思い気軽にPull Request(以後、PRとします)を送った所、「このタイミングで急に導入する意図はなにか?」「RxJavaでなければいけない理由は何か?」「きちんと学習しているメンバーが少ない中で導入してメンテは大丈夫か?」といった突っ込みが入り、PRからおよそ2時間ほどでRxJavaとお別れとなりました。

f:id:sys1yagi:20150415155611p:plain

レビューでの突っ込みはもっともで、明確な理由もないままチーム全体として学習が不十分なライブラリを持ち込むのはまずいですよね。メリットよりリスクの方が高そうです。

どのような課題を解決するのか

導入に失敗した私は反省し、まずはRxJavaによって本体開発のどのような課題を解決できそうなのかを洗い出しました。

No 課題 どのように解決できるか
1 非同期処理系の書き方に統一性がない。複数の非同期処理を合成する為のうまいやり方が定まっていない RxJavaをPromiseとして使う事で単一の機能を持つ非同期処理群を構成できる。それをドメインに合わせて合成するスタイルにすれば、AsyncTask内でCountDownLatchで待ち合わせるようなスタイルを駆逐できる
2 FragmentでgetActivity() == nullなどの状態チェック漏れによってクラッシュする問題 Fragmentのライフサイクルに合わせてunsbscribeする機構を作るかRxAndroid(RxJavaを使ってAndroid用に書かれたコンポーネント群です)等を使う事で状態チェックが不要になる

また、導入によって期待できる効果についても検討しました。

No 期待できる効果 理由
1 複雑なロジックを統一的な記述にできる Observable化した後の記述はRxJavaに沿うので統一的になる。また不自然な書き方や無理やり書いた場合などの検出がしやすい
2 メンテナビリティの向上 各処理をOperator単位で記述するので処理の意図が理解しやすくなる
3 アプリケーションアーキテクチャの改善 現在のアーキテクチャを改善していく際にRxJavaの思想が応用できるのではないか

「期待できる効果」の方はともかく、課題の解決に有効という事であれば学習コストを支払う価値は十分あるでしょう。なぜなら上記の2つの課題は本体開発においてずっと有効な手段が得られないままでいたからです。すぐにでも導入したい所ですが本当に課題の解決に有効かどうかについてどのように判断すればよいでしょうか。それはRxJavaを学習し適用しながら検証するしかないですね。この辺りが学習コストの大きいRxJavaのジレンマとなります。気軽に始められてやめたければ引き返せるような入り口を探す必要があります。

導入の為に機能を分解し、学習コストを考える

そこで、機能を分解して学習コストを抑えられるアプローチがないか探す事にしました。

RxJavaの機能の活用方法は概ね以下の様に分解できます。

  • List処理の抽象化・ストリーム化
  • Optional
  • Promise
  • Data Binding
  • Event Bus

これらのうち、学習コストを抑えられそうなのはList処理ではないかなと思います。Optionalはアプリケーションの設計に大きく関わってくるのでOptionalそのものの議論が必要になりますし、Promiseはスレッド操作周りを意識する必要がありいきなり取り組むにはコストが高そうです。Data BindingやEvent BusはSubjectを利用するので、Observableを知る前に触れるのはオススメできません。コードを書きながらObservableとOperatorの関係に触れつつ、低コスト・低リスクに学ぶにはList処理が一番よさそうです。もし「RxJavaはダメかもね」という判断になった時も、List操作での適用だけなら比較的簡単に戻せるでしょう。

ブログを書く

RxJava導入によって解決出来そうな課題と、低コストで学習できそうな方法が定まったので次にこれらをまとめたエントリを社内ブログに書きました。社内のブログなのでここには載せられませんが、概ね同じような内容を個人ブログ(RxJavaをコレクション操作ライブラリとして捉えれば学習コストと導入リスクを低減できるのではないか)に書いています。

社内ブログのエントリでは、本体開発においてRxJavaを導入する事で期待できそうな点について言及しつつ、Observableの概要と基本的な使い方をList操作をベースに解説しました。またエントリの末尾でRxJavaを導入するPRを送るという宣言もしました。

f:id:sys1yagi:20150415155644p:plain

低コスト、低リスクに導入する

社内ブログを周知したあと、ブログでの宣言通りList操作周りの実装を置き換えたPRを送りました。変更対象はListUtilsというリスト操作周りのユーティリティクラスで、T head(List<T>), List<T> tail(List<T>)などの実装をRxJavaを使って置き換えました。

これらの実装は元々あったものなので戻そうと思えばいつでも戻す事ができ、低リスクです。同時に特にメリットが無いのですが、「RxJavaを適用したコードをレビューする」という機会を作るのには最適です。

f:id:sys1yagi:20150415155714p:plain

このPRをマージした後、ビジネスロジック部分のList操作周りを中心にRxJavaを適用を進めていきました。その中で冗長な部分やわかりにくい部分をレビューで指摘してもらい改善していきました。

勉強会を開く

ある程度皆がRxJavaコードのレビューに慣れて来た段階で勉強会を開く事にしました。発表内容は初級から応用まで様々で、発表者の多くが発表の為にRxJavaの調査に力を入れていました。いわゆる勉強会駆動ってやつですね。当初は関係者6,7人で静かにやろうと考えていましたが、どんどん参加者が膨らみ最終的には20名くらいまでになりました。

f:id:sys1yagi:20150415160210p:plain

この勉強会によって、発表者はよりRxJavaの学習がより進み、参加者も基本的な部分の理解や学習のきっかけをつかめたように思います。

以下は勉強会の際に発表した資料です。公開されているものについて列挙します。

おわりに

勉強会後、本体開発にRxJavaが用いられる機会が増えました。現在ではAPI call周りのObservableを使ったPromise化が進んでいます。API callの並列化、直列化の書き方や、実行スレッドの設定をどこで書くかといった話や、可読性などについて日々レビューの中で議論しています。ゆくゆくはData BindingやEvent Bus等の適用についても検討されていくでしょう。また、解決できる課題として挙がった非同期処理やライフサイクルの問題に対しても徐々にRxJavaを適用しつつあります。今のところ大きな問題はでておらず期待通りの効果を得られています。

以上がAndroid開発でRxJavaをチームに導入する為に行った取り組みです。本エントリがRxJava導入の検討の役に立てば幸いです。

そんなクックパッドではユーザーの課題をどういう風にモバイルで解決するかを考え、実現するために情熱を注げる方を募集しています!興味がある方はぜひご応募ください。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/