八葉の日記

日々、感じたことをまとめる場として利用する

負の継承への対応(2)

負の継承への対応を具体的にまとめていく
まず、共通性を侵すことなく可変性を扱う場合は、とくに問題なくあつかえるという前提です。
また、負の可変性は正の可変性と対応した技術で実現します。例えば、正の可変性をテンプレートで
実現した場合は、負の可変性はテンプレートの特殊化になります。

負の可変性を扱うメカニズム
・テンプレートの特殊化
共通の形:型によらず共通

template < class  T>
class Stack{
    StackRep<T> *rep;
}

template < class  T>
class StackRep{
    StackRep<T> *next;
    T* rep
}

上記では、ポインタを持った構造体で管理しているが、整数型に対しては性能要件から動的配列はとれず、
素数も決まっている場合があった場合は、テンプレートの特殊化で以下のようにできる。

template < >
class Stack<int>{
    static int rep[10000];
}

・デフォルト引数
アルゴリズムに強い共通性がある場合で、値に対して弱いバリエーションがあった場合、
(言い換えると、十中八九はこの値だけど、ごくまれにコレというようなケース)

・データのキャンセル

//テキストバッファを扱うクラス
template <class CharSet >
class TextBuffer{
public:
    Line<CharSet> getLine(const LineNumber&);

private:
   
   LineNumber firstLine, lastLine;
   int currentLine;
   //はじめと最後の行、現在の行を扱えるとメンバ変数がいる
}

1行だけ扱うテキストバッファが必要で、テキストバッファに対する機能を有している上記を継承する場合は、
複数行扱うメンバーは邪魔になる。

//ページ単位でテキストバッファを扱う
template <class CharSet >
class PageBuffer : TextBuffer<CharSet>{
public:
} 

//1行単位でテキストバッファを扱う
template <class CharSet >
class LineBuffer : TextBuffer<CharSet>{
public:
} 

派生クラスで、基底クラスのメンバをキャンセルすることはできないため、可変性を外側にくくりだす。
ここでは、ページ単位でテキストバッファを扱うクラスと1行単位で扱うテキストバッファを以下のように定義する。

//まず、それぞれの実装を定義
template <class CharSet >
class PageBufferRep : public TextBufferRep<CharSet>{

   LineNumber firstLine, lastLine;
   int currentLine;
} 

template <class CharSet >
class LineBufferRep  : public TextBufferRep<CharSet> {
public:
} 

上記の実装に対して、クライアントへ提供するハンドルを以下のように定義する。

template <class CharSet >
class TextBuffer{
public:
    Line<CharSet> getLine(const LineNumber&);

protected:
   TexstBufferRep<CharSet>* rep;//最初の例と比較すると、所持していたデータメンバが抽象化されている。
   TextBuffer(TexstBufferRep<CharSet>*); //コンストラクタでRepを割り当てる
}

最終的にはこうする。

template <class CharSet >
class PageBufferBuffer : public TextBuffer<CharSet>{
public:
    PageBufferBuffer () 
     : TextBuffer<CharSet>(new PagedTextBufferRep){}
}
template <class CharSet >
class LineBufferBuffer : public TextBuffer<CharSet>{
public:
    LineBufferBuffer () 
     : TextBuffer<CharSet>(new LineBufferRep  ){}
}

図でいうと、
f:id:konboi_kun:20180225194822j:plain
以下になってます
f:id:konboi_kun:20180225200250j:plain


つかれたので、あとで整理しよう

負の継承への対応

■前置き
オブジェクト指向言語では、継承がサポートされているが、継承には2種類存在する。
・正の継承:Liskovの置換原則を守る継承
・負の継承:上記を守れないような継承
      親クラスで定義されたモノは、継承関係で共通の領域とみなされる。これを侵害するようなことを指す。

負の継承の例

struct strrecvfd {
#ifdef KERNEL
    union {
        struct file*fp;
        int fd;
    }
#else
   int fd;
#endif
   
   unsigned short uid;
   unsigned short gid;
   char util[8];
}

KERNELのシンボルの指定が基本である場合、KERNEL以外の場合には、負の可変性が生じている。
生じているのは、"fpが存在するという共通性が不要をされていること"です。
KERNELのシンボルの指定が基本である場合でない場合でも、負の可変性が生じている。
生じているのは、"fdの性質減っている"ことです。KERNELがない場合はfdのみで十分でしたが、KERNELの場合は、fd+fpで、もともとの
fdと同じ役割を果たしますので、fdの役割が一部減っています。

解決策を順にあげていく。
①負の継承に沿ってドメインサブドメインに切り分ける。
②テクニックを用いる

①についてですが、上記の例では、

struct strrecvFP {
    union {
        struct file*fp;
        int fd;
    }
   unsigned short uid;
   unsigned short gid;
   char util[8];
}

