Share
Embed
基礎からの Code Contracts
Yoshifumi Kawai
May 23, 2011
Transcript
35 slides
-
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 -
よくある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の構成物 -
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コードスニペットを使えば展開される 事後・不変・インターフェイス -
静的チェッカなしだと、どうも地味 ⚫ Premiumの人なら関係ないですねShit! そんな物足りなさを感じるアナタにVisualな贈り物 VS拡張:Code Contracts Editor
Extensions http://visualstudiogallery.msdn.microsoft.com/85f0aa38 -a8a8-4811-8b86-e7f0b8d8c71b 契約がIntelliSenseに表示される! FreeなのでVS Professionalの人でもOK 動かしたけど嬉しさ少なめ? -
標準ライブラリは何もしなくても表示される 自作の契約はReference Assemblyを作る必要がある Reference Assemblyはクラスライブラリなど、契約 が除去されたリリース用バイナリを参照する他の ライブラリが契約情報を参照したい場合に必要
(但し、決してリライト後のバイナリに契約を再 度埋め込めれるわけではない) 使い方 -
コンストラクタは表示されません ジェネリックメソッドは表示されません ⚫ Enumerable.Rangeは表示されるのにRepeatは表示さ れなかったりしてるのが確認できます ⚫ つまるところLINQのメソッドは全滅
yieldが含まれると表示されません dynamicが含まれると表示されません よく落ちます(落ちたらVS再起動まで復活しない) Editor Extensionsに関してはアルファ版だと思って 暖かく見守りましょう 但し制限も色々あり -
引数名を文字列で指定しなくてもいい ⚫ リライターが埋め込んでくれるから ⚫ コードスニペット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内で使えるメソッドは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 -
// これは静的チェッカで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コードスニペットがあるとはいえ そして平穏が訪れる -
.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以上でない と静的チェッカーが使えないのが大変痛いので、 将来は何とかして欲しいと切実に願う まとめ