without meaning

組み込み系SIerの技術メモ。

パッケージ設計の原則

パッケージ内の凝集度に関する原則

再利用・リリース等価の原則(REP)

再利用の単位とリリースの単位は等価になる。

ライブラリを提供している作者に対して、使用者が求めること

  • 保守の保証。ライブラリのテストはしたくない。
  • 古いバージョンのサポートも対応してほしい

ライブラリは、それを再利用する人の視点に立って構造化する必要がある。

  • 1つのパッケージ内に入れるクラスは、同じ対象者によって再利用されるものに統一したい。
    • コンテナライブラリと、会計処理フレームワークが同一のパッケージにあっても、大体どちらか一つしか使わないよね。
  • パッケージ内のクラスは全て再利用可能なものか、全て再利用できないものかのどちらかに統一すべき。
    • 再利用可能なクラスのパッケージに、再利用できないクラスが存在しているとノイズでしかない。

全再利用の原則(CRP)

パッケージに含まれるクラスはすべて一緒に再利用される。つまりパッケージに含まれるいずれかのクラスを再利用するということは、その他のクラスも全て再利用することを意味する。

  • 互いに強い関連性を持たないクラスを、同じパッケージにまとめるべきではない。
  • パッケージを採用するときは、そこに含まれるすべてのクラスに依存することを確認する。
  • また、自分でパッケージを作るときも、あるパッケージに1つにまとめるクラスは互いに分離できない関係になるように、また、ユーザーがパッケージから抵抗されるすべてのクラスに依存しなければならないように設計する。

閉鎖性共通の原則(CCP)

パッケージに含まれるクラスは、皆同じ種類の変更に対して閉じているべきである。パッケージに影響する変更はパッケージ内のすべてのクラスに影響を及ぼすが、他のパッケージには影響しない。

  • 単一責務の原則のパッケージ版。パッケージも変更の理由を複数持っていてはいけない。
  • 変更箇所が1 つのパッケージにしておけば、再リリースは変更されたパッケージで済む。
  • 開放閉鎖の原則と密接な関係がある。同じ理由で修正されるクラスは一つのパッケージにしておく。そうすることで、ソフトウェアの再評価、再リリースに掛かる負担を最小限に抑えることができる。

パッケージ内の原則結論

  • パッケージ内には、極力同じ理由で修正されるクラスを一つのパッケージにしておくこと
  • パッケージは再利用予定のクラス一覧と、再利用予定が無いクラス一覧で分割する。

パッケージ間の結合度に関する原則

非循環依存関係の原則(ADP)

パッケージ依存グラフに循環を持ち込んではならない

  • 中規模以上のプロジェクトになると、規約を守らない開発チームの場合、誰もが、誰かが行った最後の変更を使ってコードが動くように変更を重ねてしまう。そのため、安定したバージョンのプロジェクトをビルドすることが出来ない。

これに対する対処法が「ウィークリービルド」と「非循環依存関係の原則」

  • ウィークリービルド
    • 週に一度結合ビルドする日を設けて、それ以外の日は意識せず開発を行う。
    • この作業の仕方では、規模が大きくなるにつれてどうしても結合する時間が大きくなっていく。
  • 非循環依存関係の原則
    • 開発環境をリリース可能なパッケージに分割する。開発者たちがそれぞれを独立して開発できるようにする。
    • 注意点:パッケージを循環させない。相互依存させない。
    • 循環してしまってると:
      • 依存関係逆転の原則を適用して、上位モジュールは下位モジュールが用意するインタフェースを使用する。下位モジュールは、これまで上位モジュール部品を使用してしまっていた箇所をインタフェース化により、実体を持つことがなくなるため上位モジュールへの依存がなくなる。
      • 且つ、必要であれば、上位モジュール・下位モジュールが両方とも依存するようなパッケージを新たに追加して、両方が依存するクラスをこの新しいパッケージに移行する。
    • パッケージ構造はトップダウン設計で行うのは不可能。クラスの設計もできていないのに、パッケージ設計は難しい。この段階では、何に対して共通な閉鎖性をもたせるのかわからないし、再利用する要素についても気づかない。

結論

  • 依存関係逆転の原則適用を意識する。
  • パッケージは開発初期から必要以上に分割せず、再利用したい要素がわかってきたり、どの部分のクラスをパッケージ化するかわかってきた時に分割する。

安定依存の原則(SDP)

安定する方向に依存せよ。

変更すること意識して作られたパッケージが、変更が難しいパッケージに依存されるような立場になってはならない。

