代码协定通常也被称作契约式编程,他并不是新东西,相关理论在C/C++时代就有之,在.Net 4.0的时候也是正式规范化。由于使用.Net的代码协定是需要Code Contracts Dev使用来进行代码注入才有效果的,而这个代码注入在VS2010年代是非常慢的,小项目大概都需要3-5秒钟,基本上是不可接受,所以我也一直没怎么用它。最近换了VS2013后重拾了一下这个工具,发现现在的注入速度非常快了,便决定重新学习一下相关知识。
契约式编程一般包括如下三个部分:
-
前置条件(precondiction):为了调用函数,必须为真的条件,在其违反时,函数决不调用,传递好数据是调用者的责任。
-
后置条件(postcondion):函数保证能做到的事情,函数完成时的状态,函数有这一事实表示它会结束,不会无休止的循环
-
类不变项(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。
从上面的例子也可以看出,代码协定的基本功能是有效性判定,然后抛异常。这些是基本上任何语言本身都已经具有的功能,为什么还要专门引入库来支持它呢?我觉得原因有如下几点好处:
-
规范了协定,统一了代码。显式区分了前置条件和后置条件,并将断言也统一了起来,这对代码的规范性是非常有好处的。
-
语法糖支持。由于代码协定是使用了一些代码注入的方式实现的,因此也是有一些语法题功能的,例如前面例子中的 Contract.OldValue,如果在 代码中实现则需要增加一个临时变量,并且需要在函数尾部实现,要难看不少。
-
IDE支持。安装上Code Contracts Editor Extensions扩展后,我们可以很方便的看待函数的前置条件约束,开发过程中能有效地避免传入错误的参数。
-
性能提升。代码协定相关函数产生的代码有比较丰富的开关选项支持,在开发过程中,我们可以打开所有的检查开关,从而方便我们发现错误;而在正式的Release版本中,我们则可以只打开public方法的前置条件检查(参数有效性判定,这个是不建议去掉的),其它部分的代码检查都关掉,从而提高运行性能。
由于精力有限,今天的随笔就写到这里为止,代码协定还是有一点内容可讲的,估计后续会做3-4篇随笔发表,每1~2天发表一篇。