言葉だけはたまに見かけてましたが、どんなものか全く知ろうとせず、ずっとどうでもいいやと思っていました。
ところがこれ、TDD を進化させたようなものだったんですね。
「テスト駆動開発」ではどうしても「品質のため」と誤解されがちで、「設計のため」って言っても中々信じてもらえません。でも「ビヘイビア駆動開発」ならきっと大丈夫。
これからは TDD を捨てて BDD へ移行していきたいと思います。
ただ、.NET 界隈だと残念ながら BDD はあまり浸透してない感じですね。具体的にどういう方法で実践していくのが良いか悩みどころです。
[Web 上で見つけた BDD 関連のリンク]
BDD とは何なのかがわかります。
NUnit で BDD を実践する方法の一例が紹介されてます。
- NUnitでBDD - サピア・ウォーフの仮説は当てはまらないが。(-.-) - NAgilerの日記
- NUnitでBDD - スペックリスト (^_^) - NAgilerの日記
- NUnitでBDD-継続的インテグレーション (-.-) - NAgilerの日記
- NUnitでBDD-素振り重要 (>_<) - NAgilerの日記
MbUnit で BDD を実践する方法の一例が紹介されてます。
.NET 用の BDD フレームワーク「NSpec」
.NET 用の BDD フレームワーク「NSpecify」
.NET 用の BDD フレームワーク「NBehave」
Ruby 用の BDD フレームワーク「RSpec」 (IronRuby を使えば .NET に対応可らしい)
Ruby 用の BDD フレームワーク「Cucumber」 (IronRuby を使えば .NET に対応可らしい)
.NET 用の BDD フレームワーク「Specter」 (Python ライクの .NET 言語である Boo で記述する)
[修正履歴]
2009/05/29
・リンクに Specter を追加。
こだかたろうです : New EntityFramework
不信任投票で挙がっていた点がかなり改善されそうですね。
レイジーロードがサポートされることはなおきさんから聞いていましたが、まさか PI もサポートされるとは。
気になるのは PI レベルがどのくらいなのかですね。
How to: Define Custom Persistence-Ignorant Objects (Entity Framework) を見る限り POCO は余裕でクリアしてますが、Requirements for Using Persistence-Ignorant Objects (Entity Framework) を見るとやはりいくつか制約があります。
以下、英語苦手なのできっと間違ってる所があります。(つか、自分で意味がよくわかってない所もあるので…。誰かつっこみお願いします T-T)
- カスタムデータクラスは public でなければならない。
- カスタムデータクラスは sealed にできない。
- カスタムデータクラスは abstract にできない。
- カスタムデータクラスは IEntityWithChangeTracker や IEntityWithRelationships を実装できない。
- 概念モデル内のエンティティ型のプロパティにマッピングされた各プロパティは public でなければならない。
- カスタムデータクラスはデフォルトコンストラクタを提供しなければならない。
- 概念モデル内で定義されたナビゲーションプロパティは、対応するナビゲーションプロパティがカスタムデータクラスになければならない。関連オブジェクトの遅延ロードをサポートするためには、このプロパティを virtual として宣言する必要があり、sealed と宣言することはできない。
- 一対多または多対多の関連を表すナビゲーションプロパティは、ICollection<T> (T は関連端の型) を実装する型を返さなければならない。
- データモデル内のエンティティ型のプロパティにマッピングされたプロパティは、virtual で、getter 及び setter を実装しなければならない。
- 概念モデル内の複合プロパティは、参照型を返すカスタムデータクラスのプロパティにマッピングしなければならない。複合プロパティを、値型や列挙型にマッピングすることはできない。
独自のデリゲートを定義する代わりになんでもかんでも Func デリゲートやら Action デリゲートで済ますのはやめましょう。
これらのデリゲートは汎用的に使用できるようにと定義されていますが、大きな欠点があります。それは、パラメータに名称を付けられないということです。
例えば Enumerable.Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Int32, TResult> selector) メソッドは、Func<TSource, Int32, TResult> デリゲートを受け取ります。この Func デリゲートの第一引数はまぁ良いとして (※)、第二引数は一体何の役割を持っているのでしょうか。
前述の通り、Func デリゲートではパラメータに名称を付けられないため、引数の役割はドキュメントから読み取るしかありません。
ドキュメントを読めば、シーケンス内での要素の位置を表す値であることがわかりますが、これがもし、Func デリゲートではなく次のように厳密なデリゲートだったとしたらどうでしょう。
public delegate TSource Selector<TSource>(TSource source, int index);
これならば、ドキュメントを読む前に index という名前から第二引数の役割を推測できます。しかも第二引数の詳細を知りたければ、直接このデリゲートのドキュメントを読めば済みます。
// 追記 (2009/05/24)
パラメータ名がないとドキュメントの書き方が「第一引数は○○です」なんて説明になるので、パラメータリストの順序変更などでドキュメントとの不整合が生じやすくもなります。しっかりデリゲー ト定義すれば <param> タグが使えるので、ドキュメントとの不整合もある程度コンパイラが警告してくれます。
// 追記ここまで
というわけで、Func デリゲートやら Action デリゲートを全否定するわけではないですが、これらのデリゲートは表現力が弱いということは認識しておいた方が良いと思います。
※ これに関しては Select メソッドの型パラメータ名 (TSource) によって役割を推測できます。でも Enumerable.Aggregate<TSource>(IEnumerable<TSource> source, Func<TSource, TSource, TSource> func) なんかだと Aggregate メソッドの型パラメータ名 (TSource) からも推測できないです。
NHibernate をベースに作られていて、至る所で NHibernate の知識を求められる。というか、NHibernate の基本がわかっていれば簡単に使えるけれど、わかっていなきゃ厳しいという感じ。
気になるのは、バージョンアップが久しくされていないっぽいこと。当然、使用されている NHibernate のバージョンも 1.2 と古い。ただ、SVN リポジトリの方をちらっと覗いてみたら、ファイル更新日時がかなり最近の日付になっていたので、開発自体は継続しているようだ。
C# だと、明示的に virtual をくっつけないと仮想メソッドにはなりません。(Java だとデフォルトが仮想メソッドだった気がします。)
で、個人的に仮想メソッドはほいほい用意すべきものではないと思っています。決して、仮想メソッドが全く必要無いということではないですが。
まず、実装を完全に上書きすることができるってのがどうにも気持ち悪いです。
これが実際に行われる時は、主に継承ツリーが不適切である場合が多いです。
この場合、例えば仮想メソッドを抽象メソッドにして派生クラスの一つに実装を移すことで回避できたりします。
あと、base キーワード使って、基本クラスが実装したバージョンのメソッドを好きな位置で呼び出すことができますが (でも正しく動作させるにはちゃんと位置を考えないといけないことがよくある)、これも気持ち悪いです。
これが実際に行われる時は、クラスの内部設計が未熟である場合が多いです。
この場合、例えばテンプレートメソッドパターン等の検討を行い、派生クラスで実装する処理を的確に表現するメソッドを用意することで回避できたりします。
ただし、仮想メソッドが適切である場面もあります。
例えば、テンプレートメソッドパターン等によって用意されたメソッドだけれど、派生クラスで必ずしも実装する必要がないという場合は、抽象メソッドではなく実装が空の仮想メソッドとした方が良い場合もあります。
また、継承していくにつれ段階的に実装が膨らんでいく (あくまでも同じ目的の処理が増えていく) 可能性があるメソッド (例 : Dispose メソッド) なんかは仮想メソッドの方が適切です。
ということで、一概に仮想メソッドはダメとは言えないのですが、仮想メソッドを用意する時は他にもっと適切な方法がないかを考慮してみると良いかなと思います。
僕も列挙してみました。だいたい時系列順です。
(HTML・CSS、XML 等は含めてません。)
- Basic
- C
- VB
- x86 Assembler
- VB.NET
- Java
- JavaScript
- C#
- MSIL
- C++
- C++/CLI
- Brainfuck
- Python
- Iron Python
- PowerShell
- Common Lisp
- Boo
- Ruby
HN は "きっぽ" さんです。
トルネード・プログラミング
きっぽさんとは一緒に仕事をしたことが一回くらいしかなかったのですが、それ以外でもプログラム設計や開発方法論についての議論はよくしてました。
ブログには、主にオブジェクト指向によるプログラム設計について書いていくそうです。僕のブログを読んでくださっている方、是非きっぽさんのブログに訪問してください ^^
インストーラのウィンドウのハンドルさえ取得できれば、NativeWindow クラスを使って親子関係を持たせることもできるのですが、生憎取得できないようです。Process.MainWindowHandle で取得できるかとも思いましたが 0 が返ってきました。
そこで、ちょっと知恵を絞り出してみました。
internal static class MyMessageBox
{
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
{
using (Form dummy = MyMessageBox.ShowDummyForm(caption))
{
return MessageBox.Show(dummy, text, caption, buttons, icon, defaultButton);
}
}
private static Form ShowDummyForm(string caption)
{
Form dummy = new Form();
dummy.TopMost = true;
dummy.ShowInTaskbar = true;
dummy.Opacity = 0;
dummy.ControlBox = false;
dummy.Text = caption;
dummy.Show();
return dummy;
}
}
最前面に固定された透明のダミーウィンドウを表示し、それをメッセージボックスの親ウィンドウにしたわけです。
この方法だとメッセージボックスがタスクバーに表示されませんが、ControlBox プロパティと Text プロパティを変更することで、ダミーウィンドウがちょうど良い具合に代わりを果してくれます。
メッセージボックスが最前面に固定されてしまうという副作用はありますが、まぁ問題ないでしょう。
あと、インストーラのウィンドウとダミーウィンドウ及びメッセージボックスはあくまで無関係ですので、メッセージボックス表示中もインストールウィンドウの操作が従来通りできてしまうことには注意してください。
int GetIisMajorVersion()
{
const string keyName = @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters";
const string valueName = "MajorVersion";
int value = (int)Registry.GetValue(keyName, valueName, -1);
if (value == -1)
{
throw new Exception("インターネット インフォメーション サービスのバージョンを取得できませんでした。");
}
return value;
}
ちなみにマイナーバージョンは "MinorVersion" に入ってます。