课程目的
方法的规约
前置/后置条件
确定规约,非确定规约
陈诉式、操作式规约
规约的强度及其比较
如何写出好的规约
大纲
1.编程语言中的函数/方法
2.编程信息
3.设计规约
4.总结
3.2.1.编程语言中的函数/方法
方法——“方法”是程序的“积木”,可以被独立开发、测试、复用。使用“方法”的客户端,无需了解方法内部具体如何工作--“抽象”
参数——参数类型是否匹配,在静态类型检查阶段完成
返回值——返回值类型是否匹配,也在静态类型检查阶段完成
变量的作用域
一个完整的方法
包含方法的规约spec和方法的实现体implementation
3.2.2.编程信息
(1)记录编程
-类的层次结构和已实现接口的列表
-描述子类和接口,实现类
-对类的描述
-构造方法的摘要
-方法的汇总列出了我们可以调用的所有方法
-每个方法和构造方法的详细描述:
·方法签名:这里有返回值类型,方法名称和参数类型。
·有完整的描述
·参数:方法参数的描述
·以及返回值的类型的描述
(2)记录假设
变量的数据类型定义
Java实际上在编译时检查了这个假设,并保证在你的程序中没有地方违反了这个假设。
final关键字定义了设计决策:“不可改变”(java会进行静态检查)
虽然代码本身就蕴含着你的设计决策,但是远远不够
(3)编程信息
为什么要写出“假设”?
第一:自己记不住;第二:别人不懂。
写程序必须记住两个目标:
代码中蕴含的“设计决策”:给编译器读
注释形式的“设计决策”:给自己和别人读
(4)黑客和工程师
工程师应该是悲观主义者——测试优先
考虑自己能够想到的所有情况
随时记录代码中的“设计决策”
(2)规约(或称合约)
没规约,没法写程序;即使写出来,也不知道对错
程序与客户端之间达成的一致
Spec给“供需双方”都确定了责任,在调用的时候双方都要遵守
为什么要有规约:
现实是:
-很多bug来自于双方之间的误解
-不写下来,那么不同开发者的理解就可能不同
-没有规约,难以定位错误
规约的优点:
-精确的规约,有助于区分责任
-客户端无需阅读调用函数的代码,只需要理解spec即可
比如
-规约可以隔离“变化”,无需通知客户端
-规约也可以提高代码效率
-规约扮演“防火墙”角色
- 解耦,不需了解具体实现
规约:
-输入输出的数据类型
-功能和正确性
-性能
-只讲“能做什么”,不讲“怎么实现”
(3)行为等价性
行为等价性:是否可以相互替换 (站在客户端视角看行为等价性).
根据规约判断是否行为等价---若两个方法符合一个规约,则它们等价,和方法内部实现过程没有关系
(4)规约结构
规约结构:
-前置条件:对于客户端的约束,在使用方法时必须满足的条件
-后置条件:对开发者的约束,方法结束时必须满足的条件
ps:如果前置条件满足了,后置条件必须满足
前置条件不满足,则方法可做任何事情
#变化方法(改变了参数的方法就叫变化的方法)中的规约
除非在后置条件里声明过,否则方法内部不应该改变输入
应尽量遵循此规则,尽量不设计mutating的方法
————这是程序员之间应达成的默契
#可变的方法使简单的规约变得复杂
程序中可能有很多变量指向同一个可变对象
开发者和客户端之间的契约无法得到保证
所以不能只靠程序员所谓的“道德”,要靠严格的“契约”来规定
#可变的方法对程序的安全性造成了巨大破坏
所以避免使用可变的全局变量
但是为了性能原因,有时候却不得不用
#可变的方法降低了可变性
可变的数据类型导致程序的修改变得异常困难
(5)测试和验证规约
规约是文字说明的,在方法前面进行说明
#有方法的作用
#有参数的数据类型和值规定和意义
#有返回值的意义和返回的数据类型
正式检验
#用数学方法来证明的它的正确性
#正式证明一个实现的所有可能的参数是否符合规约
#手动努力;部分自动化;不能完全自动确定
······“测试表示存在这种可能,不代表没有bug”
测试
在受控环境下测试执行程序
-显示错误,因此可以修复(主要目标)
-评估质量
-明确说明书,文件
黑盒测试
以独立的实现方式检车测试的程序是否遵循指定的
3.2.3设计规约
(1)规约的分类
怎么比较规约的好坏:
-规约的正确性
-规约的陈述的流畅易懂性
-规约的强度
确定和欠定(未确定)的规约比较
确定的规约:给定一个满足前置条件的输入,其输出是唯一的、明确的。
欠定(under-deterministic)的规约:同一个输入可以有多个输出
非确定(Non-deterministic)的规约:同一个输入,多次执行时得到的输出可能不同
-为了避免混乱,not d == under d
-欠定的规约通常有确定的实现
声明式规约和操作式规约
操作式规约:例如伪代码
声明式规约:没有内部实现的描述,只有“初-终”状态
#声明式规约更有价值
#为什么有操作式规约?
内部实现的细节不在规约里呈现,放在代码实现体内部注释里呈现。
规约的强弱
如何比较两个规约,以判断是否可以用一个规约替换另一个?
规约的 强度S2 >=S1
条件:
-S2的前置条件比S1弱 (S2的容错能力比S1好,给了用户更宽广的使用范围)
-S2的后置条件比S1强
#满足以上条件时就可以用S2代替S1
(2)图表式规约
某个具体实现,若满足规约,则落在其范围内;否则,在其之外。
#程序员可以在规约的范围内自由选择实现方式
#客户端无需了解具体使用了哪个实现
(3)设计良好的规约
规约的质量怎么判定:
#规约应该是内聚的——规约Spec描述的功能应单一、简单、易理解
#规约对返回值的描述应该是完整(informative)的——不能让客户端产生理解的歧义
#规约应该足够强大和完整
#规约也应该简单易懂——太强的Spec,在很多特殊情况下难以达到
#规约里应该用抽象数据类型—— 在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度
#是否应该使用前置条件?在方法正式执行之前,是否要检 查前置条件已被满足?
不写Precondition,就要在代 码内部check;若代价太大, 在规约里加入precondition, 把责
任交给client(衡量标准:检查参数合法性的代 价多大?)