拙著「ちょうぜつソフトウェア設計入門」の発売から1年を記念した特別イラストです。毎年恒例だったなんとかパターンのシリーズは、ネタ切れでした。今年はこれ単品でご容赦を。
まえがき
まずは、いまこそ明かせるちょうぜつソフトウェア設計入門(通称ちょうぜつ本)制作秘話、というほどではないですが、最初はこんな流れでした、というお話をしましょう。
GoFのデザインパターンを1人で書き切るアドベントカレンダーをQiitaでやると宣言し、最終回のバズりのおかげで個人総合1位の座に輝いたのは、2019年のことでした。そこから、技術評論社さんのSoftwareDesignにマンガで釣る入門者向けのシリーズを担当させていただき、その後単著の執筆を企画する機会をいただきました。
実は当初、このキャラクターで何か書く、とは決まっていたのですが「GoFパターンツアーとかでいいんじゃないですかね」ぐらいのゆるい感じでした。が、デザパタを説明するだけでは上級者向けの内容にならないし、かといって初心者〜中堅層には、なぜそんな形のパターンが有用なのかを理解するのに必要な、手前に得ておくべき感覚が抜けていて、またあの90年代末の状況と同じことをやってしまうことになる、と考えました。
で、必要な前提の認識、さらにそれに必要な前提の認識、と想定読者がスタートラインに立つまで組み立てていった結果、ロバート・C・マーチンの著書シリーズを逆順に読んだようなものになってしまいました。とはいえ、さすがのマーチンもテスト駆動開発特集はまだ書いてない、と思いながら最終的な内容に行き着いたころ、クリーンクラフツマンシップが書かれていて、内容の7割超がテスト駆動開発だったと知りました。そんなことある!?
現代の王道を無難に端的に書いただけのものが、あのテイストで書かれている、という、刺さる人には刺さるネタ技術書のつもりで売り出したわけですが、それがなんだか、この分野の国産技術書としては近年まれに見る良書とまで言ってもらえたりするぐらい、話題と難しさのバランスがちょうどいい塩梅だったようで... おかげさまで、これまでコミュニティがクロスすることなく、面識のなかった方ともお話することができました。自分がいま主に使っているのは、PHPとJSとGoを少し、といったところでしたが、Java、Ruby、Pythonといったさまざまな言語を使う読者のみなさんに気に入ってもらえました。
そろそろオブジェクト指向の話をしようか
本のトピックの中でもっとも気になると言われたのは、「オブジェクト指向がいかに定義不能なものか」についてと、「ウォーターフォールをやろうとしてやっていた組織はなかった」ことに関してです。ウォーターフォールについては書かれているとおりなのですが、オブジェクト指向についてはだいぶあたふやでした。
オブジェクト指向プログラミングは、バートランド・メイヤーの超重量級上下巻になった「オブジェクト指向入門」が有名ですね。でも、実際に今までに登場したさまざまな言語や技法と照らし合わせると、あれはメイヤーのオブジェクト指向でしかなく、全体を半分カバーできたかできていないかしかないと感じます。オブジェクト指向の全ては説明しきれないうえ、説明したところでそれが対象読者にとって有用とは言えない禅問答になるしで、想定した読者層にとってもっとも薬になる言葉にとどめたのがあれです。
どれだけ定義不可能かを、改めて考察してみましょう。
メモリ上のデータブロックと専用のプロシージャか
これは明らかに違いますね。C言語でも、構造体のサイズぶんメモリを確保して、そのポインタを第一引数に取る専用の関数を設けることができます。UnixコマンドもC以外の言語処理系も、そのような方法を使って、純粋なC言語だけで十分にやってきました。
1978年に、ビャーネ・ストロヴストルップは、C with ClassesというCのプロプロセッサを作りました。C++の前身です。このときストロヴストルップは、この処理系をオブジェクト指向とは呼びませんでした。malloc関数を使うか、同じことをクラスで実現するかの違いだけでは、まだオブジェクト指向ではなかったのです。
最近気がついたのですが、どうも90年代以後のプログラミング言語を使う人の中には、Cの構造体と専用関数とオブジェクト指向は何が違うんだ、という疑問を持ったことがない方もいるようです。名前を付けて管理されるメモリ上のデータを、最初からオブジェクトと呼んだ経験しか持っていないわけです。そのため、ヒープに確保したデータと操作の組でさえ、オブジェクト指向に含むんだといった感覚を持っている層がいるのではないかと感じました。
が、オブジェクト指向は、C言語で意識する生のメモリのようなものを計算機科学者が駆使することに対する、アンチテーゼであったのは間違いありません。遅延束縛があるから実体を気にせずプログラミングしよう、という考えから始まったのですから。
操作対象とプロシージャの表記順序か
表記順の違いは本質的ではありません。が、実は1周回っていい線いっています。
まず、本質的でないとはどういうことか。Nim(とたしかD言語も)には、UFCS(Uniform Function Call Syntax)が設けられています。これは、同じデータとプロシージャをプロシージャ(対象,パラメータ)
の順で書いてもいいし、対象.プロシージャ(パラメータ)
の順で書いてもいいというルールです。利用箇所の表記方法が違っているだけで、定義側の考え方は何も変わりません。呼び出し側の気分で書き方の違いが可能だということが、パラダイムを表すとはとても言えませんね。(ちなみに自分は、Nimほど新しくてオブジェクト指向でない言語はないと感じます)
では、1周回っていい線の方は何のことか。Lispは数式を逆ポーランドで書きます。SmalltalkはLispのように割り切った文法システムを持ちながら、中置記法が可能です。足し算演算子がメソッドで、足す数がパラメータで、カッコやドットを使わない文法なのです。この、足し算演算子がメソッドで、カッコを書かないという特徴は、Rubyにもあります。実はJavaがベースだと考えられているScalaも、連続する操作のチェーンに、同じような考え方をします。これらの言語の中置記法の数値計算は、メソッドチェーンなのです(ただし、RubyとScalaはSmalltalkと違って、演算子の結合順位を持っています)。
そうやって、木構造ではなく、左から右へと続く連続的な記述を可能にしたことで、文が計算機の命令やS式のイメージを離れ、より人の認識にとって優しい、自然言語に近いものになりました。おかげで、人間が文章に出てくる対象をとらえやすくなったのではないかと思います。
が、Nimの例でも示したように、やはりそれが決定的な本質とは言えません。げんに、語順の違いと無関係に、オブジェクトのメッセージングが成り立っている、Scratchというプログラミング環境があります。
基本型を組み合わせた振る舞い抽象データ型か
1985年、バートランド・メイヤーは、Eiffelという言語を作りました。またその数年後には、あの「オブジェクト指向入門」の初版を書いています。彼がオブジェクト指向へとつながる道の入口だと説明したのは、メソッドを持つクラスを用いた、抽象データ型の設計でした。このときとくに印象的だったのは、継承による分類と型付けを重視した点です。
それはあくまで、オブジェクト指向につながる道のひとつにすぎません。抽象データ型をオブジェクトという用語で呼ぶことと、オブジェクト指向プログラミングとは、層の異なる問題なのです。メイヤーは抽象データ型を通ってオブジェクト指向というゴールに行こうと提案しましたが、残念なことに、多くの人は、抽象データ型(静的型が理想)イコールオブジェクト指向だと誤解しました。
オブジェクト指向という言葉の元祖Smalltalkの変数は動的型です。動的型だからこそオブジェクト指向だとさえ言っています。それはRubyにも受け継がれました。また、Rubyをイメージするとわかりますが、それらの言語のクラスには、先にデータ構造を定義するといった考え方がありません。
ちなみに、Simula-67は継承が可能なクラスと型システムを同じ概念にしようとしましたが、それはおかしいと反対されています。オブジェクト指向という言葉がない頃からすでに、クラスの継承で型システムを作っても上手く行かないと指摘されていました。TypeScriptが柔軟なJavaScriptにどう型付けをするかを想像すると、それがどういうことか、なんとなくイメージできるんじゃないでしょうか。
多相性を得たことが決定打か
犬と猫を同じように扱えることはとても大事です。ポリモーフィズムの有無は、オブジェクト指向の必要条件です。ポリモーフィズムに全く触れずにオブジェクト指向を説明している人がいたら、その人はインチキです。というより、メイヤーの示したゴールが見えず、抽象データ型と構造化をオブジェクト指向だと勘違いして、初心者で止まってしまった人、と言うべきかもしれません。
メイヤーの当初の予想に反して、継承は、使いこなせればすごいと盲信した人が乱用すると、混乱の元になる危険な代物でした。メイヤーが継承を重視したのは、差分コードの共通化だけが目的ではありませんでした。メイヤーは、同じようなオブジェクトの、少しづつ違うバリエーションを、扱う側のロジックを書き換えずに増やしたかったのです。開放閉鎖原則は彼の言葉です。全ての継承が全くの役立たずだという思い込みを持っている人がいたら、それは、ポリモーフィズムという、後からバリエーションを増やすアイデアを理解できていないことのあらわれだと考えられます。
ところで、多相性があればそれだけでオブジェクト指向なのでしょうか?
型クラスと呼ばれる、関数型プログラミングでよく使われる用語があります。これは、複数の型に共通した特性があるとき、その共通性に名前を与え、類似する型を同じように扱おうとするものです。たとえば、整数と文字列は全く異なる型ですが、どちらも、比較する関数(演算)を持っているという特徴が同じです。このとき、「比較可能」という型クラスを見出します。「比較可能な型」であることしか問わない「要素の全てが一致するか」という計算が、型クラスによって、ひとつの関数に一般化できます。
Haskellには型クラスがあります。Haskellは圏論のアイデアをうまく表します。圏論とは、対象(object)の射(morphism)を考える抽象数学です。そんなHaskellが、はたしてオブジェクト指向言語を自称しているでしょうか?
記名的型付け階層による概念ツリーなのか
では、実存の型と操作が先にあって、後から型クラスや構造的部分型を考えるのではなく、あらかじめ定義された抽象クラスやインターフェースがあって、それを後で書くクラスに付与する順になるのがオブジェクト指向でしょうか?
JavaとPHPはそのような型のツリーを活用する言語ですが、動的型のRubyはダックタイピングです。Pythonは動的型でしたが、型システムを獲得していく中で、記名的な型の従属関係と、構造的部分型のようなアイデアの、両方をサポートしました。TypeScriptも、自由すぎるJavaScriptを構造的部分型で解釈するのを好みます。
オブジェクト指向の元祖Smalltalkは、ある特定のメソッドセットを持つことを「プロトコル」と呼び、メッセージングにおいて重要な概念としていました。このプロトコルは、記名的型付けの階層ではなく、動的型に構造的部分型があるとみなす考え方そのものです。Smalltalkの継承は、EiffelやJavaとは異なり、対外的には何の意味もないものでした。継承は単に実装効率のための道具にすぎなかったのです。
そもそも複合データ構造とは限らないのではないか
C++とJavaとPHPは、クラスから生まれるオブジェクトではない基本データ型を持っています。それらの言語しか知らない人は、クラスの中に基本データ型を2つか3つ入れて、ユーザーデータ型を定義したオブジェクトを使うことを、オブジェクト指向と認識している場合もあるでしょう。
ところが、データに縛られるのは悪いアイデアなのです。Rubyはtrueとfalseまで、一貫して全てがオブジェクトである言語です。あまり言われませんが、そういう意味では、Pythonも、全ての値がオブジェクトの言語です。それらの言語の真偽値は、まぎれもなくオブジェクトですが、いったい何の複合データなのでしょうか。オブジェクトには、それ以上分解できない値としか言えないものも多くあります。あらゆるものを一貫してオブジェクトとみなす(=ありのまま対象化する)ことのほうが、より純粋なオブジェクト指向ですね。
また、データのような実存をオブジェクトだと思い、そこにメソッドを生やして凝集度が上がったと考えるのは、まずい思い込みだと言われます。データに対する操作をすべてデータのメソッドに書き足していると、後から後から書き足していかなければならなくなり、各所で共有しているデータを表すクラスは、とても不安定な存在になります。データに依存する他の部分は、つねに変更リスクに怯え、より不安定になります。いわゆるファットモデルとその弊害です。
実存でない概念をひねり出し、変わりやすいことを、頻繁に変わってほしくないものがら追い出すアイデアが、GoFのデザインパターンには多く見られます。Strategyパターン、Observerパターンなどは、データに直接メソッドを生やす書き換えを避けるための、良い例ではないでしょうか。GoFは、当時のC++ユーザーの世界観がデータにとらわれすぎていることを批判したのではないでしょうか。(教え方がうまく行ったかというと、まあそれはだいぶ怪しいのですが)
「複合データである」というとっかかりは、実は、こうした誤認を起こしやすいまずさを持っていました。アラン・ケイは「データは悪いアイデア」とも言っています。
しかし私たちはオブジェクト指向を認識している
そうは言っても、経験豊かなプログラマーは、「機能分解の構造化ツリーになってしまうのはオブジェクト指向っぽくない」「カプセル化というのはクラスのprivateフィールドに置いたデータじゃない。どこまでも広がる知識の隠蔽と抽象化なんだ」というふうに、オブジェクト指向らしさの共通認識を持っています。
ベテランでなくても、うまくできたIoCフレームワークが与えてくれるオブジェクトを使って、ビギナーが実力以上に活躍していたりします。オブジェクト指向にピンと来た達人が作ってくれた世界がなかったら、世の中のソフトウェア開発プロジェクトはもっと非生産的だったことでしょう。
何がオブジェクト指向なのかはわかりませんし、初学者にとってオブジェクト指向は、習得すればプログラミングが上達するといった幻想を与えてくれるものでもない、というのが一般的な理解になってもきました。(習得して上達するオブジェクト指向と無関係なことに、勝手にオブジェクト指向のラベルを付けて教える人もいて、状況はカオスですが)
というわけで、ちょうぜつ本ではオブジェクト指向のことを、定義はできないけれど確かに共通して認識されていること、という扱いにしました。そして、考えてもしんどいだけの禅問答をするよりも、「モノにたとえてうまくやろう(対象化)としか言えない。中身は気にするな。後で適当なものがハマってくるから」と、完全にはわからんけどそれでかまわないから、先に便利さを享受しちゃうのがいいよ、という話にしたというわけです。このアプローチもまた、オブジェクト指向的ですね。
オブジェクト指向は少なくとも、プログラムが高尚になって値打ちが上がる、みたいな神秘主義のためにやることではもうありません。同じ問題をより簡単にしてくれて、みんなに生産性を与えてくれる(ただし神秘主義者や生のメモリを叩いてこそ本物だ、といった思想の人たちを除く)ものかどうかが重要です。アラン・ケイがSmalltalkで思い描いたのはこんなビジョンです。
いまや、iPad版のマインクラフトも、Web版のScratchも、子どもたちに大人気ですね。子供はベーマガの時代よりもはるかに生産性を得ました。大人もがんばりましょう。