この記事のターゲット
この記事は以下の人々を対象としています。
- オブジェクト指向を一通りわかっている人。
- オブジェクト指向の設計力を高めたい人。
- オブジェクト指向を使っているのに、設計が綺麗にならず悩んでいる人。
- プログラムが大きくなるとオブジェクト指向設計が破綻する人。
- オブジェクト指向に限界を感じている人。
- 共同開発メンバーの設計力に差があって困っている人。
以下の人は対象外です。
- オブジェクト指向が何なのかわからない人。
- オブジェクト指向を極めている人。
- 関数型など別のパラダイムに活路を既に見いだしている人。
オブジェクトは責任ベースで考える
オブジェクト指向といえば、やれインターフェイスだメッセージだ隠蔽だカプセル化だ、みたいな用語がたくさん出て来て、どれも関連があるようでないようで意味が分からないですよね。気取ったこと言ってんじゃねぇよと。
日本で社会人経験があれば、こんなものは一言あれば一発で理解できます。それは責任です。
仕事をするとき、責任者だとか担当者だとかを職場で設定しますよね。こうした責任者というのは、責任の範囲が決まっています。任されたことについてはしっかり責任を持って対応する。そして他の人が担当している職域には踏み込まない。「その件に関しましては私の方では対応いたしかねますが、鈴木の方が担当となっておりますのでご紹介いたしましょうか」的な奴です。
各オブジェクトには責任の範囲というものがあります。クラスの設計が美しくないなと思ったら、そのクラスの責任範囲が適切かどうかを考えるのが基本です。本当にその仕事はそのクラスが担当するべきなのか。他のクラスが担当するはずじゃなかったのか。または別途新しいクラスに担当させるべきか。クラスを職種に、オブジェクトを個人に当てはめ、擬人化して考えると上手くいきます。頭の中でオブジェクトが「その件に関しては対応しかねますので…」と言い始めたらヤバいです。
人間もやることが増えすぎると仕事が手に負えなくなるように、オブジェクトも責任を抱え込みすぎると手に負えなくなります。
責任を越える仕事は、やらない
営業担当の一職員である男性が、ロビーが汚いからといって掃除を始めてしまえば、外回りに行く時間が遅れるかもしれませんし、背広が汚れて業務に支障が出るかもしれません。もしすでに総務が清掃員を午後に手配済みであれば、その作業は無駄になります。
このくらいの掃除なら自分でできるなと思っても、そこはあえて手を出さず、総務に連絡して清掃員を手配してもらいます。総務は清掃員の手配状況について知っているでしょうから、無駄な重複作業がないよううまくやってくれるでしょう。
これはロビーというグローバル変数に営業クラスの男性オブジェクトがアクセスしてしまったことによるミスです。
責任とは何か
ここまでで言いたいことを改めて要約すると、オブジェクト指向設計を考えるときに、オブジェクトを擬人化して、そいつに責任について語らせると、結構うまくハマる、ということになります。
なおメンバ関数やメンバ変数が増えてきたら、そのクラスは責任過多になっている可能性が高いです。逆に少なすぎる場合は仕事をサボっている不要なオブジェクトかもしれません。適度な粒度に保ちましょう。
オブジェクト間の関連を設計する
よし、オブジェクトの責任範囲を適切に保てば上手くいくんだな。なんかうまく作れそうな気がしてきた…。と思ったのもつかの間、プログラムが大きくなってくるとそれでもグチャグチャになってきます。なぜだ、これがオブジェクト指向の限界なのか…。
いえいえ、オブジェクトの責任範囲を適切な粒度に保つ、というのはオブジェクト指向の基本に過ぎません。そこから先は「関連」が大切になってきます。関連とはオブジェクトとオブジェクトの間の繋がりのことです。会社で言えば組織図とか連絡窓口とかです。
デザインパターン
オブジェクト指向で言う所の関連はどのように考えればよいのか。方法の1つとしてデザインパターンを参照できます。デザインパターンとはオブジェクト指向で作られたソフトウェア設計のカタログです。ググれば色々出て来るでしょうから詳細は割愛します。
このデザインパターン、GoFってやつが有名ですが、はっきり言ってそんなに綺麗に体系化されていません。頻繁に使われる奴もあれば、こんなの使わねーよって奴もあります。またGoF以外にも様々なパターンが当然考えられるわけですが、業界全体で研究してまとめて体系化しようという流れにはなっていません。
それでもGoFを一通り見てみることで、美しいオブジェクト指向設計とは何かということが掴めるようになります。
UML
もう1つの方法は図示化することです。UMLでなくても何でもよいと言えばよいのですが、いちおう規格化されているのでUMLを使いましょう。色々な図がありますが、ことオブジェクト指向設計においてはクラス図が重要です。
ざっくりいえば、設計が美しいとクラス図も美しくなり、設計が汚いとクラス図も汚くなります。関連線がぐちゃぐちゃになったら、それを綺麗に直していくだけで、設計も半自動的に美しくなります。また図にするとレビュー(他の人からのアドバイス)を受けやすくなります。
UMLの作画は専用ツールもありますが、私はVisioか手書きです。ソフトの詳細設計をVisioで書こうとすると死ねますが、それはUMLの使い方がそもそも間違っています。図を書くときは、検討したいポイントに絞って書くべきです。ソフト全体ではなく、ライブラリだけとか、特定の機能だけとか。また造りが難しくなっちゃった所だけとか。stringクラスへの関連線を全クラスから引くとか愚の骨頂です。
ポイントを絞って図を描けば、設計を考えやすくなりますし、レビューもしやすくなりますし、ドキュメントも読みやすくなります。VisioでA4を超えるような図を描いているうちは、適度な粒度感覚が身についているとは言えません。
継承とか多態とか
継承・多態が分からないとオブジェクト指向が分かったとは言えません。しかし継承や多態とは、オブジェクト間の関連の一種に過ぎません。この記事を読んでいる皆さんは優秀な人でしょうから、適当なオブジェクト指向言語をしばらくいじっていれば理解できるでしょう。というわけで説明は省略。
関連とは何か
オブジェクトの責任範囲という基本を踏まえたうえで、各オブジェクト間の関連を考えていると、結局のところオブジェクト指向設計とは、各オブジェクト間の関連を設計するということに他ならないということが解ってくるはずです。
オブジェクトの責任を超える裁量を認める
よし、オブジェクトの責任範囲をコンパクトに保ち、デザインパターンやUMLなんかを使って各オブジェクト間の関連をよく考えれば、綺麗なプログラムを組めるのだな…。
ところがプログラムがいよいよ巨大になってくると、なかなかそうもいきません。一番よくある失敗は伝言ゲームというメッセージのリレーです。
伝言ゲーム
「御社のパーツを購入したいのですが、お見積りをいただけますでしょうか」「少々お待ちください。」「A社があのパーツを買いたいと言ってますが」「B社から卸してもらわないと売れないじゃないか、B社に確認してみろ」「御社のパーツを購入したいのですが、お見積りをいただけますでしょうか」…。
こんな伝言ゲームの登場人物が2~3人までだったらまだ許されますが、4人5人と階層が深くなるようであれば要注意です。特にデータを取得するためだけに2度3度と呼ぶのはヤバいです。
伝言板
この問題を解決するにはデータベースを置きます。データベースとは伝言板のようなものです。B社のウェブページに価格が掲載されていれば、A社に問い合わせなくてもよかったわけです。
ここで言うデータベースとは、単なる共通データ置き場という広い意味で使っています。本当のリレーショナルDBやインメモリDBでもよいですし、ファイルでもよいですし、グローバルにアクセスできるkey-valueペアのメモリストレージでもよいです。
データベースがプロセスのメモリ外にあれば、プロセスを分割することも可能になります。小さなプロセスに分割して全体のアプリケーションを実装することが可能であれば、各プロセスの粒度を押さえられます。
グローバル変数の逆襲
このデータベースとは、グローバル変数そのものです。せっかくオブジェクト指向で責任範囲を区別したのに、また全ての責任を抱え込んでどうするのか。
データベースはグローバル変数なので、やはり導入しない方が望ましいです。小さなプログラムは極力オブジェクト指向の範囲内でなんとかなるはずです。プログラムが巨大になり、伝言ゲームが過ぎると感じ始めたら、そこで初めてデータベースの導入を考えてみましょう。
結局銀の弾丸はない?
グローバル変数はしばしば悲惨な結果を招きます。ちょっとでもコード量が増えると簡単に破綻します。オブジェクト指向を導入すれば、かなりコード量が増えても耐えられます。しかしプログラムが極端に大きくなりすぎると、伝言ゲームの嵐になって別の破滅を招きます。
スキルの低いプログラマが扱うデータベースは悲惨なことになります。しかし、オブジェクト指向をある程度まで極めた猛者たちが集まり、各クラスの責任範囲を逸脱しない範囲でデータベースにアクセスするのであれば、オブジェクト指向とデータベースの高度なレベルでの両立は可能でしょう。そのバランスのとり方は、高い志を持って頑張るしかないです。