3.2设计规约
1.我们需要理解方法中的前置条件以及后置条件规范,并且可以写出正确的规范
2.什么是先决条件,什么是后置条件,他们各自是什么意思
3.理解欠定规约,非确定规约
4.理解说明性和操作性规约,并且能够写生命性规格
5.了解先决条件,后置条件和规约的强度,并且可以进行相互的比较
6.能够写出,有用的,适当的规范的规约
- 编程语言中的方法/函数
- 说明:为了互相沟通编程
为什么需要有规范
等价行为
规范结构:前置条件和后置条件
测试和验证规范 - 设计规范
分类规范
图表规范
规格的质量 - 总结
二、编程语言中的函数和方法
参数部分
注意在调用方法-static时,参数类型不匹配的检查,这一部分的检查主要是在静态类型检查阶段完成的
返回值
需要注意返回值类型是否匹配,该部分也是在静态类型检查阶段完成
方法:积木
每一个程序都是由很多个小的方法构建起来的
方法可以独立开发,测试和复用
使用方法的客户端,不用了解方法内部如如何工作的,这就叫抽象
一个完整的方法包括
第一部分方法的规约说明
第二部分方法的具体实现
一、 说明:为了沟通编程
- 编程文档
假设的记录 - 变量的数据类型定义(Java实际上会在编译时检查这个假设,并保证会这样做在你的程序中没有任何地方可以违背这个假设。)
- Final关键字定义了设计决策不可以更改(java也会静态检测的)
- 代码本身就有着你的设计决策,但远远不止
2.为了交流编程
1.我们为什么一定要写出假设?因为第一我们自己有时候记不住,第二别人看了看不懂
2.编程的时候脑海里要有两个目标;要与电脑交流,代码中蕴含着设计决策,要给编译器读;要与他人交流,注释的设计决策
3.规约和合约的方法
规约有什么作用呢?
没有规约,就没法写程序,就算你写出来了你也不知道对错啊
规约是一个可以保证程序与客户端达成一致的一种契约
规约给供需双方都确定了责任,在调用的时候双方都需要遵守
为什么要有规约呢?
实际情况,有很多bug来自于双方之间的误解;如果需求方不把需求写出来,可能和开发者的理解就不同;没有规约就难易定位错误,你连错哪都不知道
有啥好处?精确地规约,有助于区分双方的责任;客户端无需阅读调用函数的代码只用理解说明就完事了
规约可以隔离变化,无需通知客户端;规约还可以提升代码的效率,规约扮演者防火墙的作用:它使得客户不会受到工作细节的影响,他屏蔽了实现的细节,这防火墙导致解耦,允许代码单元和要更改的客户端代码的独立地,只要变化尊重规范。解耦,不需了解具体实现
用户与对象的协议
输入/输出的数据类型;功能和正确性;性能
只讲“能做什么”,不讲 “怎么实现”
4.行为等价性
为了确定行为等价性,问题是我们是否可以用一种实现替换另一种实现吗
这两个函数是否等价?
行为不同,但对用户来说“是否等价”?
得出结论我们需要站在客户端视角看行为等价性!
与此同时可以根据规约 判断是否行为等价
如果两个函数复合规约,那么他们就等价
结论:单纯的看实现代码,并 不足以判定不同的 implmentation是否是 “行为等价的,需要根据代码的spec (开发者与client之间 形成的contract)判定 行为等价性,在编写代码之前,需要 弄清楚spec如何协商形 成、如何撰写
5.规范结构:前置条件和后置条件
前置条件:对客户端的约束,在使用方法时必须满足的条件
后置条件:对开发者的约束,方法结束时必须满足的条件
契约:如果前置条件满足了,后置条件必须满足
前置条件满足,则后置条件必须满足。
整体结构是符合逻辑的
暗示:如果前提条件成立
方法被调用,然后是后置条件方法完成时必须保持。
前置条件不满足,则方法可做任何事情。
– “你违约在先,我自然不遵守承诺”整体结构是符合逻辑的
暗示:如果前提条件成立
方法被调用,然后是后置条件
方法完成时必须保持。
不受后置条件的约束。
它是自由的,做任何事,包括不终止,抛出异常,返回任意的结果,任意的修改等。
Java规范
静态类型声明是一种规约,可据此进行 静态类型检查static checking。
方法前的注释也是一种规约,但需人工判定其是否满足
把先决条件扔进@param然后把后置条件放入@return 和 @throws.
参数由子句@param描述, 结果为由@return 和@throws.子句描述。
方法的规范可以讨论参数和返回
方法的值,但它不应该谈论的局部变量
方法的类的方法或私有字段。
-你应该考虑实现对规范的读者是不可见的。
-在Java中,该方法的源代码往往是不可用的
您的规范,因为Javadoc工具从您的
编码并将它们呈现为HTML。
注意点:
1…除非在后置条件里声明过,否则方法内部不应该改变输入参数
2. 应尽量遵循此规则,尽量不设计 mutating的spec,否则就容易引发bugs。
3. 程序员之间应达成的默契:除非spec必须如此,否则不应修改输入参数、
4. 尽量避免使用mutable的对象
为啥要避免mutable呢?
程序中可能有很多变量指向同一个可变对象(别名)
无法强迫类的实现体和客户端不保存可变变量 的“别名”
涉及可变对象的契约现在依赖于好的方面
每个具有对可变对象的引用的人的行为。
不能靠程序员的“道德”,要靠严格的“契约”
如何对规约进行测试
白盒测试用不着规约
黑盒测试需要规约,根据输入输出来进行测试
写测试用例的时候不要违背规约
如何设计规约
- 规约的分类
规约的确定性
规约的陈述性
规约的强度
使用规约的强度来判断规约的好坏
那么什么是规约的强弱呢?
现有两个规约s1,s2,假设s2规约强于s1
如果S2要强,就意味着可以直接拿来满足弱的规约s1
规约限制了输入的参数是什么样的,返回的值是什么样的
通过比较两者的前置条件与后置条件来判断
前置条件弱的输入限制变少,前置条件弱的规约强度高
后置条件强的输出限制变多,后置条件强的规约强度高
越强的规约,意味着implementor的自由度和责任越重,而client的 责任越轻。
强的规约意味着输入更多,返回信息更加明确
写程序时更加难写,实现变少
对于用户可以输入更多的信息,可以使用的用户变多了
程序员可以在规约的范围内自由选择实现方式
客户端无需了解具体使用了哪个实现
强的规约圈圈小
弱的规约圈圈大
如何判断你的规则写的好呢?
一个好的“方法”设计,并不是你的代码写的多么好,而是你对该方
法的spec设计得如何。
– 一方面:client用着舒服
– 另一方面:开发者编着舒服
考试是给出规约让你进行分析
分析点一,一个好的规约应该是内聚的
Spec描述的功能应单一、简单、易理解
分析点二,信息丰富
不能让客户端产生理解的歧义
分析点三,规约应该足够强大
太弱的spec,client不放心、不敢用 (因为没有给出足够的承诺)。
开发者应尽可能考虑各种特殊情况,在post-condition给出处理措施。
分析点四,规约也别太强了
太强的spec,在很多特殊情况下难以达到,给开发者增加了实现的难 度(client当然非常高兴)。
分析点五,在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度别给的太具体
分析点六,前置条件和后置条件:是否应该使用前置条件?在方法正式执行之前,是否要检 查前置条件已被满足?
衡量标准:检查参数合法性的代 价多大?
归纳:是否使用前置条件取决于(1) check的代价;(2) 方法的使用范围 – 如果只在类的内部使用该方法(private),那么可以不使用前置条件,在使用 该方法的各个位置进行check——责任交给内部client; – 如果在其他地方使用该方法(public),那么必须要使用前置条件,若client端 不满足则方法抛出异常。