前回の記事ではMVVMとElmアーキテクチャとの比較記事を元になぜMVVMがdisられるかを検討し、MVVMとそのViewModelはどのような役割なのかを述べた。この記事では私自身がMVVMとReduxなどの関数型アーキテクチャパターンの源流となったElmアーキテクチャを比較しMVVMにおけるViewModelのより良い実装を探っていく。さらにアーキテクチャパターンの選定についても述べようと思う。

一つ謝らなければならない。次回はADRの記事と予告したが、まだ用意できていない。約束守れずごめんなさい!

Elmアーキテクチャとは

ElmアーキテクチャはElm言語で開発されたアーキテクチャパターンで、Model-View-Update(MVU)というパターンで構成される。

以下のページで詳しく解説されているので見て頂くと良いと思う。

図解 The Elm Architecture の流れ

モデルは型として、ViewとUpdateは関数で定義される。

MVVMとは

MVVMはマイクロソフトのWPFなどの環境で開発されたアーキテクチャパターンで、Model-View-ViewModelで構成される。

ViewModelはViewを抽象化したもので、Modelの状態からViewの状態を作りだし、それを管理する。またViewからのデータ・イベントをModelに伝える。

処理の流れ

処理の流れを比べてみる。

ElmアーキテクチャではModelの初期値を作り、それがViewに渡され、そこから

View->ランタイム->Update->View…

というサイクルが一方向で回る。

そのサイクルの中でモデルや(ランタイムからの)メッセージがViewやUpdateに渡され処理される。Elmは純粋関数型言語であり、関数であるViewやUpdateは参照透過性という制約により純粋に保たれ、流れは一方向であり単純な流れとなる。参照透過性とは純粋関数型言語の性質で簡単にいえば「同じ値を入れたら同じ値を返す」という性質のことである。

一方MVVMは

Model->ViewModel->View->ViewModel->Model->…

という流れが双方向に行われる。つまりViewModelを間に置き、データが行ったり来たりする。

比べてみるとElmアーキテクチャパターンでは一方向でありシンプルに保たれるが、MVVMのViewModelには順方向・逆方向の流れが存在し、二つの流れがViewModelの中で組み合わされた場合ViewModelが複雑性を増す。つまり二つの流れを組み合わせるのはMVVMでは良くないということである。ただ、ViewModelの実装には制約がなく、規約として流れを組み合わせないことを求めることになる。

状態の取り扱い

Elmは純粋関数型言語なので状態はModelとして表現され、状態遷移はUpdate関数で行われる。MVVMではModelレイヤーで状態の変化が起きる。この比較はオブジェクト指向と関数型言語の比較になりそうなので割愛する。

Viewの状態について観察すると、ElmアーキテクチャはModelからViewの状態を作り出すのはView関数で行われる。ただそれを保管しておく方法は用意されていないようである。ElmアーキテクチャのElm言語以外の実装ではメモ化を利用した状態の再利用が行われているのを見たことがあり、なんらかの方法が必要になる。ただ、基本はModelからViewへの変換という流れであり、さらには純粋関数型言語であるElm言語においては関数には副作用を許容しないので、単純化されているといえる。ただしSwiftなどの純粋関数型ではない言語では間違った使い方をすると問題が発生する。

MVVMではViewModelがModelからViewの状態を作りだす。というよりViewModel自体がView専属のModelである。Modelを監視し、変更があればViewModelが状態を作りだす。Viewを作り出すわけではなく状態を作りだす。これはViewが抽象化されるというMVVMの特徴を表している。ただし実際の実装ではどの粒度のModelを監視し、既にViewModelにある状態をどのように利用するかという問題をはらんでおり、その点で設計の質が求められるといえるだろう。

副作用

上で副作用という単語を使ったがこれは関数型言語で良く使われる語で、単純にいえば「同じ操作を行っても同じ結果が返ってくると保証されていないこと」である。変数の中身を変更できたり、現在日時を取得したら毎回違ったり、である。純粋関数型言語では関数には副作用が無いが、日付を扱ったりUIからの入力を扱ったりする必要があり、副作用はモナドというもので隠ぺいして扱われることが多い。

Elmアーキテクチャでは副作用をコマンドやサブスクリプションと呼ばれるもので取り扱う。これにより参照透過性が保たれ、関数における副作用によるバグが無くなりテストも書きやすくなる。

一方MVVMでは、というかMVVMが実装されるオブジェクト指向言語などでは副作用を注意して取り扱う必要がある。副作用が発生する操作はModelレイヤーにおいて行うことであり、Modelレイヤー内で工夫して実装する。ViewModelはViewの状態を生成・管理することだけに注力する必要がある。しかしViewModel自体の変数の変更など言語自体が副作用を許容しているので純粋関数型言語での実装に比べて不利である。この点ではオブジェクト指向言語や非純粋関数型言語でのElmアーキテクチャの実装でも気をつける必要がある。

ビジネスロジックの実装

