Edited at

なぜオブジェクト指向は難しいのか


この記事の内容

この記事は「オブジェクト指向と10年戦ってわかったこと」という記事を書いた筆者が2年の時を経て「なぜオブジェクト指向は難しいのか」をテーマに、さらなるオブジェクト指向の理解を目的として、通常とは異なるアプローチでオブジェクト指向を解説したものです。


自然言語の限界



人は様々な物事に名前を付けて分類します。リンゴには「リンゴ」、車には「車」という名前を付けて分類、認識しています。そして車には「走る」という役割があり、それらの役割もまた名前をつけて分類し、認識しています。

しかし、このように自然言語を使って物事を識別しているのは人間であり、ゾウさんはリンゴを「リンゴ」という言葉に置き換えて識別したりしません。きっとゾウは「あ、これ前に食べたらめっちゃ美味かったやつやん!もっかい食ったろ!」といった感覚によってリンゴを識別している。つまり、当たり前ですが言葉とは人間が作り上げたものであり、言葉自体は音声やシンボルといった記号であって、事実そのものには成り得ません。

人が「車が走る」と識別するのは当然のことのように思えます。しかし本当のところはどうでしょう?本当に車は走るのでしょうか?

答えはNOです。車は走りません。いえ、走らないというより「ペダルが踏まれるとタイヤが回転する」といった、具体的なものが事実となります。実際、タイヤが溝にはまると車は走れなくなります。

言葉をより事実に近づけるならばもっと複雑です。おそらく「ペダルが踏まれるとガソリンタンクに貯蓄された燃料が空気と共にエンジン内部へ送られ、プラグに火花が着火することによって引き起こされた爆発の力を利用してタイヤが回転する」とかなんとかになるのでしょう。

このように、事実とは具体的な細かい事柄が全て噛み合って引き起こされます。そして、自然言語を使っている以上、どんなに詳細に具体的に言葉を並べても事実を100%記述することはできないのです。これは自然言語の言葉の限界なのです!

しかしプログラミング言語はどうでしょうか?


事実に基づいた魔法の言語



事実は最大の具象です。コンピュータはオンとオフという事実に基づいて構成されました。0と1には意味が与えられ、それは機械語となり、アセンブラと呼ばれる機械語と言語の対応表を通して、ついには「言語」と「事実」を結びつけることに成功しました。さらには、アセンブリ言語を用いてC言語といったより高級な言語を作り上げていきます。

このようにプログラミング言語は自然言語とは違い、根底に0と1の事実を基にして抽象化を重ねて進化しています。

そして、プログラミング言語によって記述されたコードは人間に読み書きされるだけでなくコンピュータによって読み書きされ、コンピュータは0と1の事実を書き換えます。

サッと述べていますが、これは物凄くパワフルで革命的なことです。なぜなら、事実を制御するプログラミング言語を使えば、その言語を解釈できるあらゆるマシンを駆使して具象度MAXの現実世界に変化もたらすことができるのですから...

ここで一つ疑問です。コンピュータを制御するこの魔法の言語は一体なぜ0と1から始まり現代のプログラミング言語に至るまで抽象化を重ねる必要があったのでしょうか?それは、モノゴトが抽象化されると「便利でわかりやすくなる」からです。


抽象化はわかりやすさと利便性を提供する



モノゴトは抽象化されると、便利でわかりやすくなります。一般的な車は高度に抽象化されているため、練習すればすぐに運転できるようになります。

しかし、レースで使うF1マシンのような車をすぐ使いこなすことは難しいでしょう。当然ですが、F1マシンは命をかけて勝負に挑むレーサーが使う車であり、勝利のために設計されています。そのためマシンの制御は複雑化し、勝つために必要な知識やスキルは普通車の運転に比べ、おそらく数十倍、数百倍の差があるはずです。

