Androidの開発現場での学び

https://www.youtube.com/watch?v=pg6klp-ZntU

1 comment | 1 point | by WazanovaNews 約2年前 edited


Jshiike 約2年前 edited | ▲upvoteする | link

日本に12月の頭から2ヶ月間滞在してますが、いくらなんでも寒すぎます。とはいえ、その合間に深刻な水不足に陥ってるカリフォルニアに唐突に嵐がやってきたりして、世界中異常気象ですね。。

さて、NewCircle / SF Android / AnDevConが主催したAndroid開発のメジャープレーヤー8名による座談会です。2時間近くの長丁場ですが、興味深いコメントだけをピックアップしてみると、ほぼSquareのJake Whartonの独演会のような構成になってしまいました。

Square

皆もよく知ってるように、初期のAndroidの基本設計は、Javaで書かれたものはどうテストすればよいかという従来の共通認識に反するものだった。Squareでは三種類のテストを実施している。最も大きな位置付けを占めるのは、ユニットテスト。例えば、Square Registerだと数千件ある。全てのコードの変更はユニットテストを通ることを義務付けている。その後に非同期でコンスタントに、実行速度の遅いUIテストをして実際のフローを画面遷移ベースで確認している。最近公開したEspressoベースのBurstというライブラリがあって、複数のパラメータのセットを用意してテストを繰り返すことができるようになっている。手動でのテストはどうしてもやらざるを得ない。6週間ごとに出発する電車モデルを採用している。(ので、そのタイミングに間に合わないものは次のリリースにまわされる。)

Juan Gomez, Eventbrite

(「何故、robotiumからEspressoに移行したのか。Robotium Recorderという有料サービスはあるけど、EspressoにUI recorderはまだない。」という質問に対して、)robotiumのコアの開発は止まっていて、ほとんどの開発及び新しい機能はEspressoという認識。

Square

robotiumは根本的にダメ。AndroidがUIのテストに提供している通常のinstrumentation API上の単なるハイレベルのラッパーで、UIや結合テストを書いてるときに実際に直面する、難しい問題を解決してくれない。例えば、ネットワークベースのコールを介したり、fragmentのトランザクションのような非同期なオペレーションを扱ったりするなど、同期的に次のフレームにpostされるかたちでは起きないもの。Espressoはとてもユニークなアプローチをとっていて、これを完璧に解決してくれる。メインスレッドに限らず全てのオペレーションが、メインスレッドのtrooper (?) が全てのメッセージをクリアにするまでは起きないようにしてくれる。fragment トランザクションがpostされるとか、非同期のタスクがバックグラウンドで実行されるとか、全てが止まるまで何も実行されない。知らない非同期のexecutionがあっても対応してくれる。例えば、サードパーティのライブラリとかネットワークコールとしようとしても、それを見つけて、「ネットワークリクエストがなくなるまでは何もしてはダメだ。」と言ってくれる。それに加えて、ものすごく素敵なAPIを備えていて、もっと宣言的なやりとりが書ける、ブラックボックス的なテストとはちょっと違って、やや引いた感じでもう少しハイレベルなスタイルのUIテストが書ける。

Ty Smith, Twitter

(「Robolectricをどう使っているのか?」という質問に対して、)mini bar?テストをTestCaseを使ったAndroid標準のものからRobolectricに移行している最中。ビジネスロジックをテストするのにかなり早いソリューションで、うちはAndroidフレームワークと必ずしもヘビーにやりとりしているわけではないけど、もしそうしているとしても、シャドークラスをつくれるので、contactとかactivityとかをもっと(聞き取れず。)できる。ローカルのJVMマシーンで実行されるので、かなり早い。ビルドのスピードアップには常に取り組んでいて、コミットの度に全てのユニットテストを実施してくれるCIシステムがあって、開発者にとって、どのエリアでもスピードがアップできれば生産性に対してボーナスになる。

使っているのは主にビジネスロジックのところ。単一責任の原則(single responsibility principle)に従ってつくっていて、全ては独立したかたちでテストできるようになっている。ユニットテストではネットワークをモックして、Androidコンポーネントを外しておいて、そのクリーンなインターフェースがやりとるするかたちになる。標準のユニットテストだろうが、JUnitだろうが、Robolectricであろうがテストできるようになっている。

Square

