Upgrade to Pro — share decks privately, control downloads, hide ads and more …

基礎からの Code Contracts

基礎からの Code Contracts

Yoshifumi Kawai

May 23, 2011
Tweet

More Decks by Yoshifumi Kawai

Other Decks in Technology

Transcript

  1.  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
  2.  よくあるnullチェックをしてみようと思った ⚫ Contract.Requiresは事前条件 ⚫ 引数がnullだったら契約違反という感じにしたい static void Hoge(string arg)

    { Contract.Requires(arg != null); }  が、実行しても無反応  Conditional属性がついているのでコンパイル時に 消える(条件付きメソッド、DEBUGとかでお馴染み) ⚫ 条件はCONTRACTS_FULL(但し自分で足す意味はない) 何か動かないよ?
  3.  よくあるnullチェックをしてみようと思った again ⚫ Contract.Requires<TException>も事前条件 ⚫ 引数がnullだったら契約違反で例外ぶん投げたい static void Hoge(string

    arg) { Contract.Requires<ArgumentNullException>(arg != null); }  が、変なアサートが飛ぶ  そしてアプリは強制終了  リライターがmustだと? 何か動かないよ? Part2
  4.  Code Contractsの利用にはリライターが必要 最終的な配布物はコンパイラオプションで契約用コードを 取り除く。従って実行効率にも影響しない。 http://ja.wikipedia.org/wiki/契約プログラミング  契約は取り除かれなければならない  そのためにはライブラリだけでは不可能で、コン

    パイル時にバイナリを弄る必要がある  契約の実現のため、現状はバイナリ改変している ⚫ 真に標準搭載されたと言えるのはリライターがコン パイラと統合された時かもね つまるところ
  5.  必須 ⚫ Contractクラスなどコードに記述するマーカー ⚫ .NET 4で現状標準搭載されているのはこれだけ ⚫ バイナリリライター(ccrewrite.exe) 

    オプション ⚫ 参照ライブラリ生成(ccrefgen.exe) ⚫ ドキュメント生成(ccdocgen.exe) ⚫ 静的チェッカー(cccheck.exe) ⚫ cccheckはPremium Editionのみ ⚫ 静的チェックなしの場合は、例外orアサートを投げる実 行時チェックという形になる Code Contractsの構成物
  6.  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のインストール
  7.  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); 事前条件
  8.  EndContractBlockはレガシー環境用 ⚫ バイナリリライターがある環境が前提なら不要  Assembly Modeの選択 ⚫ Requires, Requires<TE>はStandard

    Contract ⚫ EndContractBlockを使う場合はCustom Parameter  無印と<TE>ではリライト時に残るレベルが違う ⚫ 無印の場合はReleaseRequiresでは除去される  DebugはFull、ReleaseではPreまたはReleaseを推奨 事前条件の違い
  9.  事後 : Contract.Ensures ⚫ 戻り値を表すContract.Result<T>とセットで使うこと が多い  不変 :

    Contract.Invariant ⚫ ContractInvariantMethod属性とセットで ⚫ cimコードスニペットを使えば展開される  インターフェイスへの契約 ⚫ 書くのがヘンテコで面倒くさい ⚫ cintfコードスニペットを使えば展開される 事後・不変・インターフェイス
  10.  静的チェッカなしだと、どうも地味 ⚫ Premiumの人なら関係ないですねShit!  そんな物足りなさを感じるアナタにVisualな贈り物  VS拡張:Code Contracts Editor

    Extensions http://visualstudiogallery.msdn.microsoft.com/85f0aa38 -a8a8-4811-8b86-e7f0b8d8c71b  契約がIntelliSenseに表示される!  FreeなのでVS Professionalの人でもOK 動かしたけど嬉しさ少なめ?
  11.  コンストラクタは表示されません  ジェネリックメソッドは表示されません ⚫ Enumerable.Rangeは表示されるのにRepeatは表示さ れなかったりしてるのが確認できます ⚫ つまるところLINQのメソッドは全滅 

    yieldが含まれると表示されません  dynamicが含まれると表示されません  よく落ちます(落ちたらVS再起動まで復活しない) Editor Extensionsに関してはアルファ版だと思って 暖かく見守りましょう 但し制限も色々あり
  12.  引数名を文字列で指定しなくてもいい ⚫ リライターが埋め込んでくれるから ⚫ コードスニペット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
  13.  インターフェイスに契約すると、それを実装する ものへは何も書かなくても自動で埋め込まれる // こうしてインターフェイスへの契約を作ると(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
  14. 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); } }  これにより、積極的なインターフェイスの抽出と 契約の記述が促されます(不純動機ドリブン) それはとっても嬉しいなって
  15.  静的チェッカーで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
  16.  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の基本
  17.  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
  18. // これは静的チェッカで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
  19. // (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
  20. // 面倒くさくて耐え切れない時は静的検証オフ属性をつけてやる [ContractVerification(false)] static Func<object, object> Create(Type type, string name)

    { // (中略) }  Contract.Ensures(Contract.Result<T>() != null); がど れだけ大事かが身にしみて分かる ⚫ しかし定型句すぎて面倒くさいのは事実…… ⚫ cenコードスニペットがあるとはいえ そして平穏が訪れる
  21.  .NET4標準に入っているContractsライブラリの他に、 幾つか追加の属性が C:¥Program Files (x86)¥Microsoft¥Contracts¥Languages¥CSharp に ある(.csファイルぽん置き) ⚫ 使い方の詳細はマニュアルに載ってる

     Microsoft Researchで開発されている自動パラメタ ライズドテストPexに対してContractsが記述されて いると、有効な自動生成パラメータが生成できる ようになる http://research.microsoft.com/en-us/projects/pex/ その他
  22.  メリットを幾つかあげましたが、忘れてはならな い基本的なことは、「事前・事後・不変」の契約 が出来るということ  でも、堅苦しい理屈だけじゃなく、目で見て分か る実用的な便利さを提供してくれるのはいいね!  if-then-throwを撲滅してくれるというだけでも十 分嬉しいなって

     まずはそこからで、徐々に高度にステップアップ すればいいんじゃないかな  Expressで使えないのが痛い&Premium以上でない と静的チェッカーが使えないのが大変痛いので、 将来は何とかして欲しいと切実に願う まとめ