软件构造3.2设计规约笔记
Chapter3:抽象数据类型(ADT)和面向对象的编程(OOP)
3.2Designing specification设计规约
1.规约的作用(spec)
- 规约给“供需双方”都确定了责任,在调用的时候双方都要遵守。
- 规约可以隔离“变化”,无需通知客户端。
2.行为的等价性(Behavioral equivalence)
不同的函数行为不同,但对用户来说“是否等价”?
需要使用规约来判断是否是一致的。
我们来看一个例子。
判断两个函数的行为是否一致?
第一个函数从前向后搜索数值val的索引值。
第二个函数从后向前搜索。
函数的规约
由于规约中,需求中val值只在数组中出现一次,所以find函数无论从前还是从后进行搜索,返回的值是相同的。所以行为等价。
注意:用户需要给合理的输入,如果输入不合理,输出任何值都是对的,行为也是等价的。
3.前置条件和后置条件(pre-condition and post condition)
-
前置条件:requires(输入的描述)
-
后置条件:effects(输出的描述)
-
前置条件:是对客户端的约束,使用时必须满足条件
-
后置条件:对开发者的约束,方法结束时必须满足的条件
-
契约:如果前置条件满足,后置条件必须满足。前置条件不满足,方法可做任何事情均不违约。
4.Java中的规约写法
/**Find a value in an array.
*@param arr array to search,requires that val occurs exactly once in arr
*@param val value to search for
*@return index i such that arr[i]=val
*/
- 第一行中有两个*。
- @param描述输入的参数,@return描述输出的值。
下面是错误的规约写法
5.改变输入的方法(a mutating method)
前两个例子改变了输入的参数,第三个例子返回了一个新的列表
函数功能:把list2的元素加在list1的后面(mutating)
函数功能:把list的元素排序(mutating)
函数功能:把首字母小写,返回一个新的list(not mutating)
注意:
- 除非在后置条件里声明过,否则内部不应该改变输入参数。
- 尽量不设计mutating的spec,否则容易引发bugs。
- 尽量避免使用mutable的对象。
6.规约的强弱比较
更强的规约:
- 前置条件更弱。(输入的范围更广)
- 后置条件更强。(输出的范围更小)
举三个例子:
例子1
原始规约
输入更弱,val可以有多个值
输出更强,只能输出第一个下标
例子2
原始规约
可以随意输入,前置条件更弱了
而后置条件没有变化,在前置条件相同时,后置条件的输出不变
例子3
原始规约
前置条件更弱了,
然而后置条件也更弱了,导致无法比较。
更强的规约代表更多的客户端(more clients)能应用,而有更少的实现(fewer implementations)
7.确定的规约和欠定的规约(Deterministic and underdetermined spec)
- 确定的规约:给定一个满足precondition的输入,输出是唯一的,明确的
- 欠定的规约:同一个输入可以有多个输出
举例说明:
第一个是欠定的规约,而后三个是确定的规约。
8.操作式规约和声明式规约(Declarative vs operational specs)
- 操作式规约:例如伪代码。
- 声明式规约:没有内部实现的描述,只有初-终状态。(更有价值)
举例判断哪个是声明式规约。
结论:第三个是,前两个不是。
原因:第一个提到了输出的类型和参数,第二个提到了用循环来做,都提到了内部的实现。而第三个是描述,没有提到内部的实现。