抽象化の威力 @Tomofiles

目次

はじめに

こんにちは、Tomofilesです。

今回の記事は、コンピュータ・サイエンスにおける抽象化というテクニックについて、紹介したいと思います。
抽象化というのは、非常に掴みどころのない概念であり、なかなか自分の中でしっくりくるまで理解するのに、時間のかかるものです。
それでも、ちゃんと理解して扱えれば、非常に強力な武器になります。

今回は、いくつか既存のテクノロジーを例に挙げながら、抽象化というものについて考えて行きたいと思います。
ぜひ、すべてを理解することができなくても、抽象化というものが重要なんだなぁ、という感覚だけでも掴んで、帰っていただけると嬉しいです。

物理と論理、具体と抽象

私は、私個人の意見として、コンピュータの歴史とは、抽象化の歴史であると考えています。

抽象化とは、極端に一言で言うと、「〜として扱う」というものです。
複雑だけど価値のあるシステム(仕組み)を、簡単に理解しやすい別のものとして扱うことで、その価値を手軽に享受できる、ということです。

難しいですよね。ちょっと、色々例を挙げて、抽象化について考えていきましょう。

Linuxとファイルシステム

初心者エンジニアの皆さんは、LinuxというOSを知っていますか?
Linuxは、UNIX系のオペレーティングシステム(OS)であり、Macの遠い遠い親戚みたいな関係にあるものです。

Linux - Wikipedia

Linuxでは、例えばソースコードのようなテキストデータだったり、好きなアーティストの楽曲データだったり、あらゆるデータをファイルとして扱う仕組みがあります。
ファイルシステムと呼ばれていますが、OSの水面下で活躍している仕組みなので、あまり気にしたことがないかもしれないですね。

ファイルシステム - Wikipedia

ファイルシステムという仕組み自体は、Windows系でも採用されているので、よくわからない人は、普段パソコンで触っているファイルがそれなんだなぁ、くらいの認識で構いません。

このファイルシステムというのは、上記のようなユーザーの作成したデータをファイルとして扱う、という機能に加えて、もう一つ重要な点があります。
(正直言うと、他にもありますが、気になる人は自分で調べてみてください、Wikipediaに載ってます)

それは、OSが扱う物理的なデバイス(周辺機器)もファイルとして扱うことができる、ということです。

例えば、USBで接続するストレージ、カメラ、マイク、などなど…。挙げたらキリがありません。

デバイスファイル - Wikipedia

Linuxでは、USBでデバイスを接続すると、/devディレクトリー配下にデバイスファイルが作成されます。
例えば、WebカメラをUSB接続すると、OSが自動で接続を認識し、/dev/video0のようにvideo0というファイルを、自動で作成します。
もし、カメラの映像を録画したいと思ったときは、ffmpegのようなソフトウェアを使用することになりますが、以下のようにデバイスファイルを指定することで、ffmpegがWebカメラにアクセスして、キャプチャを始めます。

$ ffmpeg -i /dev/video0 〜省略〜

物理的に様々な種類にわたるデバイスも、コンピュータ上ではファイルとして扱うことができることによって、デバイスを利用する側のソフトウェアは、ファイルに対する操作という一種類の方法でアクセスすることができるので、利用にかかる負担が減ります。
これがもし、デバイスの種類ごとにアクセス方法が異なり、さらに、同種デバイスでも製品ごとにアクセス方法が異なっていたら、利用側はいちいち細かい違いを意識しないといけないことになります。

つまり、LinuxというOSが、複雑なデバイスごとの差分を吸収して、単一の接点・操作方法を提供してくれる、仲介役になっているのです。
この、仲介役という考え方が、以降のキーワードになるので、覚えておいてください。

AWSとクラウドコンピューティング

AWSは、Amazon Web Servicesの略称で、様々なクラウドコンピューティングのサービスを提供しています。
同じようなサービスに、GoogleのGCPや、MicrosoftのAzureなどがありますね。

Amazon Web Services - Wikipedia