変更することを意識して作られたはずのパッケージまで変更が難しくなるため。

  • 安定性は変更の回数とは直接無関係。
  • 安定しているとは容易に動かないことである。
  • パッケージ全てが安定している必要はない。変更することができなくなってしまうため。
    • 不安定なパッケージと安定したパッケージが混在しているのが理想的なパッケージ構造
    • 不安定なパッケージは上位レベルのパッケージにする。下位レベルのパッケージはユーティリティ的な汎用クラスを置く。
    • 上位レベルのパッケージが安定してしまうと、設計が固まってしまう=柔軟性がなくなり、改造しにくい状態になる。

結論

  • 依存関係逆転の原則を適用する
  • 安定性を保ちつつ、変更に耐える柔軟性を持たせるには開放閉鎖の原則を適用する。
    • 修正を加えずに拡張することができる状態を作る。=インタフェースを用意する。

安定度・抽象度等価の原則(SAP)

パッケージの抽象度と安定度は同程度でなければならない。

  • パッケージを安定させる場合、パッケージはインタフェースで構成されるべきである。
    • 拡張できるようにするため
  • 不安定なパッケージに具体的なコードを記載する。

パッケージ間の原則結論

  • 安定度・抽象度等価の原則と安定依存の原則をまとめると、パッケージ版の依存関係逆転の原則となる。
    • 安定依存の原則は依存関係が安定する方向に依存することを主張
    • 安定度・抽象度等価の原則は安定するということは抽象化されていることを意味する。
  • 安定性と抽象度は測定する事ができる。

安定性

I = Ce / (Ca + Ce)

I:不安定度(0〜1の値を取る。0に近いほど安定したパッケージ。1に近いほど不安定なパッケージとなる。)

Ca:内側に向かう結合度(このパッケージの外にあり、このパッケージの中にあるクラスに依存するクラスの数)

Ce:外側に向かう結合度(このパッケージの中にあり、このパッケージの外にあるクラスに依存するクラスの数)

抽象度

A = Na / Nc

A:抽象度(0〜1の値を取る。0はパッケージが抽象クラスを全く持たないことを意味する。1はパッケージが抽象クラスしか持たないことを意味する)

Na:パッケージ中の抽象クラスの数。抽象クラスとは、最低一つの純粋インタフェースを持ち、インスタンス化出来ないクラスのこと。

Nc:パッケージ中のクラスの数

インタフェース分離の原則(ISP)

インタフェース分離の原則とは

  • クライアントに、クライアントが利用しないメソッドへの依存を強制してはならない。

利用しないメソッドに依存してしまうと、クライアントはそういったメソッドの変更の影響を受けやすくなってしまう。=無駄にテストする必要が出てくる。

分離方法

  • 委譲を使って分離する方法
  • インタフェースを多重継承して分離する方法

基本は委譲を使って分離したほうが良いかな。

単一形式(Monad)よりも複数形式(Polyad)

複数形式の場合、同じインスタンスを複数回渡す事があるかもしれないが、

それでも、不必要なインタフェースを渡してしまう可能性がある単一形式よりも範囲を制限できる複数形式の方が良い。

InterfaceSoup アンチパターン

  • インタフェースを集約(複数継承)して、インターフェイスを作成しても、なんの意味もないのでやめる

結論

単一責務の原則に似ているが、インタフェースを太くしないということが重要。
クライアントごとに特化したインタフェースを用意する。
委譲を使用する場合と複数継承使用する場合の判断がまだ曖昧だが、
基本は委譲を使用して、親子関係にすべきと判断したものを多重継承にしたら良いと思う。

依存性反転の原則(DIP)

依存性反転の原則とは

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

上位モジュールに依存しない場合のメリットとして、

