Slide 1

Slide 1 text

Unityにおける 設計パターン 2021/2/19 とりすーぷ

Slide 2

Slide 2 text

自己紹介 •とりすーぷ • @toRisouP • VR系の開発してる • Microsoft MVP 2018~ • Developer Technologies • 最近はVRChatしてます illustrations by kota(@kt_kkz)さん

Slide 3

Slide 3 text

本を出しました •「 UniRx/UniTask完全理解 より高度なUnity C#プログラミング」

Slide 4

Slide 4 text

今回の話 •設計・アーキテクチャとは •Unityにおける設計のレベル •設計レベルをあげるためには •クリーンアーキテクチャとは •Unityでクリーンアーキテクチャ

Slide 5

Slide 5 text

発表はショート版です •調子に乗って100枚超のスライド作っちゃった •10分に絶対に収まらないので かなり端折ったショート版で発表します! •公開する資料はフル版です

Slide 6

Slide 6 text

先に言っておくと •この本読んでください

Slide 7

Slide 7 text

設計・アーキテクチャとは

Slide 8

Slide 8 text

設計とアーキテクチャ •設計 • クラス設計、コンポーネントの構成、全体の構成 • 「何をどうやって作っていくか?」という指針

Slide 9

Slide 9 text

設計とアーキテクチャ •設計 • クラス設計、コンポーネントの構成、全体の構成 • 「何をどうやって作っていくか?」という指針 俯瞰して見た設計をアーキテクチャ と呼ぶ

Slide 10

Slide 10 text

例:家の建築 •設計 • 壁の位置、形状、配管、配線… •アーキテクチャ • 家全体の形、間取り、部屋のレイアウト…

Slide 11

Slide 11 text

設計とアーキテクチャ •設計とアーキテクチャは地続きである • 詳細な設計の連続がアーキテクチャを構築する

Slide 12

Slide 12 text

「設計・アーキテクチャ」の目的 •「システムの開発・保守・運用にかかるコス トを最小限にする」ことが目的

Slide 13

Slide 13 text

コストを最小限にする •クソコードに振り回される時間を減らす •後の仕様変更にも柔軟(ソフト)に対応する

Slide 14

Slide 14 text

よくある誤解:その1 「設計を捨てて、開発メンバーが思い思いのや り方で開発した方が効率いいんだけど」

Slide 15

Slide 15 text

解 •「設計を捨てる」で得られた開発速度は、 後に訪れる莫大な保守コストで帳消しになる • 超短期的には速度が出るが、後に必ず失速する • 長期的に見るなら「ちゃんと設計する」以外に 開発コストを下げる方法はない

Slide 16

Slide 16 text

ダメなコードの例 •スパゲッティコード • オブジェクトとオブジェクトがどう関係するのかがわからない状態 • 影響範囲が謎でどこを触っていいかわからない状態 • ちょっとした機能変更にもすごく時間がかかる状態 •外部モジュールと一蓮托生した状態 • 「生殺与奪の権を他人に握らせるな」状態 • 外部モジュール(ライブラリやフレームワーク)の機能に強く依存しすぎた結果、 外部モジュールの仕様変更に振り回されてしまうような状態

Slide 17

Slide 17 text

コードは腐る •少しの油断でコードはすぐにダメになる • 割れ窓理論 • どんな状況においても「設計を守る」を強く意識して 開発に臨む必要がある

Slide 18

Slide 18 text

よくある誤解:その2 •「巷にある○○アーキテクチャっていうのを 使えば万事OKなんでしょ?」

Slide 19

Slide 19 text

解 •NOです • そもそもアーキテクチャは自分で考えてDIYするもので あり、万能なテンプレートがあるわけではない • プロジェクトに応じて柔軟な設計が求められる

Slide 20

Slide 20 text

よくある誤解:その3 •「設計とかアーキテクチャって、要件がガチ ガチに固まっている、ブレがないプロジェク トでしかまともに使えないでしょ?」

Slide 21

