让企业赢在全网营销时代
当前位置: 首页>>建站知识>>网站运营

契约式编程定义和功能的阐述

作者:admin 点击量:306次 2016-08-25 09:06:34

 左券是削减大型项目本钱的突破性技巧。它一样平常由 Precondition(前置前提), Postcondition(后置前提) 和 Invariant(不变量) 等观点构成。.NET 4.0 除上述观点以外,还增加了 Assert(断言),Assume(假定) 观点。这可以或者由罗列 ContractFailureKind 范例一窥眉目。

左券的思惟很简略。它只是一组结果为真的表达式。如若否则,左券就被违背。那依照界说,法式中就存在马虎。左券构成为了法式规格阐明的一部分,只不过该阐明从文档挪到了代码中。开辟职员都晓得,文档平日不完备、过期,乃至不存在。将左券挪移到代码中,就使得法式可以或者被验证。

正如前所述,.NET 4.0 对宏和前提编译结束形象封装。这些结果大多会合在 System.Diagnostics.Contracts.Contract 动态类中。该类中的大多数成员都是前提编译。如许,咱们就不消再应用 #ifdef 和界说 CONTRACTS_FULL 之类的标志。更重要的是,这些行动被尺度化,可以或者在多个项目中同一应用,并依据环境能否天生带有左券的法式集。

1. Assert

Assert(断言)是最根本的左券。.NET 4.0 应用 Contract.Assert() 办法来特指断言。它用来表现法式点必需坚持的一个左券。

Contract.Assert(this.privateField > 0); Contract.Assert(this.x == 3, "Why isn’t the value of x 3?");

 

断言有两个重载办法,首参数都是一个布尔表达式,第二个办法的第二个参数表现违背左券时的非常信息。

当断言运转时失败,.NET CLR 仅仅挪用 Debug.Assert 办法。胜利时则甚么也不做。

2. Assume

.NET 4.0 应用 Contract.Assume() 办法表现 Assume(假定) 左券。

Contract.Assume(this.privateField > 0); Contract.Assume(this.x == 3, "Static checker assumed this");

Assume 左券在运转时检测的行动与 Assert(断言) 左券完备同等。但对付动态验证来讲,Assume 左券仅仅验证已增加的现实。由于诸多限制,动态验证其实不克不及包管该左券。或者最佳先应用 Assert 左券,而后在验证代码时按需改动。

当 Assume 左券运转时失败时, .NET CLR 会挪用 Debug.Assert(false)。异样,胜利时甚么也不做。

3. Preconditions

.NET 4.0 应用 Contract.Requires() 办法表现 Preconditions(前置前提) 左券。它表现办法被挪历时办法状况的左券,平日被用来做参数验证。一切 Preconditions 左券相干成员,至多办法自己可以或者拜访。

Contract.Requires(x != null);

Preconditions 左券的运转时行动依附于几个身分。假如只隐式界说了 CONTRACTS PRECONDITIONS 标志,而没有界说 CONTRACTS_FULL 标志,那末只会结束检测 Preconditions 左券,而不会检测任何 Postconditions 和 Invariants 左券。假如违背了 Preconditions 左券,那末 CLR 会挪用 Debug.Assert(false) 和 Environment.FastFail 办法。

假如想包管 Preconditions 左券在任何编译中都施展感化,可以或者应用上面这个办法:

Contract.RequiresAlways(x != null);

为了坚持向后兼容性,当已存在的代码不容许被改动时,咱们需要抛出指定的准确非常。然则在 Preconditions 左券中,有一些格局上的限制。如下代码所示:

if (x == null) throw new ArgumentException("The argument can not be null."); Contract.EndContractBlock(); // 后面一切的 if 检测语句皆是 Preconditions 左券

 

这类 Preconditions 左券的格局严厉受限:它必需严厉依照上述代码示例格局。而且不克不及有 else 从句。其余,then 从句也只能有单个 throw 语句。末了必需应用 Contract.EndContractBlock() 办法来标志 Preconditions 左券结束。

看到这里,是否是感到大多数参数验证都可以或者被 Preconditions 左券取代?没有错,现实的确如斯。如许这些防御性代码完备可以或者在 Release 被去掉,从而不消做那些冗余的代码检测,从而进步法式机能。但在面向验证客户输出此类情境下,防御性代码仍有需要。再便是,Microsoft 为了坚持兼容性,并没有效 Preconditions 左券取代非常。

4. Postconditions

Postconditions 左券表现办法结束时的状况。它跟 Preconditions 左券的运转时行动完备同等。但与 Preconditions 左券分歧,Postconditions 左券相干的成员有着更少的可见性。客户法式或者不会懂得或应用 Postconditions 左券表现的信息,但这其实不影响客户法式准确应用 API 。

对付 Preconditions 左券来讲,它则对客户法式有副感化:不克不及包管客户法式不违背 Preconditions 左券。

A. 尺度 Postconditions 左券用法

.NET 4.0 应用 Contract.Ensures() 办法表现尺度 Postconditions 左券用法。它表现办法失常结束时必需坚持的左券。