struct strrecvFD {
    int fd;
   unsigned short uid;
   unsigned short gid;
   char util[8];
}

②のテクニックについては、別途まとめます。
親子で明確に意味が異なるような場合は、テクニックでなんとかすのではなく、クラスをわけるというのが
正解かもしれない。
多分、②でなんとかできるのは"コードの再利用"の観点から継承を使用しているケースに限られるのではないかなと思う。


新装版 マルチパラダイムデザイン

新装版 マルチパラダイムデザイン

  • 作者: ジェームス・O・コプリン,James O. Coplien,平鍋健児,金沢典子,羽生田栄一
  • 出版社/メーカー: ピアソン桐原
  • 発売日: 2009/12/01
  • メディア: 単行本
  • 購入: 3人 クリック: 41回
  • この商品を含むブログ (6件) を見る

DI依存性注入

依存性の注入(Wikipediaから抜粋すると)
依存性の注入(いそんせいのちゅうにゅう、英: Dependency injection)とは、コンポーネント間の依存関係をプログラムのソースコードから排除し、
外部の設定ファイルなどで注入できるようにするソフトウェアパターンである。英語の頭文字からDIと略される。

コード中に依存関係を定義するコードがあると、ユニットテストがやりにくいという問題がある。
例えば、スクリーンに何かを描画するMainScreenが実際にディスプレイドライバーを触っているとする。

class MainScreen
{
        void Disp()
        {
            DisplayDriver* drv = new DisplayDriver980() ;
            bool ret = drv.Setup();
            if (FALSE = ret)
            {
                  drv.Teardown();
                  return FALSE;
            }

            return TRUE;
        }
};

上のコードを単体試験するときは、本物ドライバに依存しているため、ドライバーがインストールしている環境でないと動かないし、
ドライバーは大抵外部から提供されものであるため、自由に中を見れずエラーケースの試験を行うことが難しくなる。

こういうときは、MainScreenに対して依存するオブジェクトをMainScreenに依存する人が渡す方法がある。
これが依存性の注入(Dependency injection)。

class MainScreen
{
        Driver& m_drv;
  MainScreen(Driver& drv) : m_drv(drv){}
        void Disp()
        {
            bool ret = m_drv.Setup();
            if (FALSE = ret)
            {
                  m_drv.Teardown();
                  return FALSE;
            }

            return TRUE;
        }
};

利用者側が依存性を解決してあげる必要があるが(*)、これで特定のオブジェクトに対する依存性はなくなり、
依存するオブジェクトが特定のシグネチャに沿っていればよくなりました。
テンプレートを使うとよくわかるかと思います。下記コードでは渡すオブジェクトには継承関係がなくても
API仕様が同じであれば一切問題がないです。

template< class T >
class MainScreen
{
        T& m_depend;
  MainScreen(class T& depend) : m_depend(depend){}
        void Disp()
        {
            bool ret = m_depend.Setup();
            if (FALSE = ret)
            {
                  m_depend.Teardown();
                  return FALSE;
            }

            return TRUE;
        }
};

(*)
依存関係を解決するのを利用者に渡しているため、利用者が必要な依存を判断する必要があります。
なので、、、
利用者に対して依存関係を知られたくない場合はこの方法は使えません。
例えば、外部の顧客に対して提供するAPIに対して、DIを利用するのは難しいかもしれません。
これは、依存するクラスをセットしてというより、モード切替といったAPIを用意する。もしくは
自分で動作環境を判断して内部で切り替えるといった仕組みを用意することが多いからかなっと思います。

ソフトウェアテスト(統合テストとユニットテスト) 覚書

■統合テスト
複数の層を1つに繋げるテストを指す。

 できること
 - UIテストで見つけにくい下位の不具合、最下層のユニットテストでは見つからない不具合を検出できる
  → 複数の部品がちゃんとつながっていることを確認できる

ユニットテスト
※はじめにUIテストのデメリット
※ - 時間がかかる。一つあたりが数秒かかる場合を考えると数がおおくなればなるほど時間がかかる
※ - 問題あることの検知は容易だが、どこに問題があるか検知するのが難しい

 できること
 - 高速に実行できる、手軽に作成できる、迅速にフィードバックできる
 - コードが正しく動くことを保証する。
 - テストを書くことでコードに意図を伝えることができる
※いつまでやるか
  →コードに自信がもてるまで。考えるべきことはすべて考えたかということがテストで明らかになる
 


ユニットテストのデメリットとしていかが起こりうる。
モックの泥沼:ユニットテストを頑張って書いた人なら一度はかんじたことがある。
       「テストコード中の事前準備(モックの設定)が嫌におおい」→テストコードを書くのに時間がかかる。
       原因は、テスト対象コード自体が保守しにくい構造になっているため、テストコードとテスト対象が密接に結びついているため。

