基礎からのcode contracts

35
基礎からの Code Contracts @neuecc – 2011/5/23

Upload: yoshifumi-kawai

Post on 15-Jan-2015

11.218 views

Category:

Technology


0 download

DESCRIPTION

C#ユーザー会セッション資料

TRANSCRIPT

Page 1: 基礎からのCode Contracts

基礎からの

Code Contracts

@neuecc – 2011/5/23

Page 2: 基礎からのCode Contracts

Twitter => @neuecc

Blog => http://neue.cc/

HNは"neuecc" 読むときは“のいえ”で

ドメン繋いだだけで特に意味はなく発音不能のため(ccは声に出しにくいのでスルーという適当対応)

Microsoft MVP for Visual C#(2011/4-)

公開してるラブラリとか

linq.js

DynamicJson

Chaining Assertion

DbExecutor <- (ちょっとだけ)Code Contracts使った

Profile

Page 3: 基礎からのCode Contracts

First Step

Page 4: 基礎からのCode Contracts

.NET4から標準搭載された?

mscorlibにSystem.Diagnostics.Contracts

(主に)その中のContractクラスのメソッド群

Code Contracts

Page 5: 基礎からのCode Contracts

よくあるnullチェックをしてみようと思った

Contract.Requiresは事前条件

引数がnullだったら契約違反という感じにしたい

static void Hoge(string arg)

{

Contract.Requires(arg != null);

}

が、実行しても無反応

Conditional属性がついているのでコンパル時に消える(条件付きメソッド、DEBUGとかでお馴染み)

条件はCONTRACTS_FULL(但し自分で足す意味はない)

何か動かないよ?

Page 6: 基礎からのCode Contracts

よくあるnullチェックをしてみようと思った again

Contract.Requires<TException>も事前条件

引数がnullだったら契約違反で例外ぶん投げたい

static void Hoge(string arg)

{

Contract.Requires<ArgumentNullException>(arg != null);

}

が、変なゕサートが飛ぶ

そしてゕプリは強制終了

リラターがmustだと?

何か動かないよ? Part2

Page 7: 基礎からのCode Contracts

Code Contractsの利用にはリラターが必要

最終的な配布物はコンパラオプションで契約用コードを取り除く。従って実行効率にも影響しない。

http://ja.wikipedia.org/wiki/契約プログラミング

契約は取り除かれなければならない

そのためにはラブラリだけでは不可能で、コンパル時にバナリを弄る必要がある

契約の実現のため、現状はバナリ改変している

真に標準搭載されたと言えるのはリラターがコンパラと統合された時かもね

つまるところ

Page 8: 基礎からのCode Contracts

必須

Contractクラスなどコードに記述するマーカー

.NET 4で現状標準搭載されているのはこれだけ

バナリリラター(ccrewrite.exe)

オプション

参照ラブラリ生成(ccrefgen.exe)

ドキュメント生成(ccdocgen.exe)

静的チェッカー(cccheck.exe)

cccheckはPremium Editionのみ

静的チェックなしの場合は、例外orゕサートを投げる実行時チェックという形になる

Code Contractsの構成物

Page 9: 基礎からのCode Contracts

Get Ready to Contracts

Page 10: 基礎からのCode Contracts

DevLabs: Code Contracts http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx

Standard Edition (Visual Studio Professional)

ccrewrite, ccrefgen, ccdocgen

Premium Edition (Visual Studio Premium,Ultimate)

Standard + cccheck

Visual Studio Express Editionでは使えない

静的チェッカーの有無も大きなところ

契約の正しさが実行時じゃないと確認出来ないというのは、何が正しいのか分からない初学者にとって学習が困難になる

Code Contractsのンストール

Page 11: 基礎からのCode Contracts

に、Code Contractsタブが追加されてる

チェックボックスをオンにすると各機能が有効に

パラメータがいっぱいあって困る?

マニュゕルを見れば勿論、説明がある

日本語で?zeclさんのスラドを見よう!

http://d.hatena.ne.jp/zecl/20110213/p2

プロジェクトのプロパテゖ

Page 12: 基礎からのCode Contracts

Contract.Requires

無印と<TException>とEndContractBlockの三種

無印はコンパラ生成のContractExceptionを投げる

コンパラ生成なので型判別したcatchは不可能

<TE>の場合は指定した例外を投げる

EndContractBlockはif-then-throwを<TE>に変換する

// これと

if (arg == null) throw new ArgumentNullException("arg");

Contract.EndContractBlock();

// これは大体等しい

Contract.Requires<ArgumentNullException>(arg != null);

事前条件

Page 13: 基礎からのCode Contracts

EndContractBlockはレガシー環境用

バナリリラターがある環境が前提なら不要

Assembly Modeの選択

Requires, Requires<TE>はStandard Contract

