33
契約プログラミング ++C++; 岩永 信之

Code Contracts in .NET 4

  • Upload
    -

  • View
    6.000

  • Download
    3

Embed Size (px)

Citation preview

契約プログラミング

++C++;

岩永 信之

Design by Contract

Effel言語が最初に導入して…

1985年

Bertrand Meyerによって作られる

実はEffel Software社の商標らしい

ので、Contract Programmingとか別呼称が流行る

で、Contract(契約)って何?

契約って何?

フォーマルな仕様

契約

契約=形式的、詳細、検証可能な仕様

仕様は可能な限りツールを使って機械的な検証ができた方がいい

• 仕様は誰が何に使うもの?• 何なら機械的な検証が可能?

仕様は誰がどう使う?

• クラスやメソッドの利用者が使う

ドキュメント

• 静的チェッカー(ビルド ツール)が使う

型情報

• 動的チェッカー(テスト ツール)が使う

テスト記述

ここでいうドキュメント

上流工程的なのでなくて

1cm/○○万円

ここでいうドキュメント

クラスやメソッドの利用者が見るやつ

つまり、↓これ

書き方に迷った時に見る:• リファレンス ドキュメント• サンプル コード• 等々

ドキュメント

理想: 実行可能なコード自体がドキュメントであるべき

クラス名、メソッド名、シグネチャ、すべてがドキュメント

コードで表現できないことを、やむなく自然言語で補足

コードの表現力が上がれば、補足は減る

契約 = コードの表現力向上

検証可能な仕様

型情報

メソッドの名前やシグネチャも仕様の一種

static void Main(string[] args){

var y = F(10);}

static int F(int x){

return x * x;}

• Fというメソッドがなければエラー• int以外を渡すとエラー

静的な型付けの言語では静的に検証可能

型情報に表れない仕様

型情報に表れない仕様はどう書く?

Docコメント?

/// <summary>/// なんか変換する。/// </summary>/// <param name="s">変換元。null であってはいけない。</param>/// <returns>変換結果。</returns>public static string Convert(string s)

• で、誰が保証するの?• 違反するとどうなるの?

検証用コード

普通、引数チェック入れるよね

DRY原則に反してる!

/// <summary>/// なんか変換する。/// </summary>/// <param name="s">変換元。null であってはいけない。</param>/// <returns>変換結果。</returns>public static string Convert(string s){

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

同じ意味のことを2か所に書いてる

コードの方だけでいい?

じゃあ、Docコメントの方消してみると…

例外発生してみて初めて利用者に見える

利用者に見えなくなる

/// <summary>/// なんか変換する。/// </summary>/// <param name="s">変換元。</param>/// <returns>変換結果。</returns>public static string Convert(string s){

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

そこで、契約

↓これと同じような意味になる何かが必要

/// <param name="s">変換元。null であってはいけない。</param>/// <returns>変換結果。</returns>public static string Convert(string s){

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

検証コード

利用者から見える情報

契約 = 検証可能な仕様

さて、じゃあ、どう書こう

契約用の新文法が欲しいところだけども

例えば・・・Spec#ではこう書く

public static string Convert(string s)requires s != null;ensures result != null;

{…

}

これは、じゃあ、どうコンパイルされるべき?.NETの場合、複数の言語での情報共有が必須

Spec# = C# + 契約

どうコンパイルされるべき?

属性は・・・

与えれる引数が組み込み型に限られる

→ 複雑な条件を書けない

ILの仕様を変更する?

サード パーティ製含め、全てのコンパイラーに変更を強要する?

Code Contracts

普通の検証コード + IL書き換え

Code Contracts

契約を、普通にコードとして書く

そして、

コードから契約を読み取って静的解析

ビルド時IL書き換えでテスト用の検証コードを埋め込み

ちゃんとXMLドキュメントに反映

public static string Convert(string s){

Contract.Requires(s != null);Contract.Ensures(Contract.Result<string>() != null);…

}

IL書き換え

動的チェックする場合にはIL書き換え