ElmアーキテクチャではビジネスロジックはUpdate関数から呼ばれる関数で実装される。それにより状態遷移が発生する。状態遷移はUpdate関数でしか行えないのでロジックがView関数など別のところに漏れ出すことが無い。

MVVMはModelで実装するのであるが、これは規約であり間違ってViewModelにロジックを書いてしまう間違いを起こす。ModelとViewModelの役割を把握すること、そしてその処理がビジネスロジックなのかViewの状態を作りだす処理なのかをただしく判断することが必要である。

ViewModelをどう実装すればいいの?

MVVMのViewModelは

  • Modelの状態からViewの状態を作り出す。
  • Viewの状態を保管する。
  • ViewからのデータやイベントをModelに伝える。

という役割を担う。しかし実装を強制する仕組みや言語的なサポートが存在しないので、Elm言語によるElmアーキテクチャにくらべて間違った実装や分かりにくい実装になりやすい。よって

  • 各レイヤーの役割を把握する。
  • データの流れや副作用に気をつけて実装する。

必要がある。

ところで上に挙げたViewModelの役割だが、Viewの状態を作りだすことはマッピング、保管するのはキャッシング(前に述べたメモ化もキャッシュの一つ)と考えることができる。つまりマッピングとキャッシングを意識してViewModelを実装すれば良いといえるのではないだろうか。

MVVMよりElmアーキテクチャの方が良いの?

Elmアーキテクチャの方が良いといえる。Elm言語とそのランタイムシステムでは。ElmアーキテクチャはElm言語の特徴とランタイムシステムを利用して作り上げられるアーキテクチャパターンである。

ではそれ以外の言語ではMVVMが良いのだろうか?

WPFなどのMVVMに必要なバインディング機構を持った環境であればMVVMが役に立つだろう。ViewModelにはViewに状態を伝達する役割はない。これはバインディングを利用するためである。

つまりアーキテクチャパターンの選定は環境に左右されるのである。

私はiOSアプリのプログラマなのでiOSの環境を例にしてみると、ElmアーキテクチャをiOSのUIKitやSwiftUIといった環境と組み合わせるためにはElmが持っている環境を模したライブラリを作る必要がある。それを利用するとElmアーキテクチャに近いアーキテクチャパターンを実現できるのだが、Viewは既に存在するのでViewは関数にはできないなど完全な模倣はできず、また言語的にも純粋関数型言語ではないので言語的優位性は無くなってしまう。

MVVMについても同様である。UIKitではバインディング機構が存在せずFRPライブラリを用いて行うことになるので複雑性が上がる。ElmアーキテクチャにしてもMVVMにしてもサードパーティライブラリに依存することになる。

SwiftUIに関してはバインディング機構が存在するのでMVVMは導入はしやすい。

このように、所与の条件でアーキテクチャパターンの選定は限定される。

ではどうすれば?

アーキテクチャパターンの思想や一部の実装を取り入れることは可能である。UIKitにおいては画面遷移にCoordinatorやRouterを取り入れることは比較的容易で効果的である。RouterはVIPERが提唱した方法である。このように思想や方法論的な部分を参考にできるといえる。

ビジネス的環境や実装における環境によっていろいろな問題があり、それを分析してどのような方法で解決するかを考え、その中でアーキテクチャパターンを活用する方法を探るのである。

おまけ: SwiftUIで関数型アーキテクチャパターンは使えないの?

サードパーティライブラリに依存することをいとわなければ使えるだろう。ただしSwiftUIの仕組みを基礎とした設計・実装であるべきだと考えている。ElmアーキテクチャはSwiftUIの仕組みを基礎としていないので新しく設計する必要がある。

SwiftUIはUIKitのようなMVCという確たるアーキテクチャパターンを定義していないので、皆右往左往している状態に見える。

The Composable Architectureというライブラリが存在する。これはSwiftUIやCombineと親和性が高くなるように設計されているアーキテクチャパターンとその実装である。ではこれを使えばいいのかというと、確かに良く練った実装であるが、UIKitでの利用も想定されているためSwiftUIのEnvironmentを活用できていなかったり(この点についてSwiftUIのEnvironmentの仕組みを利用できるという指摘がTwitterで見られた。自分でソースコードを読んだがそのような仕組みは見つけられなかったので、どなたか方法を教えて欲しい。またTCA独自のEvnironmentに関してはSwiftUIと別の系を作って管理するという手法なのでSwiftUIへの適応という点で未熟だと判断している。)、SwiftUIのViewヒエラルキーにWithViewStoreというSwiftUI的な視点で見ると不可思議なものが現れてしまう。UIKitからSwiftUIへの移行のためにUIKitへの対応が必要なのかもしれないが、それによりSwiftUI環境への適応が不十分であり、関数型アーキテクチャパターンではViewは関数として実装されるがSwiftUIのViewは型であり、その違いを埋めるための工夫が必要となっている。その点を気にしなければ採用すれば良いと思う。

