代码契约(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>();

 1         /// <summary>
2 /// 实现“前置条件”的代码契约
3 /// </summary>
4 /// <param name="text">Input</param>
5 /// <returns>Output</returns>
6 public static int CountWhiteSpace(string text)
7 {
8 // 命名空间:using System.Diagnostics.Contracts;
9 Contract.Requires<ArgumentNullException>(text != null, "Paramter:text");// 使用了泛型形式的Requires
10 return text.Count(char.IsWhiteSpace);
11 }

    • 后置条件(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>() > 0); // 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):它们是只要对象的状态可见,就必须自始至终遵循的契约。换句话说,在类的公共方法运行时,固定条件可以改变,但在方法的最后,它们仍要满足契约。

 1         public sealed class CardGame
2 {
3 readonly Stack<Card> deck = new Stack<Card>(Card.CreateFullDeck());
4 readonly Stack<Card> discardPile = new Stack<Card>();
5 readonly List<Player> players = new List<Player>();
6
7 public void DealCard(Player p)
8 {
9 players.Add(p);
10 }
11
12 [ContractInvariantMethod]
13 private void ObjectInvarint()
14 {
15 Contract.Invariant((deck.Count + discardPile.Count + players.Sum(p => p.CardCount)) == Card.FullDeckSize); // 校验总和是否一致。
16 }
17 }

        特性: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>() >= 2); if (rng == null)
rng = new Random();
Contract.Assert(rng != null); int firstRoll = rng.Next(1, 7);
Contract.Assume(firstRoll >= 1);
Contract.Assume(firstRoll <= 6); 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         /*************************************************************
2 * 说明:
3 * 1. ICaseConverter与ICaseConverterContracts互为实现(相互反向
4 * 引用)。
5 * 2. ICaseConverter只定义接口方法。
6 * 3. ICaseConverterContracts抽象类中实现接口的约束,内部要定义成
7 * 私有。
8 * 4. CurrentCultrueUpperCaseFormatter实现接口ICaseConverter,而
9 * 与抽象类隔离。
10 * ***********************************************************/
11 /// <summary>
12 /// 接口类
13 /// </summary>
14 [ContractClass(typeof(ICaseConverterContracts))] // 指定契约类
15 public interface ICaseConverter
16 {
17 string Convert(string text);
18 }
19
20 /// <summary>
21 /// 契约类
22 /// </summary>
23 [ContractClassFor(typeof(ICaseConverter))] // 声明契约所服务的类型
24 internal abstract class ICaseConverterContracts : ICaseConverter
25 {
26 public string Convert(string text)
27 {
28 Contract.Requires(!string.IsNullOrEmpty(text)); // 前置条件
29 Contract.Ensures(Contract.Result<string>() != null); // 后置条件
30 return default(string);// 如果没有实现此类,则返回默认值。
31 }
32
33 private ICaseConverterContracts() { } // 禁止实例化该类
34 }
35
36 /// <summary>
37 /// 实现类
38 /// </summary>
39 public class CurrentCultrueUpperCaseFormatter : ICaseConverter // 继承接口中的契约
40 {
41 public string Convert(string text)
42 {
43 return text.ToUpper(CultureInfo.CurrentCulture); // 实现接口方法。(由二进制重写器执行检查)
44 }
45 }

          • 失败行为

        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

工程狮必备属性:追新,执着,高内聚低耦合。 版权声明:凡是没有标注[转载]的,在引用文章时,均要加上本博客地址http://www.cnblogs.com/cuiyansong/。
 

轻量级前端MVVM框架avalon - 执行流程1

基本上确定了avalon的几个重要元素的关系:

  • M,即model,一个普通的JS对象,可能是后台传过来的,也可能是直接从VM中拿到,即VM.$json。有关的这个$json的名字还在商讨

  • V,即View,HTML页面,通过绑定属性或插值表达式,呈现数据,处理隐藏,绑定事件或动画等各种交互效果。V只与VM打交道。

  • VM,即ViewModel,我们通过avalon.define("xxx", function(vm){vm.firstName = "正美"}),这里的vm是一个临时的对象,用于定义,真正的VM是avalon.define方法的返回值。它上面的$json属性就是M,可以见VM处于一切的核心。我们对VM的每一个操作,都会向上同步到V,向下同步到M。并且出于节能低碳起见(减少对象的创建),我们在生成M时,会重复利用VM中的一些属性,比如vm的某个属性是一个对象,那么这个对象会直接搬到$json中。若它是一个数组,它里面每个元素为对象,这些数组或对象都会直接$json中去,当然有时会修复一下(比如计算属性会转换一个简单的数据类型)