EndContractBlockを使う場合はCustom Parameter

無印と<TE>ではリラト時に残るレベルが違う

無印の場合はReleaseRequiresでは除去される

DebugはFull、ReleaseではPreまたはReleaseを推奨

事前条件の違い

Page 14: 基礎からのCode Contracts

事後 : Contract.Ensures

戻り値を表すContract.Result<T>とセットで使うことが多い

不変 : Contract.Invariant

ContractInvariantMethod属性とセットで

cimコードスニペットを使えば展開される

ンターフェスへの契約

書くのがヘンテコで面倒くさい

cintfコードスニペットを使えば展開される

事後・不変・ンターフェス

Page 15: 基礎からのCode Contracts

Marriage with IntelliSense

Page 17: 基礎からのCode Contracts

.NET4からBCLも契約済み

そういう意味では標準搭載と言えなくもない

おや、標準ラブラリの様子が

Page 18: 基礎からのCode Contracts

標準ラブラリは何もしなくても表示される

自作の契約はReference Assemblyを作る必要がある

Reference Assemblyはクラスラブラリなど、契約が除去されたリリース用バナリを参照する他のラブラリが契約情報を参照したい場合に必要(但し、決してリラト後のバナリに契約を再度埋め込めれるわけではない)

使い方

Page 19: 基礎からのCode Contracts

コンストラクタは表示されません

ジェネリックメソッドは表示されません

Enumerable.Rangeは表示されるのにRepeatは表示されなかったりしてるのが確認できます

つまるところLINQのメソッドは全滅

yieldが含まれると表示されません

dynamicが含まれると表示されません

よく落ちます(落ちたらVS再起動まで復活しない)

Editor Extensionsに関してはアルファ版だと思って暖かく見守りましょう

但し制限も色々あり

Page 20: 基礎からのCode Contracts

Merit and Demerit

Page 21: 基礎からのCode Contracts

引数名を文字列で指定しなくてもいい

リラターが埋め込んでくれるから

コードスニペットcrenは文字列指定付きだけど、個人的にはそれは不要だと思う

// この文字列で引数名を書くのがかなりヤだった

if (arg == null) throw new ArgumentNullException("arg");

// それをCode Contractsではこう書き、そしてこれは

Contract.Requires<ArgumentNullException>(arg != null);

// バナリリラト後にこうなる

// 最後の"arg != null"がメッセージで、

// 条件を文字列として生成してくれているのが分かる

__ContractsRuntime.Requires<ArgumentNullException>(

arg != null, null, "arg != null");

嬉しいこと1

Page 22: 基礎からのCode Contracts

ンターフェスに契約すると、それを実装するものへは何も書かなくても自動で埋め込まれる

// こうしてンターフェスへの契約を作ると(cintfスニペット推奨)

[ContractClass(typeof(IHogeContract))]

public partial interface IHoge

{

void Show(string arg);

}

[ContractClassFor(typeof(IHoge))]

abstract class IHogeContract : IHoge

{

public void Show(string arg)

{

Contract.Requires<ArgumentNullException>(arg != null);

}

}

嬉しいこと2

Page 23: 基礎からのCode Contracts

class ClassA : IHoge

{

// 何も書いていませんが

// Contract.Requires<ArgumentNullException>(arg != null)が埋めこまれる

public void Show(string arg)

{

Console.WriteLine(arg);

}

}

class ClassB : IHoge

{

// 全てのメソッドにif(arg == null) throwを書く時代さようなら!

public void Show(string arg)

{

Console.WriteLine(arg + arg);

}

}

これにより、積極的なンターフェスの抽出と契約の記述が促されます(不純動機ドリブン)

それはとっても嬉しいなって

Page 24: 基礎からのCode Contracts

静的チェッカーでTester-Doerパターンを安全に

// こんなどうでもいいクラスがあるとして

public class ToaruClass

{

int value;

public bool IsReadOnly { get; private set; }

public void SetValue(int value)

{

Contract.Requires(!IsReadOnly);

this.value = value;

}

}

var toaru = new ToaruClass();

// IsReadOnlyをチェックしていないのでunproven

toaru.SetValue(100);

// こう書けばSafe

if (!toaru.IsReadOnly) toaru.SetValue(100);

嬉しいこと3

Page 25: 基礎からのCode Contracts

Requiresで検証する要素は外部から見えないと、バナリリラターを通りません

public class ToaruClass

{

int value;

private bool isReadOnly;

public ToaruClass(bool isReadOnly)

{

this.isReadOnly = isReadOnly;

}

public void SetValue(int value)

{

// isReadOnlyが外から不可視なのでダメ

Contract.Requires(!isReadOnly);

this.value = value;

}

}

Requiresの基本

Page 26: 基礎からのCode Contracts

