我对于Design by Contract的最佳实践 通往无错代码之路 欢迎砸场(玩笑)
来源:互联网 发布:5.8毫米子弹相关数据 编辑:程序博客网 时间:2024/06/05 09:15
我对于Design by Contract的最佳实践 通往无错代码之路 欢迎砸场(玩笑)
前言
在上一篇文章中,我简单介绍了契约设计(Design by Contract)的现状,还有自己的想法。
http://www.cnblogs.com/zc22/archive/2009/11/30/1614142.html
这篇文章主要介绍我如何实现了这个设计。
阅读本文前,想再强调一下,本文介绍的是一种实践,而不是技巧,或者能够保证您代码绝对 0 bug的方法。但是在这种实践下,代码 0 bug 是可以达到的。
正文
一个简单的契约设计,就是约束了某个方法调用的要求、以及返回的承诺。比如:
代码
{
public void Info(string message)
{
//do something here
}
}
如果我要调用Log.Info的方法,前提必须满足message非空。否则这个方法不能保证返回需要的结果。
那么根据经典的计算机原则:在正确的输入下,能够得到正确的输出。我只要保证了我所有调用方法的前提要求,我的代码就是 0 bug。这个就是契约设计。
以往我们会通过各种判断去检测输入,这种设计模式叫做:防范式设计(请看code complete 2,里面有详细介绍)。防范式设计的前提就是:我们会假设调用这个方法是恶意的,因此要对所有可能出错的输入进行检测。
在这种设计模式下,就会导致了各种与业务逻辑无关的代码充斥着我们的程序,维护困难,看起来也恶心。因此契约设计是非常反对的。
可是非常可惜,现在连微软也还走不出这个圈子,一堆的Policy Injection / AOP / attribute / SPec#等等,仍然无法成为主流。
现在我就尝试去走出这个圈子。
首先,我会对Log.Info这个方法设置一个Contract.Constraint,如果用中文就是契约里面的约束(合同约束)。
代码
{
[Contract.Constraint("message不能空")]
public void Info(string message)
{
//do something here
}
}
很简单,我什么都没有做,仅仅好像输入了一个注释,内容是messge不能为空。
然后,我去尝试调用这个方法:
{
{
string message = "hello world";
Log log = new Log();
log.Info(message);
}
}
乍看一下,好像我还是什么都没有做啊???我是不是在愚弄大家啊???别急,咱们还没有进行契约的验证(合同验证)。
{
public void test001()
{
MethodInfo method = typeof(TestCase).GetMethod("call001");
Console.WriteLine("check the call001 can be approved by contract.");
Console.WriteLine("approval result = :" + Contract.Approval(method));
foreach (IConstraint cons in Contract.GetOpenConstraints(method))
{
Console.WriteLine("the constraint of \"" + cons.ConstraintName + "\" is not committed.");
}
}
public void call001()
{
string message = "hello world";
Log log = new Log();
log.Info(message);
}
}
在test001这个方法里面,我对契约进行了验证,因此得到了输出:
代码
approval result = :False
the constraint of "message不能空" is not committed.
结果是:approval result = FAlse
意思就是,契约的约束没有被履行,方法call001是个无效的方法。大家这个时候应该清楚什么意思了吧。
简单说明下,Log.Info有个契约的约束,要使用这个方法必须履行这个约束,可是我在call001这个方法里面没有履行这个约束,所以合同无效,也就不能保证代码是正确的。
如何保证呢?请看:
{
public void test002()
{
MethodInfo method = typeof(TestCase).GetMethod("call002");
Console.WriteLine("check the call002 can be approved by contract.");
Console.WriteLine("approval result = :" + Contract.Approval(method));
foreach (IConstraint cons in Contract.GetOpenConstraints(method))
{
Console.WriteLine("the constraint of \"" + cons.ConstraintName + "\" is not committed.");
}
}
[Contract.Commitment("input of log is not null")]
public void call002()
{
string message = "hello world";
Log log = new Log();
log.Info(message);
}
}
在这段代码里面,我的call002调用了log.Info。但是我在这个call002里面做了一个承诺[Contract.Commitment],我保证了这个代码满足了log.info的契约。所以,我得到的测试结果是:
代码
approval result = :True
结果是:approval result = TRUE,再简单说下,因为我在call002里面使用了承诺,因此在这个承诺范围内,所有契约的约束我都会被满足。
这下子我的思路就清楚拉吧!!老实说,我真的很兴奋!!!因为我找到了通往 0 bug 的道路了!
源码下载
http://www.boxcn.net/shared/304p0ktnke
核心技术介绍
其实思路比较简单的,就是查看当前方法体内 所有使用了的方法是否存在了契约的约束,如果存在了但是没有被履行,那么就认为契约无效。
当然,为了实现这个技巧,我使用了Reflection.Emit,直接操作了IL语言去获取方法体内的信息。
后记
当一个问题无法解决的时候怎么办?采用最简单的方法。Design by contract是个绝对的精华,可惜大部分人总是再想怎么去用代码实现,怎么在编译解析、运行中去实现。
因此我尝试了退一步,海阔天空。就像TestDriven一样,难道我们会在代码里面放各种Assert吗?肯定不会。但是我们在coding的时候需要了。
契约设计也是一样,他是一种编码过程,而不是结果。只有这个思路清晰了,才能豁然开朗。
技术支持
reborn_zhang@hotmail.com
zc22.cnblogs.com
http://www.codeproject.com/KB/cs/DesignByContract_indotnet.aspx
精灵软件 火热讨论组
192700436
95755843 [满]
»博主后一篇:Pixysoft.Framework.Versions 版本控制框架 开发实录
不过楼主这思路貌似还不错
我自己尝试了在现有项目中使用了文章的框架,开发中测试时的确仍然有bug,但是这些bug主要来自逻辑上的不严谨,比如递归造成死循环等。
而关于输入不确定产生的bug就不会存在了。
design by contract假设前提是在正确的输入下有正确的输出。那么bug唯一来源就是:在正确的输入下 不能产生正确的输出。
不过感到很爽的是,现在在开发中找bug变得很简单了,因为很多参数问题可以忽略,专心在逻辑上。
而且以前很多对代码输是否需要进行验证这个现在变得机器话了,也不可能出错了。
没错,这个特性的确有用,它可以减少程序员找Bug的时间,我认为这才是它真正的作用。即使逻辑再完善,bug也不能保证降为0.
如果想通过这个东东寻求bug0之路,我还是感觉夸大了某种技术本身。从软件工程的角度考虑,目前这个观点恐怕也是不被接受的。
解决软件质量有很多方法,可能您对这种方式情有独钟,我个人感觉不管是您推荐的这种方式、还是单元测试还有其它的解决软件质量的办法,综合作用下会好些。至于bug0嘛,只能算是个理想了。
public void call002()
{
string message = null;
Log log = new Log();
log.Info(message);
}
问个问题,像上面这个样子是不是也算履行了契约?
从程序角度是履行了。
但是从我们coding角度,我们在发现没有履行的情况下,硬声明了履行。
有点像 我明知道有错,但是我还是去错了。
我觉得bug的定义是:
在程序员尽了最大可能去避免之后,仍然存在的软件漏洞。
那么问题就是如何最大可能去避免了。以往如果没有一定的工具,也许我们会用这种输入验证。
现在通过designbycontract,系统在编译的时候就会通知我们什么方法可能存在漏洞。
如果这样我们都仍然犯下错误,那么是bug。所以bug和程序员素质就相关了。
我觉得应该是,我的内部方法会声明调用的要求,如果不满足这个要求就会出差错。这个就是Contract.Constraint,契约约束。
当然我写这个方法就等于知道潜在的危险了,比如空指针之类的。
那么以后其他类使用了这个方法,如果不履行契约约束,就会存在现在的危险,所以其他的类就需要做出承诺Contract.Commitment
不如咱们把故事说远点。
如果以后我们调用.net x.0的类库的时候,使用了list.Get(index)这个微软提供的方法。
编译中间报错了,说这个方法要求index大于0,但是调用这个方法的类并没有履行契约。我们就会查看代码去检查传入的index是否保证了大于0.
当确认的确代码中间验证了index之后,再在我们自己的类中注明了契约承诺 Contract.COmmitment,这样就是一种契约设计。
这样的设计基本上杜绝了外界对程序内部产生的影响了。
这和.NET4.0的契约式貌似没有本质的区别吧,在.NET4.0中我们也可以指定Precondition(前置条件), Postcondition(后置条件) 以及其他一些Assert(断言),Assume(假设)等,这个只能解决一部分问题,这种方式是好的实践,但是达到0 bug可能还远远不够,Unit Test还是不可少的。
我看了.net 4.0关于dbc的实现。他做到的基本上和我的比较像。问题在于,他对dbc的实现太细力度了。比如:
public
static
string
GetTypeName(
object
obj)
{
CodeContract.Requires(
null
!= obj);
return
obj.GetType().Name;
}
的确,如果我实现是:
[Contract.Constraint(
"obj is not null"
)]
public
static
string
GetTypeName(
object
obj)
{
return
obj.GetType().Name;
}
相比之下,也许.net 4.0会在编译甚至运行的时候去执行
CodeCOntract.Require,然后报错。
但是这么简单的例子,现实生活中不可能出现的。
我现在的架构已经要面对非常复杂的输入情况,因此我选择了自然语言去描述,在程序设计时去遵守。Design by contract就像一个备忘录,一个编译时的warning,不会影响执行性能,但是会提醒程序员,这个会有问题哦!
unit test肯定是需要的,不过在dbc+unit test下,代码质量会更高。
其实任何程序在特定的场景下(如msg不为空)不管是否使用dbc都好办,我们都会去加if(msg!=“”)。但很多时候往往导致bug出现的是我们不可预知的(如msg=“1”,msg=“2”),如果我们知道这些也好办了。我们在尽可能可以确定场景的范围的前提下,多一个dbc来check某些通用的场景当然是好事,也是一种约束。
- 我对于Design by Contract的最佳实践 通往无错代码之路 欢迎砸场(玩笑)
- 对于开发 0 bug 代码的思考——Design by Contract 契约设计
- 译文:Design by Contract(契约式设计)
- Design by Contract(契约式设计)
- design by contract?
- 《Design by Contract》介绍
- Design by Contract(1)
- [摘自DbC原则与实践]Design by Contract的六大原则和六大准则
- 《Design by Contract原则与实践》勘误与评述
- Design By Contract 契约式设计
- Design by Contract 契约式设计
- 契约式设计Design by contract
- Design By Contract(契约式设计)
- AspectJ: 通往AOSD之路的最佳军火
- Design By Contract 基于契约设计的个人理解
- 油田信息化:通往智慧之路(1.3-智慧油田在全球的实践)
- 油田信息化:通往智慧之路(1.3-智慧油田在全球的实践)
- 按协约设计Design by Contract
- Android 游戏引擎——Angle
- 10个经典的Android开源应用项目
- sql Server 筛选重复的数据。
- android下当键盘弹出时拦截Back事件
- ToolRunner与eclipse hadoop 插件的替代品,简化M/R程序的开发
- 我对于Design by Contract的最佳实践 通往无错代码之路 欢迎砸场(玩笑)
- VS无法启用调试
- Android开源游戏引擎——Rokon
- 12个有趣的C语言面试题
- JavaScript编程起步
- Java中替换双引号
- Android开源游戏引擎——LGame
- 念,心随君浅舞天涯
- NSString常用方法实例