パターン

  • Poor Man's Dependency Injection
    • アプリケーションのエントリポイントに近い場所で、具象クラスをまとめて注入する(合成ルート)
      • コードが読みやすい=依存関係がわかりやすい
      • インターフェースが入れると具象クラス入れる作業が単純作業で多くなる。
    • コンストラクタ注入
      • 多々使用する依存関係の場合はコンストラクタで注入する
    • メソッド注入
      • 一部メソッドでしか使用しない場合は、メソッドで注入する
    • プロパティ注入
      • 実行時にプロパティ変更したい場合はプロパティで注入する
  • IoCコンテナ(制御の反転)
    • DIコンテナで依存関係のクラスの認識を自動で行う。
      • Registerでリストに具象クラス追加
      • Resolveでリスト内の具象クラスをインターフェースの継承先として自動で判別させる。
      • Releaseでインスタンス開放
      • Disposeはアプリケーション終了時に呼ばれる
  • 宣言型の登録
    • 実体をXMLに記載して、コードでXML読みこむ。そうすることで、実行時に読む実体を切り替えることができる
    • ただし、記載がすごく多くなってしまう。
    • また、タイポなどは実行時までわからない
  • Service Locatorアンチパターン
  • Illegitimate Injectionアンチパターン
    • コンストラクタの引数で、インターフェースに実体注入できるようになっていても、引数なしのコンストラクタで、デフォルト設定用の実体クラス生成している場合がある。それをしてしまうと、結局依存してしまってることになるので全く意味がない。
  • 規約手法
    • DIコンテナを使って、明示的にインスタンス名を記載せずに完全に自動で依存関係を判断させる方法
        • binフォルダに格納されているアセンブリに含まれるクラスを全て登録
        • それらのクラスを、クラスの名前とマッチするインターフェースにマッピング

結論

結局は、他の原則で作成したインターフェースの実体をどこで作るかという話。

簡単なソフトウェアなら、「PoorMan'sDI」を使用し、複雑でインスタンスが増大しているソフトウェアなら「規約」

を使用すれば良い。

参照

アジャイルソフトウェア開発の奥義
C#実践開発手法

リスコフの置換原則(LSP)

リスコフの置換原則とは

- 派生クラスは、その基本クラスと置き換え可能でなければならない。継承関係を作成するためのガイドラインの集まり。
- でないとクライアントは、RTTI(キャスト)と条件分岐を使用して、その派生クラスのみ通る処理を用意しないといけないが、それは開放閉鎖の原則に違反することが多い。
- こちらも、インターフェース・ジェネリックを使用して、原則を守る。

経験則と約束事

- 基本クラスと派生クラスで置き換え可能にするため、派生クラスで事前条件の追加・事後条件の緩和を行わない。
    - 事前条件とは入力値チェック、事後条件とは出力値チェック
- 不変条件は維持しなければならない。
    - 基底クラスのコンストラクタ以降、値が読み取り専用のメンバ変数の場合、派生クラスでも引き継ぐ必要がある。
- 派生型の戻り値の型には共変性がなければならない
    - 共変性:戻り値の型がクラス型の参照かポインタで、かつis-a関係にあるものは、戻り値が異なっていてもオーバーライド可能
- 派生型のメソッドの引数には反変性がなければならない。
    - 反変性:引数の型がクラス型の参照かポインタで、かつis-a関係にあるものは、引数が異なっていてもオーバーライド可能
  • 基本クラスで出来ていたことが、派生クラスで出来ないようになっていてはいけない
  • 派生クラスで例外処理を追加してはいけない。(基本クラスよりも制約を増やしてはいけない)

結論

  • 派生クラスで公開メソッドを追加しない。クライアント側で呼ばれると、基本クラスと置き換えできなくなるため。
  • 派生クラスで例外処理を追加しない。基本クラスでは許可された動きが派生クラスで禁止されてしまうことになるため。 とりあえず、上記2点を意識すれば良いと思われる。

参考:
アジャイルソフトウェア開発の奥義

C#実践開発手法

開放/閉鎖の原則(OCP)

開放/閉鎖の原則とは

  • 拡張に対して開かれている
  • 修正に対して閉じている
  • ★既存のコードはできる限り触らず、クラスのメンバ変数やインターフェイスの派生クラス追加などで対応できるように設計すること。
  • 閉じたモジュールであっても、閉じることのできない変更が必ずある。つまり、全てのケースに適用できる自然なモデルなど存在しない。
  • そのために、「先を見越した構造」と「自然な構造」を検討する必要がある。
    • この機能は今後、修正の余地がないから閉じている必要がない。
    • この機能はコア機能で、今後とも機能追加を行いたいから、修正に対して閉じていたほうが良い。など。。
  • また、抽象化自体がソフトウェアの可読性を下げる可能性もあるため、やりすぎに注意

結論

結論と言いつつ、書けない。。 ドメイン知識を理解して、どこを修正しやすくするべきか判断して、
インタフェース・ジェネリックを使用すれば良いと思う。。

例えば、リスト内の要素別の処理も、リスト数分のforループを行い、リスト内の要素をSwitch文で一々判断するという仕組みであれば、
逐次、新しい要素が追加されるたびに、Switch文の条件を追加する必要があるが、
インタフェース 型をforeachで回すような処理にしておけば、逐次Switch文の条件を追加する必要がなくなる。

