您的位置:首页 > 编程语言

对于开发 0 bug 代码的思考——Design by Contract 契约设计

2012-09-11 10:10 543 查看

对于开发 0 bug 代码的思考——Design by Contract 契约设计

前言

最近在开发一个验证框架,希望能够降低代码的bug率,提升质量;不知不觉就来到了Design By Contract,感觉这是个方向。

本文主要是批判一下现有的契约设计问题,提出自己的看法,很希望得到一些牛人的指教。

研究现状简单分析

Design by Contract(DbC)是个天才(我觉得)叫Bertrand Meyer提出来的。那个家伙同时还搞出了个Eiffel的东西,是对DbC的实践(Practise)。我先简单介绍下DbC, 下文是一个范型字典DICTIONARY[ELEMENT]:的put方法定义:

put (x: ELEMENT; key: STRING)is

-- Insert
x so that it will be retrievable through
key.

require

count <= capacity

not key.empty

do

... Some insertion algorithm ...

ensure

has (x)

item (key) = x

count = old
count + 1

end

大概意思是,调用这个方法,要求(require)是xxxx;这个方法干了什么(do),这个方法结束后,保证了什么输出(ensure)。

具体可以看:http://www.eiffel.com/developers/knowledgebase/design_by_contract.html

换句话说,一个方法有了前置条件(pre condition)、后置条件(post condition),调用起来就有了保证。

然后,一些人就开始做文章了。在c#领域里面,ms也没有闲着。首先是Spec#,个人感觉是一种模仿Eiffel的语言,一个例子:

Spec#




代码

static void Main(string![] args)

requires args.Length > 0

{

foreach(string arg in args)

{

Console.WriteLine(arg);

}

}



这个例子算简单了,(该死的找不到一个恶心的例子,也许ms的工程师们也觉得恶心,没放上来)。用了一个repuires去约束了args.具体可以看:

http://en.wikipedia.org/wiki/Spec_Sharp

http://research.microsoft.com/en-us/projects/specsharp/#documentation

没有这么牛逼的,就在.net基础上去做,比如使用断言(Assert)和属性去实现(Attribute)。比如:

http://research.microsoft.com/en-us/projects/contracts/


代码
这就是个例子,或者直接用TestDriven里面的Assert()去实现。例子太多了,比如:

http://geekswithblogs.net/Podwysocki/archive/2008/01/22/118770.aspx
http://devlicio.us/blogs/billy_mccafferty/archive/2006/09/22/design_2d00_by_2d00_contract_3a00_-a-practical-introduction.aspx
http://puzzleware.net/nContract/nContract.html#ConfiguringContractChecks

这些实践的一个极端的例子:


代码



[FormallySpecified]

[ModelField(typeof(List<char>), "Contents",

@"new List<char>(this.ToString().ToCharArray())")]

[RepresentationalInvariant("numberOfChars == stringBuilder.Length")]

public class CharBuffer

{

[Pre("value != null")]

[Post("Contents.Count == value.Length")]

protected CharBuffer(string value) { }



[Pre(@"index >= 0 && index <= Contents.Count && value != null")]

[Post(@"Contents.Count == old.Contents.Count + value.Length")]

[ExceptionalPost(typeof(ArgumentOutOfRangeException),

"index < 0 || index > Contents.Count")]

public virtual void Insert(int index, string value) { }



// Member fields

protected StringBuilder stringBuilder;

protected int numberOfChars;

}



不知道大家怎么去想的,我看见了就想吐。。。

当然,微软里面有个比较牛的家伙,开发了个叫LinFu的框架,使用了AOP去操作。这个家伙牛在直接用Emit自己实现了aop,号称性能比其他框架好很多。

http://www.codeproject.com/KB/cs/LinFu_Part5.aspx
http://www.codeproject.com/KB/cs/LinFuPart1.aspx

感觉是差不多了,可是就是心里还是觉得有道砍,非常的不爽。

我对Design by Contract的实践

DbC提出来是1986年,现在都什么年代了,为什么还停滞不前。引用柯南的一句话:真相只有一个。因为路走错了。各位ms大牛们,每天埋头钻牛角尖的,因为他们把契约设计看成了一种程序代码、一种语言

