これまで、「オブジェクト指向はすごい!」という方向性で説明してきました。
しかし、実際にはかなり多くの問題点があります。
ラス前に紹介するのもなんですが、現実から目を背けずしっかり見ていきましょう。
手間が掛かる
オブジェクト指向プログラミングでプログラムを作ると、作るプログラムが増えます。
もし理想的な方法で作った場合、まず各クラスにインタフェースが必要になります。
フィールドへアクセスするためにGetter・Setterメソッドか、それに代わるメソッドを作る必要があります。
継承を使うと、継承した分だけクラスが増え、合わせてインタフェースも増えます。
サブクラスでオーバーライドをすれば、それだけメソッドも増えます。
クラスが増えていくと、テストをするためのプログラムもどんどん増えます。
テストをする際には、ダミーのクラスも作らなければいけません。
と、こんな感じにプログラムが雪だるま式に増えていくのです。
これがオブジェクト指向プログラミングじゃなかったら、インタフェースは必要ないですし、フィールドにも直接アクセスするのでメソッドも少ないですし、テストも自然と大きな範囲で行うことになるので回数が減ります。
もちろん、そうやって作られたプログラムはクオリティに問題が出る可能性が高くなります。
が、ちんたらオブジェクト指向プログラミングなんてやってられないというほど、ソフトを作るスケジュールはギリギリのことが多いので、現実問題として「ちゃんとしたオブジェクト指向プログラミングをする余裕がない」ということが多いです。
そのため、「オブジェクト指向プログラミング」を最初はしようと思っても、途中でどんどんぐちゃぐちゃになっていき、最後はなんだかわからない状態になることもしばしばです。
融通が効かない
ポリモーフィズムを使用する場合、「使えるメソッド」は当然参照型変数の型が持っているメソッドに限定されます。
そのため、下手にポリモーフィズムを使ったプログラムを組むと「サブクラスが持っているメソッドが使いたい!」という状況が多々あります。
本来であれば、ポリモーフィズムさせる=インスタンスの型は問わない、ということなのですが、インタフェースから実装していないメソッドを呼び出したかったり、特定の型の場合に処理をしたかったりすることがでてきたりします。
このような場合、インタフェースに抽象メソッドを追加して実装させればいいのですが、インスタンスが「他のクラス」の場合もあると、すべてのインスタンスで実装しなければいけないのでかなり難しくなります。
なので、仕方なく「8.4 インスタンスの型を調べるには?」で紹介した「instanceof」を使って型を調べてダウンキャストすることになります。
ただ、これをするとどんどん泥沼になります。様々な型に合わせて処理するため、「if( A instanceof B )」が並ぶ、なんてことにもなりかねず、バグを生みやすくなります。
ポリモーフィズムさせているのに、実際にはインスタンスの型によって処理を変えたりするわけですから、そもそもポリモーフィズムさせるのが間違いとも言えます。
仕様の間違い危険
インタフェースは「したい」と「する」を分けるために使います。そのため、「する」は当然「したい」を満たす必要があります。
でも、その「したい」、ちゃんと決まってない場合があります。
「したい」とはつまり「○○を●●したい」ということですが、「○○」がちゃんと決まっていないと思わぬ仕様漏れが発生します。
たとえば「文字列が空の場合に●●したい」という場合、「文字列が空」とは「null」ということでしょうか、それとも「""」ということでしょうか。
同じく「配列が空の場合」なら「null」ということなのかそれとも「length == 0」ということなのか。
また、インタフェースを使用する場合にはポリモーフィズムさせたいこともありますから、わざと明確に書かない場合もあります。
たとえば「○○の場合出力したい」とある場合、出力先は実装するクラス任せになりますが、それこそアバウト過ぎて何出力するのか分かりません、ということにもなります。
そもそも、「プログラムでしたいこと」がまだ決まってないことだってたくさんあります。その場合にはインタフェースと実装に分けることはできなくなり、必然的に「決まったらね」ということになります。
これは先ほどの「融通が効かない」にも関係します。インタフェースと実装に分けるのは、当然分けられる時だけ、「したい」がまだ決まっていない段階で「する」と分けたら、あとで「する」を変えたいのに変えられない、結局分けない方がよかったじゃん! ということになりかねません。
そのため、インタフェースを使うのは、ちゃんと仕様が決まっていて、かっちり作れるような場合でないとあとあと大変なことになるわけです。
できる人が少ない
オブジェクト指向プログラミングは難しいです。
このコンテンツでは図を多く使って説明してきましたが、逆に言うと、図をイメージできないと分かりづらいと思います。
というのも、継承やオーバーライド、ポリモーフィズムはインスタンスの性質だからです。
プログラムにはクラスしか書かれていません。でもクラスはインスタンスじゃありません。そして「サブクラスでオーバーライドしたメソッドが呼び出される」といった挙動はインスタンスで行われます。そのため、インスタンスの状態や動きをイメージできないと、オブジェクト指向プログラミングの理解は難しくなります。
でも、そういったところまで説明している本は少ないため、結局クラス中心に学ぶことになり、それが「6.4 「すごい」クラスなのに「サブ」クラス?」のような誤解を生むことになります。
もちろん、難しいと言っても「すっごく難しい!」というほどではありません。
ちゃんと本を読んで、プログラムを実際に組んでみて、動かしてみれば、分かることだと思います。
問題は、プログラマー全員わかっている状態にするのが難しいということです。
プログラムを組むスタッフを集める際に、「ある程度オブジェクト指向プログラミングを理解している」という人材だけで揃える、ということは非常に難しいです。ばらつきが生まれてしまいますし、「理解してない」人にクラスを作らせない、といった余裕がないことがほとんどです。
そのため、一部の進捗が遅れたり、プロジェクトの最後の方になったら「全然オブジェクト指向プログラミングじゃないプログラム」ができていた、という結果になることが多いです。
設計から実装へのずれ
前ページで、「オブジェクト指向分析設計」と「オブジェクト指向プログラミング」は別、ということを説明しました。
ITシステムを作るときには、まず「オブジェクト指向分析設計」でクラスの設計図を作り、それを元に「オブジェクト指向プログラミング」を使ってプログラムを作ることになります。
「オブジェクト指向分析設計」で作られたクラスの設計図は、「仕事」「情報」をまとめることでクラスを作るため、自然と現実社会の「物」「人」を集めたものに似てきます。
しかし、それをそのままプログラムにすると様々な問題が生まれます。
まずクラスの大きさの問題があります。
元々の「仕事」がとても大きな部署でまとめて行われていると、それをオブジェクトとみなして作られた「部署クラス」がとても大きくなります。
でも、ひとつのクラスが大きくなると管理が難しくなります。たくさんのフィールドを抱えると、フィールドが「11.2 オブジェクト指向を使わないと?」のグローバル変数のような状態になってしまいます。
また逆に、小さなクラスがたくさんあるのも問題です。「元々が違う物だから」と、無数にできたオブジェクトをそのままクラスにして似たようなクラスが大量にできると、似たプログラムが増えてバグが増えやすくなります。
これらは「プログラム上でのメリット」のために、クラスを適度な大きさに分け、継承等を使って組み合わせていくことが望ましいです。
でもそれをやりすぎると「現実社会とのずれ」が生まれていき、オブジェクト指向分析設計の「ITシステムが理解しやすい」というメリットがどんどん失われていきます。
「クラスの大きさ」以外にも、実際にプログラムを作るときに、クラスの設計図と変えなければいけなくなる部分はたくさんあります。
たとえば「再利用」。
プログラムをコピペして増やすと後で修正するときに大変なので、似たような処理はまとめてひとつのメソッドなりクラスなりで行うことが望ましいです。
でもそうしてクラスを増やすとまた設計図と違ってきてしまい、現実社会から離れてしまいます。
また、「ITシステム特有の機能」はクラスの設計図に含まれていないことが多いため、それらを行うクラスをどうするかも問題になります。
ログの出力やWebシステムのセッション取得、データベースのトランザクション等、現実社会にはなくITシステムには必要な機能はクラスの設計図に含まれていない場合が多く、それをプログラムで効率的に使用する場合にはまた設計図と違ってきてしまいます。
このような「クラスの設計図」と「実際に作られるプログラム上のクラス」の間に差が生じてしまうと、先ほど説明した「融通が効かない」「仕様の間違い危険」といった問題が現れてしまい、プログラムがどんどんぐちゃぐちゃになってしまいます。
かといって無理矢理「クラスの設計図」通りに作ろうとしても、やっぱりそれは無理矢理なので無茶な実装が生まれてしまい、クオリティを下げる原因になってしまいます。
設計・実装両方できる人がいない
この「クラスの設計図」から「実際に作られるプログラム上のクラス」への変換は、適切に行えれば問題はありません。
ですが、これがちゃんとできるようになるためには、ある程度「設計手法」を知っていなければいけません。
つまり「Javaは使えるしクラスも作れるよー」というレベルではなく、クラスとクラスの関係の現実世界での意味やクラスとクラスの関係のよくあるパターンまで理解していなければ、きっちり設計されたものを作り替えていくのは難しいということです。
それは「オブジェクト指向分析設計」と「オブジェクト指向プログラミング」の両方知っているということです。
また、このような人材は、設計側にも必要です。
「物」→「クラス」の単純な置き換えだけではなく、そこから一歩進んで「物から作ったクラス」→「プログラム用のクラス」まで変換して設計すれば、プログラムを組む際に「設計通り」にプログラムを組むことができ、先ほどのような「作り替え」をしなくて済みます。
また、そこまでしなくても、プログラムを組み始めてから、プログラマーからの要望を受けて設計を修正し、それをプログラムに反映する、といったことをすれば、これもプログラムを組む時の「作り替え」が必要なくなります。
こういった、実際のプログラムに近い設計をするためには、プログラミングの知識が必要になります。
それは「オブジェクト指向分析設計」と「オブジェクト指向プログラミング」の両方知っているということです。
しかし、このように「両方できる」という人材は非常に少ないです。
「プログラマー」→「設計屋」という形でステップアップすればいいのですが、「プログラマー」→「プロジェクト管理」という形で職位が違う方向に上がったり、「プログラマー」をやらずに直接「設計屋」になってしまったりします。
また、「プログラマーも設計もできるよ」という「何でも屋」的なポジションはあまり評価されず、職位や報酬に反映されないという面もあります。それよりはどちらかを極めるか、それとも単なる管理職に落ち着いた方がお得ということになってしまいます。
加えて残念ながら、「設計」側と「実装」側には大きな壁があります。
両者は派閥のように仲が悪く、相容れない面があります。
「クラスの設計図」から「実際に作られるプログラム上のクラス」への変換は、本来であれば「詳細設計」で行うものです。
ですが、「設計」側は「それをやったらプログラマーの仕事なくなるだろ」と思い、「実装」側は「設計図通りに作るのが俺たちの仕事じゃね?」と考え、結局どちらもその変換をいやがりますし、まぁどうせどちらもうまい変換はできません。
両者の間を取り持つようなポジションかつスキルの人間がこの変換を行うのが望ましいのですが、そういう人材は少なく、なかなかうまくいかないのが実情です。
設計は「理論」、プログラミングは「実戦」なので、思想そのものが違うのかもしれません。