クラウドコンピューティングというものが、一体何者なのか考えたことがありますか?
私も、業務・プライベートでも、ヘビーに利用できていないので、それほど深い知識は持っていないのですが、Wikipediaによると以下の説明となっています。

インターネットなどのコンピュータネットワークを経由して、コンピュータ資源をサービスの形で提供する利用形態

クラウドコンピューティング - Wikipedia

一般的に、サーバアプリケーションを構築するには、データセンターなどにあるサーバコンピュータのメンテナンスが必要になります。
ハードウェアの電源やネットワーク等の配線、それに、OSアップデートなどが定期的に必要、とは聞いたことがありますが、私は経験がないですけどね。
まぁ、何にしても、コンピュータの物理面のお世話をしてあげないといけないのが、これまでの運用でした。

クラウドコンピューティングの登場により、コンピュータ資源がサービスとして受けられるようになりました。
コンピュータ資源とは、コンピュータの価値を抽象化したものです。以下が、その代表例ですね。

  • コンピュータの演算能力
  • コンピュータのストレージ能力
  • コンピュータのスケール能力
  • コンピュータのネットワーク能力

一般的にこんな用語で語られているわけではなく、私が便宜的に能力に名前を付けたのですが、大事なのはコンピュータの各種能力がサービスになっているということです。
私たちは、世界中のどこかにある、物理的なコンピュータ上にある各種リソースを、利用した分だけの料金(従量課金)で利用できるのです。
どこの物理的なコンピュータを利用者に割り当てるかという仕事や、その物理的なコンピュータのお世話は、AWSがやってくれます。

つまり、AWSが物理的なコンピュータと、利用者の間に立ち、それらコンピュータの各種能力のみを上手く抽出して、利用者に提供しているということです。
これも、仲介役ですね。

Dockerとコンテナ仮想化

Dockerは、現在のソフトウェア開発、及び運用のシーンで、なくてはならないものになっています。
まぁ、SIer界隈では、あまり利用しているのは見たことないですけどね。

Dockerは、コンテナ仮想化という技術を用いた、アプリケーションの実行環境を提供するプラットフォームです。

Docker - Wikipedia

コンテナ仮想化とはなんぞや?って感じかもしれませんが、Wikipediaには以下のように書かれています。

カーネルをホストと共有しプロセス・ファイルシステムを隔離する

余計に混乱するような内容ですが、要するに、コンピュータが動く仕組み(カーネル)を利用しつつ、自分だけが好き勝手できる環境(プロセス・ファイルシステム)を隔離して、アプリケーションを実行させる、と読み替えることができます。

コンテナ仮想化を用いずに、アプリケーションが好き勝手するとどうなるでしょうか?
もちろん、ファイルやディレクトリーといったストレージ、および、プロセス、メモリといったリソースが、様々なアプリケーションで共有されているので、誰かに意図しない影響を与えるかもしれないですね。
ウイルス対策ソフトが市販ソフトウェアと競合して、動作不良を起こすように、アプリケーション間の相性の問題なども、同じ環境を共有していると、発生する可能性があるのです。

コンテナ仮想化は、このような経験的に環境を隔離していた従来の方法に対して、コンピュータの動作する仕組みと、好き勝手できる隔離された環境をひとまとめにして、コンテナとして扱えるようにした、仮想化技術です。

OSから見ると、コンテナが一つ、二つ起動している、といった扱いとなり、アプリケーションの構成の複雑さが、見えなくなります。
コンテナの中では、隔離された仮想的な環境の上に、複雑なアプリケーションが構築できるので、コンピュータとアプリケーション両者にとって、大きなメリットがあります。

つまり、コンピュータと複雑なアプリケーションの間に立って、コンピュータの資源を最大限に活用しつつ適切な境界を設けて、それをコンテナという抽象的な単位として、アプリケーションに提供している、ということです。
これも、仲介役なのです。

Reactと仮想DOM

Reactは、前回も紹介しましたね。
最近私も学習を始めたフロントエンドのフレームワークなのですが、実に使い勝手の良いツールです。