うちも同じような方法論を使っていて、理想的にはRobolectricを使わなくて済むものならそれに越したことはなく、なるべく使わないようにしているが、ものすごくよくできたセーフティネットになっていて、bundleとやりとりしなくてはいけないだけ、intentとやりとりしまくてはいけないだけ、などというコードがあるときにすごく便利。それらをラップしてインターフェースにしたり抽象化することで純粋なJUnitテストを使えるようにするというのはまったくもって誤った決断だし、intentとかbundleはただのマップだから、Robolectricが正しい実装をするように後押ししてくれるので、テストがしっかりできる。また最近は、viewベースのソフトに注力していて、かなり工数をかけている。SquareはオリジナルのRobolectricのクリエーターを採用していて、3〜4ヶ月かけて、「それらのシャドーを取り除いて、Robolectricでなるべく実際のコードを実行するようにしよう。」という取り組みをしている。バージョン2、究極的には現在つくっているバージョン3で、多くのコードが、コンパイルされJVMで実行されるリアルのAndroidのJavaコードになり、viewスタック関連のこと、例えばviewを測ったり、レイアウトのこととか、全てリアルのAndroidコードで、実際の振る舞いを期待できる。viewが常に完璧に(デバイスのような)枠の中に収まった感じで、デバイスでなくRobolectricでテストしているのに、実際のviewコードが実行されていると信頼できる。みんなviewからは手離れして、テストはJVMで実行できればいいと思ってるはず。activityをスタートさせたり、たくさんのFragmentを走らせたり。今は単にセーフティネットを与えるかたちになっていて、実際のコードは少しだけで、super classメソッドをコールしたり、bundlerとかintentとかログとかとやりとりしたりして、たまたまAndroidとやりとりしてるだけのようなコードをテストしている。

Twitter

DI(ディペンデンシーインジェクション)とモックはともにうちにとっては重要なポイント。fabric(Twitter Developers)ではSDKを配布しているので、サードパーティとの依存関係がでてしまう。かなり軽い関係ではあるが。Daggerのような美しいDIツールは使うかたちにはなってないが、とはいえDI設計パターンに従えないというわけではない。依存関係を前面にもってきてコンポーネントをつくる。singletonで実行していなくて、モックを外せる静的なメソッドを持っていない。最終的なものでないので、mockitoが扱うことができる(できない?)。全て独立したコンポーネントで、単一責任の原則に従っていて、異なるツール間を独立したかたちでテストできる。Androidのactivityやfragementなどとやりとりする際には、設計パターンに従って、それを分けて、一つのクラスにはビジネスロジックがあり、別のクラスではactivityのバウンドが表現されていて、activityはアプリのフレームワークにおいておきていることのシェル的なものとなり、ビジネスロジックを意図通りに実行するのに安心して自分のシステムを頼ることができるようになる。(「Androidにおいて、activityがビジネスロジックを含まずにビジネスロジックをコールするというのは、必ずしもステートバインディングをもってないのに、AndroidにおけるMVCアーキテクチャにおいて、実際にはどうやって実現しているの?」という質問に対し、)色々方法はあるけれども、幸いそれほどのactivityを扱わなくてよい状況。基本的にはライフサイクルをトラックして、コントローラをactivityにバインドしている。コントローラがあれば、ステートを管理できるし、何が起きているかトラックできるし、それからそれをテストに使えるし、それが呼び出される実装に頼ることができる。やりとりしているクラスをモックできるし、fragmentやactivityなども(?)。テストと自動化はカルチャーとしてうちのチームには重要で、適切なテストが完了するまではコードはマスターに送られることはない。(「ということは、activityに対する独自のコントラーバインディングフレームワークや、独自のDIエンジンがあるので、サードパーティのソリューションには頼れないということ?」という質問に対し、)独自のDIフレームワークを使ってるとは言えなくて、単なる設計パターン。依存関係は全てコンスタラクタの前面にだす、もしくは初期化のコール。activity自身はなるべくテストからは独立(?)させる。どうやって依存関係を捕まえるかは、基本的には自らのcreateやassumeで(?)、singletonのコントローラにバインドする。

Square

テストしやすいように設計するのは大切だけど、バランスも重要だと思う。設計パターンに従い、すてきな抽象化とかインターフェースとか、モックするとかあるけど、アプリを世に出さなくてはいけないという段階になると、正しい抽象化をやるということと、やるべきことを今終わらせなくてはいけないということのバランスがでてくる。電車モデルを使って、期限がくると自動的にリリースさせるという状態をつくると、その境界を強制的に整理することができる。必ずしも前面にだす設計をしなくても、電車モデルで1回クリアにしたうえで、段階的に開発していくことは可能だと思う。