Contract.Ensures(this.F > 0);

B. 特别 Postconditions 左券用法

当从办法体内抛出一个特定非常时,平日环境下 .NET CLR 会从办法体内抛出非常的地位间接跳出,从而展转客栈结束非常处置。假如咱们需要在非常抛出时还要结束 Postconditions 左券验证,咱们可以或者如下应用:

Contract.EnsuresOnThrows(this.F > 0);

此中小括号内的参数表现当非常从办法内抛出时必需坚持的左券,而泛型参数表现非常发生时抛出的非常范例。举例来讲,当咱们把 T 用 Exception 表现时,不论甚么范例的非常被抛出,都能包管 Postconditions 左券。哪怕这个非常是客栈溢出或任何不克不及节制的非常。激烈保举当非常是被挪用 API 一部分时,应用 Contract.EnsuresOnThrows() 办法。

 

C. Postconditions 左券内的特别办法

如下要讲的这几个特别办法仅限应用在 Postconditions 左券内。

办法前往值 在 Postconditions 左券内,可以或者经由过程 Contract.Result() 办法表现,此中 T 表现办法前往范例。当编译器不克不及推导出 T 范例时,咱们必需显式指出。好比,C# 编译器就不克不及推导出办法参数范例。

Contract.Ensures(0 < Contract.Result());

 

假如办法前往 void ,则不用在 Postconditions 左券内应用 Contract.Result() 。

前值(旧值)  在 Postconditions 左券内,经由过程 Contract.OldValue(e) 表现旧有值,此中 T 是 e 的范例。当编译器可以或者推导 T 范例时,可以或者疏忽。其余 e 和旧有表达式呈现上下文有一些限制。旧有表达式只能出如今 Postconditions 左券内。旧有表达式不克不及包括另一个旧有表达式。一个很重要的准则便是旧有表达式只能援用办法曾经存在的那些旧值。好比,只需办法 Preconditions 左券持有,它一定能被盘算。上面是这个准则的一些示例:

办法的旧有状况一定存在其值。好比 Preconditions 左券暗含 xs != null ,xs 当然可以或者被盘算。然则,假如 Preconditions 左券为 xs != null || E(E 为随意率性表达式),那末 xs 就有能够不克不及被盘算。

Contract.OldValue(xs.Length); // 很能够差错

办法前往值不克不及被旧有表达式援用。

Contract.OldValue(Contract.Result() + x); // 差错

out 参数也不克不及被旧有表达式援用。

假如某些标志的办法依附办法前往值,那末这些办法也不克不及被旧有表达式援用。

Contract.ForAll(0, Contract.Result(), i => Contract.OldValue(xs[i]) > 3); // 差错

旧有表达式不克不及在 Contract.ForAll() 和 Contract.Exists() 办法内援用匿名拜托参数,除非旧有表达式被用作索引器或办法挪用参数。

Contract.ForAll(0, xs.Length, i => Contract.OldValue(xs[i]) > 3); // OK

 

Contract.ForAll(0, xs.Length, i => Contract.OldValue(i) > 3); // 差错

假如旧有表达式依附于匿名拜托的参数,那末旧有表达式不克不及在匿名拜托的办法体内。除非匿名拜托是 Contract.ForAll() 和 Contract.Exists() 办法的参数。

Foo( ... (T t) => Contract.OldValue(... t ...) ... ); // 差错

 

D. out 参数

由于左券出如今办法体后面,以是大多数编译器不容许在 Postconditions 左券内援用 out 参数。为了绕开这个成绩,.NET 左券库供给了 Contract.ValueAtReturn(out T t) 办法。

public void OutParam(out int x) { Contract.Ensures(Contract.ValueAtReturn(out x) == 3); x = 3; }

跟 OldValue 异样,当编译器能推导出范例时,泛型参数可以或者被疏忽。该办法只能出如今 Postconditions 左券。办法参数必需是 out 参数,且不容许应用表达式。

需要留意的是,.NET 今朝的对象不克不及检测确保 out 参数能否准确初始化,而不论它能否在 Postconditions 左券内。是以, x = 3 语句假如被付与其余值时,编译器其实不克不及发明差错。然则,当编译 Release 版本时,编译器将发明该成绩。

5. Object Invariants

对象不变量表现不论对象能否对客户法式可见,类的每一个实例都应当坚持的左券。它表现对象处于一个“优越”状况。

在 .NET 4.0 中,对象的一切不变量都应当放入一个受掩护的前往 void 的实例办法中。同时用[ContractInvariantMethod]特征标志该办法。其余,该办法体内涵挪用一系列 Contract.Invariant() 办法后不克不及再有其余代码。平日咱们会把该办法命名为 ObjectInvariant 。

[ContractInvariantMethod] protected void ObjectInvariant() { Contract.Invariant(this.y >= 0); Contract.Invariant(this.x > this.y); }

 

异样,Object Invariants 左券的运转时行动和 Preconditions 左券、Postconditions 左券行动同等。CLR 运转时会在每一个大众办法末尾检测 Object Invariants 左券,但不会检测对象闭幕器或任何实现 System.IDisposable 接口的办法。

