オブジェクト指向に対する考えの整理用です。

私の個人的な立場としてはオブジェクト指向には賛成ですが、それだけが正解とも考えてはいません。

オブジェクト指向は難しい

「このほうがよっぽどオブジェクト指向っぽいじゃん!」

大学時代の同期がJavaのコードを書きながらそんなことを言っていた。そこにあったのはすべてのプロパティにgetterとsetterが定義されているクラスで、実質DTO(Data Transfer Object)と変わらないものだった。外からプロパティを操作されないようにprivate修飾子を付与して、getterとsetterをとりあえず用意するというのはカプセル化にありがちな間違いである。できないよりもできるようにしておきたいという気持ちはわかるのだが。

前職の先輩も新たに立ち上げるプロジェクトを進めるにあたって組込みプログラミング向けのオブジェクト指向を学ぶセミナーに行ってきたらしいのだが、実際のコードは構造体にプロパティを定義しただけで、そのプロパティはほかのオブジェクトから直接操作されていた。下手にオブジェクト指向を取り入れたが故にコードは複雑さを増し、バイナリサイズも増え、メモリを圧迫して、なぜ動いているのかさえ怪しい製品がその後いくつか生み出された。

オブジェクト指向は難しく、中途半端な理解のままでいると、すぐ横で破滅が大きな口を開けて待っている。

虚空から現れる神

結局コードを書いている人自身が、オブジェクトの「振る舞い」がなんであるかを理解できていないし、決められていないのである。オブジェクト指向の入門書なんかには、人を表すクラスPersonがあって、その中にはnameとageがあって、直接アクセスしないように getName() を使って…みたいなことが書かれていて、そこまでは誰でも理解が追いつく。

しかし、入門書の域を超えて、現場で実際のオブジェクトの振る舞いを考える段階になると、誰がその行為に責任を持っているのか?誰が主導しているのか?なにに対して所有権があるのか?を曖昧にしたままでコードを書いてしまうので、虚空から現れた謎の存在がオブジェクトのプロパティを直接操作してやりたいことの解決を図るという非常にマズい状況が発生する。

setName()メソッドがあるのには、Personしか名前を変更できないという制約をつけるためであるはずなのだが、コードを如何様にもできる全能なプログラマ視点では、どうもその認識がなくなってしまい、グローバル領域やメイン処理と呼ばれるようなところから触手を生やしてサッと人の名前を直接変えてしまうことが往々にして起きる。

入門書も実践までは教えてくれないし、ましてやなんかいい感じにしてくれるネクロノミコンも存在しない。責任はすべてプログラマにある。

必要なのはモデリング力

的確に概念を捉える

多分オブジェクト指向それ自体は難しくはないのである。足りないのは事象に注目して、概念を分解し、適切な粒度でコードに落とし込む能力である。オブジェクト指向にはコード上で概念を表現するための機能が用意されているが、正しく扱えていないだけなのである。

物事に的確な名前をつける能力も重要である。読んだことがないならまずリーダブルコードを読むべきである。もしそのドメインに詳しくないのなら、ドメインエキスパート(その分野にスゲー詳しい人)に話しかけて知識を仕入れるべきなのかもしれない。

語彙を鍛える

「通信処理」だとかcheckValue()のような情報量が増えない適当な命名を許しているとだんだん輪郭がぼやけてきて、仕舞いにはよくわからないものになっている。chk_flg()がハードウェアタイマーがタイムアウトしたことを調べるメソッドだなんて中身を読まずに誰がわかるのだろうか?

そんなこんなで足りない語彙で適当なコードを書いているとすぐに邪神が召喚されてしまうので、触手が世界を掻き回さないように自分が今なにをしているのかを明確にして、強い意志でコードに向き合わなければならない。常によりふさわしい表現を求め続けるべきだ。

便利に甘えない

人はすでに用意されている便利機能にも惑わされやすい。Dictionary<string, object> はそのひとつである。キーを文字列、値型をobjectにすればあらゆるデータを保持できる便利な保管庫の完成だ。しかし、キーを定数として定義したとしても、添字によるデータアクセスは壊れやすいコードになるし保守が難しくなる。ときには任意のデータを扱いたい要求は発生するので一概に否定できるものではないが、なるべく避けるべきものだ。

言語によってはこの話は適用されない。JavaScriptはプロトタイプベースのオブジェクト指向を採用していることもあり、objectの利用が前提となっているからだ。

オブジェクト指向の機能について

カプセル化

そのプロパティに求めるカプセル化レベルに応じて複数の選択肢がある。基本的に以下の規則に基づけば問題ないはずだ。

  • どんな値になってもよい: 読み書き可能なプロパティとして公開する。DTOでもない限り、基本的にこの形で公開することはないだろう。
  • 特定の範囲の値になってよい: 値変更用のメソッドを公開する。値が意図したものでない場合は例外やエラーを返すか、飽和演算をする。
  • 読み取り専用か、算出プロパティである: 読み取り可能なプロパティとして公開する。プログラミング言語によっては算出プロパティが定義できないので、その場合はメソッドとして定義する。
  • 算出プロパティとして公開できるが、計算量が大きい: メソッドとして公開する。