反面、F1マシンと違って普通車は知らなくていいことを知らないまま利用することができます。車を走らせるのに必要な知識はガソリンを入れて鍵を回し、ペダルを踏むと前に進むということだけです。いちいちエンジン内部の仕組みを知る必要はありません。

このように物事は抽象化されると便利でわかりやすくなります。そして、高度に抽象化されたモノは「わかりやすい」だけでなく「正しくわかる」こととなります。(話が少し脱線しますが僕は、便利で正しくわかるモノを「カプセル化されたモノ」といい、わかりやすいけど若干の誤りがあるものを「シンプル化されたモノ」と言う時があります)

モノゴトの抽象化は車やプログラミング言語にとどまらず、人の手によって作られた全てのモノや物事の認識において重要な要素となります。英語の勉強にしても、より高度に抽象化された参考書を読めば素早く正しく英語を理解することができます。

わかりやすさは、使い手を正しく誘導する。この話は「正しい名前を付けることが大切な理由」にも記載しましたが、デザインが人間をプログラミングするということです。

つまり、車の話や、英語の参考書や、コンピュータ言語の進化を見ても分かる通り、人間には抽象化(わかりやすさと利便性)が必要であり、高度に抽象化されたものに接触すると、人は「物事を正しく素早く理解でき、利便性を得る」ことができるのです。

まさにスマートフォンは高度に抽象化され、デスクトップパソコンに匹敵する利便性と、それを上回るわかりやすさで世界中に普及しました。もちろん高度に抽象化されたモノはコンピュータに留まらず、過去登場したばかりの頃の洗濯機や電子レンジも同じです。

そして、抽象化されたものには、当然のごとく使い手だけでなく作り手が存在し、作り手は抽象化の必要性に迫られることとなります。


抽象化とカプセル化とデザインは同じもの



人間にはわかりやすさと利便性が必要であり、抽象化がわかりやすさと利便性を提供するという話をしましたが「高度に抽象化されたモノを作る」とはいったいどういうことなのでしょうか?

それは、単純に言ってしまえば「複雑なものをシンプルにする」ということです。物事をシンプルにするにはその物事の本質的な部分を抜き出して複雑な部分を見えないようにする必要があります。

今あなたが触れている目の前の機器を分解すると、あなたの目の前には、その機器の複雑な仕組みが展開されます。これはカプセルを取り除いたということです。カプセルの中身を開けるとその中には「知らなくてもよかったこと」がたくさん詰まっています。

逆に何かを作る立場になったらどうでしょうか?あなたは、自分が作り上げたものをシンプルでわかりやすいものにするために、使い手が知らなくてもいいことは隠そうとするはずです。何もかもが剥き出しで、どれがスイッチかもわからないようなものを誰かに使わせたとき、使い手が困惑して最終的に正しく使うことができなかったという結果は容易く想像できます。

しかし、それら複雑なものを隠してスイッチだけ見えるようにしたらどうでしょうか?使い手はきっと何も説明を受けなくとも、すぐにそれを使うことができるでしょう。

この話からわかることは、高度な抽象化とは高度なカプセル化だということです。

僕は「オブジェクト指向と10年戦ってわかったこと」という記事の中で「不可欠なのはカプセル化」と発言しましたが、この考え方は多くの読者様に否定されました。

もちろん否定的意見の他にも、大切なのは疎結合だ!とか、ポリモーフィズムだ!とか、イデア論だ!とか、依存性の注入だ!とか10年無駄にしちゃったね!とかいろいろフィードバックをいただきましたw(フザケてますが、皆さんのレスポンスから得た新たな知識は計り知れません!これは皆様のおかげです。本当にありがとう!)

そういういろんな意見が混ざり合って余計に混乱が起きるのは良くあることですが、Wikipediaを見ても「抽象化とは、思考における手法のひとつで、対象から注目すべき要素を重点的に抜き出して他は無視する方法である」と記載されています。これってカプセル化のことですよね?皆様も一度は「カプセル化は情報隠蔽である」というような説明を受けたと思います。