在MVVM模式中,ViewModel是贯穿整个框架的梁柱

我们现在分析下整个代码如何运作的一个流程:本文不讨论具体实现,只讨论流程

首先自然是HTML结构

<fieldset ms-controller="simple">
<legend>例子</legend>
<p>First name: <input ms-model="firstName" /></p>
<p>Last name: <input ms-model="lastName" /></p>
<p>Hello, <input ms-model="fullName"></p>
<div>{{firstName +" | "+ lastName }}</div>
</fieldset>

观察后得出结论:

@ 与常规结构不同

@ 定义了很多自定义标签  如何:ms-model ,ms-controller

@ 插值表达式 {{}}

.........等等,具体请看api手册

那么接下来开发着定义JS

avalon.ready(function() {
avalon.define("simple", function(vm) {
vm.firstName = "司徒"
vm.lastName = "正美"
vm.fullName = {//一个包含set或get的对象会被当成PropertyDescriptor,
set: function(val) {//set, get里面的this不能改成vm
var array = (val || "").split(" ");
this.firstName = array[0] || "";
this.lastName = array[1] || "";
},
get: function() {
return this.firstName + " " + this.lastName;
}
},
vm.nick = {
name: "暗黑之民"
}
})
})

大概理解下代码的意思:

  1. avalon.ready(function() { //这是domReady,相当于jQuery的 $(function(){})
  2. var model = avalon.define("aaa", [], function(vm) { //创建一个名字为aaa的ViewModel
  3. vm.firstName = "司徒"  //创建一个监控属性
  4. vm.lastName = "正美"    //创建一个监控属性
  5. vm.fullName = {//一个包含set或get的对象 用于创建一个依赖监控属性
  6. avalon.scan();//开始扫描DOM树,处理绑定
第二步就能看出来这个就是定义的一个模型了,vm->ViewModel 视图模型

整个运作都是围绕着vm展开的,mvvm模式中VM处于一切的核心,我们对VM的每一个操作,都会向上同步到V,向下同步到M。
VM的创建不仅仅只是我们看到的定义了几个属性,几个方法,其实框架内部帮我们做了很多事:

具体大概说下

VM模型的创建:

1.框架内部创建模型对象VM

2.VM吸收开发定义的处理方法

3.把开发定义的方法给经内部的转换,它的属性与方法会换胎换骨

4.返回这个被改造过的模型对象,挂到全局保存起来
所以这个里面涉及闭包与作用域链的问题了,以后再慢慢分析

那么这个VM创建好了,我们接下来干嘛呢? 
大家有没有发现在HTML结构中与VM代码中,有没有共同点?

1.HTML中定义的自定义标签与VM中的属性方法名是不是一样?
2.根据API的定义,HTML对应的每一个标签的都会对应着某一种JS的处理方式

3.现在HTML结构与JS代码都是独立的东东,所以我们想个办法让他们关联起来

如何关联?

答案:扫描绑定

avalon.scan();//开始扫描DOM树,处理绑定

简而言之呢就是扫描属性节点,文本节点,找到对应的事件处理器,执行事件绑定等一堆东东

属性节点

<input ms-model="firstName" />
发现有ms-开头的名字就会解析成对应的一个处理方法
比如ms-model
//将模型中的字段与input, textarea的value值关联在一起
var modelBinding = bindingHandlers.model = function(data, scopes) {
var element = data.element;
var tagName = element.tagName;
//是否有对应元素的标签名的处理方法
if (typeof modelBinding[tagName] === "function") {
var array = getValueFunction(data.value.trim(), scopes);
if (array) {
modelBinding[tagName](element, array[0], array[1]);
}
}
当然还会传递一些需要运行的参数

特别指出来传递的实参scopes就是ViewModel对象

所以我们大胆猜测下

* 在这一大堆扫描绑定方法中应该会哪些实现(这些方法才是最终的执行体)

待续...

 
 
分类: MVVM

代码契约CodeContract(八)的更多相关文章

  1. CsharpThinking---代码契约CodeContract(八)

    代码契约(Code Contract):它并不是语言本身的新功能,而是一些额外的工具,帮助人们控制代码边界. 代码契约之于C#,就相当于诗词歌赋之于语言. --- C# in Depth 一,概述 1 ...

  2. x264代码剖析(八):encode()函数之x264_encoder_close()函数

    x264代码剖析(八):encode()函数之x264_encoder_close()函数 encode()函数是x264的主干函数.主要包含x264_encoder_open()函数.x264_en ...

  3. WebShell代码分析溯源(八)

    WebShell代码分析溯源(八) 一.一句话变形马样本 <?php $e=$_REQUEST['e'];$arr= array('test', $_REQUEST['POST']);uasor ...

  4. 编写高质量的Python代码系列(八)之部署

    Python提供了一些工具,使我们可以把软件部署到不同的环境中.它也提供了一些模块,令开发者可以把程序编写的更加健壮.本章讲解如何使用Python调试.优化并测试程序,以提升其质量与性能. 第五十四条 ...

  5. WebRTC代码走读(八):代码目录结构

    转载注明出处http://blog.csdn.net/wanghorse ├── ./base //基础平台库,包括线程.锁.socket等 ├── ./build //编译脚本,gyp ├── ./ ...

  6. django-cms 代码研究(八)app hooks

    app钩子,啥玩意呢? 就是把现有的app,集成到cms的一种手段. 有两种实现方式: 1) 定义cms_app.py,如下: from cms.app_base import CMSApp from ...

  7. WebRTC代码走读(八):代码文件夹结构

    转载注明出处http://blog.csdn.net/wanghorse ├── ./base //基础平台库,包含线程.锁.socket等 ├── ./build //编译脚本.gyp ├── ./ ...

  8. 寻找写代码感觉(八)之SpringBoot过滤器的使用

    一.什么是过滤器? 过滤器是对数据进行过滤,预处理过程,当我们访问网站时,有时候会发布一些敏感信息,发完以后有的会用*替代,还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的, ...

  9. python 接口自动化测试--代码实现(八)

    用例读入数据库: #! /usr/bin/python # coding:utf-8 import sys,os from Engine import DataEngine reload(sys) s ...

随机推荐

  1. .net mvc ajax list post

    http://stackoverflow.com/questions/13242414/passing-a-list-of-objects-into-an-mvc-controller-method- ...

  2. A WebBrowser Toy

    原文:A WebBrowser Toy 记得上大学时,某老师为了防止学生上课不听讲,只准学生在课堂上看他放映的PPT,不准学生拷贝,而考试的内容恰恰是PPT上的内容,于是一个同学来找我,我就用VB写了 ...

  3. href 做导航 特效

    <div> <div> <%for (int i = 0; i < 200; i++) { %><%=i.ToString() %> <br ...

  4. 【Unity 3D】学习笔记四十一:关节

    关节 关节组件能够加入至多个游戏对象中,而加入关节的游戏对象将通过关节连接在一起而且感觉连带的物理效果.须要注意的是:关节必须依赖于刚体组件. 关节介绍 关节一共分为5大类:链条关节,固定关节,弹簧关 ...

  5. 多线程学习之BlockingQueue

    前言: 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题.通过这些高效并且线程安全的队列 类,为我们快速搭建高质量的多线程程序带来极大的 ...

  6. C#正则学习

    正则的力量无法小觑,短短的几个字符,往往胜过几十行的代码,大大可以简化我们冗余的代码. 以前在js里用正则比较多,今天来熟悉下C#中正则的使用方法,权当笔记了! 如果把正则当做一门语言的话,那么正则的 ...

  7. 快速构建Windows 8风格应用8-贴靠视图

    原文:快速构建Windows 8风格应用8-贴靠视图 本篇博文主要介绍如何切换出贴靠视图.关于贴靠视图的设计.关于贴靠视图的应用程序栏和如何实现贴靠视图. 如何切换出贴靠视图 第一步:水平方向全屏视图 ...

  8. CodeSmith

    完美解决CodeSmith无法获取MySQL表及列Description说明注释的方案   问题描述: CodeSmith是现在比较实用的代码生成器,但是我们发现一个问题: 使用CodeSmith编写 ...

  9. [android] 百度地图开发 (两).所在地的城市定位和城市POI搜索

    一个. 百度地图城市位置和POI搜索知识       上一篇文章"百度地图开发(一)"中讲述了怎样申请百度APIKey及解决显示空白网格的问题.该篇文章主要讲述怎样定位城市位置.定 ...

  10. 我的Android 4 学习系列之创建用户基本界面

    目录 使用视图和布局 理解Fragment 优化布局 创建分辨率无关的用户界面 扩展.分组.创建和使用视图 使用适配器将数据绑定到视图 使用视图和布局 1. Android UI 几个基本概念 视图: ...