Contract.Requires(s != null);Contract.Ensures(Contract.Result<string>() != null);return s.ToUpper();

Debug.Assert(s != null, "s != null");var t = s.ToUpper();Debug.Assert(t != null,

"Contract.Result<string>() != null");return t

最初からAssertでよくない?

メタデータにならない

Requires/Ensuresとかの情報が消える

Debug.Assert(s != null, "s != null");var t = s.ToUpper();Debug.Assert(t != null, "Contract.Result<string>() != null");return t

エラーにすべき理由がわからない静的チェック時に困る

契約と証明

契約が守られているか、静的に解析

契約として表明する条件

事前条件(precondition) Contract.Requires(条件)

事後条件(postcondition) Contract.Ensures(条件)

不変条件(invariant) Contract.Invariant(条件)

事前条件

メソッドに入る瞬間に満たすべき条件

public static string Convert(string s){

Contract.Requires(s != null);…

string s = null;var t = Convert(s);

ダメ!絶対!

事後条件

メソッドを出る瞬間に満たすべき条件

public static string Convert(string s){

Contract.Ensures(Contract.Result<string>() != null);…

var t = Convert(s);if (t != null) …

チェックしなくても非null保証あり

不変条件

常に変わらない条件

public int X { get; set; }public int Y { get; set; }

[ContractInvariantMethod]void CheckInvariant(){

Contract.Invariant(X + Y == 0);}

証明

証明(prove)

契約が満たされているか静的に検証

// 事後条件: null は返さないよ。public static string GetString() { … }

// 事前条件: s は null を認めないよ。public static string Convert(string s) { … }

var s = GetString();var t = Convert(s);

nullは返ってこない

nullは渡らない

間にnullチェック不要

制御フローも見つつ証明

コード全体を解析

if (s != null){

var t = Convert(s);} nullは渡らない

条件分岐してるんだから

契約はあくまで自己申告

全てのメソッドが正しく契約を表明しているとは限らない

// 契約に未対応。public static string GetString() { … }

// 事前条件: s は null を認めないよ。public static string Convert(string s) { … }

var s = GetString();var t = Convert(s);

証明不能

var s = GetString();Contract.Assume(s != null);var t = Convert(s);

しょうがないから「仮定」を入れる

動的なテストで確認

暗黙的な検証条件

もちろん、逆に、明示的な表明しなくてもコードから推定できる条件も

メンバー呼んでたらnon-null

x/y してたら y != 0

配列の境界チェック

コード書き換え

IL書き換え?

基本、全部[Conditional]付きで、そのままだと実行されないpublic static object GetData(int i){

Contract.Requires(i > 0);Contract.Ensures(Contract.Result<object>() != null);…

}

だって、戻り値に対する事後条件とか、メソッド中でチェックできるわけじゃないじゃない。

Requiresとかの中に掛けるメソッドは[Pure]でないと

Pure=副作用がない

[Pure]属性はあくまで自己申告なのだけども・・・

public int X { get; set; }public int Y { get; set; }

[ContractInvariantMethod]void CheckInvariant(){

Contract.Invariant(Norm == 0);}

[Pure]public int Norm { get { return X + Y; } }

問題点

契約の表明はあくまで自己申告

ウソつける(部分もある)

契約表明してないメソッドが1個でもあると・・・

それ使うたびにチェック コード挿入しないといけなくなる

静的検証すごく重い

C#の普通の文法チェックみたいに、リアルタイム チェック無理

あと、IL書き換えとか、どうなんだろうね

まとめ: 契約とは

契約=形式的仕様

利用者に見えるべき

可能な限り静的チェックすべき

静的チェックに漏れる分は動的チェックする

つまり

契約はドキュメントに含まれるべき

契約は静的チェックの対象(型情報の一部)であるべき

契約は動的チェックの対象(テスト仕様)であるべき

まとめ: Code Contracts

普通にコードで契約を書く

静的チェックしてくれる

IL書き換えで動的チェック

ドキュメントにも反映