はじめに
今までMVP設計が整備されていないプロジェクトで業務をしてきましたが、初めてMVP設計をしているプロジェクトに配属されたので思ったことを書いていこうと思います。
あくまで思ったことで、正しいとは限らないのであまり鵜呑みにはせずにご参考程度に...
UnityにおけるMVP設計
MVPとは何なのかについては、本記事のメインの内容では無いため詳細には書きません。
@toRisouP さんの記事がとても分かりやすいです👀
ざっくり図にするとこんな感じです🙃
プロフィール画面を例にすると、
プロフィール画面を最初に表示する時
1.Presenterが画面のPrefabを読み込み開く
2.Modelが通信でプロフィール情報を取得
3.プロフィール情報をPresenterを介してViewに適用
プロフィール名を変更するとき
1.Viewが名前の変更を通知(通知するだけでPresenterの存在は知らない)
2.PresenterはViewの通知を受け取り、Modelに名前変更の通信をリクエストする
3.Modelが名前変更通信を行い、名前を更新する
のような流れになります。
通知周りはUniRxがよく利用されます。
MVP設計ではなくMonobehaviourに全てを書いていたプロジェクト
Monobehaviourを継承したScene(UnityのSceneではなく独自の単位)を起点に全てのロジックを書いていくスタイルでした。
メリット
直感的
もうこの一言に尽きます笑
新しくジョインされた方も、どこをみたらいいか明確なのですぐに仕事に取りかかれるかと思います。
MVPの面倒な取り回しもないので、素早く実装できてコードも追いやすいです。
デメリット
自由
自由にロジックを組めるので、担当者が自由な設計で好き勝手書けてしまいます。
業務で書くコードは、自分だけではなく他人にも分かるようにしなければいけません。
担当者ごとにコードの書き方が違っていたら、メンテナンスが大変です。
MVP設計にしていれば、コードの追い方はある程度統一されるので、実装の把握がしやすくなります。
また、自由が一番カオスを作りやすい環境です。
Updateループの中で通信してデータを更新しようが、ポップアップを表示しようが、演出を入れようが何でもありです。
MVP設計にはこのような実装を抑止する力があります。
Viewの再利用ができない
データの更新やサーバー通信がViewに直接書かれているので、表示部分を再利用したいとなってもいらない処理も含まれてしまいます。
分岐を仕込めば再利用することは可能かと思いますが、将来コードがカオスになるのが目に見えています。
MVPの設計でしたらPresenterとModelの差し替えで対応できるはずです。
シングルトンが必要
シングルトンになりそうなものは以下のようなものがあると思います。
- サーバーデータ
- アセットバンドル
- サーバー通信
- ポップアップ・シーンマネージャー
- サウンドマネージャー
- ローカライズ
- ローカルデータ
いつどこでこれらが必要になるか分からないので、シングルトンにするのが手っ取り早い設計になってしまいます。
特にデータ周りは不具合でコードを追うときに、関係がないUIのロジックも追わないといけなくなってしまうので、メンテナンスがしにくいです。
テストがやりにくい
UIが正常に動いているかテストするときに、サーバー通信処理なども一緒になってしまうのでサーバー通信なしで確認することが難しくなってしまいます。
UI周りのロジックのみになっていれば、テスト用に仮データを実装して渡せばUIのテストが可能になります。
逆にデータのテストをしたい時も同様です。
MVP設計のプロジェクトに入って
お客様に届くサービスのコードがこんな設計でいいのかと思っていたところ、MVP設計のプロジェクトにジョインすることになりました。
MVPのために何かパッケージを導入してはなく、シーンがModelとPresenterを複数持ち、PresenterがUIを生成するといったシンプルな設計になっています。
そしてシングルトンは禁止です。
メリット
Modelだけの修正・Viewだけの修正ができる
Modelにおいては以下の修正をするときにやりやすいなと感じました。
- ローカルデータの保存方法をPlayerPrefsからLiteDBに変更したとき
- データをキャッシュしサーバー通信を減らす対応をするとき
表示周りのロジックを気にすることなく、データ周りのロジックに集中できるので生産精爆上がりです。
Modelの修正はそこまで頻繁に入るものではないですが、Viewの修正は頻繁に起こると思います。
表示の修正を行っただけのはずなのに、データがおかしくなったみたいなことがMVP設計によって回避できます。
Viewの再利用ができる
実際に最近、
「ここの画面こっちでも同じ感じだから共通化したい」
と言われました。
自分は全く、他のところでも使われる想定で設計していなかったですが、MVP設計をしていたことでViewのみの再利用がそこまで大変な思いをせずに実現できました。
ただ、過去の記事でも書いていますが、なんでも共通化すればいいという話ではありません。
シングルトンを最小限にできる
MVPは必要なところに必要なものを渡すことができる設計なのでシングルトンを最小限にすることができます。
ただ、どこにでもアクセスできるアプリケーションマネージャーを丸ごとModelやPresenterに渡したらシングルトンと変わりないですし、設計の意味が薄れてしまうのでやらないほうがいいです。
単体で利用したいと思ったときに、アプリケーションマネージャーが必須になってしまうので。
依存関係を最小限に抑えることで、テストと再利用がしやすくなります。
テストがしやすい
UIのテストはViewで、データ周りのテストはModelといった感じで役割を分けて行うことができます。
また実際の開発では、UIデザインとサーバーの実装が終わっている状態でコーディングに着手できるとは限りません。
UIデザインが決まったらViewの実装、サーバーのAPIの仕様が決まったらModelの実装をし、サーバーの実装が終わったら結合して確認といった感じで役割を分けて実装できるので作業がしやすいです。
PresenterはViewとModelで繋げられる部分が出来たら都度実装していくことになります。
デメリット
設計が崩壊するとただの読みにくいコードになる
ルールを守らないと、Modelで直接Viewの処理を読んだり、ViewでModelの処理を呼ぶなどといったことができてしまいます。
MVPの設計が崩壊してしまうともはや読み辛くメンテナンスがしにくいコードにしかなりません。
こうなってしまうくらいならMVP設計入れないほうがマシだと感じてしまいます。
コンストラクタの引数が多い
シングルトンが禁止されていることもあって、PresenterとModelのコンストラクタの引数が多くなりがちです。
Viewに対して表示に関係する値以外で渡すものが多いというのは設計を見直したほうがいいです。
DIの話になってしまいますが、VContainerのドキュメントにもMonobehaviourにオブジェクトをインジェクトしないことが推奨されています。
依存関係はなるべく減らしたほうがいいのですが、Modelで見ると、
- 通信処理
- ローカルデータ
- ローカライズ
- ユーザー情報
などに加えて、他にも処理に必要なModelを渡す必要が出てくることがあります。
・・・シングルトン使いたい!!
となりますが、Viewでもどこからでも呼べるようになってしまうので、面倒だけど設計を守る抑止力はなると思います。
また、見たことはないですが、通信の内部の処理を複数場所によって切り替えたいとなったときにシングルトンだと対応が大変になってしまいます。
インターフェースを渡すようにしておけばルート部分の修正だけで済みます。
コンストラクタの引数が多い問題はVContainerなどのDIを導入すればいい感じ解決できそうと個人的に思っています。
リアクティブスパゲッティ,コールバック地獄になる
ViewとModelまたPresenter同士が参照を持たないようにするために、コールバックやRxで処理やイベントの通知をする必要が出てきます。
なので、リアクティブスパゲッティやコールバック地獄が発生しやすくなってしまいます。
SubjectをOnNextそしてまた次のSubjectがOnNext・・・
こうなってしまったら処理の流れを追うだけでも日が暮れてしまいます。
そうなってしまうくらいでしたら、UniRxのMessageBrokerの参照だけを渡してPubSubを導入したほうが良いなと感じることがあります。
送信するイベントクラスの参照を調べればどこで使われているかが一目でわかるので。
MVPのコールバック地獄がが発生した場合、設計を疑ってみる必要はあると思います。
非同期処理周りでコールバック地獄が起こっているのであれば、UniTask,async/await,cancellationをしっかり使いこなせば回避できるはずです。
MVPをはじめとして設計周りで思うこと
MVP入れたからといってコードが綺麗になるわけではない
MVP設計はメンテナンス性を上げるものであって、コードを綺麗にするためのものではないと個人的に感じています。
コールバックやイベント通知があちらこちらで行われるので、読みやすいコードではないです。
むしろ、ボタンに登録された関数に全ての処理が書いてある方が読みやすいと思います。
もちろん設計の意図した通りにコードを書けば、綺麗なコードになります。
何のためにMVPを使うかが大事であって、コードを綺麗にするためのMVPではないと感じています。
コードを綺麗に書いたとしても売り上げが上がるわけではない
悲しいことにどれだけ完璧な設計で綺麗にコードが書けたとしてもプロダクトの売り上げは上がりません。
すごい経験が長いというわけではないですが、売り上げが出ているプロジェクトよりも後発の売り上げが出ていない新規の方が綺麗なことがほとんどでした...
だから綺麗に書かなくていいということではなく、売り上げが上がった時にさらに品質の高いサービスが提供できるかが最初の設計にかかっています。
コードが汚いか綺麗かはお客様には関係ありません。
設計ばかりに目を向けるのではなく、今実装しているものをどう良くしていくか考えるほうが大事です。
ルールが無いのも問題だが、縛りすぎるのも良く無い
なんでも好き勝手に実装していいよというのも問題ですが、設計によってガチガチに書き方が固められてしまっているのも良くないと思っています。
(Unityでゲームを作る時の話で、お金や命に関わるシステムなどはガチガチな設計は必要だと思います)
もしUnityで何かしらの画面を実装する時に、MVP以外の選択肢が取れなかったらインゲームを実装する時に困ってしまうと思います。
(MVPはUIを実装するための設計なので)
急にミニゲームを入れる話も出てくるかもしれません。
以前設計の思想に正しくない方向でこだわってしまっているコードを見たことがあり、誰もメンテナンスできないような作りになっていたこともあります。
変に複雑にせず、誰にでも分かりやすいシンプルな作りにするのがベストです。
最後に
色々書かせていただきましたが、結論としてMVP設計をしっかり活用して行きましょうと思っている側の人間です🙃
しかし、MVP設計ばかりに固執しすぎるのもよくないので、他の設計や思想を取り入れて品質の高い開発をしていくことが大切です。
過去にも設計に関する記事を書いているのでよろしければご覧ください!