参考:
アジャイルソフトウェア開発の奥義

C#実践開発手法

単一責任の原則(SRP)

単一責務の原則とは


  • 「クラスを変更する理由は一つ以上存在してはならない」という原則
    • 役割(責任) = 変更理由

適用方法


例:メソッドに「データの読取」「解析」「検証」「ログの出力」「データベースへの挿入」という5つの責務が存在する場合

①明瞭性を高めるため、1責務1メソッドに分割する。
 明瞭性を高めると、コードの読みやすさが向上する。
 この場合、3メソッド(データの読取、解析(解析+検証)、データベースの挿入)に分割される。

②抽象化を高めるため、責務にインターフェイスを適用する。
 抽象化を高めることで、ソフトウェア変更の要求に対応しやすくなる。
 この場合、データの読取、処理、データの格納という大きなインターフェイスに分割する。
 (データの読取が、ストリームではなく、Webデータから受取に変わっても対応できるよう)
 (入力データのフォーマットが変わり、新しいフィールドが追加されても対応できるよう)

シーケンスを流すクラスにコンストラクタ経由でインタフェースを継承したインスタンスを渡してやると、クラスの付替えが可能になる。

注意


  • 仮に2つの役割が必ず同時に変更されるようなケースではわざわざ分離する必要はない。
    • 設計が不必要に複雑になってしまうため。
  • ビジネスルールと、データベースなど永続性のあるシステムを包含してはいけない。ビジネスルールは頻繁に変化するし、永続性のあるシステムはビジネスルールの場合と全く違う理由で変化する場合があるため。

結論

  • 明瞭性を高めるため、1責務1メソッドに分割すること
    • 明瞭性の高いコードの書き方はリーダブルコード参照
  • 抽象化を高めるため、責務毎にインタフェースを分割すること とりあえずは上記を意識すれば良いと思われる。 クラスの分割については、実践UMLのGRASPパターンに委ねる。。

参考:
アジャイルソフトウェア開発の奥義

C#実践開発手法

ソフトウェア品質から見るオブジェクト指向の必要性

ソフトウェアの仕様策定・設計を行う際に、ソフトウェアの品質をどう担保するか検討する必要がある。

その際、なぜオブジェクト指向デザインパターンを意識してコードを記述すべきかを自己の認識を下記に示す。

まず、ISO/IEC21050※で規定されたソフトウエア製品の品質モデルを下記に示す。 ※ISO/IEC9126-1が元々規定していたものを改良した品質モデル。

f:id:wonderword22:20191116175357p:plain
参考:システム/ソフトウェア製品の品質要求定義と品質評価のための メトリクスに関する調査報告書

それぞれの内容は下記の通り。

f:id:wonderword22:20191117011445p:plain
ISO/IEC 25010 のシステム/ソフトウェア製品の品質モデルを構成する品質特性の内容

機能適合性

製品やシステムが、定められた利用状況下で定められて包含されたニーズを満たす機能 を提供する度合を示す。副特性として次がある。

  • 完全性
    • 機能がユーザの目的、定められたタスクをカバーしている度合
  • 正確性
    • 製品やシステムが必要な精度で正確な結果を与える度合
  • 適切性
    • 機能が定められたタスクや目的の遂行を円滑に行う度合
性能効率性

定められた利用状況下で利用される資源量の性能の度合を示す。副特性として次がある。

  • 時間効率性
    • 要求を満たすために機能を実行するときのシステムの応答時間、処理時間及び処理能力の 度合
  • 資源利用性
    • 要求を満たすために機能を実行するときの使用した資源の量や種類の度合
  • キャパシティ
    • 要求を満たすにあたり製品やシステムのパラメータの最大許容値
互換性

製品、システム、コンポーネントが他の製品、システムコンポーネントと情報を変換で きる度合、また、同じハードウェアやソフトウェア環境を共有し、要求される機能を実行 する度合を示す。副特性として次がある。

  • 共存性
    • 他の製品へ有害な影響を与えずに、他の独立した製品と共通の環境や資源を共有して要求 機能を効果的に実行する度合
  • 相互運用性
    • 2 つ以上のシステム、製品やコンポーネントが情報を交換し、その情報を利用できる度合
使用性

