Upload
-
View
6.000
Download
3
Embed Size (px)
Citation preview
Design by Contract
Effel言語が最初に導入して…
1985年
Bertrand Meyerによって作られる
実はEffel Software社の商標らしい
ので、Contract Programmingとか別呼称が流行る
で、Contract(契約)って何?
ドキュメント
理想: 実行可能なコード自体がドキュメントであるべき
クラス名、メソッド名、シグネチャ、すべてがドキュメント
コードで表現できないことを、やむなく自然言語で補足
コードの表現力が上がれば、補足は減る
契約 = コードの表現力向上
検証可能な仕様
型情報
メソッドの名前やシグネチャも仕様の一種
static void Main(string[] args){
var y = F(10);}
static int F(int x){
return x * x;}
• Fというメソッドがなければエラー• int以外を渡すとエラー
静的な型付けの言語では静的に検証可能
型情報に表れない仕様
型情報に表れない仕様はどう書く?
Docコメント?
/// <summary>/// なんか変換する。/// </summary>/// <param name="s">変換元。null であってはいけない。</param>/// <returns>変換結果。</returns>public static string Convert(string s)
• で、誰が保証するの?• 違反するとどうなるの?
検証用コード
普通、引数チェック入れるよね
DRY原則に反してる!
/// <summary>/// なんか変換する。/// </summary>/// <param name="s">変換元。null であってはいけない。</param>/// <returns>変換結果。</returns>public static string Convert(string s){
if (s == null) throw new ArgumentNullException("s");
同じ意味のことを2か所に書いてる
コードの方だけでいい?
じゃあ、Docコメントの方消してみると…
例外発生してみて初めて利用者に見える
利用者に見えなくなる
/// <summary>/// なんか変換する。/// </summary>/// <param name="s">変換元。</param>/// <returns>変換結果。</returns>public static string Convert(string s){
if (s == null) throw new ArgumentNullException("s");
そこで、契約
↓これと同じような意味になる何かが必要
/// <param name="s">変換元。null であってはいけない。</param>/// <returns>変換結果。</returns>public static string Convert(string s){
if (s == null) throw new ArgumentNullException("s");
検証コード
利用者から見える情報
契約 = 検証可能な仕様
さて、じゃあ、どう書こう
契約用の新文法が欲しいところだけども
例えば・・・Spec#ではこう書く
public static string Convert(string s)requires s != null;ensures result != null;
{…
}
これは、じゃあ、どうコンパイルされるべき?.NETの場合、複数の言語での情報共有が必須
Spec# = C# + 契約
どうコンパイルされるべき?
属性は・・・
与えれる引数が組み込み型に限られる
→ 複雑な条件を書けない
ILの仕様を変更する?
サード パーティ製含め、全てのコンパイラーに変更を強要する?
Code Contracts
普通の検証コード + IL書き換え
Code Contracts
契約を、普通にコードとして書く
そして、
コードから契約を読み取って静的解析
ビルド時IL書き換えでテスト用の検証コードを埋め込み
ちゃんとXMLドキュメントに反映
public static string Convert(string s){
Contract.Requires(s != null);Contract.Ensures(Contract.Result<string>() != null);…
}
IL書き換え
動的チェックする場合にはIL書き換え
Contract.Requires(s != null);Contract.Ensures(Contract.Result<string>() != null);return s.ToUpper();
Debug.Assert(s != null, "s != null");var t = s.ToUpper();Debug.Assert(t != null,
"Contract.Result<string>() != null");return t
☑
最初からAssertでよくない?
メタデータにならない
Requires/Ensuresとかの情報が消える
Debug.Assert(s != null, "s != null");var t = s.ToUpper();Debug.Assert(t != null, "Contract.Result<string>() != null");return t
エラーにすべき理由がわからない静的チェック時に困る
契約として表明する条件
事前条件(precondition) Contract.Requires(条件)
事後条件(postcondition) Contract.Ensures(条件)
不変条件(invariant) Contract.Invariant(条件)
事前条件
メソッドに入る瞬間に満たすべき条件
public static string Convert(string s){
Contract.Requires(s != null);…
string s = null;var t = Convert(s);
ダメ!絶対!
事後条件
メソッドを出る瞬間に満たすべき条件
public static string Convert(string s){
Contract.Ensures(Contract.Result<string>() != null);…
var t = Convert(s);if (t != null) …
チェックしなくても非null保証あり
不変条件
常に変わらない条件
public int X { get; set; }public int Y { get; set; }
[ContractInvariantMethod]void CheckInvariant(){
Contract.Invariant(X + Y == 0);}
証明
証明(prove)
契約が満たされているか静的に検証
// 事後条件: null は返さないよ。public static string GetString() { … }
// 事前条件: s は null を認めないよ。public static string Convert(string s) { … }
var s = GetString();var t = Convert(s);
nullは返ってこない
nullは渡らない
間にnullチェック不要
契約はあくまで自己申告
全てのメソッドが正しく契約を表明しているとは限らない
// 契約に未対応。public static string GetString() { … }
// 事前条件: s は null を認めないよ。public static string Convert(string s) { … }
var s = GetString();var t = Convert(s);
証明不能
var s = GetString();Contract.Assume(s != null);var t = Convert(s);
しょうがないから「仮定」を入れる
動的なテストで確認
コード書き換え
IL書き換え?
基本、全部[Conditional]付きで、そのままだと実行されないpublic static object GetData(int i){
Contract.Requires(i > 0);Contract.Ensures(Contract.Result<object>() != null);…
}
だって、戻り値に対する事後条件とか、メソッド中でチェックできるわけじゃないじゃない。
Requiresとかの中に掛けるメソッドは[Pure]でないと
Pure=副作用がない
[Pure]属性はあくまで自己申告なのだけども・・・
public int X { get; set; }public int Y { get; set; }
[ContractInvariantMethod]void CheckInvariant(){
Contract.Invariant(Norm == 0);}
[Pure]public int Norm { get { return X + Y; } }
問題点
契約の表明はあくまで自己申告
ウソつける(部分もある)
契約表明してないメソッドが1個でもあると・・・
それ使うたびにチェック コード挿入しないといけなくなる
静的検証すごく重い
C#の普通の文法チェックみたいに、リアルタイム チェック無理
あと、IL書き換えとか、どうなんだろうね
まとめ: 契約とは
契約=形式的仕様
利用者に見えるべき
可能な限り静的チェックすべき
静的チェックに漏れる分は動的チェックする
つまり
契約はドキュメントに含まれるべき
契約は静的チェックの対象(型情報の一部)であるべき
契約は動的チェックの対象(テスト仕様)であるべき