Requires、事前条件はメソッド呼び出し側が、正しい呼び出しが可能かの責任を負う必要がある、つまり外から検証可能でないとならない

逆にEnsures、事後条件が正しく成立するかはメソッド側の責任なので、メソッド内部できちんとEnsuresの条件が満たせる必要がある

なんでなんで?

Page 27: 基礎からのCode Contracts

Requires内で使えるメソッドはPureなもののみ

警告なので実行は出来なくはない // Pureを付けないと警告が!

[Pure]

public static bool IsNull(string arg)

{

return arg == null;

}

public void Hoge(string arg)

{

Contract.Requires(!IsNull(arg));

}

Pure、つまり副作用ナシということ

String.IsNullOrEmptyなど当然Pure属性ついてます

Pureかどうかは自己申告制だったり(非Pureなものでも付けること自体は可能、勿論それはダメですよ)

Requiresの基本 Part2

Page 28: 基礎からのCode Contracts

静的チェッカーは契約の連鎖で成り立っているので、契約されてないラブラリが混じると警告祭りになって鬱陶しい

そういう場合はContract.Assumeで、契約済みを擬態していくのだけど数が多いと心が折れる、だけじゃなくコードが汚れて可読性悪化の一方に

Typeの一部とかExpressionの一部とか、契約済みのはずの標準ラブラリの中にも上手く動かないのがチラホラ

嬉しくないこと

Page 29: 基礎からのCode Contracts

// これは静的チェッカでunproven行き

var func = typeof(Func<,>);

var genFunc = func.MakeGenericType(typeof(int), typeof(int));

// 警告を元に、こうAssumeすればいいんですがなんというかかんというか

var func = typeof(Func<,>);

Contract.Assume(func.IsGenericTypeDefinition);

Contract.Assume(func.GetGenericArguments().Length == 2);

var genFunc = func.MakeGenericType(typeof(int), typeof(int));

例えばこんなunproven

Page 30: 基礎からのCode Contracts

// (object x) => (object)((T)x).name

static Func<object, object> CreateGetValue(Type type, string name)

{

Contract.Requires<ArgumentNullException>(type != null);

Contract.Requires<ArgumentNullException>(name != null);

// Expression.Unboxに事後条件非nullの契約がないため

// Expression.PropertyOrFieldの引数が求めるrequires expr != null の検証に失敗する

var x = Expression.Parameter(typeof(object), "x");

var func = Expression.Lambda<Func<object, object>>(

Expression.Convert(

Expression.PropertyOrField(

(type.IsValueType)

? Expression.Unbox(x, type)

: Expression.Convert(x, type),

name),

typeof(object)),

x);

return func.Compile();

}

Unproven Hell

Page 31: 基礎からのCode Contracts

Expressionも基本的には契約されているんですが、Expression.UnboxとかExpression.Assignとか、.NET4で新しく追加されたものはあまり契約されてないみたい

なので山崎春のunproven祭り

Expressionは基本的に引数に突っ込んで式としてツリー上に組み立てていくものなので、Assumeするのが難しい

もしAssumeするなら、全部バラして変数にしてから組み立てなければならないけど、それはない

どういうこと?

Page 32: 基礎からのCode Contracts

// 面倒くさくて耐え切れない時は静的検証オフ属性をつけてやる

[ContractVerification(false)]

static Func<object, object> Create(Type type, string name)

{

// (中略)

}

Contract.Ensures(Contract.Result<T>() != null); がどれだけ大事かが身にしみて分かる

しかし定型句すぎて面倒くさいのは事実……

cenコードスニペットがあるとはいえ

そして平穏が訪れる

Page 33: 基礎からのCode Contracts

Conclusion

Page 34: 基礎からのCode Contracts

.NET4標準に入っているContractsラブラリの他に、幾つか追加の属性が C:¥Program Files

(x86)¥Microsoft¥Contracts¥Languages¥CSharp にある(.csフゔルぽん置き)

使い方の詳細はマニュゕルに載ってる

Microsoft Researchで開発されている自動パラメタラズドテストPexに対してContractsが記述されていると、有効な自動生成パラメータが生成できるようになる

http://research.microsoft.com/en-us/projects/pex/

その他

Page 35: 基礎からのCode Contracts

メリットを幾つかあげましたが、忘れてはならない基本的なことは、「事前・事後・不変」の契約が出来るということ

でも、堅苦しい理屈だけじゃなく、目で見て分かる実用的な便利さを提供してくれるのはいいね!

if-then-throwを撲滅してくれるというだけでも十分嬉しいなって

まずはそこからで、徐々に高度にステップゕップすればいいんじゃないかな

Expressで使えないのが痛い&Premium以上でないと静的チェッカーが使えないのが大変痛いので、将来は何とかして欲しいと切実に願う

まとめ