製品やシステムが、定められたユーザにより、定められた使用状況下で効果的、効率的、 満足度が達成される度合を示す。副特性として次がある。

  • 適切度認識性
    • 製品やシステムがユーザニーズに適しているかどうかを、ユーザが認識できる度合
  • 習得性
    • 定められたユーザにより、定められた使用状況下で満足性、安全性、効果性、効率性のあ る製品やシステムの使い方の学習が達成される度合
  • 運用性
    • 運用や管理の労力に係る度合
  • ユーザエラー防止性
    • システムがユーザを誤操作することから保護する程度
  • ユーザインタフェースの快美性
  • アクセシビリティ
    • 製品やシステムが定められた使用状況下で目標を得る上で、幅広い層の特徴や能力を持つ 人々により利用される度合
信頼性

システム、製品やコンポーネントが制限時間内で定められた状況の下で機能を実行する 度合を示す。副特性として次がある。

  • 成熟性
    • システムが通常の運用の下で信頼性のニーズを満たす度合
  • 可用性
    • システム、製品やコンポーネントが必要とされるときに運用、接続できる度合
  • 障害許容性
    • システム、製品やコンポーネントがハードウェアやソフトウェア障害が存在する中で運用 できる度合
  • 回復性
    • 障害時に製品やシステムがデータを回復し、システム状態を再構築する度合
セキュリティ

人やシステムによる読み込み・修正等が不当にアクセスされることなく情報やデータが 保護されている度合を示す。副特性として次がある。

  • 機密保持性
    • 製品やシステムが、許可されたもののみがアクセスできるようデータを保証する度合
  • インテグリティ
    • システム、製品やコンポーネントがコンピュータプログラムやデータへの修正に対して許 可されていないアクセスを防止する度合
  • 否認防止性
    • イベントやアクションがのちに拒否することができないよう、イベントやアクションが起 こされたことが証明される度合(ディジタル署名等)
  • 責任追跡性
    • エンティティのアクションが唯一のエンティティであると証明できる度合
  • 真正性
    • リソースや事項の身元が要求されるものであることを証明できる度合
保守性

製品やシステムが保守担当により修正するにあたっての効果性、効率性の度合を示す。 副特性として次がある。

  • モジュール性
  • 再利用性
    • 資産が複数のシステムや他の資産を構築する際に利用できる度合
  • 解析性
    • 製品やシステムの一部を変更したり変更された部分の特定や障害の原因や欠陥のために製 品を診断したり、一つ以上の部分の変更に当たり影響を評価する際の効果性、効率性の度 合
  • 変更性
    • 製品やシステムが欠陥の発生や既存の製品品質の低下がなく、効果的、効率的に変更でき る度合(ISO/IEC 9126 シリーズでは変更性、安定性)
  • 試験性
    • システム、製品やコンポーネントのためにテスト基準を確立し、基準が満たされたかどう か定めるために実行する際の効果性、効率性の度合
移植性

システム、製品やコンポーネントが、あるハードウェア、ソフトウェアや運用、利用環 境を他へ移行されるにあたっての効果性、効率性の度合を示す。副特性として次がある。

  • 順応性
    • 製品やシステムが、異なるあるいは進化したハードウェアやソフトウェア、あるいは運用 や利用可能な他の環境に効果的かつ効率的に順応できる度合
  • 設置性
    • 製品やシステムが定められた環境に正しく設置(インストール)されたり撤去される(ア ンインストール)されたりする際の効果性、効率性の度合
  • 置換性
    • 製品が同一の目的、環境で他のソフトウェア製品に置換(リプレース)される度合

オブジェクト指向による設計・実装、また読みやすいコード、いわゆるリーダブルコードはこの内、主に保守性を満たすために必要である。
* 関数呼び出しによるオーバーヘッドなどは、少なくともC++などオブジェクト指向対応言語が使えるような環境だともはや気にしなくてよいだろう。

そもそもオブジェクト指向は、1960にNATOが「20世紀末には世界の総人口がプログラマーになっても、増大するソフトウェアの需要に追いつかない」と、 いわゆる「ソフトウェア危機」が宣言されたことに対し、 機能の追加のしやすさ、これまで開発した機能の流用をできるようにすることで、少しでもソフトウェア開発速度を向上しようというのが目的である。

参考:
オブジェクト指向はなぜ動くのか

経済産業省 ソフトウェアメトリクス高度化プロジェクト プロダクト品質メトリクス WG システム/ソフトウェア製品の品質要求定義と品質評価のためのメトリクスに関する調査報告書

独立行政法人情報処理推進機構(IPA)技術本部 ソフトウェア高信頼化センター(SEC)連携委員 / 日本電気株式会社 ソフトウェア生産革新本部 マネージャ バグだけが品質と考えていませんか?