React

ユーザーインターフェイスという、ビジュアル的にも、構造的にも複雑なシステム(仕組み)を、コンポーネントという単位でまとめ上げ、コンポーネント同士を組み合わせることができるので、高度に構造化されたUIを実現することができます。

何よりも強力な機能が、宣言的Viewの概念です。

コンポーネントのライフサイクルと描画をReactが管理してくれるので、私たち開発者は、それらライフサイクルで何をしたいか、そして、どう表示されてほしいか、を宣言するだけで、UIが構築できてしまいます。
ライフサイクルというのは、コンポーネントの誕生(マウント)から死(アンマウント)までの、Reactによって設けられたタイミングを、イベントとして開発者に提供してくれるもので、例えばcomponentDidMountイベントは、初期処理のように利用できます。

宣言的Viewを実現するにあたり、Reactが実に様々なことを水面下で行っている想像は、なんとなくつきますよね?

実際、Reactは、かなり頑張ってくれているみたいで、開発者はかなり快適にアプリケーションの構築が実現できるのですが、その頑張りを支えるテクノロジーとして、仮想DOMというものがあります。

DOMというのは、HTMLの文脈においては、ブラウザに表示される画面の構造、修飾などを、ツリー状に表現したもので、プログラミングインターフェースを通してDOMを変更することで、ブラウザに表示される内容を変化させることができます。

DOM の紹介 - Web API MDN

従来は、jqueryというライブラリーを利用して、動的にDOMを変更することで、動きのある画面を構築していましたが、DOMを直接変更することは、先陣の開発者たちによるこれまでの経験上、仕組みをより複雑化させる要因となっており、非常に頭を悩ませるものでした。

仮想DOMは、本物のDOMと、開発者が扱いやすいコンポーネントの間に立ち、両者の翻訳作業に徹し、かつ、差分検出という技術により、効率的で高速なDOMへの反映を実現し、品質面と性能面で多くのメリットをもたらしました。

仮想DOMと内部処理 - React

お気づきの通り、これも仲介役の活躍によるものですね。

仲介役という役割

ここまで、4つの既存のテクノロジーにおける、抽象化の例をお話してきました。

◆コラム
今回紹介した内容は、それぞれのテクノロジーのほんの一つの側面でしかなくて、クラウドやDockerには、もっと重要な側面があったりするのですが、それはまた今度紹介しましょう。

すべてのテクノロジーに共通しているのは、仲介役という役割を含んでいる、という点でした。

コンピュータに接続する物理的なデバイスも、サーバコンピュータの物理的なハードウェアも、コンピュータ上のアプリケーションの実行環境も、ブラウザの表示を司るDOMも、それ自体には、直接的に利用しても、大いに価値のあるテクノロジーです。
それは、これまでの歴史が物語る、揺るぎない事実です。

でも、直接的に利用するからこそ、ユーザとコンピュータ、自分と相手、両者の結びつきがより強くなってしまい、物理・具体への認知的負荷が高くなってしまいます。
登場人物が少ないほど、一見シンプルのように思えますが、相手がよく見えるからこそ、相手の詳細をよく知らなければ、コミュニケーションが取れない関係になってしまう、という感じですかね。

figure1

こういった状況の場合、相手と自分の間に仲介役を設けることで、相手を直接的に見なくて済むようになります。
仲介役は、ユーザからの依頼を受け付けて、各価値にアプローチをかけます。各価値は、さまざまな違いを含んでいますが、仲介役は、その差異を吸収して、ユーザに統一的な方法で、価値の提供を行うことに徹します。

figure2

簡単に言えば、私が頑張るのではなく、頑張る人を作る・用意する、ということですね。

ソフトウェア開発における抽象化

抽象化は、あまりその原理を突き詰めても、面白いことはありません。
例えば、Wikipediaの抽象化の記事を読んでみても、なかなかにとっつきにくい内容になっていて、途中で飽きてしまいそうです。(私はダメでしたね)

抽象化 (計算機科学) - Wikipedia

