はじめに
オブジェクト指向での開発が当たり前となっていたが、なぜオブジェクト指向で開発するのか・オブジェクト指向が普及された経緯など全く知らなかったので本書を読んでみました。備忘としてアウトプットします。
https://www.amazon.co.jp/オブジェクト指向でなぜつくるのか-第2版-平澤-章/dp/4822284654
1.オブジェクト指向はソフトウエア開発を楽にする技術
-
オブジェクト指向で作る理由は、「ソフトウェアを楽に作りたいから」
-
オブジェクト指向が難しい理由
- 用語の洪水
- 聞きなれない特殊用語を浴びせられると辟易してしまう。
継承、スーパークラス、サブクラス、インターフエース、多重継承、オーバーロード、etc...
2.比喩の濫用
- 以下のような説明は必ずしも悪いやり方ではないが、比喩ばかり強調して、プログラミングの仕組みや目的を説明しないと間違った解釈を招く可能性がある。
動物がスーパークラスで、哺乳類や魚類がサブクラス。卵を産みミルクで子供を育てるカモノハシは、虫類と哺乳類の多重継承に相当する..
3.なんでもオブジェクト症候群
- オブジェクト指向は「モノ中心」「モノ指向」という意味だが極端な捉え方をすると、「オブジェクト指向を使えば現実世界をそのままプログラムに表現できる」という誤解を引き起こす可能性がある。
- 筆者はこの「なんでもオブジェクト症候群」こそオブジェクト指向を混乱させている最大の原因だと考えている。
-
3つの混乱を取り除けばオブジェクト指向の真の姿が見えてくる
2.オブジェクト指向と現実世界は似て非なるもの
- オブジェクト指向プログラミングの仕組みと現実世界の様子は、それなりに似ている点はあるものの、実際はかなり違うもの。
現実世界の人やモノはクラスから作らない
-
オブジェクト指向の世界では、最初にクラスを定義し、そこからインスタンスを作る仕組み。
-
現実世界は、犬が生まれるのは雄と雌が仲良くなった結果生まれる訳であって、あらかじめ定義された「犬」クラスからは作られない。
-
オブジェクト指向では、インスタンスを作るための仕組みとしてクラスがあり、インスタンスが帰属するクラスは1つだけ。
-
現実世界では先に具体的なモノ(インスタンス)があり、それを見る側の立場や興味の違いによって、いろいろな基準で分類される。(ex:会社に行けば会社員、家に帰れば父親)
3.OOPを理解する近道はプログラミング言語の歴史にあり
OOP以前のプログラミングの歴史を簡単に振り返る。
機械語
コンピュータは機械語しか理解できない。
この頃(1940年代)はプログラマ自信が機械語を使って1行1行プログラムを書く必要があった。
A10010
8B160210
01D0
A10410
アセンブリ言語
そうした非効率を改善するためにアセンブリ言語が登場した。
アセンブリ言語では、無機質な機械語を人間にわかりやすい記号に置き換えて表現した。
MOV AX, X
MOV DX, Y
ADD AX, DX
MOV Z, AX
高級言語
高級言語では命令を一つ一つ記述するのではなく、より人間にわかりやすい「高級な」形で表現する。
高級言語の登場によりプログラミングの生産性や品質は大きく向上したが、それ以上にコンピュータの普及・発展の方が爆発的に進んだため、生産性向上に対するニーズが収まらなかった。
「20世紀末には世界の総人口がプログラマになっても、ソフトウェアの需要に追いつかない」というソフトウェア危機論が広まった。
構造化プログラミング
このソフトウェア危機に対応するために注目を集めたのが構造化プログラミング。
基本的な考え方は「正しく動作するプログラムを作成するためには、分かりやすい構造にすることが重要である」というもの。
具体的には
- GOTO文を廃止
ロジックを逐次進行、条件分岐、繰り返しの3つの構造(基本三構造)だけで表現すること。 - サブルーチンの独立性を高めて保守に強くする
ローカル変数と引数の値渡しの仕組みが考案された。
構造化プログラミングで解決できない課題
1. グローバル変数
サブルーチンの実行期間を超えて保持する必要のある情報はグローバル変数として持たせる他なかった。
2. 貧弱な再利用
基本的な処理はサブルーチンでも再利用可能だったが増大する増大するアプリケーションの規模からすると微々たるものだった。
4.OOPは無駄を省いて整理整頓するプログラミング技術
- OOP(オブジェクト指向プログラミング)にはグローバル変数を使わずに済む仕組みが備わっている
- OOPには共通サブルーチン以外の再利用を可能にする仕組みが備わっている。
OOPにあって構造化言語にはにはない3つの仕組み
- クラス
- ポリフォーフィズム
- 継承
このOOPの3つの仕組みは、重複した無駄なロジックを排除し、必要な機能を整理整頓する仕組みを提供する。
クラス
- 関連性の強い関数とグローバル変数を一つにまとめて粒度の大きいソフトウェア部品を作る仕組み。
- クラスの仕組み
- まとめる
クラスにまとめられたサブルーチンをメソッド、グローバル変数をインスタンスと呼ぶ。
これにより以下のメリットが得られる。- 部品の数が減る
- メソッドの命名がしやすくなる
- メソッドが探しやすくなる
- 隠す
- privateなどのアクセス修飾子を利用して作成した変数とメソッドを他のクラスから隠すことができる。
- これにより、プログラムの保守性悪化の現況となるグローバル変数を使わずにプログラムを書くことができる。
- たくさん作る
- クラスを定義しておくことでインスタンスをいくつでも作ることができる。
- これによって同種の情報を複数同時に扱う処理でも、そのクラス内部ロジックをシンプルにできる。
ポリモーフィズム
ポリモーフィズムは「いろいろな形に変わる」という意味で日本語では「多態性」「多相性」などと訳され、サブルーチンを呼び出す側のロジックを一本化する仕組み、すなわち「共通メインルーチン」を作る仕組み。
// 名前をカウントする
public int GetNameCount(Creature creature)
{
return creature.GetName().Length;
}
// 全員スペシャルアタック!
public void AllSpecialAttack(IEnumerable<Creature> creatures)
{
foreach (var creature in creatures)
{
creature.SpecialAttack();
}
}
継承
- 共通クラスの事をスーパークラス(親クラス)、それを利用するクラスをサブクラス(子クラス)と呼ぶ。
- クラス定義の共通部分を別クラスにまとめて、コードの重複を排除する仕組み
クラスを型として利用する
- OOPではクラスを型として扱うことができる。
-
型付け方式には2種類の方法がある
- 強い型付け(静的型付け)
- プログラムのコンパイル時点でエラーを検出する方式(Java、C#など)
- 弱い型付け(動的型付け)
- プログラムの実行時点でエラーを検出する方式(Ruby、PHPなど)
- 強い型付け(静的型付け)
進化したOOPの仕組み
- パッケージ
- パッケージはクラスをさらに「まとめる」仕組み。
- 例外
- 「戻り値とは違う形式で、メソッドから特別なエラーを返す仕組み」
- ネットワーク障害、DBのデッドロックなどもこの例外として当てはまる
- この仕組みは、重複したエラー処理を1ヶ所にまとめることができ、無駄を省いたり、間違いを防止することができる
- ガベージコレクション
- どこからも使われなくなったインスタンスの削除などを忘れると不要なインスタンスがどんどんメモリを圧迫してしまうメモリリークが起きてしまう。OOPではたくさん作ることができきるが故にインスタンスを自由に作成できるが、削除は慎重に行う必要がある。
- OOPではこうしたインスタンスを削除する処理を自動的に実行する仕組みがガベージコレクションと呼ばれるもの。
OOPを生かすも殺すも心がけ次第
- OOPは手段であって目的ではない。
- クラス・ポリモーフィズム・継承の仕組みを使用するだけで保守性や再利用性が向上する訳ではない。
- まずどうやったら保守、再利用がしやすくなるか考える。
- その次に基本3構造(順次進行・条件分岐・繰り返し)や共通サブルーチンを使用して実装することを考える。
- それでも物足りないとわかった際に、クラス・ポリモーフィズム・継承の出番がやってくる。
5.メモリの仕組みの理解はプログラマのたしなみ
プログラムがどう動くかを考える
コンパイラとインタプリタの2つの実行方式
- コンパイラ
- コンパイラ方式では、プログラムの書の命令を機械語に変換してから実行する。この変換作業のことをコンパイラと呼ぶ。
- メリット
- 機械語を直接読み込むため、実行効率が良い。
- デメリット
- 一度コンパイルを行いエラーがなくなるまで修正が必要なため、実行するのに手間がかかる。
- インタプリタ
- インタプリタ方式ででゃ、ソースコードにプログラマが書いた命令を順次解釈しながら実行していく。コンパイラは必要としない。もし文法が間違っていた場合は、実行時エラーとなる。
- メリット
- プログラムを書いただけですぐに実行できる。
- 異なるマシンやOSでも互換性を保てる。
- デメリット
- 実行速度が遅い。
中間コード方式
- ソースコードを特定の機械語に依存しない中間コードに変換する。
- その中間コード専用のインタプリタによって解釈して実行される。
- これによりコンパイラ・インタプリタのいいとこ取りができる。
- 異なるマシンでも同じプログラムを配布することが可能。
- 実行効率が良い。
CPUは複数のスレッドを掛け持ちで実行する
- スレッドは処理の実行単位。
- プロセスよりも小さな単位で、1つのプロセスの中に複数存在できる。
- 複数のスレッドを同時並行して動作できる環境をマルチスレッド環境と呼ぶ。
- マルチスレッドは基本的にOSの機能として提供されている。
静的領域、ヒープ領域、スタック領域で管理
- プログラムのメモリ領域は、静的領域、ヒープ領域、スタック領域の3つに分けて管理する。
- 静的領域
- アプリケーション開始時に確保する。グローバル変数など。
- ヒープ領域
- 動的に確保するためのメモリ。
- 開始時に一定領域を確保して、都度アプリケーションに割り当てる。不要になれば元に戻す。
- スタック領域
- スレッドに1ずつ用意されている。
- OOPでいうメソッド呼び出し制御のために使われるメモリ領域で、メソッドの引数、ローカル変数、戻り先などの情報を格納する。
- LIFO方式(後入れ先出し)
OOPで書いたプログラムは、メモリの使い方が従来とだいぶ異なる。
クラス情報は1クラスにつき1つだけロードされる
- メソッドに書かれたコード情報は1クラスに1つだけロードされる。
- クラス情報をメモリにロードするタイミングは2つある。
- 事前にすべてのクラス情報を一括してロードする方式。
- 必要な時点でメモリに逐次ロードする方式。
- クラス情報がロードされるのは静的領域(メソッドエリア)に相当する場所。
インスタンス生成のたびに、ヒープ領域が使われる
- 従来のプログラミング言語では、メモリリークが起こりやすい為、ヒープ領域を使わないのが常識だった。
- OOPで書いたプログラムは、有限のメモリ領域であるヒープ領域を大量に使って動く。
変数にはインスタンスの「ポインタ」が格納される
- インスタンスはヒープ領域に格納されるが、生成したインスタンスの変数にはヒープ領域に作られたインスタンスのポインタを格納する。
- ポインタは、「メモリ領域の場所を示す情報」いわいる参照。
- メモリ⇒土地
- ポインタ⇒その住所
- ヒープ領域にあるインスタンスそのものは変化しない
継承される情報の種類によってメモリ配置は異なる
- スーパークラスから継承したメソッドとインスタンス変数のメモリ配置は全く異なる。
- スーパークラスのインスタンス変数は、ヒープ領域にあるサブクラスのすべてのインスタンスにコピーして保持される。
- 孤立したインスタンスはガベージコレクタが処分する。
6.OOPがもたらしたソフトウェアとアイデアの再利用
OOPの優れた仕組みにより再利用が進んだ
再利用部品群とデザインパターンの発展の流れは以下の通り
- 最初にOOPを利用して再利用部品群を作る。
- 次に再利用部品群に共通して現れる設計のアイデアを抽出したデザインパターンが登場した。
- 最後は反対に再利用部品群を作るためにデザインパターンを利用するようになった。
-
クラスライブラリ
-
汎用的な機能をもつクラスをたくさん集めたもの。
-
OOPのクラスライブラリと従来の関数ライブラリの違いは、アプリケーションから呼び出すだけでなく
- ライブラリ中のクラスからインスタンスを作成して、メソッドと変数定義をまとめて利用する(クラスの利用)
- ライブラリから呼び出される側のロジックをアプリケーション固有の処理で置き換える(ポリモーフィズム)
- ライブラリ中のクラスに、メソッドや変数を追加定義して新しいクラスを作成する(継承)
これらの仕組みにより再利用できる機能の範囲が飛躍的に広がった。
-
フレームワークはアプリケーションの半完成品
- フレームワークの仕組みの特徴を表現するものとしてハリウッドの原則と呼ばれる言葉がある。
- これは、「必要な時にこちら(製作者)から連絡するから、(俳優側)から売り込みの電話はするな」という意味。
- すべての制御の流れはフレームワーク側で決めておき、アプリケーションの処理はポリモーフィズムを使って必要な時に呼び出すことをユーモアを交えて上記のように表現している。
独立性の高い部品を意味するコンポーネント
コンポーネントは「部品」や「成分」と訳される。
コンポーネントの一般的な定義
- OOPのクラスよりも粒度が大きい。
- ソースコード形式ではなく、バイナリ形式として提供される。
- コンポーネントの定義情報を含めて提供される。
- 機能の独立性が高く、内部の詳細を知らなくても利用できる。
デザインパターンは優れた設計のアイデア集
- デザインパターンは、機能拡張や再利用がしやすいソフトウェアを作るために先人が工夫したノウハウ集。
- 23種類のデザインパターンのGoFのデザインパターンと呼ばれるものがある。
7.汎用の整理術に化けたオブジェクト指向
-
コンピュータは現実世界の仕事の一部を肩代わりするだけ。
-
コンピュータもソフトウェアも、現実世界をそのまま表現する訳ではなく、一部の仕事を表現するだけ。
-
コンピュータに肩代わりさせる仕事の範囲を決める要件定義などの作業を上流工程と呼ぶ。
-
上流工程におちてオブジェクト指向は2つの基本的な仕組みを提供している。
-
集合論
「1つのクラスから実行時にたくさんのインスタンスを作る仕組み」は集合論における集合と要素に似ている為、クラスとインスタンスは集合論に応用されることになった。
-
役割分担
OOPにおいて「メッセージパッシング」はインスタンスを指定して、クラスにまとめられたメソッドを呼び出す仕組みで、その仕組みが上流工程の役割分担に応用された。
-
-
結果として、オブジェクト指向は①物事を整理する仕組みと②役割分担を表現する仕組みを兼ね備えた汎用の整理術となった。
-
オブジェクト指向には、抽象的な汎用の整理術と、具体的なプログラミング技術(OOP)の2つの側面がある。
-
この2つの意味をもつことで、「オブジェクト指向は現実世界をそのままプログラムに表現する技術」という解釈が広まってしまった。
8.UMLは形のないソフトウエアを見る道具(整理術 / OOP)
- UMLはUnifield Modeling Languageの略で、日本語では「統一モデリング言語」と訳される。
- そもそもはOOPにおけるプログラムの構造を表現するために考えられたが、オブジェクト指向が業務分析や要件定義といった上流工程に応用されたことにともなってその成果物としても利用されるようになった。
- UMLは国際的な標準化団体であるOMG(Object Management Group)という組織によって13種類のダイアグラムが規定されている。以下参考になったサイトです。
UMLの使い方は大きく3つ
-
OOPのプログラム構造や動作を表現する。
-
クラス図
クラスの定義情報とクラス間の関係を表現する。 -
シーケンス図
実行時のインスタンス間のメソッド呼び出しを時系列に表現する。 -
コミュニケーション図
実行時のインスタンス間んおメソッド呼び出しをインスタンスの関係中心に表現する。
-
クラス図
-
汎用の整理術としての成果物を表現する。
-
クラス図
集合論で分類整理した現実世界の物事の関係を表現する。 -
シーケンス図
役割分担された人や組織が強調して全体の仕事を達成する様子を時系列に表現する。 -
コミュニケーション図
役割分担された人や組織が強調して全体の仕事を達成する様子を構造中心に表現する。
-
クラス図
-
オブジェクト指向で表現できない情報を表現する。
-
ユースケース図
コンピュータに任せる仕事の範囲を表現する。 -
アクティビティ図
現実世界の仕事の流れを表現する。 -
ステートマシン図
外部からのイベントによる状態変化を表現する。
-
ユースケース図
9. 現実世界とソフトウェアのギャップを埋めるモデリング(整理術)
- 現実世界とソフトウェアが表現する世界には必ずギャップが存在する。
- 業務分析、要件定義、設計でそのギャップを埋める。
- ステップ1 業務分析
- 現実世界の仕事の進め方を整理する。
- ステップ2 要件定義
- コンピュータに任せる仕事の範囲を決める。
- ステップ3 設計
- ソフトウェアをどう作るかを決める。
アプリケーションによってモデリングの内容は変わる
-
ビジネスアプリケーション
企業などのビジネス活動で利用するシステム。販売管理システム、ECサイトなど。
データ構造においては、現実世界を反映する。(シームレス)
モデリング例(図書館貸出業務)
- 仕事の流れの表現にアクティビティ図を利用する。
- 図書館業務をユースケース図で表現する。
- 図書館システムの情報を概念モデルで表現する。
-
組み込みソフトウェア
電気製品や各種装置を司るソフトウェア。装置に組み込まれたCPU上で動作する。
-
スタンドアロンアプリケーション
個人のパソコンや携帯端末などで動くソフトウェア。電子メール、ブラウザなど。
10.擬人化して役割分担させるオブジェクト指向設計(OOPの拡張)
保守に強く、再利用しやすいソフトウェア構造の目標は次のようにまとめることができる。
- 重複を排除する。
- 部品の独立性を高める。
- 依存関係を循環させない。
設計の目標1:重複を排除する
- 機能の重複があれば、その分だけ規模が大きくなるため修正漏れが起きたり、保守が大変になる。
- 新規に追加する時だけでなく変更を加えるときも機能の重複が起きないように気を付ける。
設計の目標2:部品の独立性を高める
- 一般的に、複雑なものをわかりやすくするコツは「分割」すること。
- ソフトウェアにおいても分割することが重要だが、たんにサブシステムや部品に分割するだけでは不十分で、部品の「独立性が高いこと」が重要。
- 独立性を高めるための2つの考え方
-
凝集度
- 個々の部品の機能のまとまりの度合いを評価する尺度。
- この凝集度が「強い」ほど良い設計。
-
結合度
- 部品間の結びつき度合いを評価する尺度。
- 結合度は「弱い」ほど良い設計(ex クラス同士の結合は弱い方が良い)
-
凝集度
- OOPにおいては、クラスに定義する機能(メソッドや変数)の意味的なまとまりを強くし(凝集度)、クラスのやりとりを少なくすること(結合度)に相当する。
部品の独立性を高めるコツ
- 一言で表現できる名前をつける(クラス、パッケージ、メソッド等)
- クラスが外部に公開する情報を最小限にする。
- 小さく作る(ex 1つのメソッドの上限の目安は20~30行程度にとどめる)
設計の目標3: 依存関係を循環させない
- ソフトウエアに秩序を持ち込むためには、パッケージやクラスの依存関係を循環させないことが重要
- 依存関係とは、例えばある部品Aが別の部品Bを利用している関係のことで、さらに部品Bは部品C、部品Cは部品Aに依存...といった循環をさせないことが重要。
11.オブジェクト指向から生まれたアジャイル開発とTDD(OOP、整理術いずれにも関係しない)
- 開発プロセスは、ソフトウェア開発を円滑に進めることを目的とした作業項目・手順・成果物形式・メンバーの役割分担など体系的に定義したもの。
- 技術やノウハウだけでなくこの開発プロセスがソフトウェア開発を成功させるためにも重要。
ウォーターフォール型プロセス
- ウォーターフォール型プロセスは、要件定義・設計・プログラミング・テストの作業を1回ずつ実施する。
- 基本的に大きなコストがかかることを前提とした開発プロセス。
- 2つの問題
- 要求の問題
- 最初の段階でシステムに要求される機能をすべて定義しようとするが必ずしもうまくいかない。仕様変更が発生する
- 技術の問題
- 新しい技術や新製品の採用した際、問題に気づくのがテスト時など開発期間の後半になってしまうことが多い。
- 要求の問題
反復型開発プロセス
-
ウォーターフォール型プロセスの問題に対応するために考えられたのが、反復型開発プロセス。
-
反復型開発プロセスでは、要件定義・設計・プログラミング・テストまでの一連の作業を「繰り返し」「反復」を意味するイテレーションと呼ぶ。そして、このイテレーションを複数回実施して開発を進める。
-
XP
- XPは「エクストリームプログラミング」の略で形式的な作業手順や文書形式の成果物をほとんど定義しない特徴がある。
- その代わりに基本理念である「4つの価値」と「12のプラクティス(実践方法)」を 定義している。(4つだったり5つだったりと言われていますが、以下記事は参考になるかと思います)
-
アジャイル開発手法
-
XPと似たような考えを持つ軽量な反復型開発手法が沢山登場し、それらをひっくるめてアジャイル開発手法と名付けられた。
-
2001年に、これらの提唱者や推進者たちが共同でアジャイルソフトウェア開発宣言をまとめた。(以下参考記事)
https://qiita.com/karamage/items/9c98bf3f379f2b16ee47 -
アジャイル開発手法の代表的なプロセスとして、テスト駆動開発(TDD)・リファクタリング・継続的インテグレーション(CI)の3つがある。
-
12.オブジェクト指向をを使いこなそう
- 自分で実際に手を動かしてみることが重要。
- クラス、ポリモーフィズム、継承の仕組みをきちんと理解するには、Java等でプログラミングを行い、デバッガを使って動かしてみるのが一番。
- UMLについても、最初から全て覚えようとするよりも既存のプログラムを理解するために、クラス図とシーケンス図あたりから使い始めるのが良い。
- オブジェクト指向技術を利用する際に気を付けることは、オブジェクト指向の利用自体を目的にしてはいけないということ。
まとめ
- オブジェクト指向を目的にしてはいけない。⇒個人的に感じたのはアジャイル宣言12の原則は2001年に宣言されたものなので、現代に当てはめるとまた違った視点がでてくるかもしれない。
- アジャイルだから良い、ウォーターフォールだから悪いではなく用途に合わせて開発プロセスも選定することが重要。選定するにはまずそれぞれの特徴を理解する必要がある。オブジェクト指向≤=>関数型も然り。
参考
https://qiita.com/i-tanaka730/items/2188ef8ac9ac4d64c27b
https://qiita.com/taiteam/items/fc07fba2c92404384175
https://qiita.com/newta/items/636a47c40f6a1650e827
https://qiita.com/karamage/items/9c98bf3f379f2b16ee47
https://qiita.com/i-tanaka730/items/c63c6c22abd1477e0ba0