我在 C# 中实践契约式设计。





1.00/5 (5投票s)
我在 C# 中实践契约式设计。
引言
最近我尝试开发一个验证框架来减少系统中的 bug。我接触到了契约式设计 (DbC)。DbC 的思想确实很棒!但目前还没有可行的实践。所以我自己实现了一个。
更新于 2009-12-02 200400:很多人仍然不理解我的意思。所以我重写了这篇文章。
背景
契约式设计最初由 Bertrand Meyer 提出。您可以访问他的主页:契约式设计,EIFFEL。这位仁兄也为 DbC 开发了 Eiffel。
在 dotnet 中,我们也有一些关于 DbC 的优秀工作,例如:Spec#,LinFu。但它们要么试图开发一种新语言 (spec#),要么使用 AOP (LinFu),要么使用特性 (Microsoft...)。结果呢?仍然是一团糟。
为什么契约式设计不能成为 OO、XP 的主流?因为它们都走错了方向。所以……请看看我的工作。
使用代码
在契约式设计中,当我们编写类中的方法时,我们假设用户会满足方法的约束条件,然后方法就会返回承诺的结果。这就是 DbC。就像下面的代码一样
public class Log { public void Info(string message) { //do something here } }
当我调用 Log.Info 时,消息必须非空,否则,函数无法保证返回正确的结果。
以前,我们会尝试在 Info 方法中进行检查。
public class Log { public void Info(string message) { if(string.IsNullorEmpty(message)) throw new Exception("missing message."); //do something here } }这被称为:防御性编程。然而,如果情况变得复杂,我们会在业务逻辑中发现成千上万的防御性代码。这真是太糟糕了!!!
更新于 2009-12-02:.net framework 4.0 中发生了什么?看看:微软 .net 4.0 契约式设计
public class Log { public void Info(string message) { CodeContract.Requires(null != message); //do something here } }现在,停下来,仔细想想。以前的代码有什么变化吗?也许微软会说,这是契约式设计,编译代码时我们会收到警告……等等。
请停止自欺欺人。如果它真的如你所想,为什么不随处使用它呢?(因为有时我们已经确定代码是正确的。)那么为什么仍然有很多 bug?(嗯……有些错误是 DbC 无法捕捉的。)好吧……请停止找借口。我告诉你真相。
计算机语言的能力是有限的,但人类的需求是复杂的。微软开发了一个伟大的工具 .net,以及 C# 语言。它可以满足大多数人类需求,并将需求转化为机器代码。但在契约式设计方面,情况就不同了。
如果我们遵循传统的方式,无论使用何种技术,我们都会发现编写“契约”所花费的时间与编写“业务逻辑”所花费的时间一样多。最后,一半的代码是契约。所以这就是我们不接受 DbC 的原因。
判断输入参数非空,这很简单。但是,如果我们传递一个对象呢?如果对象包含另一个对象呢?如果……所以,如果我们仍然想用计算机语言(c#/java/whatever)写下契约,我们只是在浪费时间。我们需要另一种思路。
契约式设计不支持防御性编程。
不幸的是,即使微软仍在研究防御性编程……请看 Spec#……所以我尝试走另一条路。
首先,我为 Log.Info 设置了一个约束。
public class Log { [Contract.Constraint("message is not null")] public void Info(string message) { //do something here } }在上面的代码中,我实际上什么都没做,只是在方法上设置了一个属性。
然后,我尝试调用方法 Log.Info,如下所示
public class TestCase { public void call001() { string message = "hello world"; Log log = new Log(); log.Info(message); } }它工作了,对吗?当然。那么它与契约式设计有关吗?请耐心点,我们还没有验证契约。
public class TestCase { 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); } }在 Testcase.test001 中,我使用 'Contract.Approval' 来验证 method call001。并得到结果
check the call001 can be approved by contract. approval result = :False the constraint of "message is not null" is not committed.这意味着,Log.Info 的约束在 call001 中没有被满足,因此 call001 不是一个有效的方法。现在您可以对契约式设计有所感受了。接下来做什么?阅读契约,并遵循契约。例如,我们确定 message 非空,然后继续下一个例子
public class TestCase { 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 的输入非空。所以当我运行 test002 时,我得到了
check the call002 can be approved by contract. approval result = :True是的!太棒了!通过!这就是我想要的!所以,现在您可以理解我的意思了。
所以……是的,我没有使用机器来验证约束。而是,我将契约式设计应用到编码、应用到“设计”中。这就是我想要的,我相信,这就是契约式设计。
摘要
当问题出现时,找到最简单的解决方案。它是最好的解决方案。契约式设计不是一种新语言,也不是编码,也不是围绕代码的某些东西。契约式设计是一种实践,就像测试驱动开发一样。我相信契约式设计将在 XP 中变得流行,并成为测试驱动开发的伙伴。
更新于 2009-12-02:伙计们,我们通过创造而生活,而不是通过复制/粘贴,也不是通过下载。契约式设计是一种实践。不是一种新语言,或某种属性/或 AOP。
如果有一天,当我们写下一个方法名:call001(),它使用微软 .net framework 12.0 中的方法,该方法是 Log.info
public void call001() { string message = "hello world"; Log log = new Log(); log.Info(message); }并且在编译代码后,VS2020 给我一个警告,log.Info 的契约是='message is not null',我们需要满足它,那么您最终就会理解我的工作。
嗯,把脑子里的每一个字都打出来很累。希望未来有人能用“跳跃思维”来阅读我的文章。