かまずにまるのみ。

文鳥とかビールとか

アジャイル設計と5つの原則

アジャイルソフトウェア開発の奥義 第2部「アジャイル設計」の自分用まとめ。

アジャイル設計

アジャイルな設計

「原則」「パターン」「プラクティス」を継続的に適用することで、読みやすく変更に強い状態を保つことができる設計。

悪い設計

第2部の中で「貧弱な設計の兆候」「腐敗するソフトウェアの兆候」として、以下の7つが挙げられている。

  1. 硬さ (設計変更が難しい)
  2. 脆さ (設計が壊れやすい)
  3. 移植性のなさ (再利用が難しい)
  4. 扱いにくさ(正しい設計をするのが困難なソフトウェア、面倒な開発環境)
  5. 不必要な複雑さ("後で必要になるかもしれない"と考えて先行実装したコード)
  6. 不必要な繰り返し (コピペ)
  7. 不透明さ (目的や意図がわかりにくい)

原則

システムに悪い設計の兆候が見られるとき、その原因がオブジェクト指向設計の原則に反していることだったりする。
ただし無条件で原則に従うと「不必要な複雑さ」を招くこともあるので注意。

  1. 単一責任の原則 (SRP : Single Responsibility Principle)
  2. オープン・クローズドの原則 (OCP : Open-Closed Principle)
  3. リスコフの置換原則 (LSP : Liskov Substitution Principle)
  4. 依存性逆転の原則 (DIP : Dependency Inversion Principle)
  5. インタフェース分離の原則 (ISP : Interface Segregation Principle)

f:id:tdakak:20130629230122p:plain:w350



単一責任の原則 (SRP : Single Responsibility Principle)

ざっくり

クラスを変更する理由は1つ以上存在してはならない。

クラスの役割

この原則では「役割(責任)=変更理由」と定義している。
クラスの変更理由が2つ以上ある場合、クラスの役割も2つ以上あると考えることができる。基本的に、1つのクラスに割り当てられる役割は1つになるようにする。

f:id:tdakak:20130701170931p:plain:w350


オープン・クローズドの原則 (OCP : Open-Closed Principle)

ざっくり

  • 拡張に対して開いていなければならない(Open)
  • 修正に対して閉じていなければならない(Closed)

拡張に対して開かれている

仕様変更があった場合、振る舞いを追加することで変更に対処できる。

修正に対して閉じている

振る舞いを追加しても既存のコードは影響を受けない。


リスコフの置換原則 (LSP : Liskov Substitution Principle)

ざっくり

派生型はその基本型と置換可能でなければならない。

要約読んでもよくわからないので詳しく

ここで望まれるのは、次の述べるような置換可能な性質である:S型のオブジェクトo1の各々に、対応するT型のオブジェクトo2が1つ存在し、Tを使って定義されたプログラムPに対してo2の代わりにo1を使ってもPの振る舞いが変わらない場合、SはTの派生型であると言える。

ねこととりと赤いもの

上記の引用の例をねこととりと赤いものに例えてみる。

  • ねこがS型のオブジェクトo1
  • ねこにだっこされている赤いものがT型のオブジェクトo2
  • とりがTを使って定義されたプログラムP

ねこ(o1)は赤いもの(o2)を含む。
とり(プログラムP)は赤いものを使う。
とりに対して赤いものの代わりにねこを使ってもとりの振る舞いが変わらない場合、SはTの派生型。これはリスコフの置換原則に従っている

とりにねこを渡してとりの振る舞いが変わってしまう場合は、リスコフの置換原則に従っていない。
LSP に違反すると OCP にも違反してしまう。

f:id:tdakak:20130703153744p:plain:w400


依存関係逆転の法則 (DIP : Dependency Inversion Principle)

ざっくり

  • 上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである。
  • 「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべきである。

上位モジュールと下位モジュール

  • 上位モジュール
    • アプリケーションの方針に基づく重要な判断
    • ビジネスモデル
    • アプリケーションの存在理由を決定づける
  • 下位モジュール
    • 上位モジュールの実装の詳細を担当