そして、高度な抽象化や高度なカプセル化をするという行為は高度なデザインをするということです。そのため、抽象化とカプセル化とデザインの本質は同じものなのです。

また、経営学などで「完全な全体集合」を意味するMECEもまた、これらとの類似点があります。なぜなら、高度に抽象化、カプセル化、デザインされたものは、高度に情報整理され、完全な全体集合に近い存在と言えるからです。


デザインは人間をプログラミングする



いきなりデザインとか言われても俺プログラマなんだけど...なんて思われるかもしれませんが、プログラミングとは言い方を変えればシステムデザインです。モノ作りをする人々にデザインの要素を切り離すことはできません。

デザインとは人間を誘導するために前もって作っておくということです。持ちにくいものに取っ手を付ければ、取っ手を握ってモノを持つよう人間に誘導をかけることができます。

デザインという言葉は様々な業界や現場で使われますが、人が人と交わって目的を達成しようとする時、そこにはデザインが必ず付きまといます。そのため、デザインは異なる業界や現場によってときに意味が変わってきたりしますが、デザインの本質は変わりません。

デザインの本質は、対象から注目すべき要素を重点的に抜き出してそれ以外を隠蔽(抽象化とカプセル化)することで、人間を誘導し目標を達成するということです。

グラフィックデザインを例にとっても同じです。グラフィックデザインでは人の目を引くために重要なものほど大きく見やすく描いたりしますが、それは注目すべき要素を重点的に抜き出し、その他は隠蔽するという、まさに抽象化(カプセル化)を行なっています。

頂いたの意見の中で、「大切なのは抽象化であってカプセル化ではない」と言ったものもありましたが、僕は「同じことを違う言葉で言っているだけで、互いに同意し合えたはずなのにそれができなかったなんて何て勿体ないんだ!」と思いました。

デザインとは高度なカプセル化であり、高度なカプセル化は高度な抽象化のことを指します。そして、高度に抽象化されたモノはわかりやすさと利便性となって、わかりやすさと利便性は人間を誘導(プログラミング)するのです。


LISPの悟り体験から学ぶ真実



いやいや、抽象化がわかりやすさを提供し、人間には抽象化が大切なことなんて解りきってるんだよ!俺が聞きたいのは、名前をつけることが抽象化の第一歩であってオブジェクト指向でプログラミングする意味は無いってことなんだよ!オブジェクト指向なんか使わなくても抽象化の手段は他にもあって、構造体(データ)と関数(コード)の組み合わせで十分できるでしょ!

実際その通りだと思います。LISPのような最小の言語を見ても分かる通り「コードとは、データを解釈してデータに変換するデータ」であり、プログラムとは前もってそれらを記述する行為である。コンピュータは、データとデータを変換するデータの作用によって動作しており、それ以上でも以下でもない。

例えば、目の前のボールペンを手にした時、我々はボールペンというデータを利用して紙に線を引くことができますが、誰もボールペンはあなたに紙の上でなぞってくれとは言っていないわけです。

これはLISPを学習することで得られる悟り体験です。コードが「データを解釈するデータ」であるならば、生命は「宇宙を解釈する宇宙」となります。データや宇宙は、それ単体では意味を持たず「それらを解釈するそれ」があって初めて意味が生まれる。それ自身はそれそのものであり、全てはそれである。梵我一如。(これは根本原理なのですが、ワケワカンネと思った方は、データとコードは全部リストであり、LISPはそれを教えてくれるんだ!という解釈でOKです。そしてLISPはこのことを利用して通常の言語では出来ないことを可能にします。もっと詳しく知りたい方はこちらをどうぞ)

つまり、ボールペン(データ)には.write()という機能は備わっておらず、我々(コード)がボールペンの特性を解釈し利用したから紙に線を引くことができた、というわけです。

