一、Functions & methods in programming languages
规约实例:
二、Specification: Programming for communication
1.Documenting in programming
Documenting Assumptions :代码中变量的数据类型定义;
final关键字定义了设计决策:“不可改变”;代码本身就蕴含着你的设计决策,但是远远不够。
代码中蕴含的“设计决策”:给编译器读
注释形式的“设计决策”:给自己和别人读
2.Specification and Contract(of a method)
没规约,没法分派任务,无法写程序;即使写出来,也不知道对错。
规约是
程序与客户端之间达成的一致,
明确双方的责任 ,
定义正确实现的含义。
Spec给“供需双方”都确定了责任,在调用的时候双方都要遵守。
Why specifications?
Reality:
很多bug来自于双方之间的误解;不写下来,那么不同开发者的理解就可能不同;没有规约,难以定位错误。
Advantages:
精确的规约,有助于区分责任;客户端无需阅读被调用函数的代码,只需理解spec即可。
3.Behavioral equivalence
The notion of equivalence is
in the eye of the
client
(站在客户端视角看行为等价性)
.
根据规约判断是否行为等价。
总结:
4.Specification structure: pre-condition and post-condition
前置条件:对客户端的约束,在使用方法时必须满足的条件
后置条件:对开发者的约束,方法结束时必须满足的条件
契约:如果前置条件满足了,后置条件必须满足。前置条件不满足,则方法可做任何事情。
当前置条件被违反时,说明客户端有bug,尽管实现者没有义务提醒,但可通过快速失败使bug更容易被找到和修复。
静态类型声明是一种规约,可据此进行静态类型检查static checking。方法前的注释也是一种规约,但需人工判定其是否满足。
Parameters are described by @param clauses and results are described by @return and @throws clauses.
Put the preconditions into @param where possible, and postconditions into @return and @throws.
Specifications for mutating methods:除非在后置条件里声明过,否则方法内部 不应该改变输入参数。应进料量遵循此规则,尽量不设计
mutating的spec,否则就容易引发bugs。程序员之间应达成的默契:除非spec必须如此,否则不应修改输入参数
。
mutable对象会使规约复杂化。
三、
Designing specifications
1.
Classifying specifications
规约的确定性(描述的输出是否确定);规约的陈述性(只是描述了输出,还是描述了如何计算输出);规约的强度。
spec变强:更放松的前置条件+更严格的后置条件
Rules:
– Weaken the precondition: placing fewer demands on a client will never upset them. 更少的要求
– Strengthen the post-condition, which means making more promises. 更多的承诺
2.
Diagramming specifications
更强的后置条件意味着实现的自由度更低了
➔在图中的面积更小
更弱的前置条件意味着实现时要处理更多的可能输入,实现的自由度低了➔面积更小
3.
Designing good specifications
一个好的“方法”设计,并不是你的代码写的多么好,而是你对该方法的spec设计的如何。
– 一方面:client用着舒服
– 另一方面:开发者编着舒服
Spec描述的功能应单一、简单、易理解;不能让客户端产生理解的歧义。
太弱的spec,client不放心、不敢用 (因为没有给出足够的承诺)。开发者应尽可能考虑各种特殊情况,在post-condition给出处理措施。
太强的spec,在很多特殊情况下难以达到,给开发者增加了实现的难度(client当然非常高兴)。
在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度。
客户端不喜欢太强的precondition,不满足precondition的输入会导致失败。
惯用做法是: 不限定太强的precondition,而是在postcondition中抛出异常:输入不合法。
尽可能在错误的根源处fail,避免其大规模扩散。
▪ 归纳:是否使用前置条件取决于(1) check的代价;(2) 方法的使用范围
– 如果只在类的内部使用该方法(private),那么可以使用前置条件(方法内部不需要判断输入是否满足,认为client会保证前置条件),在使用该方法的各个位置进行check——责任交给内部client;
– 如果在其他地方使用该方法(public),那么可以不使用/放松前置条件(在方法内部检查输入是否满足),若client端不满足则方法抛出异常。