Upload
yoshifumi-kawai
View
11.218
Download
0
Embed Size (px)
DESCRIPTION
C#ユーザー会セッション資料
Citation preview
基礎からの
Code Contracts
@neuecc – 2011/5/23
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
First Step
.NET4から標準搭載された?
mscorlibにSystem.Diagnostics.Contracts
(主に)その中のContractクラスのメソッド群
Code Contracts
よくあるnullチェックをしてみようと思った
Contract.Requiresは事前条件
引数がnullだったら契約違反という感じにしたい
static void Hoge(string arg)
{
Contract.Requires(arg != null);
}
が、実行しても無反応
Conditional属性がついているのでコンパル時に消える(条件付きメソッド、DEBUGとかでお馴染み)
条件はCONTRACTS_FULL(但し自分で足す意味はない)
何か動かないよ?
よくあるnullチェックをしてみようと思った again
Contract.Requires<TException>も事前条件
引数がnullだったら契約違反で例外ぶん投げたい
static void Hoge(string arg)
{
Contract.Requires<ArgumentNullException>(arg != null);
}
が、変なゕサートが飛ぶ
そしてゕプリは強制終了
リラターがmustだと?
何か動かないよ? Part2
Code Contractsの利用にはリラターが必要
最終的な配布物はコンパラオプションで契約用コードを取り除く。従って実行効率にも影響しない。
http://ja.wikipedia.org/wiki/契約プログラミング
契約は取り除かれなければならない
そのためにはラブラリだけでは不可能で、コンパル時にバナリを弄る必要がある
契約の実現のため、現状はバナリ改変している
真に標準搭載されたと言えるのはリラターがコンパラと統合された時かもね
つまるところ
必須
Contractクラスなどコードに記述するマーカー
.NET 4で現状標準搭載されているのはこれだけ
バナリリラター(ccrewrite.exe)
オプション
参照ラブラリ生成(ccrefgen.exe)
ドキュメント生成(ccdocgen.exe)
静的チェッカー(cccheck.exe)
cccheckはPremium Editionのみ
静的チェックなしの場合は、例外orゕサートを投げる実行時チェックという形になる
Code Contractsの構成物
Get Ready to 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のンストール
に、Code Contractsタブが追加されてる
チェックボックスをオンにすると各機能が有効に
パラメータがいっぱいあって困る?
マニュゕルを見れば勿論、説明がある
日本語で?zeclさんのスラドを見よう!
http://d.hatena.ne.jp/zecl/20110213/p2
プロジェクトのプロパテゖ
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);
事前条件
EndContractBlockはレガシー環境用
バナリリラターがある環境が前提なら不要
Assembly Modeの選択
Requires, Requires<TE>はStandard Contract
EndContractBlockを使う場合はCustom Parameter
無印と<TE>ではリラト時に残るレベルが違う
無印の場合はReleaseRequiresでは除去される
DebugはFull、ReleaseではPreまたはReleaseを推奨
事前条件の違い
事後 : Contract.Ensures
戻り値を表すContract.Result<T>とセットで使うことが多い
不変 : Contract.Invariant
ContractInvariantMethod属性とセットで
cimコードスニペットを使えば展開される
ンターフェスへの契約
書くのがヘンテコで面倒くさい
cintfコードスニペットを使えば展開される
事後・不変・ンターフェス
Marriage with IntelliSense
静的チェッカなしだと、どうも地味
Premiumの人なら関係ないですねShit!
そんな物足りなさを感じるゕナタにVisualな贈り物
VS拡張:Code Contracts Editor Extensions
http://visualstudiogallery.msdn.microsoft.com/85f0aa38
-a8a8-4811-8b86-e7f0b8d8c71b
契約がIntelliSenseに表示される!
FreeなのでVS Professionalの人でもOK
動かしたけど嬉しさ少なめ?
.NET4からBCLも契約済み
そういう意味では標準搭載と言えなくもない
おや、標準ラブラリの様子が
標準ラブラリは何もしなくても表示される
自作の契約はReference Assemblyを作る必要がある
Reference Assemblyはクラスラブラリなど、契約が除去されたリリース用バナリを参照する他のラブラリが契約情報を参照したい場合に必要(但し、決してリラト後のバナリに契約を再度埋め込めれるわけではない)
使い方
コンストラクタは表示されません
ジェネリックメソッドは表示されません
Enumerable.Rangeは表示されるのにRepeatは表示されなかったりしてるのが確認できます
つまるところLINQのメソッドは全滅
yieldが含まれると表示されません
dynamicが含まれると表示されません
よく落ちます(落ちたらVS再起動まで復活しない)
Editor Extensionsに関してはアルファ版だと思って暖かく見守りましょう
但し制限も色々あり
Merit and Demerit
引数名を文字列で指定しなくてもいい
リラターが埋め込んでくれるから
コードスニペット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
ンターフェスに契約すると、それを実装するものへは何も書かなくても自動で埋め込まれる
// こうしてンターフェスへの契約を作ると(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
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);
}
}
これにより、積極的なンターフェスの抽出と契約の記述が促されます(不純動機ドリブン)
それはとっても嬉しいなって
静的チェッカーで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
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の基本
Requires、事前条件はメソッド呼び出し側が、正しい呼び出しが可能かの責任を負う必要がある、つまり外から検証可能でないとならない
逆にEnsures、事後条件が正しく成立するかはメソッド側の責任なので、メソッド内部できちんとEnsuresの条件が満たせる必要がある
なんでなんで?
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
静的チェッカーは契約の連鎖で成り立っているので、契約されてないラブラリが混じると警告祭りになって鬱陶しい
そういう場合はContract.Assumeで、契約済みを擬態していくのだけど数が多いと心が折れる、だけじゃなくコードが汚れて可読性悪化の一方に
Typeの一部とかExpressionの一部とか、契約済みのはずの標準ラブラリの中にも上手く動かないのがチラホラ
嬉しくないこと
// これは静的チェッカで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
// (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
Expressionも基本的には契約されているんですが、Expression.UnboxとかExpression.Assignとか、.NET4で新しく追加されたものはあまり契約されてないみたい
なので山崎春のunproven祭り
Expressionは基本的に引数に突っ込んで式としてツリー上に組み立てていくものなので、Assumeするのが難しい
もしAssumeするなら、全部バラして変数にしてから組み立てなければならないけど、それはない
どういうこと?
// 面倒くさくて耐え切れない時は静的検証オフ属性をつけてやる
[ContractVerification(false)]
static Func<object, object> Create(Type type, string name)
{
// (中略)
}
Contract.Ensures(Contract.Result<T>() != null); がどれだけ大事かが身にしみて分かる
しかし定型句すぎて面倒くさいのは事実……
cenコードスニペットがあるとはいえ
そして平穏が訪れる
Conclusion
.NET4標準に入っているContractsラブラリの他に、幾つか追加の属性が C:¥Program Files
(x86)¥Microsoft¥Contracts¥Languages¥CSharp にある(.csフゔルぽん置き)
使い方の詳細はマニュゕルに載ってる
Microsoft Researchで開発されている自動パラメタラズドテストPexに対してContractsが記述されていると、有効な自動生成パラメータが生成できるようになる
http://research.microsoft.com/en-us/projects/pex/
その他
メリットを幾つかあげましたが、忘れてはならない基本的なことは、「事前・事後・不変」の契約が出来るということ
でも、堅苦しい理屈だけじゃなく、目で見て分かる実用的な便利さを提供してくれるのはいいね!
if-then-throwを撲滅してくれるというだけでも十分嬉しいなって
まずはそこからで、徐々に高度にステップゕップすればいいんじゃないかな
Expressで使えないのが痛い&Premium以上でないと静的チェッカーが使えないのが大変痛いので、将来は何とかして欲しいと切実に願う
まとめ