kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

Visual Studio 2010 後の世代のプログラミング(1) Code Contracts


Visual Studio 2010 「後」って事で VS2010 には正式には組み込まれてないんだけど、その後にリリースされた各種拡張がどんな感じでプログラミングに影響を与えるかの話。

(1)って事で連載。

 

というわけで最初は Code Contracts、Code Contracts については一回書いてるけど素晴らしく使える代物って事で導入から設定まで流した上で軽くイントロダクション

まずダウンロード、DevLabs Code Contracts から Download Standard Edition / Download Premium Edition どっちか選んでインストールする。僕は Premium を選んでインストールしたぜって事でそれをベースに書きます。

(ってか Static Checker 無いと契約違反をビルド時に検出できないんで魅力半減っていうより魅力なっしんぐ)

これをインストールするとプロジェクトのプロパティに Code Contracts にページが追加されます。追加されたページを通して一通りの設定をすると、Code Contracts による契約チェックが利用できます。

まず、デバッグ設定から、Static Checking のチェックを有効にして静的解析を有効にします。そして、Check in Background のチェックを外します。多少ビルド時間がかかる弊害があるっちゃあるのですが、ここにチェック入ってるとVisual Studio のエラーと警告の所に出ませんので、いくらチェックしても無駄です。

次に Release 設定、自分は以下をお勧めしてます。

Perform Runtime Contract Checking にチェック、ドロップダウンでは Precondition、Only Public Surface で公開部分の呼出しについてチェックを有効にします。

こんぐらいが実行時パフォーマンスに影響を与えず、本来 ArgumentExceptionが飛ぶべき時に飛んでくれるとかの最低限のラインだと思います。

んでオモムロにビルドすると CLR4 なプロジェクトでは結構色々警告されると思います。これは .NET Framework 4の各種アセンブリには Code Contracts の契約情報がデフォルトで埋め込まれてるので .NET Framework コンポーネントを普通に使ってるところで変なパラメータを渡してると契約にマッチしてないよと確認してもらえるからです。よーするにCode Contracts有効にしてる静的チェックを有効にすると ArgumentNullException が飛んでくるようなコードは警告が出ます。デバッガなんて必要ナッシング。

これが思わぬバグを見つける事は良くあります。

自分のバグは恥ずかしいので他人のバグという卑怯な振る舞いしますが WCF で XML-RPC を扱うサンプルを MS の中の人が書いているんですが ( XML-RPC with WCF (Updated) ) Code Contract が以下の部分を警告します。

class XmlRpcDataContractSerializationHelper . Serialize

                else if (valueType == typeof(Stream))
                {
                    int chunkSize = 1024 * 5;
                    byte[] buffer = new byte[chunkSize];
                    int offset = 0;
                    int bytesRead;
                    writer.WriteStartElement(XmlRpcProtocol.ByteArray);
                    do
                    {
// CodeContracts: requires unproven:  count <= (buffer.Length – offset)    
                        bytesRead = ((Stream)value).Read(buffer, offset, buffer.Length);     
                        writer.WriteBase64(buffer, 0, bytesRead);
                        offset += bytesRead;
                    }
                    while (bytesRead == buffer.Length);
                    writer.WriteEndElement();
                }

どんなバグかわかります?

よくやりがちなんですが、Stream.Read の offset パラメータは、ストリームのオフセットじゃなく、格納先バッファのオフセットです。これを間違えている事がこの警告の原因です。offset でなく0を与えればいいわけですね。(そうすると offset を誰も使いやしねーよって ReSharper が言ってくれるので、offset 変数とその操作をまるまる消す事でこのバグが解消します)

どう?すごいでしょ?僕はコードを動かしてテストでバグを探すなんて事を全くしてないわけです。でもバグを発見できるし直すこともできます。Code Contracts を有効にするだけでバグったコードを書くと警告してくれるわけです。いまデバッガ叩きながら必死にデバッグしてるそこのあなた!Code Contractsの静的解析通してみたら「そのバグの原因ここよ!」が一発でわかるかもしれませんですよ。

 

んで、Code Contracts を有効にするとフレームワーク呼出しのこの警告をまず消す事になるんですが、基本的には Contract.Assert で警告された事をまず書くだけで消えます。Contract.Assert( hoge!=null ); とかで null じゃない事を確認してあげれば良いわけです。これをやるときにあからさまにオカシイ事やってると普通の人は気付きますよね。これが Code Contractsの効果です。デバッグ版でテストを走らせれば該当部分を通ればきっと Assert されるでしょう。

Assertの場合には実際に確認のコードが生成されます。確認処理が重くなりかねない場合などは Contract.Assume を使う事でここではこうなってる「はず!」と言い聞かせる事ができ、これで警告は消えますが、その「はず!」が満たされてる事はテストなりなんなりでちゃんと確認した方が良いでしょうね。

もちろんパラメータで受け取ったものとかを AssertやAssumeするのはおかしな話で事前条件として Contract.Requires で書いた方がいいでしょう。

また、「var x = 自分で作ったメソッド(); FrameworkMethod(x) 」という風に自分のメソッドの戻り値を Contract.Ensure してあげれば良いわけですね。

という訳で Ensure が出てきました。戻り値に対する Contract.Ensure はちょっと特殊な書き方をします。

Contract.Ensures(Contract.Result<T>()!=null)

このように Contract.Result() に対して条件を記述します。Ensureはメソッド内のどこに書いても構いませんがメソッドからの戻り時にこの条件がチェックされます。

既存コードの警告つぶしをやってると大抵は Contract の書き方に慣れます、そこから interface に対する契約とか、設計での活用に進むと良いでしょう。

という訳で「僕と契約しないか?」な Code Contracts の紹介でした。次回は Contracts に絡めて Pex を使ってコードカバレッジの高いテストの自動生成です。

コメントを残す