(「Androidの開発者がスケールさせる際に代替えとして考慮すべきアーキテクチャとは?」という質問に答えて、)fragmentとloaderが提供してくれるパターンはよく知られているけど、実装の観点からはかなり物足りない。比較的シンプルなアプリだとよい。2、3個のfragmentをもついくつかのactivityがあるアプリであれば問題ない。Squareは、全てが単一のウィンドウの中にあるかたちのシングルactivityモデルに移行したいと思っていて、その際は、fragmentマネジャは単なる不透明なボックスで 実際のフックやステートを確認するものがなくて、うまくいってほしいと祈りながら自分で外部に同期する必要がある。これに様々なバグや整合性やバージョンの違いの問題が絡んで、非同期に実装してトランザクションとやりとりするとか、ダイアログが完全に別のウィンドウになったりするのに対応したり、ステートマシンがネストしたり、それで今度はネストしたfragmentなんかもでてきて、ステートマシーンからステートマシーンをへてステートマシーンへと(はまっていく。)。発見した根本的な問題というのは、全てを皆に適用できるようにとつくってきたこと。そうすると何の意見もでないので、巨大なAPIができあがって、大きなライフサイクルが生まれしまい、理解し難くなってバグがでる。16ヶ月で数千回のクラッシュに出くわして、単純に取り除くことを決めた。viewがあって、スマートコントローラがあって、トランザクションモデルを管理できて、異なるviewのアニメーションをイン & アウトできるので、fragmentは特に何か新しいことをもたらせてくれるわけではなく、ただviewを管理しているだけ。そこで完全にアプリから外すことにした。その過程で4種類以上のバージョンの独自のfragmentとfragmentマネジャを実装した。オープンソース化していない理由は、自分たちに必要なように設計したのであり、皆のために完璧なAPIをデザインするのでなく、自分たちのユースケースを解決すればよいだけだったので、それだとAndroid全体のエコシステムからするとごく一部のサブセットだから。アドバイスをするとしたら、fragmentを小さなスケールで使っているうちはまったく問題なくて、シングルactivityモデルなどの野心的な取り組みをするときや、深くネストした複雑な深い階層の大きなアプリをつくるときや、複雑なステートが画面を占拠するようなときは、何か別の方法を考えなくてはいけないということ。6週間単位のリリースを繰り返し、14人の開発者がSquare Registerにどんどん機能を追加しても、fragmentを減らすたびにクラッシュ率は下がってきている。振り返って理由を考えてみると、シングルactivityモデルでfragmentに無理を強いていたのだなと思う。Squareの方法が必ずしもベストな代替策だとは思わない。fragmentを想定して考えればよく、単にviewやバックスタックを管理するだけの、何かすごくシンプルなものになるかもしれない。と言いつつ、Sqaureは将来全然違ったアプローチのものをリリースするかもしれないけど。(笑)viewマネッジメントとバックスタックを維持するところをリプレイスするのは単に500行のコードで済む。fragementを生き残らせるために色々機能を追加していくから肥大化していってしまう。

(「複雑化してきてAndroidの世界において、アーキテクチャをスケールさせるのに、積極的にreactiveスタイルのプログラミングを採用しているか?」との問いに答え、)かなりヘビーに使っている。RxJavaをAndroidで使うときの問題は、RxJavaはフレームワークになりたがっており、ライブラリ志向ではないということ。かなり手動でアプリをやりくりしたり、シミュレーションしたり、API対応したりすると思うけど、そのあたりのことをactivityとかfragmentとかserviceでは扱いたくないはず。やることがあふれてしまう。できるにはできるが、たくさんのコードを書くはめになる。Sqaureではヘルパーをたくさん用意していて、シンプルにviewにバインディングしたり、RxJava上にRxAndroidモジュールをつくって、非同期のオブザーブできるコンセプトをAndroidのコンセプトにバインドして、activityライフサイクル、fragmentライフサイクル、viewライフサイクル(をどうする?)、サンプルアプリが、データレイヤとUIレイヤをドライブするためにどのように非同期にイベント駆動型なreactiveプログラミングを使うか示してくれる。RxAndroidでオープンソースとしてサンプルを提供している。まだちょっと荒野の西部的な状態だけど、注目も浴びていているので、この先の数ヶ月でかなり気の利いたサンプルも揃ってくると期待している。

(「シングルactivityモデルに移行することは、チームで仕事するうえでは難易度があがるし、スケールアップしづらいように思えるが、その移行への背景をシェアしてほしい。」との質問に対して、)activityは単なるviewに対するホスト。windowが後ろにあれば、windowにはviewがある。自分たちがやってるのは、activityが表現するコンセプトを何か別のものに置き換えただけ。自分たちにとってそれはプレゼンターであり、システムがどのようにナビゲートすればよいかわかっている状態。activityとfragmentと基本的なコンセプトは同じ。viewをカプセル化する手法があり、プレゼンターやコントローラがあり、その間をナビゲートする手段がある。システム、つまりライブラリがどのように変更すればよいかわかっている。Androidは新しいactivityをもってきてライフスタイルの対応をしてくれる。そういう意味では同じ。プレゼンターがあって、プレゼンターが「viewがほしい。」と言えば、別のスクリーンが用意されて、そしてライブラリが「どうやればよいか知ってるよ。」と言って、こいつのライフサイクルを閉じて、こちらのライフサイクルを立ち上げて、アニメーションを描く。つまりコンセプトは変わってない。fragmentのコンセプトは概ね正しい。基本的には有益で、ビジネスロジックがあって、viewがあって、それが一つの再利用可能な状態にカプセル化されている。従来からactivityがやってることも同じ。再利用できるコンポーネントで、それがまとまることによりアプリができあがる。根本的に違うことは何もしていない。具体的な実装が違っているだけ。(「この部分に関してはAndroidのフレームワークをリプレースしているようだが?」)そのとおり。

Ty Smithの話し方は口ごもっていて聞きづらく、Jake Whartonは思いついた端からしゃべりはじめるけど文章が完結しない話し方なので、頑張って推測して補足しましたが、ちょっと内容の正確さに怪しい箇所があるかもしれません。すみません。


2014 Topアクセスランキング


Back