また、ボールペンを使わなくとも指先にイカスミをつけて紙の上をなぞれば、紙の上に線を引くことはできます。この現象を「これはイカスミの機能だ!イカスミにはモノを描く機能があるんだ!つまりikasumi.write()だ!!」なんて解釈しませんよね?車も同様、実は.run()という機能は車には備わっていないのです。ペダルを踏んだら車が走るなんて誰が言いました?僕だったらペダルを固定して車を逆さまにし、タイヤをコンベア代わりに使ってランニングしますよ!!

だからcar.run();ではなくrun(car);が正しいと。

これは真実です。現実世界のあらゆるモノにはあらゆる可能性があり、人が思っている以上にモノの使い方は制限されていないのです。


オブジェクト指向の嘘



オブジェクト指向には、理解の妨げとなる間違った解釈が存在します。それは「オブジェクト指向は現実世界をそのままソフトウェアに表現する」という考え方です。皆様も一度は聞いたことがあるのではないでしょうか?

実はこれ、全くの嘘っぱちです。現実世界に宇宙の創造物はあっても、オブジェクト指向の世界には宇宙の創造物など存在しません。オブジェクト指向の世界であなたが作ったDogは生命宿る動物の犬ではなく、あくまで仮想ロボット犬なのです。

どんなに努力して改良してもロボット犬はどこまでもロボット犬。Dogを生命の宿った動物の犬に磨き上げることはできません。当然ですが、人の手によって作られたモノは魂を持てないのです(子作りはモノ創りの範囲外です。また、数十年後にはAIの技術が進歩して人類は魂の創造が可能になるかもしれないけど混乱の元なので今はその話はしません)

生命はモノを解釈し、自在にモノを変換することができます。走りたいと思えば肉体というモノを動かして自分の意思で前進することができます。しかし、ボールペンや石ころのような生命ではないモノは自分の意思によってモノを変換することができません。

物作りとはカプセル化されたパーツを使って更なるカプセル化を行う行為であり、車のパーツを組み立てるようなもの。宇宙の循環によって自動的に創造された動物や植物の創造とはワケが違います。

これら宇宙の創造物と人間の創造物を混合することによってオブジェクト指向は、わかるようで何だかよくわからない代物になってしまうのです。

詳細は「オブジェクト指向に生命を持ち込むな!」をご覧ください。

LISPは現実世界を「宇宙は宇宙を解釈する宇宙のお祭りである」と教えてくれました。

悲惨なことにオブジェクト指向は「現実世界をそのままソフトウェアに表現」とか言っておきながら、現実世界をそのままソフトウェアに表現できているのはLISPのような「データとデータを解釈するデータ」によって構成されたプログラムの方なんです。

事実に基づいて抽象化されてきたプログラミング言語は、オブジェクト指向という謎のバラダイムの登場によって現実離れしたものとなってしまいました!なんということでしょう!


オブジェクト指向の価値



オブジェクト指向の登場により、本来存在していたデータの可能性がカプセル化されてしまい、その結果プログラミングは現実離れしたものとなってしまいました。

なぜオブジェクト指向は、可能性を殺してまで抽象化する必要があったのでしょうか?それは、可能性には「選択肢多すぎて明確さが失われる」というデメリットがあるからです。

選択肢が多いものを適切に扱うということは容易ではありません。例えば、便利だと思って鞄に入れておいた十徳ナイフが、鞄の中で眠ったまま実際に使うことは全く無かったというような経験は皆様もあるのではないでしょうか。

考え方次第で色々な使い方ができるモノは、そこにたどり着くまでの思考のコストが掛かります。本来、全てのものにはあらゆる可能性があり、あらゆることに利用できるのですが、人間の脳みそはサボリ癖が強いので「思考コストが低いもの」を好む傾向があります。

「冷蔵庫は冷やすもの」「ディスプレイは映すもの」そして「車は走るもの」このほうが人間には分かりやすいのです。本来、全てのモノに制限は無いので、コンセントを抜いた冷蔵庫を収納ボックスに使ったり、ディスプレイを真っ白にして机の上に倒せば、ディスプレイはトレース台として使えるでしょう。しかしそのような使い方は、何というか、とてもハッカブルです。通常、そのような使い方は思いつきません。

