ADP + OOP
四、设计规约
阅读资料:
MIT 课程阅读06-规格说明
MIT 课程阅读07-设计规格说明
1、编程语言中的函数和方法
参数类型是否匹配,返回值类型是否匹配,都在静态检查阶段完成。
方法是程序的积木,可以被独立开发、测试、复用;使用方法的客户端,无需了解方法内部具体如何工作——抽象
2、规约Specification
(1)编程中的记录
代码本身蕴含着我们的设计决策,但是远远不够;
我们需要写出我们的假设,防止自己记不住,以及别人看不懂。
-
代码中蕴含的设计决策:给编译器读
-
注释形式的设计决策:给自己和别人认读
(2)方法的规约
规约(规格说明)spec是团队合作的关键,没有规约没法写程序,即便写出来,也不知道对错。
规约是程序与客户端之间达成的一致,给双方确定了责任,调用时双方都要遵守
为什么要使用规约
- 很多bug的产生是由于双方对接口行为的理解不同
- 不写规约,不同程序员对接口的理解出现偏差
- 没有规约,当程序崩溃时很难发现问题所在
规约的好处
- 精确的规约有利于区分责任
- 客户端无需阅读要用函数的代码,只需理解spec
- 更改实现策略无需通知客户端
- 省略一些检查和处理,提高代码效率
- 作为防火墙隔开客户端和开发者——解耦:客户端代码和开发者代码遵循规约可以独立发生改动
对象与其用户之间的协议:输入/输出的数据类型,功能性和正确性,性能
(3)行为等价性
判断行为是否等价,我们要判断一个方法是否可以替换另一个方法;根据规约判断行为是否等价,符合同一个规约,即等价。
根据代码不能判断行为等价 ⇒ \Rightarrow ⇒ 需要根据spec判断 ⇒ \Rightarrow ⇒ 编写代码前,先撰写spec
(4)规约结构:前置条件和后置条件
前置条件:关键词requires
,对客户端的约束,在使用方法时必须满足的条件
后置条件:关键词effects
,对开发者的约束,方法结束时必须满足条件
如果前置条件满足了,后置条件必须满足;若前置条件不满足,不需满足后置条件
Java中的规约
静态类型声明是一种规约,可据此进行静态类型检查;
方法前的注释也是一种规约,需人工判定是否满足
参数说明:@param
——前置条件
返回说明:@return
——后置条件
如以下一个规约:
static int find(int[] arr, int val)
requires: val occurs exactly once in arr
effects: returns index i such that arr[i] = val
在java中的注释:
/**
* Find a value in an array
* @param arr array to search requires that val occurs exactly once
* @param val value to search for
* @return index i such that arr[i] = val
*/
static int find(int[] arr, int val)
除非spec必须如此,否则方法内不应该改变输入参数;
尽量避免使用mutable的对象。
3、设计规约
(1)规约的分类
- 规约的确定性:是否对输入输出进行严格的限制
- 规约的陈述性:是否仅陈述了输出应该是什么,或者是否描述了是如何计算的
- 规约的强度:实现方法是否仅限于一个小集合,或者有很多满足要求的方法
更强或更弱的规约
规约强度 S 2 ≥ S 1 S_2 \geq S_1 S2≥S1: S 2 S_2 S2的前置条件更弱,后置条件更强 ⇒ \Rightarrow ⇒可以用 S 2 S_2 S2替换 S 1 S_1 S1
(2)图示化规约
对于某个具体实现,若满足某规约,则落在其范围之内;否则就落在其范围外。
规约更强,在图中表达为更小的区域:
(3)设计好的规约
一个方法设计有多好,不是因为代码写的好,是因为对这个方法的规约设计的好,使客户端便于使用,使开发者容易编写。
-
规约应当功能简单,易于理解
-
规约应当足够“强”,保证后置条件的强度,处理特殊情况,便于客户端使用
-
规约应当足够“弱”,保证前置条件的强度,对功能进行充分的说明,限制使用
-
规约应当多使用抽象的数据类型,给使用者和实现者更多自由,List、Map而不是ArrayList、HashMap。