Slide 21 text

解 •認識が逆です • 「変化に柔軟に対応できるようにアーキテクチャ を作っていく」が正解 • 機能の変更・追加・破棄があるのはソフトウェアの宿命 • 変化を受け入れられるアーキテクチャを作る、が理想 (現実はメテオフォールなどの根本的なちゃぶ台返しもあり上手くいかないこともあるが、 それは組織体制の問題であって開発者だけで改善できる話ではない)

Slide 22

Slide 22 text

言い方を変えると •「変更にかかるコストを最低限にする」を達成して いればそれでアーキテクチャとして正解 • 巷にある「○○アーキテクチャ」から外れてたり、 アーキテクチャに汚い部分があってもそれで上手く 開発が回っているならそれでOK (設計しなくていい、と言っているわけではない!)

Slide 23

Slide 23 text

よくある誤解:その4 •「そもそもUnityって特殊なフレームワーク(FW) だと思うんだけど、設計ってやって意味あるの?」

Slide 24

Slide 24 text

解 •あります。 • FWに振り回されるのは別にUnityに限った話ではない • Unityだけが特別なわけではない • 「(様々な)FWとの上手な付き合い方」はソフトウェア開発の 一般的な問題 • その対策として「クリーンアーキテクチャ」などの ○○アーキテクチャが考案されてきた

Slide 25

Slide 25 text

設計・アーキテクチャ まとめ •設計・アーキテクチャを使う目的 • 開発・保守・運用にかかるコストを最低限にすること • プロダクトの価値を高め続けられる状態を維持する •アーキテクチャを死守するのが開発者の使命 • 安易に「設計を捨てる」という選択肢を取らない

Slide 26

Slide 26 text

Unityにおける設計のレベル

Slide 27

Slide 27 text

レベルわけ •Unityにおける「設計のレベル」を 自分が勝手に決めてみました • 異論・反論は受け付けます(むしろ議論したい)

Slide 28

Slide 28 text

図の意味 GameObject (with MonoBehaviour) C# Pure C# Package (Assembly Definition Files)

Slide 29

Slide 29 text

レベル0:設計が存在しない • 好き勝手作られた状態 • GameObjectベタ置き • 神クラスがたくさん • 相互参照、循環参照、 密結合でぐちゃぐちゃな状態

Slide 30

Slide 30 text

レベル1:制御フローが整理された状態 • 制御フローが整理された状態 • オブジェクトの役割が明確になった • 参照関係が一方向に • 機能ごとに関係性が整理されている • 密結合なのはそのまま

Slide 31

Slide 31 text

レベル2:制御フローと依存関係の分離 • 疎結合化が進んだ状態 • SOLID原則が意識されている状態 • 依存関係逆転を考えてインタフェースが 要所要所に利用される C# C# C#

Slide 32

Slide 32 text

レベル 3:モジュール化が意識される • 機能ごとにモジュール化される • モジュールの安定性が考慮される • 機能の再利用性が考慮される • asmdefが切られたりする (コンポーネント、という呼び方もあるが UnityのComponentと被るのでここでは モジュールと呼びます) C# C# C#

Slide 33

Slide 33 text

レベル 4:ピュアなC#を活用する • ピュアなC#がメインロジック化 • MonoBehaviourを使わない実装が増える C# C# C# C# C# ←Unity依存はここだけ

Slide 34

Slide 34 text

レベル 5:アーキテクチャが意識される • 全体のアーキテクチャを考慮 して一貫した作りになる • 詳細設計のみではなく、 全体を俯瞰してみた設計 (アーキテクチャ)が 強く意識される状態 • レイヤの概念が入ってくるなど C# C# C# C# ↓Unity依存はここだけ C# C# C# C#

Slide 35

Slide 35 text

レベル分け 5:アーキテクチャが意識される 4:ピュアなC#が活用される 3:モジュール化が意識される 2:制御フローと依存関係が分離する 1:フローが整理される 0 : 設計なし

Slide 36

Slide 36 text

レベル分け 5:アーキテクチャが意識される 4:ピュアなC#が活用される 3:モジュール化が意識される 2:制御フローと依存関係が分離する 1:フローが整理される 0 : 設計なし ハイリスク ローリターン どんな状況でも最低限達成したい できてればよりGood コストは高いが メリットも大きい

Slide 37

Slide 37 text

設計レベルをあげるメリット •長期開発における機能追加・保守・運用の コストが下がる • 機能追加や仕様変更に柔軟に対応できるようになる • 大人数で開発しても破綻しにくい体制が作れる

Slide 38

Slide 38 text

設計レベルをあげるデメリット •開発者に対する負担も上がる • 開発規模に見合わない設計レベルは逆に手間を増やす • 開発者に求められる設計スキルも高くなる •動作パフォーマンスが落ちる場合もある • 「DIが遅い」「GCアロケートが増える」 • チューニングのためベタ書きコードがどうしても必要になる

Slide 39

Slide 39 text

トレードオフ ⇔ • 理想を語るなら「長期的な運用のしやすさ」に振りたいが… • 現実は様々な理由で妥協しなくてはいけないことが多い • 場面によってはレベル1~4の範囲で収まってても全然よい 短期的な開発のしやすさ 極限を求めるチューニング 長期的な運用のしやすさ

Slide 40

Slide 40 text

大事なこと •チームがやりやすいレベルで止めてもよい • 難しいと感じたらレベルを下げるのもあり • ただし最低でも「レベル1」は維持したい •設計レベルをハイブリッドにしても良い • チューニングしたいところはUnityと密にする • Unityと関係ない部分はできるだけピュアC#で書く

Slide 41

Slide 41 text

ハイブリッドなやり方 C# C# C# C# C# C# C# C# C# C# レベル2で 構成された領域 レベル5で 構成された領域 依存

Slide 42

Slide 42 text

設計レベル まとめ •Unityにおける設計をレベルわけしてみた • 勝手に決めたレベルなので異論反論は受け付ける •どのレベルで開発するかバランスをみるの大事 • 理想としてはレベル5を目指していきたいが、 現実はそこまでいけることは稀 • どこかでバランスを取る必要がある

Slide 43

Slide 43 text

設計レベルをあげるためには

Slide 44

Slide 44 text

設計レベルの壁を超えるためには? 5:アーキテクチャが意識される 4:ピュアなC#が活用される 3:モジュール化が意識される 2:制御フローと依存関係が分離する 1:フローが整理される 0 : 設計なし ここの壁を超えるためには?

Slide 45

Slide 45 text

レベル0→レベル1 •制御フローの整理を意識しよう • 制御フローをキレイにすることが何よりも大事 • フローさえ整っていれば最低限スパゲッティコードは 回避ができる

Slide 46

Slide 46 text

制御フロー •「誰が」「誰に」命令をするのかの流れ • オブジェクトの責務、メッセージの流れ ぐちゃぐちゃなフローの例 整理されたフローの例

Slide 47

Slide 47 text

制御フローの整理 1. オブジェクトのもつ役割をハッキリさせる 2. オブジェクトごとの主従関係をハッキリさせる 3. 相互参照、循環参照を避けて データの流れをわかりやすくする これで レベル0 → レベル1 に上がる

Slide 48

Slide 48 text

レベル2以上を目指すなら •制御フローに実装が振り回されない対策 が必要になる • オブジェクトの制御フローをそのままコードに落とし込んでい るだけではダメ(密結合化してしまい柔軟性がなくなる)

Slide 49

Slide 49 text

学ぶべきもの •「設計原則」を学ぶべし • 設計原則は「制御フローを変えずにどうキレイに実装す るか?」というやり方をまとめたもの

Slide 50

Slide 50 text

設計原則 •SOLID原則 • SRP、OCP、LSP、ISP、DIP •コンポーネントの凝縮性の原則 • REP、CCP、CRP •コンポーネントの結合性の原則 • ADP、SDP、SAP

