Chapter 5:Designing Specification(设计规约)
本章重点:
5.1. Functions & methods in programming languages(程序设计语言中的函数和方法)
介绍了函数和方法的返回值和参数等基础概念。注意静态类型检查阶段会检测参数类型和返回值类型是否匹配。
5.2. Specification: Programming for communication(规约)
1. Documenting in programming
写注释形式的“设计决策”,给自己和别人读。
2. Specification and Contract (of a method)
规约是团队开发的关键,是分配责任的基础。规约是实现者和使用者之间的契约,双方都要遵守。有了规约,客户端就无需阅读调用函数的代码,只需理解spec即可。
例子:
规约提供了这种可能性:实现者可以在保证实现规约的前提下自由修改代码,这种变化无需通知客户端。此外通过在规约中增加对用户输入的限制,让实现者可以省略掉部分正确性检查工作,提高了代码效率。所以可以说,规约扮演了“防火墙”的角色。
在规约中,我们关注以下几点:输入/输出的数据类型、功能和正确性以及性能。我们在规约中只考虑“能做什么”而非“怎样实现”。
3. Behavioral equivalence(行为等价)
判断行为等价性,关键是看一个规格说明实现(implementation)是否能替换另一个。要站在客户端视角看行为等价性。
单纯的看实现代码,并不足以判断不同的implementation是否是行为等价的,需要根据代码的spec(开发者与用户端之间的规约)判定行为等价性。在编写代码之前要=需要弄清楚spec如何协商形成、如何撰写。
4. Specification structure: pre-condition and post-condition(规约结构:前置条件和后置条件)
前置条件:对客户端的约束,在使用方法时必须满足的条件。
后置条件:对开发者的约束,方法结束时必须满足的条件。
如果前置条件满足了,后置条件必须满足。而前置条件不满足,则方法可做任何事情。
前置条件和后置条件中对类型的声明将由编译器进行检查,也就是静态类型检查(static checking);其余部分也在方法前的注释中,例如@param、@return,但需要人工判定其是否满足。
可变方法的规约:除非明确声明,否则默认输入值不可变。尽量避免使用mutable对象。
可变对象使简单的契约变复杂。对于可变对象的多处引用,需要程序维护其一致性。虽然可变对象较为便利性能好,难以确保其正确性。
5. Testing and verifying specifications(测试与验证规约)
黑盒测试:你的测试用例不应该依赖于任何具体的实现。测试用例必须遵守约定,就像其他用户一样。
5.3 Designing specifications(设计规约)
1. Classifying specifications(规约的分类)
规约的确定性:对于确定的输入规约定义了唯一的输出还是多个合法输出?
规约的陈述性:规约只是描述输出还是给出了具体计算输出方法?
规约的强度(重点):规约限制了较少的实现方式还是更多的?
如何判断一个规约能不能替换另一个?
当一个规约S2的强度大于等于S1时,表明此时S2的前置条件更弱,后置条件更强。此时可以用S2替代S1。
几个例子:
第三种情况下两者是不可比较的(容易出题)。
2. Diagramming specifications(图表规约)
每个规约是一个区域,其中每个点是一个实现。某个实现若满足规约,则落在其范围内,否则在其外面。
更强的规约表达为更小的区域。因为加强后置条件意味着对输出要求更高,实现自由更少;弱化前置条件则说明实现时要处理更多的可能输入, 实现的自由度也会更小。
3. Designing good specifications
(1)规约应该是内聚的:
规约描述的功能应单一、简单、易于理解。
(2)规约应该足够强:
要确保通常情况的正确处理,同时也要关注异常情况。对于不合理的参数在抛出异常处理之后,不要允许随意修改,否则用户无法确定发生了什么。
(3)规约也应该足够弱:
太强的规约在很多特殊情况下难以达到,给开发者增加了实现的难度。
(4)规约应该使用抽象类型:
在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度。例如我们可以使用List或Set而不是ArrayList或HashSet。
(5)前置条件还是后置条件?
对于编程者来说,如果内部检查代码正确性代价很大,就应该在规约中加入前置条件,将责任交给用户。
对于用户来说,Java API类倾向于利用后置条件处理:也就是不限定太强的前置条件,而是在参数不合适时抛出异常—输入不合法。
最好可以快速失败(fail fast),避免bug大规模扩散。
采用前置还是后置条件的衡量标准是:检查参数合法性的代价多大。