解決策
1.リファクタリング:試験対象を直して、試験コードもきれいにする。
2.入出力にのみ注目する:テスト対象レイヤのインプットとアウトプットのみに注目して試験を行う。

両者ともに試験対象と試験コード間の結合度を下げる。2については、ポートとアダプタというアーキテクチャを利用できる。

ユーザーストーリマッピング覚書 2018/01/02

ユーザーストーリマッピングを読んでいるので覚書

■ユーザーストーリーって何?
・ユーザーストーリを作るのは何のためか
 プロジェクトに関わる人間が作るものに対して共通理解を保つため
 → 実際のプロジェクトはできの悪い伝言ゲームのように皆が違うことを考えている。←防止する

 本当に作らないといけないものを見極めるため
 → 実際のプロジェクトでは必要のないものも作ってしまっている。←防止する

 

・念頭に置くこと
 誰の為に作るのか、何で必要なのかを常に考えよう
 自分たちの作ったものがどう使われて、誰が喜ぶのかを考えよう
 より多くの成果物を作るのではなく、より大きなインパクトを与えられるか考えよう

■どうすれば描ける?
・考えを整理する
 - 自分の考え(アイデアや提供する機能)を表に出して整理する。
  ホワイトボードやポストイットに書き出す。ポストイットの方が並び替えしやすい
・抜け漏れがないか確認する
 - アイデアを利用する人の1日を声に出して言ってみる
  表に出したアイデアで足りてないことがないか確認し、抜けていたら足す

→アイデアのマップを作る!


 ストーリを洗い出す時は、細かいところを初めから見ないようにする。大きな枠で大筋を掴み。まず整理してから
 細かい部分を洗い出す。

■洗練していくには?
・大きいマップを見やすくする
 - 複数のアイデアをまとめる。←より上位もしくは抽象化された概念にまとめるとマップがスッキリしませんか?
・優先順位をつけましょう
 - 依存関係から順番を見つけましょう
 - 利用者に対してインパクトの大きいアイデアから作りましょう
・要らないものは削っていきましょう
 - 優先度の低いものを作成する時間は削る。必要ないのであれば作らないとしましょう。
 - MVPリリース:望まれる成果を実現できる最小の製品リリース
  を見つけよう


補足:今は以下の書籍を勉強中です
ユーザーストーリーマッピング

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

〇インターフェース分離の原則(ISP:Interface Segregation Principle)
クライアントにクライアント利用しないメソッドへの依存を強制してはならない

あなたがある機能を実現するモジュールを作成しているとして、そのモジュールが外部に公開しているIFを見るとメソッドが100個以上あって、複数クライアントから利用されているといったことはよくあると思います。
各クライアントはすべてのインターフェースを利用しているわけでなく、自分がほしい機能に関係するインターフェースを利用しているだけなので、機能ごとにインターフェースをグループ化して分けることで、クライアント利用するインターフェースを分離することができます。

こうすることで、クライアントは自分に関係あるインターフェースに対する変更した影響しなくなります。

SOLID原則:5つ目 依存関係逆転の原則(DIP:The Dependency Inversion Principle)

〇依存関係逆転の原則(DIP:The Dependency Inversion Principle)
上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである
「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべきである。

構造化分析に従うと、システムをトップダウンで分析していき、主要機能を実現するのに必要なサブ機能を見つけていきます。これをそのままSW設計と実装に適用するとサブ機能の組み合わせで上位ができるため、上位は下位に依存する構成になります。

余談

こうなった時のデメリットとしては、些細な実装の変更が上位のモジュールに伝わってしまうため、すぐ変更が必要になってしまうことです。自分が作っている範囲ならいいのですが、上位と下位モジュールを別組織で作成していたりすると、変更自体が難しくなります。
例えば、下位担当が楽な変更を考えたとしても、上位への影響が大きいとか言われて、下位担当は楽じゃない変更をすることが起こりえます。また、上位からすると下位がちょっと変えてくれればいいのに他の上位モジュールにも影響があるから下位は変更できないとなります。
つまり、最終的に望むのは上位と下位が直接依存しないことになります。

閑話休題

以下の図では、Policy(方針)がMechanism、MechanismがUtilityに依存している。ここまでダイレクトな依存関係だとUtilityを変更することPolicyへ影響することになってしまいます。(そして、余談にかいてあるようなやりとりが発生します。f:id:konboi_kun:20171203140947p:plain)

f:id:konboi_kun:20171203135609p:plain
参考書籍にあった例:認識の甘いレイヤ構造

上位は自信が期待する振る舞いを定義したInterfaceを用意し、下位はInterfaceに沿った実装をする(下位が上位へ依存する)。この結果、上位はInterfaceに、下位はInterfaceに依存する構造のため、Interfaceで決めた範囲の変更では、余談に書いたようなことは起こりません。

アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技

アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技