この記事は呉高専エンジニア勉強会 Advent Calendar 2017 の 19日目の記事として書かれています.
こんにちは.
呉高専エンジニア勉強会OB chigichan24です.
最近はもっぱらAndroidのことを考えながら寝床につき,夢の中でバグと格闘して朝を向かえるという生活をおくっているのですが,そんな中思ったことを幾つか紹介したいなぁと思います.
とりあえずモットーは,「人を恨まずコードを恨め」(インターン先で聞いた)なので,書いた人をどうこう言うんじゃなくてエンジニアならコードで語れや👊みたいな部分に心を打たれ,それ以降この言葉を大切に生きてます.
ところでお前は誰だ.
そうですよね.OB(詐称)なのでまじでお前誰だよ状態ですよね.
ぷろぐらみんぐをはじめてまだ一ヶ月なので右も左もわかりません😉
端的に説明すると euglena1215という人がいるのは皆さんご存知だと思いますが,彼の同級生とだけ覚えておいてください.
私は彼にこのアドベントカレンダーを埋めないとお前覚えとけよ(意訳)(要約)(自己解釈) と言われたので怯えながら書いてます.
今日の話
今日は私が開発に関わったアプリSyncPodについて,ちょっとお話しようと思います.
そもそも
私がこの開発に携わり始めたとき,既に幾つかの機能が実装されていた状態でした.
これをいい感じにリファクタしながら,機能追加する感じで頑張ろうと思ってました.
今までの設計
このアプリ,特に設計もせずにどんどん機能追加していきました.ましてや,MVVMのような高尚なアーキテクチャの上でも動いていなかったので,結構大変な感じになってました.
このアプリの現状をまとめると,こんな感じになってます.
もう,嫌な予感しかしないですね...
さて,実際に起きた問題と共に振り返って見ましょう.
問題1 複数のAPIを捌く問題
問題
このアプリではYouTubeのアプリっぽいことをするので,YouTubeのAPIを使っています. YouTubeのAPIは動画情報であったり,検索であったり,と複数の用途でAPIが別れているらしいです.
つまり,複数のAPIを併用して使う必要があるわけです.*1 今回のアプリでは2つのAPIを使っていました.
で,今回のSyncPodではそのうちの1つのAPIをラップしてアプリに特化したAPIサーバを立てています.
ところがところが...
2つのAPIの戻り値がほとんど一緒なんです!!!???
一緒なのに別のAPIであることを意識しながら,しかも呼び出し方が2つで異なっていてもう次に追加する時はどうするんじゃい状態です.
一方は1,もう一方は2から呼び出しています.
そもそもこれが起きる原因として,
- smartUIになっている.
- どのAPIからデータが決まるかを意識してViewがアップデートされている.
の二点が考えられると思います.
解決策
- smartUIを解決する. smartUIとは,日本語に直訳すると"賢いユーザインタフェース".これは皮肉として使われています.
UIはただ表示するだけ,それだけを責務として持つのがいいと思います.UI(View)の仕事は
- これ表示して!!とかあれ表示して!!!て言われるのを言われるがまま表示すること.
- ユーザのタップ情報とか,スクロールの情報を受け渡すこと.
の二点だと思います.それゆえ,Viewつまりは,Activity,Fragmentは完結に表現できるはずです.smartにはならないはずなんです.
つまり,smartUIを解決すると,ViewからAPIを呼ぶことなんてまずありえなくなります.
- どのAPIからデータが決まるかを意識しない.
UseCaseにおいて,このAPIからデータが来るからここを...とやっていると規模が小さい時はなんとかなりますが,規模がでかくなると収集がつかなくなります. 特に,APIだけでなく,データというのは様々な場所に格納されています(ex. 端末内のデータベース.メモリ.API).それらがどこに所属するか?というのはUseCaseで考えることなのでしょうか?
私はNoだと思います.APIに限らずデータがどこに保存されているかなんて,考える必要はないと思います.それを体現するのがRepositoryパターンです.
概念図としてはこんな感じです.
UseCaseはRepositoryから上方向に流れてくる情報だけを考えれば良いです. 欲しい情報をRepositoryに渡せば,どこに保存されているのかを考えることなく情報を使えるようになります.
実際には,その下でLocalStoreなのか,RemoteStoreなのかまた,それらが若干の依存を持ちながら取得されるのか(例えばTokenが発行されているか否かで処理が変わるのようなもの)というのをUseCaseから見るとよしなに処理して結果を戻します.
このようにすれば,APIが複数あるからどうこうとか,冗長だからどうこうのような問題は全て解決されます.
ここで,注意すべきはUseCaseで処理するべきなのか,それともRepositoryの下で処理すべきなのかを適切に見極めることです. これは非常に難しい問題です(私自身も考えるのが苦手)が,ここをうまく分けることができると良いです.よくある議論としては,validationはどこでやるべき処理なのかというのが挙げられます.
問題2 コピーアンドペーストが引き金で...
問題
コピーアンドペーストは世界の様々な開発者のノウハウを一秒で実現できる方法ですが,適切に見極めないと事件を起こします.
事件の例として,
冗長になる.
拡張性が無くなる.
なんで動いているのかわからなくなる.
といった問題があります.これは今回の開発においても発生しました.
今回コピペが多発していたのはサーバとやり取りをするためのHTTP Requestに関する部分です.SignInやSignUpなどはリクエストパラメータが変わるだけで本質的にはPOST操作であります.しかし,二,三行しか異ならないのにファイルが分けてある部分があったりしました.
また,コピペ部分のコードを弄る必要が出てきたときに挙動が意味不明すぎて手間取ったこともありました.
解決策
これを見た私はまず共通化を図りました.冗長だった部分つまりは,HTTP Request部分をabstractな感じにしてそれを使うclassに継承させました.
しかし後に,さらに問題が発生しました.
それは,非常にクリティカルな問題でした.よくよく考えると,コピペのコードを使っているメリットとは??となりました.
Webに転がっている記事は文量を最低限に抑えるために,かなり簡潔に書かれています.それゆえ,そのままコピペするといろいろ問題が置きます.
よくよく考えてみれば,こんなの一から実装するのではなくokHttp + Retrofitのような解決策が最適だったはずです.
変に固執して頑張るのではなく,なにが最もこの問題を将来的にも解決できるかを意識するのが大切です.
問題3 Android4.x 系だけで動かなくなる
問題
まあ有名なAndroidバージョンレベル低いとなぜか動かなくなる系の問題です.これはまじで殺意が湧きます.
じゃあこういう問題と直面したときにどうやってバグを特定するのが最善だったのでしょうか? 適切に依存を分けることができていれば,もっと容易に特定できたはずです(実際には,Wiresharkでパケット解析しながら突き詰めていきました.).
ちなみに,踏んだバグはAndroid5.x以上はURLにUTF-8そのままで行けたのですが,4以下だと適切にエンコードをしなければならなかったようです.
解決策
端的に言うと,Layerをキチンと分け,その上で一方向に依存するようにつくるということです.
方向が一方向であれば,あっちいってこっちいってみたいに原因を探さなくてもすぐに原因究明できます.私はMVVMを今回は採用していきたいと思います.
そして
最終的にはこのような感じでアプリを改修したいと考えています.
おわりに
いかがだったでしょうか?私自身設計を一から考える機会はあんまり経験したことがなく.そう考えるメリットってなんだろうて感じることが多くありました.しかし,そうしないと起きる不都合を実際に経験するとその大事さを身にしみて体感しました.
このアプリはまずAndroidからこの設計方針に沿って再実装します.
さらに,いずれiOSにも対応します(断言).そういう設計ができているのか?また自問自答し,メンバーと話し合い良い設計を見つけていきたいです.
最後の最後に.
このアプリはSyncPodといいます. ぜひぜひぜひぜひぜひぜひぜひぜひぜひぜひダウンロード・インストールして遊んで私に連絡してください!!!!!!!!!!!
*1:そもそもそれは本当なのか?という問題があります.それを自分自身が確認していないので本当なのか知らないですが,まあそうなんでしょう.そう信じてます.