抽象化が苦手

DBに関する本とかネットの記事読むと、多対多のテーブルとか出てくるじゃないですか。よくあるのが授業管理システムとかで、

・授業テーブル
授業ID:1
授業名:経済学
教授ID:100

・学生テーブル
学生ID:1
名前:佐藤

・受講テーブル
学生ID
授業ID

授業は複数の学生が受けていて学生は複数の授業を取っている。

どの学生がどの授業をとっているかを中間テーブルを挟むことで管理するみたいなことは思い浮かぶんだけど、「受講」っていう言葉をパッと思いつく人ってすごいなぁと思う。これは簡単な例なので当たり前じゃんって思うんだけど、実際の業務だともっと専門性があって複雑なので、そういう場合にここでいう「受講」にあたる言葉が閃かないんだよなぁって気がしてる。

頭のいい人って物事と物事の関係性を捉えたり、共通している部分もしくは特異性のある部分を見抜くのが速いし、利害関係調整したり、問題がある場合に双方の落とし所を見つけるのが大抵得意なんだけど、あれはなんなんだろうか。生まれ持ってのものなのか、それとも経験なのか。

『Head First オブジェクト指向設計』第7章

第7章 アーキテクチャ

アーキテクチャとは、設計の構造であり、アプリケーション内において重要である部分とそれらの重要部分間での関係に着目する。

アーキテクチャとは、システムの組織化された構造である。各部分への分割、部分間の接続、相互作用メカニズム、システムの設計時に使用される原則と決定が含まれる。

ユースケースとFeatureを作成したがどこから開始すべきか

  • 一番重要なFeatureを選択する
    システムにとって最も重要であると思える事柄から作業を開始するようにすれば良いスタートを切れる。
    Featureはシステムが行わなければいけない機能を表現している。
    アプリケーションにおいて、本当に重要である事柄はアーキテクチャ的に意味のある部分なので、最初に着目すべきである。

  • 重要なFeatureかどうかの判断
    そのFeatureは システムの本質(最も基本的なレベルでシステム内に存在するもの) の一部であるか。
    このFeatureを実装しなくても、システムは必要とする処理を行えるかどうかを問いかける。
    そのFeatureが存在しないシステムを想像できなければ、システムの中核である可能性が高い。

ゲームシステムワーク Feature一覧
1. 様々な地形をサポートする。
2. 様々な時間軸をサポートする。
3. ゲームごとに異なる複数の種類の軍隊やユニットをサポートする。
4. 作戦や戦闘を追加するための、アドオンモジュールをサポートする。
5. 正方形のマスで構成される盤を1枚提供し、各マスには地形が存在する。
6. 誰のターンであるかを制御する。
7. 基本的な移動を管理する。

リスク軽減

主要なFeature 主要なリスク
ゲームの盤 - システムの本質 システムの中核となるFeatureがかけてしまうと顧客がシステムに満足しないというリスク。
ゲーム固有のユニット - 本質とどのような意味であるか Featureの意味がわからないので手戻りや修正が発生し納期を守れなくなるリスク。
移動の管理 - その意味と実行方法 実行方法が明確ではないので、実現できないか長い時間を要するかもしれないというリスク。

主要なFeatureのどこから手を付けるべきか?
これらのFeatureがアーキテクチャ的に意味がある理由はすべてプロジェクトにリスクをもたらすから。

ここで重要なのはどのFeatureに最初に取り組むべきかではなくリスクを軽減させること。詳細が不明な現在の段階ではリスクの軽減に役立たないFeatureに注意をそらしてはいけない。

シナリオ

  • [x] システムの本質であり一番大きいリスクを持つゲームの盤から着手する。

シナリオを作成しBoardモジュールの基本的な部分のみを作成する。
シナリオとは、ユースケースにおいて最初から最後までステップが揃っているパスのこと。

Boardのシナリオ
ゲームデザイナーが高さを幅を指定して盤を作成する。
プレイヤー2が戦車を(4,5)に移動する。
プレイヤー2が軍を(4,5)に移動する。
プレイヤー1が砲兵を(4,5)に移動する。
ゲームが(4,5)にあるユニットを要求する。
プレイヤー1がプレイヤー2と戦闘する。
ゲームが(4,5)の地形を要求する。
  • なぜユースケースを作成しないのか?
    主要Featureの概要が明確になり大きなリスクに対処できたら各モジュールに詳細を追加していく。

次にどのFeatureに取り組むべきか

BoardとUnitには関係があり、ゲーム固有のユニットは主要なFeatureなので、Unitモジュールに取り組む。 - [x] ゲーム固有のユニット - 本質とどのような意味であるか