手っ取り早く、初心者エンジニアの皆さんに関わりのあるあたりで、お話をすると、プログラミング言語のJavaにおけるinterfaceabstract classではないでしょうか?
Javaという言語の、これらの仕様が、いったいどういう目的で設けられているか、もしかしたら全く理解できずに諦めた人もいるかもしれませんね。
私が指導した初心者エンジニアで、interfaceabstract classの意味を理解できていた人は、あまりいませんでした。

abstract classは、ちょっと話がややこしくなるので、一旦置いておいて、interfaceについて、今回の抽象化の観点からお話していきます。

◆コラム(難易度高め)
abstract classについては、ネット検索すると色々出てきますが、基本的にあまり信用しないほうがいいです。説明になっていないのが多い…。
そもそも、abstract classは継承関係を作る仕様ですが、抽象化を継承関係だけで解決しようとすると、認知的負荷が高くなります。
詳しくは別の記事を書こうとは思っていますが、継承によるis-a関係よりも、コンポジットによるhas-a関係で、オブジェクトを整理したほうが、構成が把握しやすくなると、私個人は考えています。

interfaceの本当の役割

ここまで、この記事を読んできた皆さんなら、なんとなくわかってきたかと思いますが、Javaのinterfaceとは、〜として扱うという性質をクラスに付与するものです。

実際の既存のテクノロジーを例に、物理・具体を抽象化して扱うことで、二者間の結びつきを弱くする効果について、お話しました。
これらの例は、仲介役という役割があるとお話しましたが、言葉を変えると、インターフェースが設けられているとも言うことができます。

インタフェース (情報技術) - Wikipedia

例えば、Linuxのファイル。
ファイルは、OS上のあらゆるリソースをファイルとして扱えるように抽象化された仕組みですが、ファイルというのがどういう性質を持っているか、ここで改めて考えてみたいと思います。

ファイルには、超簡単にまとめると、以下の性質があります。

  • 開く(open)
  • 読み込む(read)
  • 書き込む(write)
  • 閉じる(close)

ファイルとは、開く、読み込む、書き込む、閉じることができるもので、この性質を持っていれば、どんな物理・具体でも、利用側はファイルとして扱うことができます。
つまり、インターフェースとして、これらの操作(つまり、メソッド)が用意してあれば、相手の正体がなんであれ(つまり、どんなクラスであれ)、それはファイルとして扱えるのです。

interface File {
    void open();
    byte[] read();
    void write(byte[]);
    void close();
}

figure3

今回は、具体的なソースコードは掲載しないですが、重要なのはJavaの構文ではなくて、interface何であり、何でないかを、ちゃんと理解することです。
もう一度言いますが、interfaceは、〜として扱うという性質をクラスに付与するものです。

interfaceについても、ネット検索して上位に出てくるページは、あまり参考になりません。Javaの構文の話がメインですし、使いどころが書いていません。
ルールを覚えるのは重要ですが、もっとも重要なのは抽象化の考え方です。まずは、それをちゃんと抑えてください。

◆コラム
特に、ネット上の説明の中で気になったのは、interfaceをimplementsすると、クラスにメソッドの実装を強制できるという点を、メリットと説明しているものがありました。
しかし、interfaceがクラスに性質を付与するものと考えるならば、メソッドの実装を強制するのはそもそもの大前提です。実装する責任があるからです。
そうして作られたクラス間の関係が、どのような抽象的効果を発揮しているかが、実際は重要な点なのです。

マイクロサービスは究極の抽象化

マイクロサービスとは

ソフトウェアの設計手法として、マイクロサービスというものがあります。
なんとなく皆さんも、モノリシックなソフトウェアが批判されていて、これからは、マイクロサービスの時代だ、というような話を、どこかで聞いたことがあるかもしれないですね。

マイクロサービス - Wikipedia

モノリシックなソフトウェアというのは、実行体(例えば、Javaならjar、warの単位)イコール、アプリケーションのすべての機能、という構成で、まず大前提として、一般的な構成のことを指します。
モノリシックなソフトウェア自体は、なんの間違いでもなくて、普通にアプリケーションを構築するなら、基本的にはモノリシックで問題ありません