结果导致了与业务毫不相干的、难看的(spec#)、奇怪的代码充斥我们优美的业务逻辑,然而DbC真正要解决的问题却没有解决。正如一个笑话说的:

苏联的优势在哪里?在于他解决了其他制度国家不存在的问题。(仅笑话,不要扯上政治)

我个人认为Design by Contract是一种设计模式!是一种习惯!是一种开发中的辅助语言

先是一个简单的调用例子:



class A

{

public void Foo()

{

int interval = 1;

B b = new B();

b.Foo(interval);

}

}

class B

{

public void Foo(int interval) { }

}



A调用了B的Foo方法,传入参数interval。

如果B对interval的约束很简单,比如要求interval>0,这样很轻松,用之前的spec#、aop、attribute、assert之类的都容易实现。可是现实生活不是这样,假设:

B要求interval非负数,当小于10的时候必须连续、当大于10的时候,必须每连续2个数字之后断开1个数字。

这怎么办??亲爱的spec#们,傻眼了吧。因为他们的工具对precondition的描述太有限了,而我们的需求又太复杂了,所以导致了design by contract停滞不前。

针对这些问题,我们为什么要用程序语言去约束?为什么就不用自然语言?为什么设计的时候内部的类(internal)使用自然语言规定了传入的要求,然后最终暴露在外部的类(public)再去针对这些要求去做验证?比如:


代码

class B

{

[Contract("interval小于零,大于0的时候,10以内连续,10以外每连续2次就断开1次")]

public void Foo(int interval) { }

}

的确,对于B.Foo我什么都没有做,只是添加了语言去描述约束。但是,当我编写A的时候,我亲爱的VS20xx就会自动的去检测调用对象的情况,然后汇总contract。比如:



是不是感觉清晰了很多?如果A是个最终暴露给用户的类,我们只要在调用A.Foo的时候,对他的方法的contract都做个验证,就足够了。

Design by Contract理论形态

我们开发设计的时候,一定会分interval/public class去写,暴露给用户的public class要尽量的少,剩余的工作全部交给内部的类去实现。这样一般会采用Facet的设计模式,由他负责提供方法、提供对象,而不是让用户自己去new。这个是我DbC的前提。

Design by Contract深入的去思考,实际上是对类方法的传入参数的约束(请先不要考虑返回值,让我先解决50%的问题)。对于内部类而言,会默认传入参数符合调用要求,不会对传入参数进行验证。

这样,当类与类之间调用后,暴露在最外面的类就负责起了最终参数传入的验证工作,所谓一夫当关,只要最外面的类把好关,那么剩下的业务逻辑我们会默认"在正确的输入下,会得到正确的输出"。

因此,如果我们知道外部类的某个方法需要负责哪些contract,这样我的design by contract就完成了。

因此,首先技术上要解决的是,我的外部类的方法如何知道需要的contract。目前.net的语言来看,反射还不足以完成任务,也许需要使用emit等高级工具。因为有时候有些内部类的contract会被另外的内部类保证了,这样外部类需要负责的contract就少了。

其次,就是如何去负责这些contract,这个就可以使用合适的设计模式了,针对外部类每一个参数,进行一个contract的严格验证,验证过程可以在新的类完成。比如以下伪代码:


代码



class A

{

public void Foo(

[Supervise(CheckVar1)] // 对传入参数进行验证

string var1,

[Supervise(CheckVar2)] // 对传入参数进行验证

string var2)

{

int interval = 1;

B b = new B();

b.Foo(interval);

}

}



然后验证过程在新的方法实现了。这样开发,就变得非常的清晰了。

小结与后续

在design by contract的框架开发下,大部分的类的方法会使用自然语言去描述contract;到了关键的边缘区域(内部与外部交互的区域),会查询此区域的contract(当然是自然语言描述的集合),然后我们再针对这些contract去检视传入参数。
如果有些内部类会履行某个类的contract,那么这个类的履行也需要使用检视。

如果我的想法能够在现有的技术下实现了,我觉得出现一个全新的开发过程,一种新的practise。以后的程序员会在class标注各种contract,然后最终会在某些类上使用Supervise,同时在某些类可以查看他需要履行的contract是什么。

这样开发起来,bug会降低到0,不是梦想。

(偶狂敲了1个小时,吐了几千字的废话,希望各位支持一下,能给点思路,指出我的错误。在此感谢了!)

技术支持

reborn_zhang@hotmail.com

zc22.cnblogs.com

------------------------------------------

精灵软件 火热讨论组

192700436



95755843 [满]

绿色通道:好文要顶关注我收藏该文与我联系







关注 - 14

粉丝 - 203

荣誉:推荐博客
+加关注

1
0
(请您对文章做出评价)

«博主前一篇:Pixysoft.Framework.Verifications
验证框架 开发实录

»博主后一篇:我对于Design by Contract的最佳实践 通往无错代码之路 欢迎砸场(玩笑)

posted @ 2009-11-30 23:31
辰 阅读(1683) 评论(18)
编辑 收藏



发表评论

回复引用
#1楼2009-11-30 23:40 |

Jeffrey Zhao

再怎么样,bug降到0永远是个梦想吧……
支持(0)反对(0)

回复引用
#2楼2009-12-01 01:39 |

深山老林

@Jeffrey Zhao

假如写个程序,只写一行代码,或许bug为0还是能实现的。
支持(0)反对(0)

回复引用
#3楼2009-12-01 06:20 |

bkkkd

兄弟,不好意思,没从你的代码里看出怎么才是design by contract
支持(0)反对(0)

回复引用
#4楼2009-12-01 08:14 |

Franz

@深山老林

哪有有什么意义呢?只是为了追求0bug么 ?

再说了.你这个保证了,系统层面可没有保证.允许你的程序需要操作系统,操作系统的某些原因导致你的一行代码也没有运行起来,那是不是bug呢
支持(0)反对(0)

回复引用
#5楼2009-12-01 08:16 |

Ariex

Supervise部分就是“契约”的描述吧?通过自定义属性的方式,加上通过反射或者其他方法加上契约的校验部分,或者类自然语言描述-比如linq)。