Slide 51

Slide 51 text

設計原則 •SOLID原則 • SRP、OCP、LSP、ISP、DIP •コンポーネントの凝縮性の原則 • REP、CCP、CRP •コンポーネントの結合性の原則 • ADP、SDP、SAP ←制御フローと実装の分離に必須(レベル2に必須)

Slide 52

Slide 52 text

設計原則 •SOLID原則 • SRP、OCP、LSP、ISP、DIP •コンポーネントの凝縮性の原則 • REP、CCP、CRP •コンポーネントの結合性の原則 • ADP、SDP、SAP ←より大局的な視点での設計の話 (レベル3以上に必須) ←より大局的な視点での設計の話 (レベル3以上に必須)

Slide 53

Slide 53 text

詳しくは •この本読んでください

Slide 54

Slide 54 text

必要なこと 5:アーキテクチャが意識される 4:ピュアなC#が活用される 3:モジュール化が意識される 2:制御フローと依存関係が分離する 1:フローが整理される 0 : 設計なし 制御フローを意識しよう SOLID原則を学ぼう コンポーネントの 凝縮性・結合性の原則 を学ぼう

Slide 55

Slide 55 text

一番大事なテクニック •「依存関係の逆転」 • 依存関係逆転の原則(DIP)を行うのに必須 • これができるだけでもかなり違うので絶対覚えたい

Slide 56

Slide 56 text

依存関係の逆転 •制御フローはそのままに、 モジュールの依存関係を逆転させるテクニック • インタフェースを活用するワザ • これができると疎結合化が進む

Slide 57

Slide 57 text

uGUI.Text UnityEngine.UI 例:MVPパターン •Model – View – Presenterパターン • PresenterがModelとViewの連結を行うデザインパターン • (Unityの場合は View = UnityEngine.UI) Presenter Model

Slide 58

Slide 58 text

uGUI.Text UnityEngine.UI 制御フローと依存関係が一致 • PresenterがModelとViewの2つを参照する Presenter Model 依存する 依存する

Slide 59

Slide 59 text

コード例

Slide 60

Slide 60 text

コード例 PresenterがViewとModel を参照している

Slide 61

Slide 61 text

uGUI.Text UnityEngine.UI 依存関係 Model Presenterパッケージ Presenter.cs 依存する 依存する

Slide 62

Slide 62 text

依存関係を逆転させたい •PresenterがViewを操作するのは変わらないが、 依存関係は逆にしたい Presenter Model 制御フロー 制御フロー 依存する 依存する uGUI.Text UnityEngine.UI

Slide 63

Slide 63 text

やり方 1. Presentersパッケージ内にインタフェース を定義する

Slide 64

Slide 64 text

依存関係 Model Presenterパッケージ Presenter.cs 依存する ITextPrinter.cs uGUI.Text UnityEngine.UI

Slide 65

Slide 65 text

やり方 2. Viewsパッケージを定義して、 そこでITextPrinterを実装する

Slide 66

Slide 66 text

依存関係 Model Presenterパッケージ Presenter.cs 依存する ITextPrinter.cs TextView.cs 依存する Viewsパッケージ uGUI.Text UnityEngine.UI

Slide 67

Slide 67 text

やり方 3. PresenterがITextPrinterを使う

Slide 68

Slide 68 text

依存関係の逆転完了 Model Presenterパッケージ Presenter.cs 依存する ITextPrinter.cs TextView.cs Viewsパッケージ uGUI.Text UnityEngine.UI 依存する

Slide 69

Slide 69 text

依存関係の逆転完了 Model Presenterパッケージ Presenter.cs 依存する ITextPrinter.cs TextView.cs Viewsパッケージ uGUI.Text UnityEngine.UI PresenterとViewの依存が逆になった! 依存する

Slide 70

Slide 70 text

制御フローは変わってない! Model Presenterパッケージ Presenter.cs 依存する ITextPrinter.cs TextView.cs Viewsパッケージ uGUI.Text UnityEngine.UI 更新 R/W 依存する