ただ、なぜこんなに、マイクロサービスがもてはやされているかというと、モノリシックには大きな弱点が一つ、あるからなのです。
それは、実行体にすべての機能が固まっているので、部分的に改修をかけようとすると、全ビルドのやり直しと、(基本的に)実行環境を一度停止しないといけない、という作業が伴うためです。

企業向け業務システムならば、メンテナンス期間を設けて停止できますが、消費者向けWebサービスだと、サービスの停止、イコール、売上の損失&信用の損失に繋がってしまうので、簡単には止められないですよね。

継続的な稼働を維持しつつ、継続的な開発を続けなければいけないWebサービスは、多くの経験的ノウハウを集約して、ソフトウェアを小さく分散させて作ることで、対処しようとしたのです。

ソフトウェアを小さく分散して作ることで得られる特徴として、局所的な改修が容易になる点と、局所的なスケールアウトが可能になる点があります。

アプリケーションの実行体が小さくなると、改修のためにビルドする単位も、サービスを停止する単位も小さく済むので、サービスの全体に影響を与えない構成にできます。
さらに、ユーザからのアクセスの負荷が高い機能を小さくまとめておけば、その小さいアプリケーションを、サーバの負荷状況に合わせて、並列に増加・減少させることで、スケールアウトが実現できます。

スケーラビリティ - Wikipedia

こういった、柔軟な構成のアプリケーションを構築できるマイクロサービスは、ある意味で、ソフトウェア構成の正解とも言えるのですが、実現するのはそう簡単ではありません。

マイクロサービスの難点

皆さんは、今回のお話の内容から、価値を供給する側と、価値を消費する側が、お互いがあまりにも良く見えすぎてしまうと、結びつきが強くなってしまうということは、もう理解しましたよね?

マイクロサービスは、複雑なビジネスの知識を、小さいアプリケーションに分散・協調させることで、実現するソフトウェア設計手法です。
小さいアプリケーションに分散させることで、それぞれの小さいビジネス知識は、お互いに関係性を持つことになります。
すると、これらの小さいアプリケーションの間にも、強い結びつきが発生することになり、分散化したのにも関わらず、モノリシックで構築したときよりも、返って仕組みが複雑化してしまうことになります。

小さいアプリケーションの関係性にも、価値を供給する役割のアプリケーションと、価値を消費する役割のアプリケーションが、お互いに連携する状況がありえます。
例えば、あるリソースを管理するマイクロサービスと、そのリソースを使ってUIに表示する情報を生成するマイクロサービス、という感じです。

マイクロサービスで設計するということは、常にマイクロサービス同士の関係性を意識して、強い依存関係になっていない(疎結合という)状態を、維持し続けなければならない、ということです。
そのためには、今回紹介した仲介役のような、抽象化の手法を駆使して、マイクロサービスごとの役割と関係性を整理し続けることが必要です。

◆コラム(難易度高め)
例えば、ドメイン駆動設計(DDD)というソフトウェア設計手法における、コンテキストマップという考え方があります。
コンテキストマップの目的再考と運用ヒント
DDDは、マイクロサービスと相性が良いので、そのエッセンスはよく流用されているようです。
コンテキストマップというのは、小さいビジネス単位同士の関係性のパターンをカタログ化したもので、この中にも顧客/供給者の関係が含まれています。

これができないようなプロジェクトでは、マイクロサービスの設計手法で開発すると、プロジェクトが破綻してしまうそうです。
(私は経験がないのでわからないですが、まぁ、どう考えても、確かにそうなるでしょう…)

抽象化の入門

今回は、いろいろな例を用いて、あえて細かく話をしているのですが、もうそろそろわかったよ…という感じですかね?

抽象化の例は、普段触れているものが多いこともあって、何のことを言っているのか、理解しやすいものではあります。
初心者エンジニアにも、私がこういう話をすると、話は理解できて、なるほどって顔はしているんですが。

