僕の業務上のAndroid GUIプログラミング変遷
特に深いことを書く余裕がないんだけど。 それでもいいって、余裕がある人は読んでいってね。
僕はプログラマとして特にAndroidのアプリの開発で一番コードを書いてきた。 ちょっと分解していうなら、Javaな言語仕様な感じのAndroidなフレームワーク依存のGUIプログラミングを主にやって来た。 そういう経験下で得られた認知的バイアスがあると思うのでそういう風に受け取ってもらえると嬉しい。
始まりはMVCなんて知らなかった
僕が最初書いたAndroidの製品のためのコードはどんなだったろうか?はっきり言って全く覚えてない。
でもそれはMVCを知らなかった頃に書いたものだということはわかる。あと誰かと書くのではなく一人で納品までやってた。
ネットでも自分が観測している中でMVCやデザパタが定期的に話題になるようなことがなかったような頃だと思っているんだけど。
違ったらすまん。でもその頃はアーキテクチャなんてものの実例を僕は見つけることができなかった。
そういう時代に書いたものだから、当然ちゃんと依存関係が整理されたコードで書かれたものではないはずだ。
実際僕が個人で出したAndroidのブラックジャックゲームのアプリも、通信がないにしろ、SurfaceViewにレンダリング処理をべた書きした類のコードで、いわゆるスパゲッティだった。
そのころは通信ができるアプリを作れるなんて、本当にうらやましい、チート感を感じる時代だった。
(そういう感覚を覚えた人もいるかも?今だったらFirebaseつかってブラックジャックのスコアのランキングシステムを実装しただろうし、もしかしたら適当な有料ガチャなんて作ったかもしれないなぁ)
その個人のアプリのクオリティを高め、7年くらい前僕は29歳ではれてAndroidプログラマとして就職したわけだが。(俺もうAndroid7年目とかなのか・・・・)
もしかしたらこんなコードでも書いていたかもしれないし、もっと通信処理もその場に書くような生々しいコードを書いていたかもしれない。
public class MainActivity extends Activity { .... void loadData() { new Thread(new Runnable() { @Override public void run() { final UserData user = mClient.loadUserData(); mHandler.post(new Runnable() { @Override public void run() { mTextView.setText(user.getUserName()) mButton.setEnabled(true); } }); } }); } }
こんな処理、想像できるだろうか? そこそもちゃんとしたチームでこんなコードは書くことはほとんどないだろう。 *1
そして、割と早い段階で知ることができて良かったのがAsyncTask。
これは便利な気がした。コマンダーパターンのように使え、適切に通信との実装が分離できたので、Activityに通信用のコードを直接書かなくて済むのだから当然ある程度はActivityのコードがきれいになった。
AsyncTaskを使った非同期処理のきほん | Developers.IO
当時はこんな感じのサンプルコードを当てにしていて、Viewの更新もAsyncTaskの中でやっていたかもしれない。
しかしながら全然まったく覚えてない。でもこういうものに依存していた僕はおそらくAsyncTaskの中でViewの更新をしていただろう。
そうやっておそらく僕が、Android流の通信からUIの更新方法を学んだだけで満足していたころ。
Loader vs AsyncTaskLoader
のような話が巷では割と真面目に話されて、それに煽られた感じでちょっと触ってみたけどこれは俺が触りたいもんじゃないなーという気がしてなんとなく使わなくて済めばいいなー程度にしか見ていなかった。 *2
そうこうしているうちに、2年くらい歳月が流れて?Volleyっていうライブラリが通信で使えるらしいって聞いて、ほーんって思っていたころBaseActivityがツライって話も流れてきた気がする。
当時の僕は、わりと大き目の会社のお客さんが社内企画プレゼンや営業先で使うようなAndroidのデモアプリや、TizenのホームアプリデモなるjsのアプリをjQueryを使ったりして作らされていたり、WindowsCE用につくられた秘伝のたれのような組み込みミドルウェア(C/C++)の保守や、よくわからない海外製の基板にAndroidのイメージを焼きこんでカーナビのシステムデモアプリを作ったりと割と画面サイズやリリースサイクル、ストアに申請などを気にしないで良いような、普通の?Webな路線のAndroiderな人たちとは別路線を走っていたので、BaseActivity辛い!みたいなことに出くわすことはなかった。
まぁそれでも、僕も僕なりに紆余曲折があって、割と真面目に成長しようと思い始めたころにMVCの話が沢山はてブから流れてくるようになった気がする。(僕がはてブをみるようになっただけかもだが)
なので一応MVCについてはわからないながらに、自分なりのコードが書けるようになっていた。
おそらく4年目くらいなんだけど、ちょっとずつ脂が?乗ってきた感じの頃。僕がずっと客先常駐なAndroidなプログラマーというアイデンティティを抱えていた上でも、割といい案件に巡り合えた。
ずっと*3 同じ組み込み系のお客さんのところでお仕事をさせていただいていて、B工業の子会社のEなんとかという会社がD社に対抗すべくAndroidタブレットでそれまでWindowsCEでつくっていたもの*4のAndroidアプリバージョンを作るという案件があり、僕はそれのメインプログラマとして抜擢してもらえたのだ。*5
客先のプロパでJavaが書ける人や、C#メインでやってた人がいる中で、その人たちの誰かではなく、社外人間である僕が選ばれたのは非常に名誉あった。*6
MVC無双
僕はようやく、Java言語仕様の上で、プログラムを書く勘所がつかめ始めていて、そういう場がもらえたのだから、無双が一段と過激さを増した。 (給料以外は…*7) - そこではMVVMがーとかMVPの話も少しずつ聞こえていたけれど、まだMVVMについてはC#界隈の話だったり、MVPも実際のプロダクトに採用する例の話も全くなかったので学びたてで確実に使えるMVCモドキを採用した。
当時の僕が関わってい製品は全体的に、回転などもなく特にキャンセルについて考慮する必要もなかったので、java.util.Oberver/Obserbable。Volleyを利用したコードになった。
たしかそのころRxJavaがいいらしみたいな話も流れてき始めていて。よく理解しないながらにコードの書き方のインスピレーションを受け、Volleyのコールバックの処理をちょっと綺麗にかける感じのコマンダーパターンなラッパーを作ったりしていた。
あんまり覚えていないんだけど、たぶんこんなコードだった気がする。
/** * MainActivity **/ public class MainActivity extends Activity implements java.util.Observer { .... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mUserDataModel.addObserver(this); mUserDataModel.loadUserData(); } @Override public void update(Observable o, Object arg) { UserDataResult result= mUserDataModel.getUserDataResult(); if (result.isSuccess) { mTextView.setText(userName); mButton.setEnabled(true); } else { showUserDataError(); } } @Override protected void onDestroy() { mUserDataModel.removeObserver(this); super.onDestroy(); } }
/** * UserDataModel **/ public class UserDataModel extends java.util.Observable { private UserDataResult mResult; public void loadUserData() { new GetUserData(createRequestUserData()) .setResponse(new Response<UserDataRes> { @Override public void onResponse(UserDataRes userDataRes) { String userName = userDataRes.getName(); mResult = new UserDataResult(userName); notifyObservers(); } @Override public void onError(Exception e) { mResult = new UserDataResult(e); notifyObservers(); } }).start(); } }
まぁRxとは似ても似つかないコードだがその当時は、こういうコールバックできれいにやればいいんだ!くらいの理解だった。
※つか余談だけど、いまKotlinでVolleyのサンプルコードあるんだ。レガシーだと思ってるものが公式にちゃんと更新されているのすげーw
Send a simple request | Android Developers
こうやってみると、まぁRxなくても、処理をいろいろ別クラスに端折ってしまえば、シンプルなリクエストからのコールバックの処理はそれなりに書けるものであるというのがよくわかる。
で、そういえばなんでMVVMやMVPを選ばずMVCを採用したのかって話はしたけど。
MVCのようなパターンを利用しようとしたかを簡単に言うと、
やはりどこにどう書けばいいのか?まったく指針がなく、AsyncTaskの中で漠然とUIを更新する処理とかいれてしまったりして、保守的な面倒さを感じていたので、それなりに責務を分担した書き方っていうのを、何となくやってみたかったというのがあるのだと思う。*8
結果的にそれなりに分離してやったわけだけど問題なく書けてしまったわけだった。*9
この時の僕は、委譲という言葉は知っていたけどやり方は知らなかったので、共通するActivityの処理は例えばTabScreenFragmentやUserFrontActivity、MaintenanceActivityなりの基底クラスをつくって、BaseActivity/BaseFragmentじゃないActivity/Fragmentで関心事を分離させてしまえばいいじゃなーいと思いながら継承を使っていた気がする。*10
まぁそうやってMVCモドキを採用しても、Activityのコードがすっきりするわけじゃなく、微妙にめんどくさいViewの表示切替の分岐コードを分離するためにMVPを強引に採用したりもした。これはPにViewのインスタンスを直接を持ち込むパターンでやってしまった。まぁそれでも悪いものではなかった。テストコードを書く文化ではなかったし、それで別にメモリリークで困るなんてドジを踏むこともなかった。
そうやって、MVCだのMVPだの、少しかじったものを積極的に取り入れては、自分のやったものがどういう意味があって、どういうことには向いていないとか、何がどう良くて、そうじゃないものがどう悪いのかがだんだんわかるようになってきた。
こういう業界の全体的な潮流に合わせて、自分の設計のようなものに対する仮説の実証実験が実プロダクトで行えたのは、過渡期だったからだったと思うし。それはそれでよい時代だったなーという気がする。
そしてMVP無双へ
そして現在。フリーランスになって、現在のチームではGoogleSamplesのMVP-Cleanを取り入れている。
GitHub - googlesamples/android-architecture at todo-mvp-clean
これは現在のお客さんのところのチームリーダーと、今のアプリのVersion2.0案件でLoaderを使っているところの負債返済をしながらテストコードがかけるようなコードにしていきたいという話から、僕もそのサンプルのコードを前々からいろいろいじっていたので、積極的に推し進める方向になったんだと思う。
僕も一度、以前の案件でMVPで悩んでいた経験やGoogleのサンプルのコードがどういうことを期待していたものかを理解していたつもりなので、積極的に発言してプッシュした。 *11
けれどUserCaseHandler部分が不必要に冗長なのと、RxJavaを取り入れたときにコードの変更がそう多くなくて済むように、ちょっとした工夫を入れて特性なUserCaseHandlerを実装させてもらった。
DDDはアーキテクチャではない
どこかのだれかが言ってたけど、アプリ界隈でDDDやClean Architectureだのまぜこぜで語られて胡散臭いって話。
当たり前なんだけどそれらは同列の代物ではないし、みんなわかっている。言っていればカッコイイと思っている人も一部いるかもしれないがほとんどは、そうではないのだ。
だいたいDDDやClean Architectureが今出てくるのは、技術的負債の機関銃掃射を浴びせられたり、生兵法で技術的負債に立ち向かおうとしたために流れ弾で負傷を負ってしまった兵士が、テスタブルやメンテナブルにして技術的負債に立ち向かいたいという思いの上で語られているのがそれらの話だ。
まずClean Architectureはスパゲッティで責務がキメラなコードをすっきり分割させることができる。*12
Clean Architectureは別にGUIやAndroidやiOSのためのものではない。ようやくAndroiderやiOSerな界隈のプロダクトのライフサイクルが一巡したりして、あの過ちを繰り返すまいとしているだけなのだ。
そしてDDDも、GUIプログラミングのためのものではない、DDDはドメインロジックが表現されるコードを理想的な形にするための方法論だ。一概に"アプリの開発にはDDDのような重厚な設計手法は不要!"と言うには時期尚早だろう。*13
それにAndroidな人達がだけがいっているわけではないし、僕が観測しているScala界隈でも積極的に聞く話だ。
ただ、AndroidなJava/KotlinとScalaのコミュニティの間には、溝こそないがあんまり活発な交流が行われているようではないし、言語自体にある機能や文化的な差からも、お互いが同じ情報共有プールの場にはないというだけだ。
そういった感じで、AndroidはAndroid特有の状況下で、他の言語やフレームワークとは違う特有のコードデザインが重要になってきている状況にあるからこそ、二つの別々のレイヤーにあるものが同様の場所で語れるのようであるのも仕方ないかなーと考える。
ただ、問題提起した人がそうだとは言わないけど、そういう背景がわからない人なくてこれからDDDをたしなみとして読もうみたいな人には少しわかりづらい状況ではないかなーという気がする。
あとDDDの本はたしなみで読む本としては動機が薄くて読みづらいかなーという気がする。実際のプロダクトのコードを書いて、レビューしあってでしか学べないことが多すぎる気がしている。
とりあえず以上。
*1:まぁ今でも外部のライブラリに依存していない分、ピュアなコードとしては良いのではないかという気はする…が責務を分けてクラスに分割くらいはちゃんとしよう
*2:当時の僕には何がいいのか全く分からなかったし、今もわからない。でも、その後がっつりLoaderが使われているプロジェクトに入ってがっつり、ExecutorとHandlerをラップしたものに書き直すなんてことがあるとは想像もしていなかった
*3:半年~1年くらいソシャゲ界隈に島流しに合ったこともあったが
*4:常駐先の会社がメインで開発し保守していた
*5:Androidプログラマは少なかったので、割とその頃にAndroidやってた人達は局所的にはなかなかエモい仕事を任せてもらえたりしたんじゃないか?
*6:僕はずっとその会社で、プロパのデザイナーさんに同じプロパだと思われていたりするくらいの気兼ねしない物言いだった上に、一年で3,4案件をこなしていて失敗した案件もなく、小さな山の中では無双状態だった。給料以外は…
*7:そのころの年収は300万程度だった
*8:それで問題なくコードがかけるのかっていう実験場だ
*9:今となっては当然なのだが、昔はどういう書き方が正しいかなんてのは知るよしもなく・・・
*10:いやいや今考えるとこれはないわw関心事が分離されているとはいえ、関係ない処理を不要なサブクラスに持ち出さないようにするには、それ専用の親クラスを用意するようになってかえって煩雑になる。でもまぁそれくらいわわかっていた。けれどinterafaceと委譲や分割でなんとかするなんて思いついていなかったし、委譲使っても結局受け取る部分はそのままやしーコードは薄くなるけど、メソッド数とか無駄に増やす感あるーって思ってたらそれだけが委譲ではなかったのでした
*11:あわよくばRxJavaも入れられるかもと思っていたが、そうは問屋が卸さず断念・・・。学習コストや、保守の面で不安があったためと解釈している
*12:まぁ厳密にそれがClean Architectureな実装かどうかという話はあるのだが、Androidではハードルを下げて特にGoogleのやつのような軽量なものでよいと思う
*13:個人的にはDDDがっつりやらなくても、明解な責務の分離と暗黙知を排除するためのコーディングデザインとしてDDDが一部うまく作用してくれればそれでいい気はしている