本章重点:方法的规约,规约的前置条件和后置条件,规约的设计,规约强度的比较
方法的规约
方法是程序的积木,可以被独立地开发、测试、复用。
使用者往往只关心方法的作用和使用方法,而不关心其内部实现,需要将这种抽象的观念将应用在开发过程中。
代码本身就含有对于方法的设计策略,但需要更为显示的记录来帮助别人了解方法的情况,这就是规约。规约是方法实现者与方法调用者之间的约定,说明了双方的责任,要有稳定性。这种方法实现了用户与代码的安全隔离,对于提高代码效率有重要作用。
行为等价性(是否可以相互替换):要在约定的spec下判定,不能根据代码判定。
前置条件与后置条件
1、spec由以下几个部分组成:
precondition(requires):对于输入\客户端的要求
postcondition(effects):对于输出\开发者的要求
Exceptional behavior:对于意外行为抛出的异常
方法签名:方法的参数个数、参数类型、返回值类型、方法名
对于函数功能的一些文字性说明(只讨论方法的参数值、返回值和作用,千万不要加入方法的类的局部变量、私有域或实现细节,否则造成表示泄露,若导致client依赖方法内部具体的实现细节,对于之后程序的修改会带来麻烦)。
2、契约:前置条件满足了,后置条件必须满足;前置条件不满足,方法可以做任何事情。非义务情况,为了使程序的健壮性更好、在前置条件不满足时能更好地定位错误,可以以最快速度在程序内抛出异常来帮助client定位错误原因,更易修正bug。
3、约定俗成的规则:除非后置条件声明过,否则不应该改变输入参数(尽量避免使用mutable类型)。
改变输入参数\使用mutable类型会带来的麻烦:
(1)使spec变得复杂:有别名使用时,代码思路分析起来复杂,spec设计复杂;
(2)降低了程序的可改变性:使得程序的修改很复杂,双方的责任界定会很复杂,应该直接在规约和实现里限定为不可变类型。
4、广义的规约:变量定义时的静态类型声明,可以进行静态检查; 方法前的注释,需要进行人工检查。
规约的设计
1、规约的分类:(依据确定性)欠定规约与非确定规约与确定性规约
(依据陈述性)陈述性规约与操作性规约(后者含有具体的实现步骤暴露了内部内容,更倾向于使用前者)
(依据规约强度)
2、如何选择规约\哪个规约更好\一个规约能否替换另一个:规约强度的比较(考试重点)
前置条件更弱或相等、后置条件更强或相等(在前者的前置条件下比较)
注意:若两者不是同时向上述方向变化,而是有一个相反方向,这种情况下是不可比的。
3、图表形式的规约:用整个矩形表示所有实现,每一个点都是方法的具体实现,规约是点的集合。更强的规约,更小的范围。
4、好的规约要求:开发者与客户都很适应。
(1)内聚性:每个方法的功能应该单一、简单、易理解
(2)信息丰富的:表达信息清楚,不能产生歧义
(3)足够强:尽可能考虑足够多的情况并给出处理措施,保证程序异常后的正确处理
(4)足够弱:太强的spec实现起来不现实,徒增麻烦
(5)使用抽象类型:使用接口,给用户更大的自由度,也不会为开发者带来额外的麻烦
(6)precondition的设定:衡量内部check的代价和方法的适用范围,看某些条件是否要写入precondition。
check代价大时交给precondition;在类的内部使用方法,假设内部调用会满足条件,使用precondition;在其他地方使用,不使用或放松使用precondition,需要在内部check抛出异常。
规则:不限定太强的precondition,在postcondition中抛出输入不合法异常(程序的鲁棒性),尽可能在根源处fail,避免错误扩散且容易定位。