程序员修炼之道(读书笔记):4.注重实效的偏执

按条约设计

前条件(precondition)
为了调用例程,必须为真的条件,例程的需求。在其前条件被违反时,例程绝不应该被调用。传递好数据是调用者的责任。

后条件(postcondition)
例程保证会做的事情,例程完成时世界的状态。例程有后条件这一事实意味着它会结束:不允许有无限循环。

类不变项(class invariant)
类确保从调用者的视角来看,该条件总是为真。在例程内部处理过程中,不变项不一定会保持,但在例程退出,控制返回到调用者时,不变项必须为真(注意,类不能给出无限制的对参与不变项的任何数据成员的写访问)。

例程与任何潜在的调用者之间的合约可解读为:

如果调用者满足了例程的所有前条件,例程应该保证在其完成时,所有后条件和不变项将为真。

在“正交性”中,我们建议编写“羞涩”的代码。这里,强调的重点是在“懒惰”的代码上:对在开始之前接受的东西要严格,而承诺返回的东西要尽可能少。记住,如果你的合约表明你将接受任何东西,并允诺返回整个世界,那么你就有大量的代码要写了。

实现DBC(按合约设计)

尽管用文档记载这些假定是一个了不起的开始,让编译器为你检查你的合约,你能够获得大得多的好处。在有些语言中,你可以通过断言对此进行部分的模拟。为何是部分的?不能用断言做DBC能做的每一件事情吗?

遗憾的是,答案是”不能“。首先,断言不能沿着继承层次向下遗传。这就意味着,如果你重新定义了每个具有合约的基类方法,实现该合约的断言不会被正确调用(除非你在新代码中手工复制他们)。在退出每个方法之前,你必须记得手工调用类不变项(以及所有的基类不变项)。根本问题是合约不会自动实施。

DBC与早崩溃

谁负责检查前条件,是调用者,还是被调用的例程?如果作为语言的一部分实现,答案是两者都不是:前条件是在调用者调用例程之后,但在进入例程自身之前,在幕后测试的。因而如果要对参数进行任何显示的检查,就必须由调用者来完成,因为例程自身永远不会看到违反了其前条件的参数。

考虑一个程序,它从控制台读出数字,(通过调用sqrt)计算其平方根,并打印结果。sqrt函数有一个前条件–其参数不能为负。如果用户在控制台上输入负数,要由调用代码确保它不会传递给sqrt。该调用代码有很多选择:它可以终止,可以发出警告并读取另外的数,也可以把这个数编程正数等等。无论其选择是什么,这都不是sqrt的问题。

通过在sqrt例程的前条件中表示平方根函数的参数域,你把保证正确性的负担转交给了调用者–本应如此。

通过早崩溃,在问题现场找到和诊断问题要容易得多。

不变项的其他用法

1.循环不变项:是对循环的最终目标的陈述,但又进行了一般化,这样在循环执行之前和每次循环迭代时,它都是有效的。你可以把它视为一种微型合约。经典的例子是找出数组中的最大值:

int m =arr[0]; //example assume arr.length>0
int i = 1;

//loop invariant:m = max(arr[0:i-1])
while(i<arr.length){
m = Math.max(m,arr[i]);
i = i+1;
}

2.语义不变项:表达不可违反的需求,一种“哲学合约”。

我们曾经编写过一个借记卡交易交换程序。一个主要的需求是借记卡用户的同一笔交易不能被两次记录到账户中。换句话说,不管发生何种方式的失败,结果都应该是:不处理交易,而不是处理重复的交易。

这个简单的法则,直接由需求驱动,被证明非常有助于处理复杂的错误恢复情况,并且可以在很多领域中指导详细的设计和实现。

一定不要把固定的需求,不可违反的原则与那些仅仅是政策(policy)的东西混为一谈。这就是为什么要使用“语义不变项”的原因–它必须是事物的确切含义的中心,而不受反复无常的政策的支配(后者是更为动态的商业规则的用途所在)。

断言式编程

在自责中有一种满足感。当我们责备自己时,会觉得再没人有权责备我们。

如果它不可能发生,用断言确保它不会发生。

注意:
1.传给断言的条件不应该有副作用。

while(iter.hasMoreElements()){
Test.ASSEST(iter.nextElement()!=null);
object obj = iter.nextElement();
//这样的asset有副作用啊!
}


应该改为:

while(iter.hasMoreElements()){

object obj = iter.nextElement();
Test.ASSET(obj!=null);
//...
}

2.断言可能会在编译时被关闭–绝不要把必须要执行的代码放在asset中。
3.不要用断言代替真正的错误处理。断言检查的是绝不应该发生的事情。

何时使用异常

异常应该保留给意外事件。

例如,如果你的代码视图打开一个文件进行读取,而该文件不存在,应该引起异常吗?

回答是:取决于实际情况。如果文件应该在那里,那么引发异常就有正当理由。某件意外事件发生了–你期望其存在的文件好像消失了。另一方面,如果你不清楚文件是否应该存在,那么你找不到它看来就不是异常情况,错误返回就是合适的。

1.如果打开文件/etc/password,这个文件在所有的UNIX系统上都应该存在,那么可以返回异常。
2.如果是某个寻常文件,那么返回异常就不合适。

配平资源

嵌套的分配:
对于一次需要不只一个资源的例程,可以对资源分配的基本模式进行扩展。建议

1.以与资源分配的次序相反解除资源的分配。这样,如果一个资源含有另一个资源的引用,你就不会造成资源被遗弃。

2.在代码的不同地方分配同一组资源时,总是以相同的次序分配他们。这将降低发生死锁的可能性。

无论是谁分配的资源,他都应该负责解除该资源的分配。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值