インタフェースの所有権は誰に

上位モジュールが下位モジュールに依存している場合、問題を抱えてしまう。

  • 下位モジュールの変更が上位モジュールに影響を与えてしまう
  • 上位モジュールの再利用が難しい

下位モジュールが宣言したインタフェースに上位モジュールが従う形は、インタフェースの所有権が下位モジュールにある状態といえる。

f:id:tdakak:20130703173354p:plain:w300

逆に上位モジュールがインタフェースを宣言し、下位モジュールはそれに従う形にすることで、インタフェースの所有権を上位モジュールに持たせることができる。

f:id:tdakak:20130703173412p:plain:w300

抽象に依存する

具体的なクラスに依存せず、プログラム内の関係は抽象クラスかインタフェースで終わるようにする。

  • 具体的なクラスへのポインタや参照を保持する変数はだめ
  • 具体的なクラスから派生するクラスはだめ
  • 基本クラスで実装されているメソッドは上書きしちゃだめ

 

インタフェース分離の原則 (ISP : Interface Segregation Principle)

ざっくり

クライアントに、クライアントが利用しないメソッドへの依存を強制してはならない。
利用していないメソッドに依存してしまうと、そのメソッドが変更されたときに本来無関係であるはずのクライアントまで影響を受けてしまう場合がある。

インタフェースを分離する

それぞれのクライアントが実際に利用するメソッドだけに依存するようにする。
関連性を持ったインタフェースはグループ化し、クライアントごとに特化したインタフェースを用意。これによりクライアントは、自分が利用しないメソッドとの依存関係を断ち切ることができる。


補足

無条件で原則に従わない

たとえば「単一責任の原則」では1つのクラスが1つの役割を持つことが求められているが、2つの役割が必ず同時に変更されるようなケースで無理に分離させると不必要な複雑さを生んでしまうことがある。
DB 設計でガッチガチに正規化した結果、クライアント側の手順が複雑になってしまうケースなどに通じるものがあるように思う。何事も柔軟に。

抽象重要

上記と同様、抽象もむやみやたらに使えばよいというものではない。
以下はオープン・クローズドの原則の章から引用。

開発者が担当する部分で最も頻繁に変更されるプログラム部分にだけに絞って抽象を適用するように努めるべきである。早まった「抽象」をしないことも、「抽象」を使うのと同等に重要なことなのだ。

早まった「抽象」をしないことと「抽象」を使うことは同じように大事

契約による設計(DBC : Design By Contract)

事前条件と事後条件

これから設計するクラスに関する契約を明文化することでリスコフの置換原則(LSP)を適用できるようになる。
この「契約による設計」では、各々のメソッドについての事前条件と事後条件を前もって取り決めておく。

  • 事前条件…メソッドを実行する前に成立していなければならない条件
  • 事後条件…メソッドが終了したときに成立していなければならない条件

派生型の事前条件と事後条件のルール

派生型の条件には以下のようなルールがある。

  • 派生型の事前条件
    • 基本型のそれよりも強くしてはならない
    • 基本型が許可することをすべて許可しなくてはならない
  • 派生型の事後条件
    • 基本型の事後条件をすべて含んでいなければならない
    • 基本型に課せられている事後条件を無視した振る舞いを許可しない

条件の明文化

これらの契約内容はコメントやユニットテストなどで記述しておくとよい。


思ったこと

上記の原則に沿うような設計を心がけることで、オブジェクト指向らしい設計に近づく気がする。
クラス設計時のポイントを押さえておくみたいな感じ。

原則やデザインパターンの章を読んでてたびたび思うのが、以前関わっていたプロダクトやライブラリのコードについて「あーあの設計はそういう意図だったのかー」とか「だからあの実装になっていたのかー」ってこと。
自分が感覚頼りで設計していたことを痛感させられたり、もやもやしてたところが明確化されてすっきりしたりでおもしろい。

アジャイルソフトウェア開発の奥義では、上記の原則についてそれぞれ詳しい説明と設計の例が掲載されている。