ゲーム固有にユニットには、属性と能力が異なる様々な種類のユニットが存在する。ゲームごとに異なるプロパティをユニットが保持し、複数のデータ型のプロパティをサポートする必要がある。

共通性と変動性を見つける

  • 共通性
    共通なのはユニットには種類があり、プロパティの集合を保持すること。

  • 解決策1

  • 解決策2

第5章の楽器クラスとよく似ている。解決策1の場合、ユニットの種類ごとにサブクラスが必要になる。 解決策2の場合、Unitクラスが1つあれば異なる種類のユニットをいくつでもサポートできる。

  • よい設計は常にリスクを軽減させる。
    Unitクラスの設計がよければ、Unitクラスを劇的に変更しても他のコードに影響と与えることがなくなる。 プロジェクトの途中・終了間近になって、Unitクラスが変更されても他のクラスのコードを変更する心配をしなくてよい。

Featureの意味がわからないとき

  • [ ] 移動の管理 - その意味と実行方法 Featureの意味が定かでないとき、行えることの1つは顧客に尋ねること。

  • 顧客に尋ねる

  • 共通性分析
  • 実装計画

  • 顧客に質問するのは本当に良いことなのでしょうか?横道に逸れてしまう事態にならないでしょうか?

    作成しているのは顧客のシステムなので、顧客に質問するのは通常よいことです。しかし、質問する側が取り組むべきことをきちんと把握していないと、顧客の言葉に惑わされ、間違ったことに向かってしまうこともたしかにあります。目標が何であるかを明確にするために会話を行い、具体的な内容に対して質問すれば混乱や逸脱を招くような事柄はうまく避けることができるでしょう。

  • 移動の管理とは・・・
    ユニットごとに移動プロパティがあり、移動可能なマスの数が指定される。ゲームの地形を確認して、その移動が行えるかを判定する。

  • 共通性分析

共通するもの 変動するもの
移動が可能であるかのチェックが移動の前に行われる。 移動が可能であるかを判定するアルゴリズムはゲームごとに異なる
ユニットのプロパティが移動距離の計算に使用される。 使用されるプロパティの種類と数はゲームごとに異なる。
ユニット以外の要因が移動に影響する。 移動に影響する要因はゲームごとに異なる。

「ゲームごとに異なる」というフレーズが何回も登場することが明らかになる。
→移動の処理はフレームワークの処理から外し、ゲームデザイナーに自分で処理させるほうが有益であると判断した。

まとめ

  • プロジェクトにとって、システム内の最重要Featureがアーキテクチャ的に意味を持つ。
  • システムの本質であるFeature、意味が曖昧なFeature、実装方法が明確ではないFeatureに最初に着目する。
  • プロジェクトのアーキテクチャ段階で行うべきことはすべてプロジェクトが失敗するリスクを減少させるべきである。
  • Featureの内容がわからなければ、顧客に尋ね、その答えを一般化することで、Featureの正しい理解につなげるべきである。
  • 柔軟なソフトウェアを構築するために、共通性分析を使用する。

『Head First オブジェクト指向設計』第6章

第6章 本当に大きな問題の解決

今まではせいぜい10個程度のクラスのアプリケーションを開発してきた。より大規模なアプリケーションの開発はどうやって行うのか?
→大きな問題も小さな問題と同じように解決する。

  1. 顧客が必要とする処理をソフトウェアが実行するようにする。
  2. オブジェクト指向の基本原則を適用し柔軟性を高める。
  3. 保守と再利用が可能な設計を追求する。

ソフトウェア開発はこれのイテレート。

大きな問題は小さな問題に分割して個別に対処する。本当に大きな問題を解決するために多くの小さな問題を解決する。
この章では、大きな問題を小さな問題に分割して解決していき、システムの全体像を把握する。

要件とFeature

システムがどのようなものであるのか(共通性)、またどのようなものでないのか(変動性)を把握する。 Featureと要件の意味や用語の用い方は人によって異なる。ここではFeatureは多くの要件が集まった1つの大きな機能を意味するものとする。

  • 顧客からFeatureを得て、そのFeatureの実装に必要な要件を把握する。
顧客からのFeature 要件
様々な地形をサポートする マスは地形と関連付けられる。
ゲームデザイナーは独自に地形を作成することができる。
各地形、ユニットの移動に影響する特質がある。
  • Featureまたは要件の一覧を用いて、システムが実行する必要のある大きなことを把握する。