私はといえばSwiftUIの機能を活かした実装を探っていこうと思っている。実はSwiftUIを見た時にDocument-Viewアーキテクチャの様にも見えた。かなりシンプルな構成であるということだ。また、Viewが宣言的になってコントローラが不要になったことでFlutterのWidgetようにViewが起点となったが、これは別の部分に起点を設ける実装も不可能ではないのではないか。ではなぜSwiftUIではViewが起点となっているかというとSwiftUIのViewがEnvironmentといった環境を持ち、アプリの状態でありアプリそのものなのかもしれない。なのでEnvironmentを持つViewを起点とした実装が最もSwiftUIに適しているのではないかと考えている。

やはりおまけが長くなってしまったが、役に立てば幸いであり、次回こそADRについて書こうと思う…


iOSアプリではMVVMが多用されている。UIKitとFRPライブラリであるRxSwiftを組み合わせて実装されるのが一般的である。(私はReactiveSwiftの方が好きだけど…)

MVVMはマイクロソフトのWPFで考案されたソフトウェアアーキテクチャパターンで、それがiOSに導入されて広まった。

しかししばしばiOSにおけるMVVMは批判の的となってきた。もっとも俎上に上がるのはVMの肥大化・複雑化である。最近では以下の記事があげられる。

なぜ MVVM は Elm Architecture に勝てないのか

この記事を元になぜMVVMが批判されるのかを見ていこうと思う。

ViewModelは複雑化する論

上記記事ではやはり「複雑化しすぎたViewModel」と主張している。

複雑化したコードが掲載されているので全文は転載しないが、主要な個所を見てみる。

まずViewModelの状態の管理を検討する。