つまり、モノの効率的な使い方の提案です。オブジェクト指向では、データがどう使われると効率よく便利に使えるかという、データの使い方の提案をオブジェクトにひとまとめにしたということであり、オブジェクト指向の価値はここにあります。

先日、調理器具を整理していた時に、よくわからない道具を見つけて捨ててしまおうかと思ったことがありました。捨てる前に何に使うものか調べてみたところ、それはエッグセパレーターと呼ばれる道具で卵の黄身と白身を分ける道具だということがわかりました。もしも、器具がパッケージされた状態で保管されていたら、おそらく僕はパッケージを見ただけですぐ何に使うものか理解できたはずです。

オブジェクト指向の素晴らしい点は、使い手が迷わず使えるようにオブジェクトに使い方の提案を含めた抽象化が行われていることです。そのため、オブジェクト指向によって完成されたモジュールは、時にドキュメントに目を通さなくともすぐに利用できる「わかりやすさと利便性」が得られることがあります。

そして、まな板や包丁、フライパンやガスコンロが備えてあると「これはキッチンだな」と理解できるように、オブジェクト指向で作られたプロジェクトはプロジェクトの全体像も把握しやすくなるのです。(ドキュメントも含めてのコードだ!と言う人には僕の投げキッスをプレゼント:kissing_heart:


システムとしての価値

オブジェクト指向の勉強はしてきたけど、オブジェクト指向がわかりやすさと利便性を提供することなんて重要視してなかったよ?オブジェクト指向は「わかりやすさと利便性」を提供するというだけでなく、システムとしての価値もあるでしょ!

はい。その通りです。詳しくは「オブジェクト指向と10年戦ってわかったこと」に詳細を記載しておりますが、システムとしてのオブジェクト指向の価値は、親クラスやインターフェイスを利用して抽象に対してプログラミングすることで、ある型がそれらに属していれば動く柔軟性のあるコードを中心にプログラミングすることができることにあります。

これはとても喜ばしいことです!似たようなコードが乱立することは好ましくありません。同じようなことを少し内容が違うだけでたくさん定義してあったら、コードを読むのが大変になります。それは、単純に1枚の作文より10枚の作文の方が読むのに時間がかかるという意味だけでなく、それぞれの違いを識別する必要が生まれる他、不具合が散らかってデバッグを困難なものとするため、掛け算で複雑さが増すというデメリットがあるのです。

しかし、抽象に対してプログラミングすることで、大量のコードがひとつにまとまったらどうでしょうか?それは、単純に覚えることが少ないというわかりやすさの向上につながるだけでなく、型の違いによって大量に定義する必要性から解放される利便性が生まれるのです。それはたとえ後から新しい型が増えたとしても問題は起こりません。

また、継承を利用することでモジュールの一部のメソッドを元のコードはそのまま都合よく書き換えて拡張したり、メソッドの枠組みだけ用意してサブクラスを作成するだけで交換可能なシステムを作り上げたりすることができます。このようなテクニックを詳しく知りたい方はデザインパターンの学習をお勧めします。

詳細は先ほどの別記事にもありますが、このシステムとしてのオブジェクト指向の価値を最大限に引き出すためにはコツが必要です。それは、継承はインターフェイスであるという認識を持ち、機能の受け継ぎにはコンポジションを利用し、newの扱いをメイン関数に集約して、1つのクラスに複数の役割を与えず、正しい名前をつけることで、実践におけるオブジェクト指向の混乱は大いに避けることができるでしょう:smiley:


まとめ



オブジェクト指向が難しい理由は、オブジェクト指向を超えて重要な原則がカプセル化であり、カプセル化がデザインのセンスを必要とするからです。

このデザインのセンスを培うためには、デザインとは何かを深く理解する必要があります。デザインは時々グラフィックスと勘違いされますが、グラフィックスとデザインは全く別物です。それは、GUIの存在しないシステムをデザインすることを考えた時に、そこにはグラフィックスの概念が存在しないことからも、グラフィックスとデザインには直接的な関連がないことがよくわかります。デザインとは、人間を誘導するために前もって作っておくということです。

そして、高度なデザインをする上でMECE(情報整理術)の原則を知ることはとても重要です。デザインのセンスの正体は、いかにMECEできたかであると言えます。

また、デザインとカプセル化と抽象化は本質的に同じものであり、それらによって高度にまとめられたモノを「使うとき」と「作るとき」とで明確に区別する必要があることも重要です。iPhoneを使うのは簡単ですが、iPhoneを作ることは到底できません。

そして、あれこれ頑張って新しいものをカプセル化できたら、そのものに正しい名前をつける必要があります。が、正しい名前をつけるということは容易ではありません。このことは「正しい名前を付けることが大切な理由」にも記載しましたが、オブジェクト指向ではオブジェクトそのものの名前だけでなく、データ操作の提案に対しても適切な名前をつけて完全な全体集合(MECE)を作成しなければならないという苦戦を強いられます。

もし、カプセル化が大変だと思ったことないけどなぁと思った方は、囲碁の最善の手を連発するみたいにプロジェクトに必要なオブジェクトをことごとく作ることができるスーパーハカーの可能性が高いため、これができる方は是非連絡してください!(僕には君の力が必要だ!)役に立たないものをカプセル化することに意味はなく、プロジェクトが必要とするクラスやモジュールを全体集合としてまとめ上げることに意味があります。

そしてオブジェクト指向は、LISPが教えてくれた真の現実世界の掟を破って、人間の都合の良い解釈で抽象化なされてしまっているということも知っておく必要があります。それによって得られるわかりやすさのメリットは大きいですが、データの可能性が大きく失われるデメリットがあった上で使っているんだという認識があると、オブジェクト指向をもう一歩引いた視点で捉えられるようになるはずです。

また、オブジェクト指向の解釈には「嘘」が含まれているため、その嘘に惑わされないように気をつける必要があります。「オブジェクト指向は現実世界をそのままソフトウェアに表現する」と誰しも一度は解説され、AnimalやDogといった生命を使った例えがなされるにもかかわらず、僕らは生命を創造できないのです。そのため、オブジェクト指向は国王プログラマの管理下にある「SFロボット王国」をソフトウェアに表現できると解釈しましょう。詳しくはコチラ

また、デザインパターンの理解も必要です。オブジェクト指向でプログラミングしてる人は多いのにデザインパターンをよく知らないプログラマさんは多いと思います。デザインパターンは「取っ手をつけたら持ちやすい」といった原則をパターン化することでオブジェクト指向デザインのセンスを培う加速器となります。

そしてSOLID(オブジェクト指向デザイン原則)はオブジェクト指向プログラミングをする上で必須の知識となります。もし、オブジェクト指向は散々勉強してきたつもりだけどSOLIDは知らないという方がいたら、SOLIDを知ることで一気にオブジェクト指向プログラミングの霧を晴らすことができるでしょう。

ここまで聞いてわかる通り、オブジェクト指向は学習コストが相当高いです。この、学習コストの高さが最もオブジェクト指向の難しさに繋がっていることは言うまでもありません。

デザインのセンスとか言われても...って感じですが、全ての物事と向き合い、繰り返し思考し続けることによってでしかこのデザインのセンスは得られません。そんなもの、すぐに習得できるはずがないのです。

かといってオブジェクト指向プログラミングをやめる必要は全くありません。適切なクラスやモジュールが作れなくとも、完成されたクラスやモジュールを使わせていただければ、オブジェクト指向で最も価値ある「わかりやすさと利便性」を得ることができるのですから。