CsharpThinking---代码契约CodeContract(八)
代码契约(Code Contract):它并不是语言本身的新功能,而是一些额外的工具,帮助人们控制代码边界。
代码契约之于C#,就相当于诗词歌赋之于语言。 --- C# in Depth
一,概述
1.1 未引入“代码契约(特指MS代码契约)”之前的状态---“契约”
• 契约:20世纪80年代,Bertand Meyer在设计Eiffel语言时就将其作为重要的部分。已有大量的计算机科学研究开始探究正式的规范说明和验证,它允许在编译时检查程序的正确性,不过契约的作用还不止于此。
• 契约编程的核心理念是将API的需求和承诺与实现相分离。
• 契约约定比文档约定方式更“同步”一些。
• 或使用Debug.Assert方式,但其不能在Release构建时不会捕获非法参数。
/************************使用代码契约方式之前*****************************/
/// <summary>
/// Counts the number of whitespace characters in <paramref name="text"/>
/// </summary>
/// <param name="text">String to examine. Must not be null.</param>
/// <example cref="ArgumentNullException"><paramref name="text"/> is null.</example>
/// <returns>The number of whitespace characters</returns>
public static int CountWhiteSpace(string text)
{
if (string.IsNullOrEmpty(text))
throw new ArgumentNullException("text");
return text.Count(char.IsWhiteSpace);
}
/************************使用代码契约方式之前*****************************/
/// <summary>
/// 说明:只能在Debug模式下规范”契约“。
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static int CountStringLength(string text)
{
// 命名空间:using System.Diagnostics;
Debug.Assert(text != null);
return text.Length;
}
1.2 代码契约
• C#代码契约起源于微软开发的一门研究语言Spec#(参见http://mng.bz/4147)。
• 契约工具:包括:ccrewrite(二进制重写器,基于项目的设置确保契约得以贯彻执行)、ccrefgen(它生成契约引用集,为客户端提供契约信息)、cccheck(静态检查器,确保代码能在编译时满足要求,而不是仅仅
检查在执行时实际会发生什么)、ccdocgen(它可以为代码中指定的契约生成xml文档)。
• 契约种类:前置条件、后置条件、固定条件、断言和假设、旧式契约。
• 代码契约工具下载及安装:下载地址Http://mng.bz/cn2k。(代码契约工具并不包含在Visual Studio 2010中,但是其核心类型位于mscorlib内。)
• 命名空间:System.Diagnostics.Contracts.Contract
• 代码契约优势:编译前执行检查(如若设置异常类型使用EnsuresOnThrow<Exception>则会在编译时抛出异常),这样比较编译后检查有明显的优势。
1.3 契约种类介绍
• 前置条件(precondition):是对方法调用者提出的要求,而不是表示普通条件下方法本身的行为。Contract.Requires<T>();
/// <summary>
/// 实现“前置条件”的代码契约
/// </summary>
/// <param name="text">Input</param>
/// <returns>Output</returns>
public static int CountWhiteSpace(string text)
{
// 命名空间:using System.Diagnostics.Contracts;
Contract.Requires<ArgumentNullException>(text != null, "Paramter:text");// 使用了泛型形式的Requires
return text.Count(char.IsWhiteSpace);
}
• 后置条件(postcondition):表示对方法输出的约束:返回值、out或ref参数的值,以及任何被改变的状态。Ensures();
/// <summary>
/// 实现“前置条件”的代码契约
/// </summary>
/// <param name="text">Input</param>
/// <returns>Output</returns>
public static int CountWhiteSpace(string text)
{
// 命名空间:using System.Diagnostics.Contracts;
Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(text), "text"); // 使用了泛型形式的Requires
Contract.Ensures(Contract.Result<int>() > ); // 1.方法在return之前,所有的契约都要在真正执行方法之前(Assert和Assume除外,下面会介绍)。
// 2.实际上Result<int>()仅仅是编译器知道的”占位符“:在使用的时候工具知道它代表了”我们将得到那个返回值“。
return text.Count(char.IsWhiteSpace);
} public static bool TryParsePreserveValue(string text, ref int value)
{
Contract.Ensures(Contract.Result<bool>() || Contract.OldValue(value) == Contract.ValueAtReturn(out value)); // 此结果表达式是无法证明真伪的。
return int.TryParse(text, out value); // 所以此处在编译前就会提示错误信息:Code Contract:ensures unproven: XXXXX
}
• 固定条件(invariant):它们是只要对象的状态可见,就必须自始至终遵循的契约。换句话说,在类的公共方法运行时,固定条件可以改变,但在方法的最后,它们仍要满足契约。
public sealed class CardGame
{
readonly Stack<Card> deck = new Stack<Card>(Card.CreateFullDeck());
readonly Stack<Card> discardPile = new Stack<Card>();
readonly List<Player> players = new List<Player>(); public void DealCard(Player p)
{
players.Add(p);
} [ContractInvariantMethod]
private void ObjectInvarint()
{
Contract.Invariant((deck.Count + discardPile.Count + players.Sum(p => p.CardCount)) == Card.FullDeckSize); // 校验总和是否一致。
}
}
特性:1. 固定条件方法是无参数、无返回值、私有的。
2. 用[ContractInvariantMethod]标签修饰。
3. 执行起来代价低廉,不必担心性能损失。
4. 如果检查集合内容还可用Contract.ForAll or Contarct.Exists.
[ContractInvariantMethod]
private void InvarintCollection()
{
Contract.Invariant(Contract.ForAll(players, item => item.Name != "Stephen")); // 校验集合。
Contract.Invariant(!Contract.Exists(players, item => item.Name != "Stephen")); // 与上句作用相同。
}
• 断言和假设 :可检测在代码进行到“一半”时发生的事情。
断言Assert:静态检查器会检测Assert是否正确,而Assume不会。大多数情况下使用。
假设Assume:正因为静态检查器不会检查,所以某些情况下需要过滤掉静态检查器无法检验的东西。
public static int RollDic(Random rng)
{
Contract.Ensures(Contract.Result<int>() >= ); if (rng == null)
rng = new Random();
Contract.Assert(rng != null); int firstRoll = rng.Next(, );
Contract.Assume(firstRoll >= );
Contract.Assume(firstRoll <= ); return firstRoll;
}
• 旧式契约:本质上是另一种形式的前置条件。DoNet2.0支持。
public static int CountSpace(string text)
{
if (text == null)
throw new ArgumentNullException("text");
Contract.EndContractBlock(); // 此方法不做任何事情,只是让二进制编译器知道,此句以上的部分是契约。 return text.Count(char.IsWhiteSpace);
}
二, 使用ccrewrite和ccrefge重写二进制
2.1 契约重写:重写刚刚获取的程序集的某些部分,以替换原始的程序集。
替换的事件序列:
• 检查前置条件。
• 为OldValue方法调用获取初始的状态。
• 执行代码的功能部分,如Assert
• 检查后置条件
• 检查固定条件
2.2 契约继承(重要)
• 特性:1. 覆盖某方法(或实现某个接口方法,规范契约)会继承该方法中的契约。
2. 不能再继承的方法中添加额外的前置条件,但却可以添加固定条件和后置条件。必须符合Liskov替换原则(里氏代换原则http://zh.wikipedia.org/wiki/Liskov%E4%BB%A3%E6%8F%9B%E5%8E%9F%E5%89%87)。
• 继承:使其子类也有父类中的前置条件、后置条件等,作为一种条件约束,但也有其限制,而且继承很容易被滥用,不如实现接口方式稳妥。
[Pure]
public static bool Report(string text)
{
Console.WriteLine(text);
return true;
} public class ContractBase
{
public virtual void VirtualMethod(string text)
{
Contract.Requires(Report("Base precondition"));
Contract.Ensures(Report("Base postcondition"));
}
} public class Derived : ContractBase
{
public override void VirtualMethod(string text)
{
Contract.Ensures(Report("Dervied postcondition"));
}
} /* *******************************************************
* 结果:
* Base precondition
* Dervied postcondition
* Base postconditon
*
* 注:尽管Dervied中覆写的方法没有调用base.VirtualMethod(),
* 契约仍然被执行。
* *******************************************************/
• 为接口指定契约:
/*************************************************************
* 说明:
* 1. ICaseConverter与ICaseConverterContracts互为实现(相互反向
* 引用)。
* 2. ICaseConverter只定义接口方法。
* 3. ICaseConverterContracts抽象类中实现接口的约束,内部要定义成
* 私有。
* 4. CurrentCultrueUpperCaseFormatter实现接口ICaseConverter,而
* 与抽象类隔离。
* ***********************************************************/
/// <summary>
/// 接口类
/// </summary>
[ContractClass(typeof(ICaseConverterContracts))] // 指定契约类
public interface ICaseConverter
{
string Convert(string text);
} /// <summary>
/// 契约类
/// </summary>
[ContractClassFor(typeof(ICaseConverter))] // 声明契约所服务的类型
internal abstract class ICaseConverterContracts : ICaseConverter
{
public string Convert(string text)
{
Contract.Requires(!string.IsNullOrEmpty(text)); // 前置条件
Contract.Ensures(Contract.Result<string>() != null); // 后置条件
return default(string);// 如果没有实现此类,则返回默认值。
} private ICaseConverterContracts() { } // 禁止实例化该类
} /// <summary>
/// 实现类
/// </summary>
public class CurrentCultrueUpperCaseFormatter : ICaseConverter // 继承接口中的契约
{
public string Convert(string text)
{
return text.ToUpper(CultureInfo.CurrentCulture); // 实现接口方法。(由二进制重写器执行检查)
}
}
• 失败行为
Contract全局失败事件:Contract.ContractFailed ,可注册并捕获所有Contract失败事件
Contract.ContractFailed += new EventHandler<ContractFailedEventArgs>(Contract_ContractFailed); ...
static void Contract_ContractFailed(object sender, ContractFailedEventArgs e)
{
Console.WriteLine("{0}:{1},{2}", e.FailureKind, e.Condition, e.Message);
e.SetHandled();
}
三, 静态检查
• 意义:在编译时执行检查,任何错误将在Error List中显示警告信息和错误信息。
static string DontPassMeNull(string input)
{
Contract.Requires(input != null);
Contract.Ensures(Contract.Result<string>() != null);
return input;
} static string MightReturnNull()
{
return "Not null really";
} /// <summary>
/// Error list中显示警告信息
/// </summary>
public static void DoTest()
{
DontPassMeNull("definitely okay"); // 总能通过
DontPassMeNull(MightReturnNull()); // CodeContracts:requires unproven:input != null 无法证明
DontPassMeNull(null); // 提示错误信息
}
• 静态检查选项:在属性页中选择Implicit Non-null Obligations选项可执行前置空引用的检查,Implicit Array Bounds Obligations选项可检查数组是否越界。
• 有选择性的执行检查
1. BaseLine(基线)方法:
在属性页中,选中:
则,在程序跟目录会生成baseline.xml文件,包含所有警告信息。
将错误导出到文件中有利于分析错误。
2. 特性控制检查:
[assembly: ContractVerification(false)]
[ContractVerification(false)]
四,后记(契约实战)
4.1 契约是一种稳固的保障:它不仅意味着在满足前置条件时,代码将以特定的方式运行,还意味着在不满足的时候,就不会继续执行。
4.2 不要考虑为不易变类型设置固定条件。如果某些情况不会改变类型的状态,那它也肯定不会破坏固定条件。相反,如果在构造时有必须遵循的规则,那么也应该是前置条件。
更多关于“代码契约”内容请阅读《C# in Depth》
官方文档:http://download.csdn.net/detail/cuiyansong/5580307
CsharpThinking---代码契约CodeContract(八)的更多相关文章
- 代码契约CodeContract(八)
代码契约(Code Contract):它并不是语言本身的新功能,而是一些额外的工具,帮助人们控制代码边界. 代码契约之于C#,就相当于诗词歌赋之于语言. --- C# in Depth 一,概述 1 ...
- x264代码剖析(八):encode()函数之x264_encoder_close()函数
x264代码剖析(八):encode()函数之x264_encoder_close()函数 encode()函数是x264的主干函数.主要包含x264_encoder_open()函数.x264_en ...
- WebShell代码分析溯源(八)
WebShell代码分析溯源(八) 一.一句话变形马样本 <?php $e=$_REQUEST['e'];$arr= array('test', $_REQUEST['POST']);uasor ...
- 编写高质量的Python代码系列(八)之部署
Python提供了一些工具,使我们可以把软件部署到不同的环境中.它也提供了一些模块,令开发者可以把程序编写的更加健壮.本章讲解如何使用Python调试.优化并测试程序,以提升其质量与性能. 第五十四条 ...
- WebRTC代码走读(八):代码目录结构
转载注明出处http://blog.csdn.net/wanghorse ├── ./base //基础平台库,包括线程.锁.socket等 ├── ./build //编译脚本,gyp ├── ./ ...
- django-cms 代码研究(八)app hooks
app钩子,啥玩意呢? 就是把现有的app,集成到cms的一种手段. 有两种实现方式: 1) 定义cms_app.py,如下: from cms.app_base import CMSApp from ...
- WebRTC代码走读(八):代码文件夹结构
转载注明出处http://blog.csdn.net/wanghorse ├── ./base //基础平台库,包含线程.锁.socket等 ├── ./build //编译脚本.gyp ├── ./ ...
- 寻找写代码感觉(八)之SpringBoot过滤器的使用
一.什么是过滤器? 过滤器是对数据进行过滤,预处理过程,当我们访问网站时,有时候会发布一些敏感信息,发完以后有的会用*替代,还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的, ...
- python 接口自动化测试--代码实现(八)
用例读入数据库: #! /usr/bin/python # coding:utf-8 import sys,os from Engine import DataEngine reload(sys) s ...
随机推荐
- 03_汇编语言(n个数找最大值)
程序要求: 先输入一个数n(0<n<=100),再输入n个无符号数K(0<=K<=65535),找出这n个数的最大值并输出 测试实例保证输入每个数之后,都会以回车结束 代码: ...
- Linux学习之五——压缩与备份
一.Linux下常见的压缩文件 *.Z compress 程序压缩的档案(现在不流行了,用gzip也能解压): *.gz gzip 程序压缩的档案: *.bz2 bzip2 程序压缩的档案: *.ta ...
- [转]Asp.NET MVC Widget开发 - ViewEngine
本文转自:http://www.cnblogs.com/hsinlu/archive/2011/03/02/1968796.html 在Asp.NET开发博客类系统,我们经常都会用到Widget,像在 ...
- Laxcus大数据管理系统2.0(5)- 第二章 数据组织
第二章 数据组织 在数据的组织结构设计上,Laxcus严格遵循数据和数据描述分离的原则,这个理念与关系数据库完全一致.在此基础上,为了保证大规模数据存取和计算的需要,我们设计了大量新的数据处理技术.同 ...
- selenium处理div生成弹框
目前遇到的弹框有两种,一种是alert,一种是div,如果遇到div模拟的弹框,在用alert就不行了. 1. public static Alert getAlert(WebDriver dr) { ...
- 我是如何来做网站优化(Seo)的?(二)
在上一篇文章<我是如何来做网站优化(Seo)的?(一)>讲述了网站优化几个注意项目. 在我们日常网站维护中,难免会使用到各种工具,文章的第二节我们就来讲讲这些工具. 网站提交通道 百度站长 ...
- 搜索服务Solr集群搭建 使用ZooKeeper作为代理层
上篇文章搭建了zookeeper集群 那好,今天就可以搭建solr搜服服务的集群了,这个和redis 集群不同,是需要zk管理的,作为一个代理层 安装四个tomcat,修改其端口号不能冲突.8080~ ...
- 使用Unity开发Android的几种调试方法
前言 本文举例几种Android 调试的方法(PS:我是通过unity引擎来开发安卓游戏) Eclipse + adt 查看LOG 1.为Eclipse 装上adt 插件 2.打开Eclipse 的L ...
- Fidder--实现手机的抓包
今天闲着没吊事,来写一篇关于怎么抓取Android中的app数据包?工欲行其事,必先利其器,上网google了一下,发现了一款神器:Fiddler,这个貌似是所有软件开发者必备神器呀!这款工具不仅可以 ...
- 【有奖】NOIP普及组模拟赛 个人邀请赛 乐多赛
题目描述 日本数学家角谷有一个猜想:任意一个自然数,经过以下过程,最终会得到1.现在请你打印出任意一个数使用角谷猜想转换为1需要几次. 演变方式: 1.如果这个数为奇数,则将它×3+1.如果这个数为偶 ...