実際に、業務でソフトウェアの機能を設計するときになると、彼らは、この抽象化の考え方が頭から吹っ飛んで、何故か素敵なソースコードを作り上げてしまうんです。
クラスとクラスの関係は密結合になり(密です、密です)、カプセル化は崩壊し、同じようなロジックのソースコードが量産されていくのです。

SIerの業務機能の開発手法だと、必要なものは何?って目線で作ろうとするので、抽象化って置き去りにされやすいんですよね。
だからこそ、気をつけて設計していかないと、何もかもが密結合になったソフトウェアができあがってしまうんです。

これを読んでいる初心者エンジニアの皆さんは、こうなってほしくないなぁ、と思うんで、一つアドバイスをしましょう。

普段自分たちが行っている設計・コーディングの作業で、価値を供給する側の機能(オブジェクト)と、価値を消費する側の機能(オブジェクト)があったら、間に一つ仲介役をかましてみてください。
理由は、これまで語ってきたとおりです。
実際に、成果物に含めるかどうかに関わらず、まずは思考実験のつもりでやってみてください。

もう一度、上で貼った画像を再掲します。

figure2

仲介役を間に立たせることで、両者の関係がスッキリすることがあります。
翻訳をやってくれる、読み替えをやってくれる、差分を吸収してくれる、形が合わないものを統合できる、信用できない・信用したくないライブラリを隔離できる、などなど、効果は色々見込めるかもしれません。
Reactの仮想DOMも、仲介役が差分検出アルゴリズムで、効率よく高速にDOMに反映できるという効果が発揮されており、性能向上のアプローチにも有効です。

仲介役の特徴をしっかりと研究して洗練させていくと、Linuxにおけるファイルのような、〜として扱う認知しやすい概念に昇華させることができるかもしれません。
その仕組みを自分でライブラリ化して公開すれば、もしかすると、ソフトウェア開発のデファクト・スタンダードになって、みんなに使ってもらえるようになるかもしれません。

夢は広がりますね。

おわりに

SIer界隈でエンジニアをやっていると、ときどき予想もつかないところから、マイクロサービスという言葉が聞こえてくるときがあります。

マイクロサービスは、今回少し語りましたが、簡単なテクノロジーではありません。生半可な気持ちで採用すると、地獄を見ます(という、テックブログの記事をいくつか読みました)。
私だって、実務経験がないので、様々な経験と知識をフル回転させて想像してみたり、プライベートで色々検証してみて、実際に実行するのは難しそう、という感覚を、どうにか持てたレベルです。(知識だけは、たくさん吸収しています)

SIer界隈でマイクロサービスをやりたいと騒いでいる人がいたら、私だったら止めます。皆さんも、止めてあげてください
マイクロサービスは、継続的に開発が行える環境で、抽象化思考が強くて、意志が強い人がいないと、成り立たないと、私は考えています。
あまりないですよ、そういう環境と人材は。開発をコストと考えている限り、無理です。
(だいたいSIer界隈でこういうワードを連発する人は、世界に技術的に先立っていると、マウント取りたい人たちです。中身は伴っていません。ついていくと、きっと地獄を見ます)

ということで、今回は抽象化のお話でした。

どうですかね、理解できましたか?
抽象化というのは、今回の例のように、たくさんの既存のテクノロジーの適用例を知ることが大切です。
キーワードは、「〜として扱う」です。

ライブラリーやフレームワークなど、これから、いろんなテクノロジーを勉強していくと思いますが、〜として扱うというフレーズが出てきたら、その裏には複雑な仕組みがあり、両者の間に立つ仲介役がいるということを、意識して学んでください。
そして、その感覚を掴んだら、ぜひ自分の仕事として、設計・コーディングするときに、抽象化を駆使してみてください。

きっと、皆さんのアウトプットの質が上がることと思います。

それでは、今回はこの辺で。

  • この記事をはてなブックマークに追加
Tomofiles

Tomofiles

Drone Software Developer.

Read More