6. Contract 动态类中的其余特别办法

.NET 4.0 左券库中的 Contract 动态类还供给了几个特其余办法。它们分别是:

A. ForAll

Contract.ForAll() 办法有两个重载。第一个重载有两个参数:一个聚集和一个谓词。谓词表现前往布尔值的一元办法,且该谓词应用于聚会合的每一个元素。任何一个元素让谓词前往 false ,ForAll 结束迭代并前往 false 。否则, ForAll 前往 true 。上面是一个数组内一切元素都不克不及为 null 的左券示例:

public T[] Foo(T[] array) { Contract.Requires(Contract.ForAll(array, (T x) => x != null)); }

 

B. Exists

它和 ForAll 办法差不多。

7. 接口左券

由于 C#/VB 编译器不容许接口内的办法带有实现代码,以是咱们假如想在接口中实现左券,需要创立一个赞助类。接口和左券赞助类经由过程一对特征来链接。如下所示:

[ContractClass(typeof(IFooContract))] interface IFoo { int Count { get; } void Put(int value); } [ContractClassFor(typeof(IFoo))] sealed class IFooContract : IFoo { int IFoo.Count { get { Contract.Ensures(Contract.Result() >= 0); return default(int); // dummy return } } void IFoo.Put(int value) { Contract.Requires(value >= 0); } }

.NET 需要显式如上述申明从而把接口和接口办法相干联起来。留意,咱们不能不发生一个哑元前往值。最简略的方法便是前往 default(T),不要应用 Contract.Result 。

由于 .NET 请求显式实现接口办法,以是在左券内援用雷同接口的其余办法就显得很愚笨。由此,.NET 容许在左券办法以前,应用一个部分变量援用接口范例。如下所示:

[ContractClassFor(typeof(IFoo))] sealed class IFooContract : IFoo { int IFoo.Count { get { Contract.Ensures(Contract.Result() >= 0); return default(int); // dummy return } } void IFoo.Put(int value) { IFoo iFoo = this; Contract.Requires(value >= 0); Contract.Requires(iFoo.Count < 10); // 否则的话,就需要强迫转型 ((IFoo)this).Count } }

8. 形象办法左券

同接口相似,.NET 中形象类中的形象办法也不克不及包括办法体。以是同接口左券异样,需要赞助类来实现左券。代码示例再也不给出。

9. 左券办法重载

一切的左券办法都有一个带有 string 范例参数的重载版本。如下所示:

Contract.Requires(obj != null, "if obj is null, then missiles are fired!");

 

如许当左券被违背时,.NET 可以或者在运转时供给一个信息提醒。今朝,该字符串只能是编译时常量。然则,未来 .NET 能够会转变,字符串可以或者运转时被盘算。然则,假如是字符串常量,动态诊断对象可以或者抉择表现它。

10. 左券特征

A. ContractClass 和 ContractClassFor

这两个特征,咱们曾经在接口左券和形象办法左券里看到了。ContractClass 特征用于增加到接口或形象范例上,然则指向的倒是实现该范例的赞助类。ContractClassFor 特征用来增加到赞助类上,指向咱们需要左券验证的接口或形象范例。

B. ContractInvariantMethod

这个特征用来标志表现对象不变量的办法。

C. Pure

Pure 特征只申明在那些没有副感化的办法挪用者上。.NET 现存的一些拜托可以或者被认为如斯,好比 System.Predicate 和 System.Comparison。

D. RuntimeContracts

这是个法式集级其余特征(详细若何,俺也不太清晰)。

E. ContractPublicPropertyName

这个特征用在字段上。它被用在办法左券中,且该办法相对付字段来讲,更具可见性。好比公有字段和大众办法。如下所示:

[ContractPublicPropertyName("PublicProperty")] private int field; public int PublicProperty { get { ... } }

 

F. ContractVerification

这个特征用来假定法式集、范例、成员能否可被验证履行。咱们可以或者应用 [ContractVerification(false)] 来显式标志法式集、范例、成员不被验证履行。

.NET 左券库今朝的缺点

接下来,讲一讲 .NET 左券库今朝所存在的一些成绩。

值范例中的不变量是被疏忽的,不施展感化。

动态检测还不克不及处置 Contract.ForAll() 和 Contract.Exists() 办法。

C# 迭代器中的左券成绩。咱们晓得 Microsoft 在 C# 2.0 中增加了 yield 关键字来赞助咱们实现迭代功效。它其实是 C# 编译器做的糖果。如今左券中,呈现了成绩。编译器发生的代码会把咱们写的左券放入到 MoveNext() 办法中。这个时侯,动态检测就不克不及包管可以或者准确实现 Preconditions 左券。

Well,.NET 左券式编程到这里就结束了。嗯,就到这里了。

PS : .NET 左券库固然曾经相称优雅。但博主认为,其跟 D 说话实现的左券式编程仍有一段间隔。

PS : 有谁乐意当俺的 Mentor 。您可以或者享用如许的权力和任务:地狱般可怕的发问和骚扰。非诲人不倦者勿扰。