不过感觉这样写校验跟直接写在代码里面没有什么区别啊?

一般来说“契约”似乎都指的是接口,描述了应尽的义务。lz的契约似乎是来形容完成任务时给的条件和最后的结果是否是正确的?更像是现在NUnit在做的事情……
支持(0)反对(0)

回复引用
#6楼2009-12-01 08:45 |

深山老林

呵呵,一句玩笑话而已。
支持(0)反对(0)

回复引用
#7楼2009-12-01 09:29 |

Yuanyi Wang

交付时0bug,第一次听说这句话是从日本项目组成员那里,听他们解释后,可以理解为“交付时,没有已知Bug”,你永远也不能知道你的系统究竟还遗留多少Bug,你能做的就是设计阶段,如何让Bug更容易的找出、重现、定位;编码时避免白痴错误;测试时如何尽量多的找出Bug。一般来说,系统达到一定的无故障时间就可以了,0 bug是不肯能的。关键的问题是如何确认你的系统没有Bug了?答案肯定是谁也不能确认自己的系统没有Bug所以就不存在0Bug的系统,只能是0已知Bug的系统。
支持(0)反对(0)

回复引用
#8楼2009-12-01 11:43 |

Ivony...

老实说一直觉得这个东西很扯淡,另外,.NET 4.0里面的所谓的契约式设计的支持看起来也很扯淡。。。

契约不仅仅包括函数的输入和输出,如果仅仅为了解决LZ提出的对输入参数的复杂约束,直接做一个复杂类型作为输入参数就可以了。不是什么很高深的事情。

契约设计没有调试和编码支持就是扯淡的东西。。。。
支持(0)反对(0)

回复引用
#9楼2009-12-01 12:11 |

鹤冲天

楼主举的各种契约式实现的方式很有意思。
支持(0)反对(0)

回复引用
#10楼2009-12-01 12:27 |

szwe

没太理解楼主的目的,这个契约的功能就是接受预想之外的操作时抛出异常么?
支持(0)反对(0)

回复引用
#11楼2009-12-01 13:02 |

riccc

想法挺好,但这么好的想法为什么只用来做dbc,应该用来描述需求,直接产出软件

dbc有eXtensible C#, C#AL, Effiel也有.net实现
支持(0)反对(0)

回复引用
#12楼[楼主]2009-12-01 14:19 |



@bkkkd

东西很快就会有了,现在design by contract的原型已经开发出来了。

会有编译的约束,如果有contract没有履行,或者通过,直接在编译的时候就报错。

这样,就是个真正的dbc
支持(0)反对(0)

回复引用
#13楼2009-12-02 10:31 |

Tony Zhou

如果参数不符合不是应该argument exception抛出来么?
支持(0)反对(0)

回复引用
#14楼2009-12-02 11:52 |

Daniel Cai

引用
深山老林:

@Jeffrey Zhao

假如写个程序,只写一行代码,或许bug为0还是能实现的。

一行代码的东西算不上程序。Console.Read()没意义。
支持(0)反对(0)

回复引用
#15楼2009-12-02 16:46 |

浪子

不同意楼主对DBC的定义.DBC不是为了开发0bug的代码,DBC是为了用代码规定提供者和调用者应尽的义务和可以享有的权利.

最近正在使用DBC做项目开发,使用 http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx 类库.

有空交流下:)

支持(0)反对(0)

回复引用
#16楼2009-12-02 16:47 |

浪子

@ Ariex

感觉DBC正是在做UnitTest的工作,所以使用DBC(MS的CodeContract)之后,可以再使用Pex直接生成UnitTest
支持(0)反对(0)

回复引用
#17楼[楼主]2009-12-02 16:52 |



@浪子

0 bug不是我的观点,是design by contract的祖宗 Bertrand Meyer的观点,他在:

Building bug-free O-O software: An introduction to Design by Contract ™

提到了Design by Contract,为了bug-free。

用dbc不能够保证0 bug,但是能够进一步实现这个目标,我开篇也说了。
支持(0)反对(0)

回复引用
#18楼[楼主]2009-12-02 16:55 |



@浪子

不过你说的code contract之前也看到了,他们会在code里面去叠加各种Contract.Require。

我第一篇分析过,这样的做法仍然在亡羊补牢,就是防范式编程,守住了public的关口,默认了内部实现是正确的。

但是前提就是到底有多少需要去防守?这个问题ms没有解决,所以就走不出大圈子。

我的设计就是提供了这个信息,我们去设计一个类,到底存在了什么危险,有什么契约约束需要遵守,我的框架提供了。至于如何遵守,那么可以用Contract.Require之类的去做检测了。
支持(0)反对(0)

刷新评论刷新页面返回顶部
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: