设计规约
一、程序设计语言中的函数和方法
“方法”是程序的“积木”,可以被独立开发、测试、复用
使用“方法”的客户端,无需了解方法内部具体如何工作—“抽象“
二、规约:Programming for communication
1、编程时的记录
为什么要写出“假设”?
不写的话,第一:自己记不住;第二:别人不懂。
代码中蕴含的“设计决策”:给编译器读;注释形式的“设计决策”:给自己和别人读。
2、规约和契约
没规约,没法写程序;即使写出来,也不知道对错
契约是程序与客户端之间达成的一致,Spec给“供需双方”都确定了责任,在调用的时候双方都要遵守。
为什么要写规约?
(1)现实:很多bug来自于双方之间的误解,不写下来,那么不同开发者的理
解就可能不同。没有规约,难以定位错误
(2)优点: 精确的规约,有助于区分责任;客户端无需阅读调用函数的代码,只需理解spec即可
规约的作用:
(1) 规约可以隔离“变化”,无需通知客户端。
(2)规约也可以提高代码效率
(3)规约:扮演“防火墙”角色。这个“防火墙”可以导致解耦,允许独立地更改代码单元和客户端代码,只要改变尊重规范。
规约只讲“能做什么”,不讲“怎么实现”,比如Interface (API)
3、行为等价
根据规约判断是否行为等价,需要站在客户角度看行为等价性,而不是代码角度
4、规约的结构:前置条件和后置条件
前置条件:对客户端的约束,在使用方法时必须满足的条件
后置条件:对开发者的约束,方法结束时必须满足的条件
契约:如果前置条件满足了,后置条件必须满足。
前置条件不满足,则方法可做任何事情。
静态类型声明是一种规约,可据此进行静态类型检查static checking。
方法前的注释也是一种规约,但需人工判定其是否满足
参数由@param子句描述,结果由@return和@throws子句描述。
方法的说明可以讨论方法的参数和返回值,但绝不应该讨论方法的局部变量或方法类的私有字段。
尽量避免使用mutable的对象。否则程序中可能有很多变量指向同一个可变对象(别名),无法强迫类的实现体和客户端不保存可变变量的“别名”,这些都让契约变得更加复杂
三、设计规约
(1)给规约分类
规约可以在这几个方面进行比较:规约的确定性、规约的陈述性、 规约的强度
spec变强:更放松的前置条件+更严格的后置条件
如果规约的强度S2>=S1,那么S2就可以替代S1
例子:
越强的规约,意味着implementor的自由度和责任越重,而client的责任越轻。
(2)图表规约
规范在所有可能的实现空间中定义了一个区域。某个具体实现,若满足规约,则落在其范围内;否则,在其之外。
更强的规约,表达为更小的区域。即:更强的后置条件意味着实现的自由度更低了,在图中的面积更小;更弱的前置条件意味着实现时要处理更多的可能输入,实现的自由度低了,面积更小。
(3)设计规约
一个好的“方法”设计,并不是你的代码写的多么好,而是你对该方法的spec设计得如何。一方面:client用着舒服;另一方面:开发者编着舒服。
好规约的标准:
Spec描述的功能应单一、简单、易理解。
Spec应该够强,太弱的spec,client不放心、不敢用 (因为没有给出足够的承诺)。开发者应尽可能考虑各种特殊情况,在post-condition给出处理措施。
Spec应该也要够弱。太强的spec,在很多特殊情况下难以达到,给开发者增加了实现的难度(client当然非常高兴)。
Spec应该用抽象类型,在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度
是否使用前置条件取决于(1) check的代价;(2) 方法的使用范围
– 如果只在类的内部使用该方法(private),那么可以不使用前置条件,在使用该方法的各个位置进行check——责任交给内部client;
– 如果在其他地方使用该方法(public),那么必须要使用前置条件,若client端不满足则方法抛出异常。