代码协定(一)——简介

时间:2022-01-06 16:06:40

代码协定通常也被称作契约式编程,他并不是新东西,相关理论在C/C++时代就有之,在.Net 4.0的时候也是正式规范化。由于使用.Net的代码协定是需要Code Contracts Dev使用来进行代码注入才有效果的,而这个代码注入在VS2010年代是非常慢的,小项目大概都需要3-5秒钟,基本上是不可接受,所以我也一直没怎么用它。最近换了VS2013后重拾了一下这个工具,发现现在的注入速度非常快了,便决定重新学习一下相关知识。

契约式编程一般包括如下三个部分:

  1. 前置条件(precondiction):为了调用函数,必须为真的条件,在其违反时,函数决不调用,传递好数据是调用者的责任。
  2. 后置条件(postcondion):函数保证能做到的事情,函数完成时的状态,函数有这一事实表示它会结束,不会无休止的循环
  3. 类不变项(class invariant):从调用者的角度来看,该条件总是为真,在函数的内部处理过程中,不变项可以为变,但在函数结束后,控制返回调用者时,不变项必须为真。

这三个部分看起来比较玄乎,其实如果我们编程中多多少少都用到了这些的:例如,前置条件就是我们所谓的入参判断,后置条件就是返回值判断(在函数体,而非调用方处),而类不变项则我们在代码中也大多写过一些CheckValid函数,都不是那么神秘的东西。另外还有一个我们经常使用的编程手段:断言。

从本质上来说,在代码中添加代码协定相关操作并不能给程序带来任何feature,可能很多人对之不屑一顾,认为是浪费时间。实际上,磨刀不误砍柴工,实现代码协定是可以非常有利于提前发现错误的,对编写健壮稳定的代码来说是非常有益的,这些对大型软件尤为重要。例如,很多软件公司基本上都把入参检查当作编程规范了。

.Net中的代码协定

在.Net 4.0中,在System.Diagnostics.Contracts名字空间下引入了一系列代码协定的相关库函数,一个简单的示例如下:

    public void Deposit(int amount)
    {
        Contract.Requires(amount > 0);
        Contract.Ensures(Balance == Contract.OldValue(Balance) + amount);

        Balance += amount;
    }

这个例子使用了两个协定:第一行是一个前置条件协定,检查入参的有效性,第二行是后置条件协定,检查返回值的准确性。运行这段代码后(需要使能代码协定,具体的方法后面介绍),如果入参非法,则会抛ContractException,同样,如果修改了错误的代码导致逻辑不一致,第二行后置条件检查也会抛ContractException。

从上面的例子也可以看出,代码协定的基本功能是有效性判定,然后抛异常。这些是基本上任何语言本身都已经具有的功能,为什么还要专门引入库来支持它呢?我觉得原因有如下几点好处:

  1. 规范了协定,统一了代码。显式区分了前置条件和后置条件,并将断言也统一了起来,这对代码的规范性是非常有好处的。
  2. 语法糖支持。由于代码协定是使用了一些代码注入的方式实现的,因此也是有一些语法题功能的,例如前面例子中的 Contract.OldValue,如果在 代码中实现则需要增加一个临时变量,并且需要在函数尾部实现,要难看不少。
  3. IDE支持。安装上Code Contracts Editor Extensions扩展后,我们可以很方便的看待函数的前置条件约束,开发过程中能有效地避免传入错误的参数。
  4. 性能提升。代码协定相关函数产生的代码有比较丰富的开关选项支持,在开发过程中,我们可以打开所有的检查开关,从而方便我们发现错误;而在正式的Release版本中,我们则可以只打开public方法的前置条件检查(参数有效性判定,这个是不建议去掉的),其它部分的代码检查都关掉,从而提高运行性能。

由于精力有限,今天的随笔就写到这里为止,代码协定还是有一点内容可讲的,估计后续会做3-4篇随笔发表,每1~2天发表一篇。