ソフトウェア設計における抽象とメタの違い
ソフトウェアは、現実世界をそのまま扱えない。
たとえば、ある町の地図を描くとする。同じ町でも、目的が違えば描き方はまるで変わる。
https://gyazo.com/efb8403e3e00d7c36e645151fa715cd9
路線図は、駅と乗り換えさえ分かればよい。だから、実際の線路の曲がりも、駅どうしの距離のばらつきも捨てて、等間隔に並んだ駅とまっすぐな線だけを残す。
道路地図は逆で、道の形や交差点が命になり、電車の路線はいらない。
どちらも、目的に必要なものだけを取り出し、それ以外を捨てている。これが抽象化である。
抽象化とは、難しい言葉に言い換えることではない。現実のものを、目的に合わせて扱いやすい形に切り出すことである。
抽象化は、いきなり型になるわけではない
路線図をソフトウェアにするとする。
最初に書くべきなのは Station という型ではない。まず考えるのは、経路案内から見たときに、鉄道について何が必要かである。
code: (text)
経路案内から見た鉄道
- どの駅があるか
- どの駅とどの駅がつながっているか
- つながりを一つ進むのにどれくらいかかるか
距離や線路の形は、ここには出てこない。路線図がそれを捨てたのと同じである。
ここまでが抽象化である。頭の中でも紙の上でも、これで済む。誰がいて、どうつながり、どれだけかかるか。必要なものさえ押さえれば、それ以上の細かい形はいらない。
ソフトウェアでは、構造を書き出さなければならない
だが、これをソフトウェアにするなら、そうはいかない。選んだものを、構造として書き出さなければならない。どんな項目があり、それぞれどんな型か。
code: (java)
record Station(StationId id, String name) {}
record Connection(StationId from, StationId to, Duration time) {}
頭の中の「誰がいて、どうつながるか」で済んでいたものが、ここでは id、name、time といった、はっきりした形になる。この構造の記述は、抽象化そのものには要らない。ソフトウェアに書くから要る。
さらに、曖昧さも残せない。人どうしの会話なら「乗り換えて行けばいい」で通じるが、プログラムはそうはいかない。乗り換えは一つの駅として表すのか、路線ごとに別のノードに分けるのか。乗り換えにかかる時間はどこに持つのか。運休中の区間は、つながっていないと見なすのか、印だけ付けるのか。これらを決めなければ、経路探索は動かない。
ここで視点を変えてみる。ここまで考えてきた対象は駅だった。しかし、いまコードに書いた瞬間、私たちが扱っているのは駅そのものではなく、「駅をどう表すか」という構造になっている。
メタとは、構造がデータについての記述であること
ここまでは、駅データの構造を作ってきた。どの項目があり、どの型で、何を許すか。抽象化で必要なものを選び、コードでそれを曖昧さなく書ききった。
だが、書き上げたこの構造は、駅そのものではない。駅のデータがどんな構造かを述べた記述である。データについてのデータ、つまりメタデータである。構造を書いた時点で、相手はもう駅ではなく、扱う対象が駅データの記述に変わる。これがメタである。
https://gyazo.com/b97e4fcb6a091cd53658435b40dcbcfc
抽象化とは向きが違う。抽象化は、どれだけ簡単にしても、相手は現実の鉄道のままである。路線図も、描いているのは現実の鉄道である。メタは、相手が現実の鉄道から、それを書き出した構造へ移る。「一つ上に上がる」と言うと、抽象の層を上がること、つまりより抽象的になることと混ざるが、それとは別の動きである。
抽象もメタも多層的である
さきほど、抽象化とメタは向きの違う動きだと述べた。加えて、どちらも一つの層では終わらない。それぞれが層を重ねている。
抽象化の層は、新宿駅を「駅」にまとめ、それを「交通の拠点」にまとめる、というふうに重なる。上の層ほど大まかになる。あるものは、下の層から見れば抽象、上の層から見れば具体になる。抽象化の層は境目がゆるく、間にいくらでも中間の層を作れる。どこまで刻むか、どこで止めるかは目的が決める。
メタの層は、駅のデータがあり、その構造を述べた型があり、さらに「型とは何か」を定める言語がある、というふうに重なる。記述の、そのまた記述、と積み上がる。メタの層は、抽象のような連続的な変化ではなく、対象そのものが切り替わる離散的な層として現れる。データと、型と、型を定める言語は、質の違う別ものだからである。抽象化のように、目的に応じて無段階に中間層を挿入できるわけではない。
抽象の層とメタの層は、別々に積み上がっている。型を「駅」から「拠点」へ一般化しても、それはまだ「データを述べた記述」のままで、メタの層は動かない。逆に、型を固定したまま「型とは何か」へ話を移せば、抽象度は変えずにメタの層だけ上がる。片方を動かしても、もう片方は動かない。「一つ上」という言い方がずっと紛らわしかったのは、この二つの「上」を混ぜていたからである。
https://gyazo.com/4f6264e20287f67a945fce233ae02ecb
抽象の層に天井はなく、上へはいくらでも大まかにでき、下へはいくらでも細かくできる。メタの層は層を重ねても実用上の意味は薄いことが多い。「型とは何か」を定める言語は、その言語自身のことも同じやり方で書けるので、その上に層を重ねる必要がないからである。
メタの層は、実務では「誰がいつ構造を決めるか」に対応する。駅の構造を型として固定するか、実行時に読み書きできるデータとして持つか。これは、その記述をメタのどの層に置くかの選択であり、それが決める人と決めるときを分ける。型として固定すれば、決めるのは開発者で、決まるのはコードを書くときである。データとして持てば、決めるのは利用者で、決まるのは動かしているときになる。後者ほど自由に見えるが、そのぶん型による約束は薄くなる。
構造を書くことは、選ぶことである
データを扱うソフトウェアを作るなら、そのデータの構造を書き出すことは避けられない。型を書けば、いやでも何かを選び、何かを捨てている。駅に id と name だけを持つ構造であるとした時点で、駅長やホームの数などは捨てている。選択は、意識しなくても必ず起きている。
問題は、その選択が目的に合っているかどうかである。目的を考えずに画面の項目をそのまま写しても、構造はできあがるし、ソフトウェアは動く。だが、目的に合った形になっているとはかぎらない。
抽象化とは、この避けられない選択を、目的に合わせて意図的に行うことである。「抽象化が足りない」とは、選択がないという意味ではない。選択が、目的ではなく実装や画面都合によって決まっているという意味である。
形だけの抽象化に注意する
ソフトウェアには、抽象を表現するためのツールがある。インターフェースや抽象クラスがそれである。だが、道具があるだけでは抽象化にはならない。同じ道具で、本物の抽象も、形だけの抽象も、抽象ですらない別のものも書けてしまう。道具の見た目では判断できない。
本物の抽象は、実在する共通点を、目的に沿って選んでいる。相手は現実のものでなくてよい。「順序をつけられる」という振る舞いの共通点を選べば、それも立派な抽象化である。だから、見分ける問いは「物理的なものか」ではない。「実在する共通点を、目的をもって選んでいるか」である。
形だけの抽象は、インターフェースという見た目はあるのに、選ぶ相手に幅がなく、目的もない。具体クラスをそっくり写しただけのインターフェースを切る。共通のコードをまとめただけの抽象クラスに、それらしい名前をつける。形はしているが、中身が空である。
ただし、これと取り違えやすいものがある。「実装を差し替えるため」に置くインターフェースである。UserRepository を RDB から別のストアに替えられるようにする、といったもの。これは抽象化(共通点を選ぶこと)ではなく、実装を仕様から切り離す別の仕事を、同じインターフェースという道具でやっているだけである。「抽象ではない」と言えば足りる。空と決めつけると、正当なものを取り違える。
もう一つ紛らわしいのは、インターフェースが「現実のものそのものではなく、その記述」だという点である。だがこれは前に述べたメタの性質で、書けば必ずそうなる。Shape は実在の図形を目的に沿って選んだ本物の抽象化であり、同時に記述(メタ)でもある。「そのものじゃない」という違和感は、空だからではなく、メタだからである。
なぜソフトウェアで「空の抽象」が蓄積するのか。現実世界で抽象化を行うには、まず目的を定め、何を残し何を捨てるかを考えなければならない。抽象とは、その意思決定の結果として生まれるものである。
一方、ソフトウェアでは、その意思決定を経なくても抽象の「器」だけは簡単に作れてしまう。IDEを使えば、ボタン一つでインターフェースを抽出できる。つまり、抽象化という思考を伴わずに、抽象化されたような形だけを低コストで作れてしまう。空の抽象が、ソフトウェアでは蓄積しやすい。
まとめ
抽象化とは、現実のものから、目的に必要なものだけを取り出すことである。メタ化とは、それを、データについての記述として書き出すことである。
ふつう、この二つは別の作業である。だがソフトウェアでは、抽象化した結果を型やスキーマとして書き出した瞬間、それはデータについての記述になる。つまり、抽象化という設計判断と、メタ化という記述が、ほとんど同時に起きる。だから、抽象化とメタ化は分かれにくいと言える。