[考察] GUIアプリプログラミングの課題は何か、それに対して開発環境はどう変わってきたのか

GUI アプリ開発のプラットフォームがどういった流れで進化していったのか、自分の中での解釈をまとめたくなったので書いてみました。

なお、MVC や MVVM のようなパターンについて書くと一般的な説明をなぞるだけで大変になってしまう[^1]ので、直接的な言及は最小限に抑えています。またプログラミング以外の要素が強い GUI 自体の進化についてはあまり書いていません。

[^1]: 細かい分類と各プラットフォームでの実装とかまとめるだけで大変になりそう、原点辿るのが大変、すでに語り尽くされてる、など。

GUI プログラミングの基本コンセプト
================

まず、プログラマがアプリを作る時に出てくる要素と関係を書いてみました[^2]。この要素と関係は、昔から現在まで変わらないと考えています。

[^2]: これは [MVC のコンセプト](http://heim.ifi.uio.no/~trygver/2007/MVC_Originals.pdf)を元に筆者が考察した結果できたものですが、 MVC そのものではありません。一般的な GUI プログラミングをモデル化したものであり、あるべき論ではありません。

<img width="512" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122842/f6fd8384-b573-7b69-8856-9092003802ed.png">


**メンタルモデル: アプリケーションの世界**
ユーザーとプログラマの頭の中にある世界観です。両者が考えるメンタルモデルはおおよそ一致しています。

**データ: メンタルモデルの状態**
メンタルモデルの「現在の状態」をどこか(ディスクやメモリやDBなど)に記録します。

**表現: メンタルモデルの投影する事**
現在の状態を描画し、ディスプレイに出力したり、適宜音を出したりします。

**リアクション: メンタルモデルの更新**
ユーザー操作や時間経過など、プログラムの外からの入力を受け取り、データを更新します。

#### 例: 文書作成アプリ

例えば文書作成アプリを考えると以下のようなものになります。

- **メンタルモデル** … 紙の上に文字を書いていく、あるいはタイプライターで打っていくイメージ
- **データ** … UTF-8 で表現された文字列
- **表現** … UTF-8 をデコードし、各文字を描画する
- **リアクション** … キーボード入力を受け取り、文字列をエンコードしてデータに追加する


GUI プログラミングの課題
----

GUI のプログラミングで行なっている事は上に書いた通りで複雑ではないのですが、いざアプリを書いてみると思ったよりもずっと大変になります。

理由はいろいろあると思いますが、GUI の場合は比較的「データ」よりも「表現」と「リアクション」のための開発コストが問題になりがちです。これは GUI がリッチであればあるほどその傾向があります。

これらの開発コストは主にデバイスや人間の制約によって生じます。この「表現」と「リアクション」によって生じる主な課題をまとめました。


### 描画効率

高度な表現を実現するためには、適宜描画を省略したりキャッシュしたりするといった、描画効率を意識したプログラミングが必要になってきます。

### 表現のための記述量の多さ

メンタルモデルを表現するために必要なコードが意外と大きくなりがちです。その理由は例えば以下のようなものになります。

- ユーザーに見せるべき情報が意外と多い(特に閲覧中心のアプリだと多くなりがち)
- 時代とともにユーザーがアプリに期待している表現が高度化している(レスポンシビリティやエフェクトなど)

その上で、図形をコードで表現するのは直接的でないという点があります。

### 中間状態の管理

ディスプレイサイズや人間の制限を補完するため、アプリケーションは**中間状態**を管理する必要が出てきます。

文書作成アプリの場合、以下のような対処が必要になります。

- ディスプレイに文章の全体が入らないので**スクロールする**。そのためには現在の表示位置の管理が必要
- テキストスタイルを変えられるように「太字」などの**ボタンを用意する**。そのために現在のスタイルの管理が必要
- 任意の場所でテキストを挿入するために**カーソルを動かせるようにする**。そのためにはカーソル位置の管理が必要
- 入力を間違えた時に**アンドゥをする**。そのためには操作履歴の管理が必要

開発プラットフォームが解決しようとしてきたもの
----

アプリ開発のプラットフォームは「効率的な描画」という制約の中で**「表現の記述」**と**「中間状態の管理」**の負荷を下げ大規模にも耐えられるように進化していきました。

以下、「表現の記述」と「状態管理」の面における進化をまとめました。


表現の記述の効率化
================

View
----

アプリ開発ではスクリーンやウィンドウに線や文字を直接描くコードを書く事は少ないです。その代わり、ボタンやテキストエリアのような組み込みの「View」の組み合わせで構成されています。

これらの View は以下のような特徴があります。

#### 抽象化

「座標XYにマウスクリック」という形ではなく「ボタンが押された」事をボタン自身が認知し、アプリケーションプログラムはそれを入力として受け取ります。

また「現在のスクロール位置」のような中間状態をある程度吸収してくれるので、自前で管理せずに作成する事が出来ます。

#### 再利用性

View を組み合わせてより抽象度の高い View を作る事が出来ます。View を再利用すれば高い品質を維持しつつコードを少なく済ませられるようになります。

#### プラットフォームによるレイアウト管理

適切に View の状態を更新すれば、それに応じて必要な部分だけ描画されます。また、絶対座標指定の代わりに View の位置を順に配置したり View 間のマージンなどの制約を指定する事で、動的なコンテンツや画面サイズの変更などに柔軟に対応できるようになります。

View 構成の記述方法
----

かつての GUI プログラミング環境では「View 生成・設定してから subview として追加する」というコードを繰り返す事で View を構築していました。しかし現在は View の記述を簡易的にするための手法が用意されているのが一般的です。

View の記述方法は主に以下の3つに大別されるでしょう。

- GUI ビルダーを使う
- View 構築用の言語(HTML/XMLやテンプレートのような外部DSL)を使う
- プログラミング言語をベースとした記述言語(内部DSL)を使う

これらの手法は以下のような特徴があります。

#### 素のプログラミング言語を使うより見通しが良い

昔の「View を生成し、属性を設定、その後 subview として追加する」という手順は変数宣言や複雑な配列操作が入り長く複雑になってしまいがちでした。

View 構築のDSLを使うと、変数宣言や配列操作などを書く必要がありません。その結果コードが短くなり可読性が向上します。

一方で GUI ビルダーを使う場合は見た通りに表示されます。そのため表示のされ方などに関してはさらに見通しが良くなります。その代わり、グラフィカルに表現できない設定やスタイル定義などは直感的とは言い難い「インスペクタ」で操作する事になりがちです。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122842/58af7daa-f5b4-51c7-e1fd-7c3653b9a463.png">

#### プログラミング言語を知らなくても記述ができる

HTML や GUI ビルダーが使えればプログラミング言語がわからなくても記述ができるようになります。これにより、例えばプログラムが書ける人とGUIが得意な人で分担して開発するという選択肢が取れるようになります。


状態管理の効率化
================

表現方法の記述方法の進化と同時に、中間状態の管理についても進化していきます。

中間状態をどこで管理するか?
----

現在の GUI の中間状態の管理は、その多くは View の状態管理になります。

View の状態管理方法で最も素朴な方法は、View に任せる事です。シンプルなアプリや、View の状態をその View 自身以外に知る必要がない場合(例えば大半のスクロール)はこの方法でも問題はありません。しかし少し複雑な状態になるとこの方法は上手くいきません。

例えば文書作成アプリで「太字の入力モードかどうか」を判別する場合、「パレットの太字ボタンが押された状態かどうか」で判別できます。しかし「太字モードで入力」した後「カーソルを全く別の場所に移動」してから入力した場合、移動先が太字かどうかに関係なく太字で入力される、という意図しない挙動になってしまいます。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122842/a28bc33e-d03f-a462-8bdf-fbfba7247130.png">

状態と View の同期
----

この問題に対する解決手段の一つは、View の正しい状態を Controller などの View の外で管理し、状態が更新された時に適宜 View に反映する事です。

<img width="512" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122842/d7af6a74-76d4-22ea-7abb-2d9d329c331c.png">

こうしたプログラミングを徹底し、View の更新を状態更新を経由して行う場合、View の更新は同期の部分に集約されます。その結果 View にアクセスする必要があるのは View 構築と同期の部分のみになります。

データバインディング
----

当然ながら一度作成した View を更新するためには View を参照できるようにする必要があります。従来は、例えば View に ID を振ったり、View 構築時に Controller や Application Model などのインスタンス変数に代入したりする事で View を参照できるようにしていました。

しかし View にアクセスするのは構築と同期の部分だけだと考えると、これらの手法は View を必要以上に公開してしまう事になります。これは可読性という意味では望ましくありません。

この問題はデータバインディング[^3]を使う事で解決できます。データバインディングを使う事で、View の同期を View 構築のコードの中で記述する事ができます。その結果、View について記述する箇所は事実上構築時の一箇所のみになり、View に ID などを振る必要がなくなります。

[^3]: Cocoa Binding、WPF、Android の DataBinding、AngularJs(1.x) など。

差分更新
----

View の同期を View 構築の中で記述する別のアプローチとして、状態を更新する度に管理下にある全ての View を構築し直すという方法があります。このアプローチは React の仮想 DOM が有名です[^4]。

[^4]: React 以外だとVue や Flutter、Apple の SwiftUI などが該当します。

View を構築し直すというのは大胆なアプローチですが、文字通り View を差し替える事は現実的ではありません。View が内部で管理している状態(スクロール位置など)もリセットされてしまいますし、描画効率も悪いです。

そのため、このアプローチでは代わりに構築した新しい View と現在の View の差分を検出し、変更された部分だけを更新します。

この差分更新のアプローチはリストの更新の時に便利です。リストを更新する時、従来であれば更新や挿入、削除をする場所を指定する必要がありました。新旧のリストから差分を検出してくれる事でこれらの判断は半自動的に行われる[^5]ので、リスト更新の管理が軽減されます。

[^5]: リストの要素にIDを降ったりする必要が出てくるので、完全自動ではありません。

なお、差分を検出して適宜更新するアプローチは仮想 DOM のようなレイアウトエンジンでしか使えないわけではなく、特にリスト更新に関しては他の手法でも活用されています[^6]。

[^6]: Android の DiffUtils や Angular など。


さらなる変化
================

プログラミング言語による View 構成の記述
----

前述の通り、View 構成の定義は通常のプログラミングとは別の方法で行うのが一般的です。一昔前は GUI ビルダーや XML のような専用の言語を使う事が一般的でした。しかし現在では通常のプログラミング言語をベースとした記述方法(内部DSL)を使う流れができました。

この流れの背景は、主に三つあると考えています。

### プログラミング言語の記述力の向上

アプリで使われるプログラミング言語の記述力が向上し、View 構成を十分記述できるようになりました。具体的には、キーワード引数や関数オブジェクトの登場、リスト加工の関数(mapなど)ができるようになりました。

### 差分更新ベースのプラットフォーム

差分更新をベースとした View 更新の仕組みが出来たのも大きいでしょう。

従来のデータバインディングでは同期するデータを直接渡す事はできず、状態の更新通知をする特別なオブジェクトを経由する必要がありました[^7]。同期の設定を専用の言語で書けばこれらを隠蔽する事は可能ですが、プログラミング言語で書くとどうしても独特な記述になってしまいます。

[^7]: プログラミング言語の中で View を書けるのは React や Flutter のような差分ベースのものがほとんどのはず。

差分更新ベースの仕組みであれば、そういった特別なオブジェクトを経由せずに View を構築する事ができます。

### 需要の変化

現在(2019 年)の GUI のプラットフォームは 1990 年後半 〜 2000 年前半くらいに普及した物をベースとした物が多いです。当時は GUI をデザインする人とプログラムを書く人を分業できる事がメリットとされていた事が多かったように思われます。

しかし現在は GUI を記述するのはエンジニアというケースが一般的です。その結果、言語やファイルを分離するメリットがあまりなくなってしまった(場合によっては一緒にした方が良い)のではないでしょうか。

また GUI ビルダーは Git などを使ったソース管理との相性が悪いケースも多く、あまり使われていないように見えます。

ただし、現在でもプログラミングなしでアプリを作りたいという需要はあり、需要が無くなる事もないと思われるます。将来はプログラミング言語を使わずに GUI を定義する手法が主流になる可能性は十分にあると思います。

GUI プログラミングモデルの再定義
----

GUI プログラミングに関しては冒頭に書いたように「データ化されたメンタルモデルの状態」に対して「リアクション」と「表現」を記述する、という流れは変わっていないように思います。

しかし、以下のように「表現」と「リアクション」の記述方法は洗練されてきているように思います。具体的には、以下のようにまとまってきているのではないでしょうか?

- 表現 … 状態から View 構造への写像の宣言的な記述
- リアクション … 状態の更新と通知

おまけ: 古き良き Web アプリ
----

余談ですが、JavaScript が使われる以前の Web アプリを最初のプログラミングモデルに当てはめてみます。

- 状態 … URL、DB、セッション
- 表現 … 状態から HTML への写像
- リアクション … DB やセッションの更新、リダイレクト

個人的には、現在の GUI プログラミングモデルに割と近い形になっているのではないかと思ってしまいます。