ゲームシステムワーク Feature一覧
1. 様々な地形をサポートする。
2. 様々な時間軸をサポートする。
3. ゲームごとに異なる複数の種類の軍隊やユニットをサポートする。
4. 作戦や戦闘を追加するための、アドオンモジュールをサポートする。
5. 正方形のマスで構成される盤を1枚提供し、各マスには地形が存在する。
6. 誰のターンであるかを制御する。
7. 基本的な移動を管理する。

ユースケースユースケース

ユースケースは詳細であり全体像を提示することはない。システムに必要とされる処理の詳細が決まっていない状態では、ユースケースを作成することで全体像を見失う危険がある。
ここでは詳細には立ち入らずシステムがどのようなものであるのか全体像を把握するためにユースケース図を作成する
Featureをユースケース図に当てはめて、ユースケースがFeatureを網羅することを確認する。

アクターは人である必要はない。またアクターはシステムを使うのであってシステムの一部ではない。

参考:超絶分かるユースケース図-全知識と書き方5ステップ

『Head First オブジェクト指向設計』第5章 後半

第5章 後半 良い設計=柔軟なソフトウェア

柔軟ではないコードに対処する

アプリケーションの変更時に問題が発生するのは、ソフトウェアの柔軟性が足りないから。「凝集度」を高めて結合の問題を解決する。

・凝集度と結合度
設計におけるオブジェクトの責務分配に有効なものさし -凝集度と結合度-

問題点

現在の設計

楽器を追加するたびに新しいクラスを作成しなければいけない。

Instrumentクラス(楽器)は概念であり抽象クラスにするべきであるため楽器の種類ごとにサブクラスが必要になる。 またそれぞれの楽器はプロパティをInstrumentSpec(楽器仕様)のサブクラスで表すため、楽器ごとに~Specクラスが必要になる。 これらははじめは良い設計のように思えたが、楽器の種類を追加するたびに新しい仕様クラスを追加するため、あまり仕事をしない大量のクラスが作られることになる。その結果変更が困難になる。

楽器ごとに振る舞いは変わらないのでサブクラスにする必要はなかった。

設計上の間違った決断を正す

  • 自分の設計における間違いを消し去ることは難しい
    楽器の種類ごとにInstrumentのサブクラスを作ることに意味はなかったが、そのときには意味があるように思えた。
    実際に動作しているものを変更することは難しいが、設計が改善されれば長期的には時間が節約される。

設計はイテレーティブであり、他のプログラマーから受け継いだ設計だけでなく、自分で行った設計も、必要に応じて変更できなければいけません。

  • 変更点
    • 楽器固有のサブクラスの削除
    • 楽器が追加されるたびに新しいプロパティをInstrumentSpecに追加しなくてすむように、楽器のプロパティを動的に追加できるように変更。

設計の見直しによって
新しい楽器を追加するたびにサブクラスを作る必要はなく、「製造年」でも検索できるようにしたいと言われても、コードを変更する必要はなくなった。

どうすれば設計が上達するのか

ソフトウェア設計の上達には、実際にソフトウェアを作成することが一番です。GuitarやMandolinクラスを追加するなどの、間違った道に何度も迷い込まないと、行うべき正しいことには気づきませんでした。たいていのよい設計は、悪い設計を経て誕生します。最初から正しい人はほとんどいません。したがって、意味のあることをひたすら行い、オブジェクト指向の原則やパターンを適用して、今まで行ったことを改善できるか確認していくしかないのです。
『Head First オブジェクト指向設計』p255

凝集度

凝集度は、単一のモジュール、クラス、オブジェクトの要素間における接続の程度を測定する。ソフトウェアの凝集度が高ければ、アプリケーション内の各クラスの責務が明確に定義され、関連付けられている。そして、各クラスは特定された密接に関連するアクションを実行する。

  • 「高凝集のクラスは1つのことをとてもよく行い、他のことを行おうとはしない。」
    • 高凝集のクラスは特定の作業にだけ集中する。
    • すべてのメソッドがクラスの名称と関連しているか。場違いに思えるメソッドがあれば、他のクラスに移したほうがよい。
    • 高凝集・低結合は、オブジェクトが相互に依存していないため拡張や分割、再利用が容易なソフトウェアにつながる。

良い設計の目標は、高凝集度・疎結合なソフトウェア

  • よい設計のソフトウェアは変更と拡張が容易である。
  • カプセル化や継承などのオブジェクト指向の基本原則を用いて、ソフトウェアの柔軟性を高める。
  • 設計が柔軟でなければ、変更せよ。変更しなければいけないのが自分の設計であっても、悪い設計をそのままにしておいてはいけない。
  • クラスが高凝集であるようにする。どのクラスも1つのことを本当によく行うことに特化させる。
  • ソフトウェア設計のライフサイクル全体を通して、凝集度を高めることに常に務める。