Slide 71

Slide 71 text

依存関係の逆転 •どんなものでも依存関係は逆転できる • 制御フローはそのまま維持できる! •これを上手く活用してねっていうのが 「依存関係逆転の原則(DIP)」

Slide 72

Slide 72 text

設計レベルをあげるためには まとめ •制御フローを意識しよう • オブジェクト間の関係をハッキリさせる •制御フローに振り回されない対策をしよう • 設計原則をしっかり抑える • 依存関係の逆転がかなり大事

Slide 73

Slide 73 text

(補足) • UnityのMVPパターンでここまでやるのは「冗長」 • 最初のPresenter→Viewの参照の形で困ってないなら、無理に この形に変形する必要はないです(あくまでサンプルとして挙げただけ) Model Presenterパッケージ Presenter.cs 依存する ITextPrinter.cs TextView.cs 依存する Viewsパッケージ uGUI.Text UnityEngine.UI

Slide 74

Slide 74 text

クリーンアーキテクチャ(CA)

Slide 75

Slide 75 text

すっごい雑にいうと 「君だけの最強の アーキテクチャをつくろう!」 (そのための指標に“クリーンアーキテクチャ” を使うべし!)

Slide 76

Slide 76 text

アーキテクチャの考え •そのソフトウェアに最適なアーキテクチャは、 自分たちで模索して育てるしかない • 万能な「アーキテクチャ」は存在しない! • 自分たちのニーズに応じて自分たちでアーキテクチャを 考えて作るしかない

Slide 77

Slide 77 text

クリーンアーキテクチャとは 様々なアーキテクチャに 共通するルールをまとめたもの こういうルールでアーキテクチャを作ると上手くいくことが多いよね、 という指標を提示するもの

Slide 78

Slide 78 text

ざっくり言うと 過去に提唱されてきた「オニオンアーキテクチャ」「ヘキサゴナルアー キテクチャ」とかって何かどれも同じこと言ってね? ↓ つまり基本的な考えは同じでやり方が異なるだけでは? ↓ 共通部分を抜き出してみよう ↓ クリーンアーキテクチャ

Slide 79

Slide 79 text

クリーンアーキテクチャのルール 「依存関係は上位レベルに向け一方通行にしろ」 「制御フローと依存関係を分離しろ」

Slide 80

Slide 80 text

クリーンアーキテクチャでは •具体的に「こうしろ!」とは言ってない • レイヤ数やコンポーネント名は自由に決めていい • レイヤ/コンポーネント構成を後から変更しても良い • 小さいアーキテクチャから始めてだんだん大きくしても良い

Slide 81

Slide 81 text

この図は何なの?

Slide 82

Slide 82 text

「レイヤを4つ用意するんだな…?」 「実装をこの図に上手く配置しろってことだな?」 よくある誤解

Slide 83

Slide 83 text

「レイヤを4つ用意するんだな…?」 「実装をこの図に上手く配置しろってことだな?」 よくある誤解

Slide 84

Slide 84 text

この図でいいたいところ • 依存性は常に上位レベル(内側) にのみ向かってなくてはいけない! この小さい矢印が一番重要

Slide 85

Slide 85 text

• この図はただの「たとえ」 • 「Webを例にするならこういう作り方もあるよね」くらいのノリ • レイヤ数やコンポーネントは自由に定義してよい この図に振り回されてはいけない

Slide 86

Slide 86 text

ここの部分 •「依存関係の逆転」 を使って依存関係と制御フロー を分離しろ

Slide 87

Slide 87 text

クリーンアーキテクチャ まとめ •クリーンアーキテクチャは「指標」 • DIYするときの指針をまとめたもの、くらいの認識 • 具体的なレイヤ定義やコンポーネント定義は一切してない •設計原則を学ぶのが一番大事 • 設計原則を抑えておかないとうまくアーキテクチャは作れない • CA本でも第22章までCAの話が出てこない(それだけ前提知識が必要)

