設計
コーディングを行うとき、事前に設計フェーズに時間を割いていますか?
プログラムをしている人でも
- 作るものがぼんやりしてるから設計できない
- 早く動くのもを見たい(見せないといけない)ので、コーディングから着手する
- 先のことを考えるのが苦手だからコーディングしながらその都度考えよう
のような考えから、設計とかは深く考えずにコーディングから始めてしまう人が意外と多いのではないでしょうか?
しかし、実現したいことを最短でコーディングしてしまうと、往々にして後からの変更に弱い成果物になってしまいがちです。
どう作るか (how) のみを念頭にコーディングしてしまうと、設計の余地なくただ動くように作ってしまうのでお勧めしません。 何を作るか (what) を考えて設計フェーズで事前に方向性を決めておくことで、柔軟性と可読性を備えたコーディングを行うことが出来る、というのが私の考えです。
本記事では設計の指針として CleanArchitecture 1を取り入れてこの指針の下、具体的なアプリケーションのコーディングについて解説します。
設計が大事とは言われるが捉え切れてないない人、設計に対する他人の考え方を知りたい人、CleanArchitectureの具体的実装に悩んでいる人は是非参考にしてください。
Clean Architecture
CleanArchitectureの思想は以下の図に現れていると言われます。2
この図で抑えるべきことは同心円状に層(レイヤ)をなしていることです。
似た(元になった?)考え方にヘキサゴナルアーキテクチャやオニオンアーキテクチャがあったりします。3
この図で重要なことは以下の4点でしょうか。
- 黄色など内側に入るほど、後で変更されない固定されたロジックである
- 逆に青色など外側に出るほど、後から変更されやすい部分である
- 依存の方向は 外側から内側 である
- 赤色のアプリケーションのロジックと青色の具体的な実装(View/Web/DB/Devices)の中間に緑の層があること
各層について説明していきます。唐突ですが具体的なイメージのために身長と体重から BMI を求めるアプリ開発を想定します。4
Enterprise Business Rules
アプリの根幹となる(利用者に価値を提供する)部分です。業務ロジックとも呼ばれます。上図では Entities という名前がついていますが、これが混乱の元になることがあるようです。5
BMI計算アプリにおいては以下の計算式が該当します。この定義はよほどのことが無いと変わらないでしょう。
BMI = \frac{体重}{身長^2}
Application Business Rules
アプリを動かすためのロジックです。上図では Use Cases と言われています。
BMIのアプリでは身長と体重の入力をUIから受け取り、BMI計算式に当てはめて結果を表示。あるいは保存する処理になります。
UseCaseはアプリの実際の機能を指すと言えるでしょう。仮にログイン機能が必要なら新たにログインのUseCaseも追加する必要があります。
Interface Adapters
Use Caseがアプリの機能を実現するためには、そのための専用の部品が必要です。しかしUse Caseがそれらの部品を直接持つのでなく、この中間層を介して参照します。
一見無駄に思えるでしょうが、この中間層のお陰でUse Caseの実装を変更することなく、部品だけを取り換えてアプリの修正を行うことが可能になり、改修の際の影響範囲を絞ることが出来ます。
Frameworks & Drivers
アプリの各部品の具体的な実装を持つ層です。どんなUIにするか、データをファイルに保存するかクラウドに投げるか(さらにそのクラウドはAWSかGCPか)、などが挙げられます。開発エンジンやミドルウェアの選択肢もここに含まれる場合もありそうです。
CleanArchitectureを取り上げた本6では 詳細 という言葉がよく出てきますが、正にこの層に該当します。この層の決定(実装)を先延ばしにできるのがCleanArchitectureの利点でもあります。
また、原著では details という単語が使われているようですが、日本語訳には詳細に加えて 些細なこと というニュアンスもあるようです。
では概念を一通り説明したところで、もう少し実装へと掘り下げて解説します。
各層の捉え方
BMI計算アプリの実装を行う上で各層の捉え方について私の考えを述べます。
本アプリの典型的シーケンスは、
1. UIから身長と体重の入力をうけつける
2. 身長と体重からBMIを計算する
3. 結果のBMIの値をUIに表示する
となります。
UseCase
アプリ実装の中心となるのはUseCaseです。
図の中心はEntitiesになっていますが、アプリの実装の中心はApplication Business Rulesという名前の通り、UseCaseだと捉えることにします。
数字の入力をPresenter経由で受け、BMI計算を呼び出し、結果をPresenterに返す実装をUseCaseで行います。
しかし、UseCaseにアプリのすべてを実装してはいけません。例えば、
-
float height = float(textInput.text)
のようにUIの情報を直接参照してはいけません -
float bmi = weight / (height*height)
のように計算ロジックを書いてはいけません
float height = 0.0F;
float weight = 0.0F;
void Start() {
// Viewのイベントを受信
HeightInputView.OnValueChanged += x => { UpdateBMI(x, weight); };
WeightInputView.OnValueChanged += x => { UpdateBMI(height, x); };
}
void UpdateBMI(float h, float w) {
// cm を m に変換
var hm = h / 100.0F;
// BMI計算結果をViewに反映
BMITextView.text = w / (hm * hm);
// フィールドに保持
height = h;
weight = w;
}
UseCaseはアプリの振る舞いを記述するだけです。UseCaseにViewがどうとか、計算ロジックがどうとか難しいことをさせてはいけません。UseCaseは低学年くらいの小学生だと思ってください。
小学生に「数値は 画面のどこから 取得されるの?」とか聞いても
「わかんない」
と返答されます。(それでいいのです)
難しいことは大人達に任せます。
Presenter
大人その1です。Interface Adaptersに当たります。View(大人その2)から受け取った情報をUseCase(小学生)に渡します。そしてUseCase(小学生)から情報をもらった情報をView(大人その2)に返します。しかし、こいつにも難しいことはさせていけません。Presenterは言われたことだけを行うやる気のないダメ会社員だと思ってください。
UseCase(小学生)に渡す情報が何に使われているのかも、View(大人その2)に渡した情報がどこに行きつくのかもこいつは関与しません。右から左、左から右に渡すだけの楽な仕事です。
こいつに質問してもほとんどの場合、
「いや、ちょっとわかんないすね。自分、これをUseCase(小学生)に渡せばいいって言われてるだけなんで...」
と返答されるでしょう。(それでいいのです)
難しいことは専門家達に任せます。
View
大人その2です。UIの詳細を担う専門家です。
Frameworks & Driversの層では特定のframeworkなどの処理をふんだんに使って目的の機能を実現してください。逆に言えば特定のframeworkに依存する処理は可能な限りこいつに閉じ込めてください。
こいつは専門家なので色々質問しても回答してくれます。
問:「文字入力は何のコンポーネントでうけとりますか?」
答:「はい。それは画面上のここのInputField
からです。そして結果のBMIはこっちのText
に反映されます。」
といった具合です。
有能ですが、悲しいかなCleanArchitectureではこいつを交換可能な部品として扱います。アプリ全体の方針転換によっては別の専門家を雇って配置換えが行われます。新たな専門家も前からいるダメ会社員とコミュニケーションさえできればOKというわけです。(別のViewを実装することになっても既存のPresenterに接続できるように作ればUseCaseには影響しないということです)
Domain
業務ロジックのEnterprise Business Rulesです。
小学生でも使える道具のように簡単に実行できるメソッドを持ち、抽象化されているとなお良いでしょう。
CleanArchitectureの説明で使った図にはDomainという言葉は出てきません。図に準拠するならEntitiesですが、単語の意味の通りやすさを考えてDomainとしてます。
実装
長くなりそうなので具体的な実装の説明は実装編として別記事に分けました。
小学生、ダメ会社員、専門家、道具をどう組み合わせて(抽象化して)アプリケーションを動かすのかという話はそちらで行います。