最近はコンピュータの性能も上がってきており、プロパティを変更しない不変(イミュータブル)なオブジェクトとして定義する方法もある。不変なオブジェクトは見通しがよくなり、意図しないデータの変更を防ぐことができるが、内部データの一部を変更するときには大きなコストが発生することがある。

例として、不変のUserオブジェクトがあり、その中に複数の兄弟への参照を持つこれまた不変のSiblingsプロパティがあったとする。あるUserのSiblingの一部を削除した新しいUserオブジェクトを得ようとすると、Userのコピーが発生するし、Siblingsでも削除される要素以外のコピーが発生する。もしかしたらオブジェクトによってはコピーに大きなコストが発生するか、コピーをすべきでない状況もある。ましてや芋蔓式にコピーが発生するようなことは避けたい。そのため、不変オブジェクトを盲信するのではなく、妥協点を探るのも肝要だ。

継承

破滅へのショートカットなので慎重に選択すべし。

Animalクラスを継承してCatクラスとDogクラスを作るという話は何万回と聞いた話だが、実際に継承を使うとなると使いどころが難しい。もしかしたら忘れたほうがいいのかもしれない。そもそも実際のコードでAnimalクラスの様な典型的なis-a関係が出てくるかと言われればあまり出てこない。出てきたとしても別の問題が発生することもある。

ひとついい例があるとすれば、ふたつのクラスの合の子を作る場合だ。DemonクラスとDragonクラスがあって、そのふたつを掛け合わせたDemonic DragonあるいはDraconic Demonを作ろうとしたときに、どちらもDemonとDragonの性質があるのは当然である。ただ、Demonic DragonはDragonなのでDemonicな要素を別途足さないといけないし、Draconic DemonはDemonなのでDraconicな要素を足さなければいけない。多重継承が許されている言語であればそれで解決できるが、多重継承ができない言語では困ったことになる。そもそもそんな存在を作るなって?悪魔とドラゴンが合体したら絶対ツエーと思うんですよ私は。

そのためクラスを使って「〇〇である」と表現するよりもインタフェースを使って「〇〇の性質を持つ」と表現して、IDemonやIDragonを与えたほうがやりたいことに対して柔軟にできるように思う。そうすれば悪魔的なドラゴンだけでなく、ドラゴンの性質を持つ竜人だって作れる。

多態性 (ポリモーフィズム)

基本的に前項で述べたように継承ではなくインタフェースで実装することが望ましい。特にC# 8.0以降やRustなどはデフォルト実装ができるので最善策となる。Pythonでもabc.ABCMetaがある。

オブジェクト指向を取り巻く環境

staticおじさんについて

技術選定については様々な事情があるだろうし、氏のたどり着いたひとつの解なので、実はオブジェクト指向ってしっくりこないんです!については一定の理解を示す。すべてがpublicでstaticなので扱うデータが明らかで、データの流れがある意味とてもわかりやすいと言える。

オブジェクトの理解についても「プログラムのアルゴリズムとは無関係」と言っていることから、氏はアルゴリズムすなわち処理の概念をオブジェクトとして捉えることはしておらず、おそらくだがstaticメソッドを処理の単位として捉えているのだろう。なるほど、主張としては筋が通っている。

しかし、オブジェクト指向を齧ったことがあるなら、staticであろうとなかろうと作った製品もまたオブジェクトとして捉えることができるのではないか?というメタ的な視点が生まれてくる。上の記事では触れられていないので氏が実際どう考えているかは想像の域でしかないが、このstaticな世界ではオブジェクトより上位に位置するものはすべてアルゴリズムと捉えているのかもしれない。なので、staticによって作られた製品はアルゴリズムであるし、それ同士の連携もまたアルゴリズムである。

では逆にオブジェクトとはなにか?という話になるのだが、文中では「ホントにモノ」であるものと定義している。おそらくそれ以上分解すると概念が保てなくなるものの意ではあると思うが、それは粒度にもよるし一概には言えない。アルゴリズムとオブジェクトの境目がどこにあるのかが読み取れないが、なんとなく言いたいことはわかる。

個人的には、趣味の範疇かひとりで担当する業務で、それで管理し切れるなら他人が目くじらを立てるほどのことではないと考えている。一方でチームで開発をしているのであれば、同僚はつらい思いをしているかもしれない。オブジェクト指向が広く浸透した現在において、共通認識になっている考え方を捨てているので、みんなと歩調を合わせることは難しくなる。

ものには言い方というものがあるので、static賛成派も否定派も攻撃的にならずpublic staticによるプログラミングのいいところを主張して、あえてこのパラダイムを選択しているということが明確に主張できればこうも騒ぎになることもなかったのではないか。

Smalltalkについて

煽りでもなんでもなく純粋な期待として、Smalltalkが市場を支配する千年王国の到来はいつですか?

オブジェクト指向の先にはなにがある?

その先にはきっとなにもないし銀の弾丸でもない。ましてや求道者になった覚えもない。

オブジェクト指向は利点も欠点もある道具のひとつなので、チームや個人でそれを選択したなら使うとよい。つまりは課題を解決するために十分な機能があればよい。

それにオブジェクト指向で開発すると決めても、オブジェクト指向狂信者になる必要もない。世の中的にはマルチパラダイムに向かっており選択肢が増えてきているので、その時々で最適な技術を使い分けることが必要である。