Slide 88

Slide 88 text

Unityでクリーンアーキテクチャ

Slide 89

Slide 89 text

Q. UnityでCAってできるの? •A.「アーキテクチャの基本的な考え方」 に則ればUnityと共存できます

Slide 90

Slide 90 text

とある考え方 •「Unity」という土台の上にクリーンアーキテ クチャで世界を作ろうとしている Unity クリーンアーキテクチャ

Slide 91

Slide 91 text

とある考え方 •「Unity」という土台の上にクリーンアーキテ クチャで世界を作ろうとしている → この考え方だと失敗する Unity クリーンアーキテクチャ

Slide 92

Slide 92 text

アーキテクチャの考え方 •「フレームワークと結婚するな!」 • アーキテクチャの上位レイヤにフレームワークを持ってくるな! • フレームワークの都合でアーキテクチャが振り回されるべきではない!

Slide 93

Slide 93 text

何と結婚するのかよく考えること •ライブラリ系もよく考えよう • UniRxとかそういうやつ • 上位レイヤで使ったほうが話が早いなら結婚してOK • ただし後から切り離しはできなくなるので覚悟が必要

Slide 94

Slide 94 text

Unityでクリーンアーキテクチャ •Unity側がアーキテクチャ側に依存するべき • Unityとは独立してCAでコンポーネントを構成し、 それをUnity側が利用する形にするべき CAな世界 Unity 依存

Slide 95

Slide 95 text

実際どうするのか? 「レベル間の違う領域のハイブリッド」 をやればOK

Slide 96

Slide 96 text

2つの世界の両立 C# C# C# C# C# C# C# C# C# C# UnityEngineに べったりな世界 (レベル2) CAで構築された世界 (レベル5) 依存

Slide 97

Slide 97 text

使い分け •Unity非依存でロジックが構築できる部分 → クリーンアーキテクチャが適用可能 •UnityEngineを使わざるを得ない部分 → MonoBehaviourベタ書きを多用で対処する

Slide 98

Slide 98 text

両極端に振れず、 ハイブリッドにするという択があることを 忘れてはいけない

Slide 99

Slide 99 text

UnityでCA まとめ •UnityでCAは利用可能 • Unityが関係無い部分はCAで作ることができる • ただしUnityEngineの機能に強く依存する部分は相性悪い •UnityとCAの関係性には注意 • UnityのアーキテクチャにCA側が振り回されてはいけない

Slide 100

Slide 100 text

(補足)CAFU •CAFU: Clean Architecture for Unity • そっくりそのまま利用はオススメしません(断言) • 黎明期に提案されたCAのテンプレート • コンポーネント定義がちょっとやりすぎで煩雑 • 作者本人も「やりすぎた」と反省している • テンプレートそのまま使うのではなく、 開発規模にあったサイズのアーキテクチャを自分で考えるべき

Slide 101

Slide 101 text

最後のまとめ

Slide 102

Slide 102 text

最後に •最初から完璧なアーキテクチャは存在しない • 「アーキテクチャ」もまた開発過程で成長していく • 「プロダクトの開発・保守・運用ににかかるコストを最 低限にする」が目的であり、そこが達成できるなら 多少キタナイ部分があってもOK

Slide 103

Slide 103 text

クリーンアーキテクチャになってなくても、 設計原則に違反する部分があっても、 Unityと密結合してる部分があっても、 それがやりやすいならそれで良い (設計しなくていいと言ってるわけではない!「バランス感を持て!」という話)

Slide 104

Slide 104 text

参考資料 • 世界一わかりやすいClean Architecture • https://www.nuits.jp/entry/easiest-clean-architecture-2019-09 • Adaptive Code ~ C#実践開発手法 第2版 • https://www.amazon.co.jp/dp/B07DJ2BL4Y/ • Clean Architecture 達人に学ぶソフトウェアの構造と設計 • https://asciidwango.jp/post/176293765750/clean-architecture