こんにちは、赤坂です。
山崎さん、ご意見ありがとうございました。
Susumu YAMAZAKI <yamazaki@....jp> san wrote:
> > こういった場合には、中間結果が変更されると、それに(おそらく、get/setを
> > 介しても、データ構造に)依存したクラス、およびテストも変更を余儀なくされ
> > てしまいますよね。
> > # よくあるケースですね。
>
> その点を悩んでおりました。
僕も現在進行形で悩んでいます(^^;;
> > コードは以下みたいな感じで書きたいです。
> > // Input ⇒ (Intermediary ⇒) Output
> > Input input;
> > Intermediary inter = Input.toIntermediary();
> > Output output = Inter.toOutput()
>
> Intermediary inter = input.toIntermediary();
> Output output = inter.toOutput();
>
> ですよね?
え?そうですけど、何か違ってましたか?
# inputが未初期化のこと?...きっと、入力パラメータですね(^^;;
> > こういうケースは良くありますし、これで悪くないと思いますが、
> > 個人的にはエンティティ同士で(できれば相互に)変換出来る方が好みです。
..snip..
> なるほど。機能クラスではなくエンティティクラスを前面に出す構成にするわ
> けですね。
# 自分へのコメントになってしまいますけど (^^;;
これは状況によりますよね。
Preprocess、Postprocessは重要な概念だと思うので、先ほどのコンテキストで
クラスにしないのはどうかと思います。> 自分 (^^;;
> Input -> Intermediary -> Output のようにクラスが変化するならばこれで良
> いと思うのですが、同一のクラスに、データが蓄積されたり、内部データが変
> 換されたりする場合には、どのようにするのが良いとお考えですか?
データが蓄積される場合は、それは重要な別の概念としてクラスを抽出します。
内部データが変換されるケースは、状況にもよると思いますが、
内部データの状態(に付属する変化するデータ構造)として扱いたいですね。
> > テストはInput、Intermediary、Outputをまとめた単位で、
> >
> > // input ⇒ intermediary 変換のテスト(逆変換で検証).
> > inter = input. toIntermediary();
> > atctual = inter.backtoInput();
> > assertEquals(inter, actual);
> >
> > という感じで使いたいところです。
> > # それぞれのエンティティのテストでは変換以外のデータ要素だけにします。
>
> このテストって、変換と逆変換がともに正しいことを期待していると思うので
> すが、変換も逆変換も間違っていてたまたま assertEquals を通ってしまう場
> 合に対処できないと思うのですけど。何らかの形で変換が正しいことを確かめ
> る必要があると思います。
おっしゃる通りです。
実際には、検証用のIntermediaryオブジェクトを用意して比較するのでしょうね。
# もちろん、正変換と逆変換での検証もやりたいですよね(^o^)。
// 期待値オブジェクト(構造体の初期化風)
Intermediary expect = {...};
// 中間結果に生成(変換)する
Intermediary actual = input. toIntermediary();
// 生成(変換)した中間結果を検証
assertEquals(expect, actual);
または
assertEquals(expect.toString(), actual.toString());
という感じになるのでしょうか。
# 前述の擬似コードは間違ってましたね。すみませんでした。
> > あと、Preprocessor, Postprocessorについては、必要があれば、
> >
> > // Input ⇒ (Intermediary ⇒) Output
> > Input input;
> > Intermediary inter = Input.toIntermediary(preprocessor);
> > Output output = Inter.toOutput(postprocessor)
> >
> > こんな感じになるのでしょうか。
> > # でもこうしてしまうと、変更時のテストの影響は大きそうですね。
>
> なるほど。実際には preprocessor や postprocessor は変換アルゴリズムの
> バリエーションを表す Strategy である場合がよくあるので、このコードをそ
> のように受け止めることが出来そうですね。
実は最初に思いつくのがこの形なんですけど、
実際には(変更の)影響が大きいので、テスト容易性を満たしてない気がします。
> 自分でも考えてみました。
>
> 問題は「一連の処理を分割した際に、その中のある処理の責務を変更すると、
> その影響が非常に大きい(特にテストケースの変更が問題)」ということです。
>
> 責務の変更が生じる理由を考えてみますと、分割された処理も実は内部的には
> いくつかの処理に分割することが出来て、その内部処理が境界を越えて移動す
> る時に責務の変更が起こるのではないか、と考えてみました。
..snip..
> そこで、解決策としては処理を分割する時に、将来の変更の余地がないほど粒
> 度を細かく分割し、粗粒度の機能クラスを細粒度の機能クラスのコンポジショ
> ンとして構成すればよいのではないかと考えてみました。
>
> 図に書くと次のようなイメージです。
> +------------------------+
> |Process1 | -> ...
> +------------------------+
> | | |
> +------+ +------+ +------+
> |Sub1-1| |Sub1-2| |Sub1-3|
> +------+ +------+ +------+
>
> テストケースは細粒度の機能クラスに対して書きます。テストケースクラス自
> 体は増えますが、一つ一つの機能クラスは単純なので、一つのクラスに対して
> 考えるべきテストケースは少なく抑えることができ、全体のテストの数はあま
> り増加しないでしょう。このテストは処理の組み換えが起こっても再利用する
> ことができます。
なるほど、小粒度のクラスに分割する訳ですね。
一つのクラスごとの凝集度としては高くなるような感じがしますね。
> 粗粒度の機能クラスに対するテストは結合テストとなるので、処理の組み換え
> が起こると書き直す必要があるでしょう。しかし、細粒度の機能クラスの単体
> テストが十分整備されていれば、結合テストの数を抑えることができるので、
> 書き直しのコストは減るものと思われます。
そうですね。
「単体テストが安定する単位」でクラスを導出することで、仕様変更時の他のク
ラスへの影響を最低限に抑えられそうですね。
この視点はとても重要に思います。山崎さん、ありがとうございますm(_._)m。
でも、(これも状況次第になってしまいますが)
実はこれら小粒度の機能クラスについては、適切なメソッドに分割されていれば、
「メソッドの移動」で充分に対応可能な(それほど大変ではない)のではないか
とも思いますが、いかがでしょうか?
きっと、山崎さんの視点から、いいアイディアがでてきそうな気がします(^o^)
ではでは。
--
赤坂 英彦 (Hidehiko AKASAKA)
akasaka.h@....com