private let state: BehaviorRelay<String> = .init(value: “!!!”)public func doSomething() -> Observable<String> {

このコードは一般的にコントローラーにおいてViewとバインドされる時に利用され、バインドのためにRxSwiftの部品を利用している。stateプロパティに格納してそれをdoSomethingで出力している。ここでWPFのコードを参考(掲載しないのでコードを検索してね)にしてみると、ViewModelのプロパティは特段FRPライブラリを利用しておらずget/setを定義したプロパティである。そしてWPFには独自のバインディング機構を利用してViewとなるXAMLにおいてViewModelのプロパティを参照してViewの中でバインドを行っているのである。

iOSではFRPをバインドに利用することが多いが、そのために2重のコードになり肥大化が起こっている。

またViewModelの肥大化と関係するが、Controllerでのバインドの記述が長くなるのもバインディング機構が無いためである。Controllerでバインドを行うのはAppleのMVCに見られるViewとModelの間にControllerが介在する実装と双子のようである。

これはViewModelとViewを疎結合にするために行っているように感じられるが、WPFを参考に実装するならばViewModelをViewに渡してBehaviorRelayを利用しないプロパティをViewで参照する、もしくはなんらかのバインディング機構を用意すると良いのである。私はそこまでMVVMの研究を行っていないので、実際にどのように実装すれば良いかは提示できない。だがiOSにおいてよく行われているViewModelの状態の管理は参考としたWPFの手法とは似ても似つかないものになっているのである。今回はUIKitでの実装を検討しているが、これがSwiftUI+Combineでの実装となると話が違ってくる。@Publishedを付与したプロパティをViewでバインドできるようになるのである。

ちなみにElmアーキテクチャをUIKitで利用するのもまた別の問題が発生する。これはUIKitと組み合わせるために発生するものであるといえる。

まとめると、UIKit+RxSwiftで一般的に行われているバインディングは間違った(とまでいっていいのか分からないが…)場所で、行いにくい方法で行っており、上記記事もそれを踏襲しているのである。

次に入力から出力までの流れを検討する。

ここでまず以下の行に連なるコードに注目してみる。

output1 = input1.asObservable()

これはinput1からの入力をViewModelの状態と合わせてoutput1に出力している。ところで一般的なMVVMの入力はモデルレイヤーのオブジェクトに渡されて処理され、その結果であるModelの状態をViewModelが受け取って表示に適した状態に加工する。しかし上記のコードは入力をViewModelで処理し、ViewModelの状態を利用して新しい状態を作成している。これはModelでの処理を、間違ってViewModelで行っているのである。この間違いは結構多いらしく、ViewModelの肥大化・複雑化を招く。

また

output2 = input2.asObservable()

上記に連なるコードではinput2の入力に多くのViewModelの状態を利用して出力としている。ViewModelは入力の加工に利用するのは間違いでModelに入力を渡すのが由緒正しい実装であろう。

さらに

output3 = Observable.combineLatest(input3, input4, output2)

以下のコードでは複数の入力とViewModelの状態を組み合わせている。入力はモデルに渡してそこで合わせるなり何なりをするのが由緒以下略。

つまり参照記事のコードは意図的か分からないが悪い見本を提示しており、悪い見本をリアクティブ・スパゲティと読んでいるのである。確かによく行われる間違いであるが、これを元にiOSでのMVVMを批判するのは不公平の感が否めない。

iOSのMVVMがdisられるのは、このようなことから起こっているのではないかと考えられる。

またiOSにおけるMVVMはFRPライブラリを利用せずとも実装できるのである。つまり参照記事がFRPを前提とした比較を行っているが、それはFRPを利用することが多いだけで、FRPを利用しない別の比較もできるのである。当のElmがFRPとさよならしたのであるし。

MVVMはどのような課題を解決しようとしたのか

そもそもMVVMはどのような課題に対応しようとしたのであろうか。一つにはViewにおける状態と状態を作りだす処理をViewから引きはがすことであるといえるのではないだろうか。MVCはModelを独立させModelの状態を用いてViewを更新する仕組みである。理想的には状態は全てModelにあり、Viewはそれを表示するだけである。しかし現実的にはModelからViewに適した状態を作りだしその新しい状態を管理する必要がある。これを私は勝手にViewへのラストワンマイル問題と読んでいる(オレオレ用語である)のであるが、Viewへ最後に、どこで荷物を積みなおし、管理し、運ぶのか、である。その役割がMVCではViewに存在し、MVVMはそれらをViewModelに移管したのである。これによりViewは軽量になり描画だけの役割となったことで宣言的な実装が可能になった。

では件の記事で比較されているElmアーキテクチャはどのような状況であろうか。本家Elmではコンポーネント論争が行われていたらしい。要はUIの状態をどこで管理するかである。ElmのViewは純粋関数で状態は持てない。であるならModelがUIの状態を管理するのかというとそれはそれで避けたい。であるならコンポーネントを作るのか。という話である。結局のところコンポーネントは不適とされたらしいが、現在どのような実装か詳しくは知らないがElmにおいてもViewの状態の管理は課題となっていた。また一般的にUIの状態はViewの関数のなかでModelから生成されて利用される。つまりMVVMが解決しようとしたラストワンマイル問題は残されているのである。Elmアーキテクチャでの状態の管理に関してElm以外での実装でメモ化を利用したものを見たことがある。View関数の中で管理する手法の一つであるといえる。

これはElmアーキテクチャだけではない。ReduxにおいてもViewの状態の管理は課題であり、Reduxで管理する状態にModelとViewの2つの状態を入れ「正規化」することで管理し、Viewの状態を生成するのもreducerもしくはViewで行う必要がある。Redux+MVVMでその課題を解決しようとした例もあるのである。

The Composable Architectureはlocal storeを利用することでViewの状態を別個に管理することは可能らしい。ただlocal storeはModelの状態をlocal stateに変換するために存在するので、reducerの役割にViewの状態へ変換する機能が含まれることになる。レイヤーの概念を持ち込むと同じレイヤーで処理していることになる。

つまり関数型アーキテクチャパターンはMVVMが解決した課題をいまだ持ち続けているのである。

これらの関数型アーキテクチャパターンは、SwiftUIのViewが状態を管理できるので、SwiftUIの力を借りて活用することができる。Viewのキャッシュもあるらしい。作成した状態を補完しておく必要も少ないのかもしれない。また、CombineというFRPライブラリの力を使うことで実装が楽になるだろう。これはiOSにおけるMVVMにも恩恵をもたらす。まぁ富豪的ではあるが。

またアーキテクチャパターンではないがReact HooksやRecoilは状態の管理に注目しているようである。iOSにおいてもそれらを参考にしたライブラリが発生して関数型アーキテクチャパターンと組み合わせる流れが発生するかもしれない。

おっとClean Architectureを忘れては困る

みんな大好きClean Architectureである。VIPERも入れようか。Clean Architectureには基本的にViewModelは含まれないと考えられている(よね?)。Clean ArchitecruteはPresenterでViewを抽象化する。がViewModel程の抽象化はできず、バインディングも一般的には行わないので、SwiftUI時代にはそぐわない…わけではなくClean Architecture自体が自由に変更可能であるからViewModelを組み合わせてもClean Architectureだと言い張ることは可能ではないかな。ずるいけど。

MVVMはElmアーキテクチャに勝っているのかいないのか

MVVMはモデルの詳細な規定が無い。モデルは自由である。からしてモデルの実装が難しくなる。これがViewModel肥大化の要因の1つである。しかしこの点で「勝っていない」といいたいわけではない。MVVMのモデルが規定されないことでReduxと組みああせても(Clean Architectureと組み合わせても?)多くの人の認識はMVVMの様である。これは長所であるとも言える。しかしこれでも勝っているとは言えない。全てのアーキテクチャパターンはそれぞれ解決しようとする課題が別であるから別の実装であるといえる。そしてソフトウェアの課題はそのビジネス要件やフレームワーク要件により多岐にわたり特定のアーキテクチャパターンはあるの課題には対応できるが、別の課題に対応できない場合がままある。であるからアーキテクチャパターンから要素を取り除いたり組み合わせたりして対応する。ElmアーキテクチャやReduxは要素を取り除くことは難しいが、MVVMと組み合わせた事例のように、相互で補い合うのである。MVVMが必要ないなら使わなくて良いし、必要であれば使えばいい。よって勝っている・負けているということは存在せず、それぞれが今後も発展しつつ補いつつ存在していくと私は考えている。

基本的な実装をした方がチームメンバーの認識の統一が測りやすいって?そういう場合はArchitecture Decision Records。

はいっ!次回「Architecture Decision Recordsを開発に組み込もう!」乞うご期待!

追記

参照記事で、FRPを利用した入出力が似た形になるのは当然であると考えられる。しかし、数学的に一方が一方を構成できると言っても、その入出力がモデルの状態に関する入出力なのか、ビューの状態の入出力なのかで役割が違い、代用できるかは役割による。FRPを基礎としないMVVMとElmの比較をFRPを通して行なっており、恣意的な感が否めない。著者は数学に基づく関数型プログラミング理論の研究をされ、SwiftでFRPを利用したElmアーキテクチャライブラリを作成されているので、それが話の発端であると考えられる。

ただ、私は上記の分析にはそれほど興味はなく、なぜdisられることがあるのか、どうすればdisられずに活用していくことができるのかに興味があるのでこの記事を書いた。そのような視点で読んでいただくと光栄である。


私はUX/UIデザイナではないのだが、個人でアプリを作るのでデザインのことも学んだりしている。その中で「ペルソナ」には否定的である。

ペルソナとはユーザーを具体的な人物像として描写したものをさす。ペルソナの語源は「仮面」だといわれたりする。

ペルソナを作るにはターゲットユーザを調査して作り上げるのだが、たとえばデザイナがターゲットであるギャル(死語か…)のペルソナを作ったとして私がそのペルソナからなんらかの役に立つヒントでも得られるかというとまったく見当違いの想像、つまり役に立たない推論をするだけだろう。

なぜ見当違いの推論をするのだろうか。それは普段行う推論は領域固有性を持っているからだとされている。領域ごとの知識を使って推論を補う。そのため詳しくない領域だとおかしな推論を行ってしまうのである。

単純に既に作成されたペルソナを使って詳しくない領域についての推論を行っても役に立たない案が出てくるのである。よってその領域の知識を得る必要がある。

ペルソナは役に立つといっている方の意見をよく聞いてみるとこのようなことをおっしゃっている。

「過程が大事」

つまりペルソナを作る過程でその領域のことに詳しくなり、その知識が役に立つということである。

そこで私は不思議に思う。

ペルソナの作成に参加していない人はどうやってその知識を得れば良いのだろうか?

知識が得られたのであれば作成したペルソナは必要ないのではないだろうか?

メンバーの認識をあわせるのに役に立つかもしれないが、過程に参加していれば十分認識はあっていると考えられるのではないか?

私はその「過程」は大切だと考えている。自身が知識を得て詳しくなることでより良い推論ができるようになる。

もしくは詳しい人を仲間に入れるという手もあって、その手を使っている会社もある。

何れにせよその領域に詳しくなる、さらにはユーザ=当事者になることが重要なのである。

と、最近素晴らしい記事を拝見した。以下の記事である。

愛されるプロダクトを作る秘訣!ユーザーになりきるコスプレUXとは?

要約すると「コスプレをしてユーザになりきることでユーザー目線で課題を見つけることができるようになる」ということである。

これはつまりペルソナを被ってユーザになろうとしているのである。コスプレにより当事者になろうとしているのである。なりきることで次第にコスプレ(=ペルソナ(私の認識では))が洗練されていき、ついには当事者に近い推論が行えるようになっていく。

ペルソナを作って役に立てよう、ではなくペルソナを被ってなりきることがユーザ理解に繋がるという実例として私はとらえ、今後の参考にしたいと思った次第である。

(断っておくが、上記記事の著者さんはペルソナについてなにもおっしゃっておらず、この記事は私の認識を語っているだけのものである。)


DDDとは

DDD。日本語でいうとドメイン駆動設計である。
Wikipediaには以下のように記されている。

ソフトウェアの設計手法であり、「複雑なドメインの設計は、モデルベースで行うべき」であり、また「大半のソフトウェアプロジェクトでは、システムを実装するための特定の技術ではなく、ドメインそのものとドメインのロジックに焦点を置くべき」であるとする。

DDDで設計するのはビジネスや社会における諸問題を「解決する」ためである。

ビジネスの「諸問題」問題

ビジネスや社会における「諸問題」と述べたが、ビジネスは問題定義から始まるといわれる。問題を発見し定義する。それが「諸問題」となる。そしてその解決の枠組みを組み合わせる。その組み合わせがビジネスのモデルであると言える。
しかし、近年は「問題が希少」で「解決能力が過剰」になっているという。

諸問題とはいっても希少化しているのであれば新たに見つけることになる。

問題定義からの再出発

現在DXが話題のひとつとなっている。DXは以下のような定義になるようである。

DXとは、一言でいうと「企業がデータやデジタル技術を活用し、組織やビジネスモデルを変革し続け、価値提供の方法を抜本的に変えること」

ビジネスモデルを変革し価値提供の方法を抜本的に変える、つまりこれは

  • 新たに問題を定義する
  • 新たに解決の枠組みを作る

ということである。つまりデジタル技術で「問題が希少」「解決能力が過剰」を乗り越えてようということである。

DXを行おうとする企業や既存IT企業、ITスタートアップをはじめデジタル技術で問題を解決しようとする組織(や個人)は、デジタル技術を振るうまえに問題定義からはじめることになる。

デジタル技術を前提とした解決の枠組み

問題定義をしたら当然次は解決の枠組み、解決手法が課題となる。それはDDDでいうところのドメイン、つまり業務領域として定義されるといえる。

デジタル技術を利用した問題解決は当然デジタル技術の存在を前提としたドメインとなるのではないだろうか。そしてそれはDXやIT系スタートアップ(ベンチャーといった方がいいのかもしれない)においては新たな問題・新たな方法であるのでそこにはドメインは存在しないのである。

閑話休題。「DX時代」以前においてもドメインは本業の人でも理解していないことがままあったらしい。マニュアルがない、用語が正式に定義されてない、IT化によりブラックボックスになっている、といった理由からであるそうだ。

話を戻すと、現代でも別の要因によりドメインが不明な状況が発生しているのである。そしてそのドメインはどうやらデジタル技術を含み、非IT企業にとってそのドメインを構築するのは単独では困難であると考えられる。アメリカなどでは企業がIT技術者を雇い内製化しているらしい。またスタートアップの多方面への進出は目覚ましい。

デジタル技術によるドメイン

スタートアップに所属している技術者の方は、技術者もビジネスのことを考えて欲しいと経営層から思われていることを感じているのではないだろうか。スタートアップでなくても経営者的視点を求められるのも同様のことなのかもしれない。前項で述べたようにデジタル技術を前提とした場合、ドメインの構築のために技術者が参加する必要があるからだと考えられる。さらにいえば技術が問題を解決するならば技術者が問題定義から参加することも重要であると言える。外部の力を借りるのであればIT企業が問題定義から参加することになる。

問題定義・ドメイン構築にIT企業やその技術者が参画するということは、自身がドメインエキスパートの一人になるということである。それも、はじめは存在しないドメインの、である。

現代におけるDDD

DDDは「解決」の手法であると述べた。ドメインエキスパートと連携してユビキタス言語を定義しドメインモデルを構築していく。

しかしこれまで述べてきたようにIT企業や技術者が技術が組み込まれたドメインを新たに構築していく時代に、非IT企業の「既に存在するドメインのドメインエキスパート」と連携するだけで対応できるだろうか。それは少なくともDXにおいては否であるといえる。ITスタートアップにおいては既存のドメインエキスパートは存在すらしない。

先にドメインを構築しておいてDDDで解決する、という意見もあると思うがIT企業や技術者が構築(さらには問題定義)時点で組み込まれ、デジタル技術を利用したドメインを構築する前提であり非IT企業がドメインを構築しておくのは不可能である。当然IT系スタートアップは独自にドメインを構築するのであるから、解決手法では歯が立たない。もし先に構築できるとしても、次にDDDを利用する、というのは二度手間である。はじめからモデルを含めて新たなドメインを構築する方が良いのである。

リーンスタートアップはドメインを構築しつつ開発を行う方法であると考えられる。さらにはピボットという問題定義を再び行う行為も存在する。

このように私はDDDでは対応できない時代となり、問題定義から解決まで一気通貫した手法が求められている時代ではないかと考えているのである。

ではDDDはもう役に立たないのだろうか。私はそうは思わない。ドメイン駆動という考え方は現在の状況でも十分役に立つはずである。エンティティやバリューオブジェクト、アグリゲーションなどの実装面での活躍も期待できる。つまりタイトルのように古典として学び、現代に通じる部分を取り出すことが重要だと言える。

おまけ

おまけとしていくつか考えていることを書いておく。

今回は技術者に関しての考察だが、デザイナも当然関係してくる。以前デザインにも抽象化が存在すると教えられたのだが、それは「蓋」ではなく「あけられるもの」というようにより上位の概念にさかのぼっていくことらしい。ビジネスにおける問題定義も同様の抽象化を行うと良い、というか、それが本質なのかもしれない。「新しい手法」にはデザイナも組み込まれて、開発者と一緒に作業することになるのではないか。

DXにおけるユビキタス言語はデジタル技術やその概念を含むものとなると考えられる。非IT企業のドメインエキスパートには理解が難しい用語が出てくることが考えられる。非IT企業とIT企業が連携する場合、ユビキタス言語の定義にはそのような問題があり、どのように解決しているのだろうか。

DDDの実装面の特徴のひとつにレイヤードアーキテクチャがあげられるが、Clean Architectureと結びついて多くのレイヤーに分けられることがある。DDDはドメインに注目した手法であり機能的凝集が重要であると私は考えており、多くのレイヤーに分けることは機能的凝集を毀損することになりかねない。現代においてRuby on Railsは密結合により機能的凝集を高める方向に向かったと私は考えている。批判はあるが成功も収めているのは間違いない。結合度と機能的凝集のバランスをとるためにレイヤーの構成は十分な検討が必要だと考えている。

ドメインサービスという名前は実装においては曖昧すぎる。なぜそのような名前になったかを考えたのだが、実装内容は若干抽象的な概念となるのでドメインエキスパートが理解できるユビキタス言語として表せなかった面があるのではないか。上記のユビキタス言語の定義問題にもかかわるが、具体的に命名することができないだろうか。ユースケースとして定義する場合もあるらしいので、命名については工夫が必要だろう。

なぜこのような文章を書いたのか。DDDを知った時に旧来のSIer的だと思ったからである。それ以上にはなれない。ITの時代は作れないと考えたのである。何年もかかってやっとこの文章を書くことができた。時代が教えてくれたと思う。

おまけが長い…


Image for post
Image for post

世の中にToDoアプリはたくさんある。基本的な使い方はタスク入力→完了という流れだ。

ところでそのアプリで管理するタスクはどこから湧いてくるのだろうか。気付いたら頭の中にあってそれをアプリに書きだすだけなのだろうか。

タスクが日常生活や仕事で自然に発生するだろうかと考えると、それは自分の経験とは違う。食事を作るためメニューを決め食材を検討し料理手順を決定する。どこで何を買うのか。いつ何を行うのかを決定する。仕事のプロジェクトでの課題・問題は何か、それはどのように解決すればいいのかを考える。このような私の経験では先に課題や問題があり、それをどのように解決するか考えることでタスクが発生する。タスクは自然発生するのではなく自分で考えて作り上げなければならない。

ではタスクの元となる課題・問題は自然に発生するのだろうか。これも自分の経験とは異なっている。たとえば仕事における課題や問題は自分や仕事仲間により気付いたり設定したりしなければ、少なくとも自分の内には、存在しない。

課題や問題を設定したり認識したりして、さらにそこから検討してやっとタスクが確定するのである。

実はタスク管理はタスク完了で終わらないこともある。例えば仕事のタスクが紐付く課題は作業の記録が必要なことがある。完了することでその成果を記録しておくこともあるのである。

以上からタスク管理と言われることは以下の作業を含んでいることになる。

  • 課題や問題の認識
  • 課題・問題の管理
  • タスクの検討
  • タスクの管理
  • 付随する情報の管理

つまり理想のToDoアプリは上記の作業すべてを行えるアプリだといえる。

しかし多くのToDoアプリは狭義のタスクの管理しか提供していない。私は以前OmniFocusやTodoistなどを利用してタスク管理を行おうとしたが、自分が望むタスクを列挙することすらままならなかった。課題や問題の認識を別に行う必要があったので、その工程をタスク管理の流れの中で自然に行うことができなかったからだ。

では現在はというとNotePlanというアプリを利用することで上記の作業全てが行えている。NotePlanはノートの記述とToDoの管理がMarkdownで行えるアプリである。Mac・iPhone・iPadで利用することができる。


プログラミングにおいてロギングはデバッグなどに欠かせません。Dartにおいても同様です。ではDartでのロギングはどのように行うのでしょうか。

基本のprint()

最も基本となるのがprint()でのログ出力です。

print('log by print');

標準出力に

flutter: log by print

と出力されます。この出力はAndroid StudioなどのIDEのコンソールでも確認できます。

直接標準出力に出力するには以下のように行います。

stdout.writeln('log by stdout');  // 標準出力
stderr.writeln('log by stderr'); // 標準エラー出力

これらは

log by stdout
log by stderr

がそれぞれ標準出力と標準エラー出力に出力されるのですが、IDEのコンソールでは確認できませんでした。一方、Dart DevToolsでは確認できました。

print()では不十分な場合はdebugPrint()

print()では長いログが途中から破棄されます。その場合はdebugPrint()を利用します。

debugPrint('log by debugPrint');

折り返しを指定することもできます。

debugPrint('log by debugPrint', wrapWidth: 2);

これは以下のように出力されます。

flutter: log
flutter: by
flutter: debugPrint

debugPrint()はDebugPrintCallback型の変数で、デフォルトの実装であるdebugPrintThrottledメソッドの動作はデータ損失を避けるための処理によってprint()と一緒に使うとログの順序が狂う可能性があります。

スロットルを行わないdebugPrintSynchronouslyというメソッドもあり、テストで利用されたりします。

debugPrint = debugPrintSynchronously;

のようにdebugPrintの実装を変更できます。また、

debugPrint = (String message, {int wrapWidth}) {...};

のように独自の実装に変更することもできます。

dart:developer log()でより多くの情報を出力

dart:developer log()でさらに多くの情報を出力できます。

import 'dart:developer' as developer;developer.log('log by developer.log');

dart:developerをインポートしてlogメソッドを実行すると以下のように出力されます。

[log] log by developer.log

以下のようにnameを付与すると

developer.log('log by developer.log', name: 'mylog');

[log]ではなく[mylog]として表示されます。ログの分類に利用できます。

[mylog] log by developer.log

さらに、エラー情報を追加することができます。

developer.log(
'log by developer.log',
error: FormatException('exception'),
);

出力は以下のようになります。エラーの部分はIDEコンソールでは赤で表示されます。

[log] log by developer.log
FormatException: exception

スタックトレースを表示することも可能です。

developer.log(
'log by developer.log',
stackTrace: StackTrace.current,
);

その他 DateTime time, int sequenceNumber, int level, Zone zone を渡すことができます。

より詳細なログを求めて

print, debugPrintにはファイル名や行番号などの情報が不足しています。, dart:developer log()ではスタックトレースを出力することでファイル名・行番号を得ることができますが、スタックトレースの表示が長大になってしまい、ロギングに支障が出ます。

Dartの公式loggingパッケージでもファイル名・行番号は出力されないようです。以下ロギング用パッケージであればファイル名や行番号を出力してくれます。

どちらもログのファイル名をクリックするとそのファイルの行にジャンプすることができます。loggerはやや大仰な出力です(Android Studioではログのカラーリングはプラグインが必要なようです)ので、simple_loggerの方が私は好みです。loggerにはFlutter向けの拡張パッケージも存在しています。

Dartではprint(), debugPrint(), dart:developer log(), ロギング用パッケージを使うことでロギングを行うことができます。それぞれ長所・短所があるので使い分けていきましょう。


We can see a preview of the SwiftUI view on Xcode. But I want to use AppCode. So I make a way to preview the view on Xcode from AppCode.

First, I made AppCode open a file of view with Xcode in the following way.

  1. Open preferences of AppCode.
  2. Tools -> External Tools
  3. Create an external tool.
  4. Input… Name: Xcode, Program: open Arguments: -a Xcode $FilePath$, WorkingDirectory: $ProjectFileDir$
  5. Open Keymap pref view.
  6. Assign shortcut key [Control+Shift+X] to the ‘Xcode’ item.
Image for post
Image for post

Second, I made the following script in AppleScript. AppCode application name needs to be changed according to the version.

tell


データを保持して、一度だけデータを読んだらnilになる仕組みが欲しくなることありませんか?私はあります。例えばエラーメッセージを表示するときにエラー情報を読み出したらnilになって欲しいんです。これをProperty Wrappersで仕組みを作ってみました。

単純に、値を読み出すときに保存している変数にnilをセットするだけです。


class A {
@YORO var error: Error?
}
let a = A()
print(a.error) // nil
a.error = NSError(domain: "", code: 1)
print(a.error) // Optional(Error Domain= Code=1 "(null)")
print(a.error) // nil

このように利用します。


`replaceNil(with:)` method exists in Publisher of Combine when it’s Output is Optional. This method replaces Optional.none as Output with the parameter and transforms Output to Optional.Wrapped.
In this case, the argument of `replaceNil,` that is, the default value is needed.
I wanted a method to ignore nil. So I implemented an `ignoreNil’ method inside the Publisher.

I wanted to implement the method when `Publisher.Output` is Optional, but I couldn’t rule Optional as it is by `where` phrase. So I created OptionalType protocol, made Optional conformed the protocol, and implemented it when `Publisher.Output` is OptionalType.

The implementation with flatMap returns `Empty` when Output is Optional.none, and returns `Just` when `Optional.some`.
I ran the following.

let c = ["first", nil, "second", nil, "third"]
.publisher
.ignoreNil()
.sink { string in print(string) }

And I got that outputs and confirmed nil is ignored.

first
second
third


SOLIDとはオブジェクト指向設計の5つの原則で、原則それぞれの頭文字をとってSOLIDと呼ばれます。Swiftを用いてどのような原則かを述べ、iOSのView Controllerの実装を検討したいと思います。

「アジャイルソフトウェア開発の奥義」という書籍では、以下の傾向が一つでも現れたらソフトウェアが腐敗し始めた兆候と述べられています。

  • 硬さ
  • もろさ
  • 移植性のなさ
  • 扱いにくさ
  • 不必要な複雑さ
  • 不必要な繰り返し
  • 不透明さ

これらは仕様変更により設計が劣化することで発生します。ソフトウェアの腐敗をくいとめるための設計の原則がSOLIDです。

SOLIDのS

SOLIDのSは「Single Responsibility Principle」です。日本語では「単一責任の原則」です。どのような原則かというと「クラスを変更する理 …

Ryoichi Izumita

iOS / Flutter / Objective-C / Swift / Dart

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store