软构:设计规约

设计规约

目  录

第1 编程语言中的函数和方法

第2 规约:为了沟通的编程

第3 设计规约

  1. 编程语言中的函数和方法

Method方法:程序的积木,大程序用许多小的方法构建而成,方法可以被单独开发、测试和重复使用,方法的使用者不需要知道它是如何工作的---这叫做 "抽象"

  1. 规约:为了沟通的编程

2.1在编程中写文档(Documenting)

(1)类的层次结构和实现的接口的列表。

(2)直接的子类和接口,实现的类

(3)类的描述

(4)构造方法摘要

(5)方法摘要列出了我们可以调用的所有方法

(6)每个方法和构造器的详细描述:

method signature:方法签名,我们看到返回类型、方法名称、参数和异常

description:完整描述

returns:方法返回值的描述

(7)编写“假设”:变量类型记录了对它的假设:例如,这个变量总是指代一个整数。实际上,Java在编译时就会检查这个假设,并保证你的程序中没有任何地方违反了这个假设;final关键字定义了设计决策:“不可改变”,Java会在静态类型检查阶段检查它。

为什么要写“假设”:第一:自己记不住;第二:别人不懂。

  1. 编写程序时,必须考虑到两个目标

Communicating with the computer:与计算机进行交流。首先说服编译器你的程序是合理的 - 语法正确,类型正确。然后使逻辑正确,以便在运行时给出正确的结果。

Communicating with other people:与其他人沟通。使程序易于理解,以便将来有人要修复它、改进它或改编它时,他们也可以这样做。

2.2规约(Specification)和契约(Contract)(对于方法而言)

  1. 为什么要写规约:

没规约,没法写程序;

即使写出来,也不知道对错;

规约是程序与客户端之间达成的一致:说明方法和调用者的责任,定义了实现的正确性的含义;

Spec给“供需双方”都确定了责任,在调用的时候双方都要遵守;

为了应对现实情况:很多bug来自于双方之间的误解,不写下来,那么不同开发者的理

解就可能不同,没有规约,难以定位错误。

优势:精确的规约,有助于区分责任,客户端无需阅读调用函数的代码,只需理解spec即可。

  1. 规约的作用:

规约可以隔离“变化”,无需通知客户端;

规约也可以提高代码效率;

规约:扮演“防火墙”角色:它使客户免于了解该单元的工作细节。它使实现者免于了解该单元的使用细节。这种“防火墙”的效果是“解耦”,允许单元的代码和客户端的代码被独立地改变,只要这些改变遵守规约。

2.3行为等价性(Behavioral equivalence)

判断是否等价:

  1. 确定行为等价性,是看我们是否可以用一种实现来替代另一种实现。行为不同,但对用户来说“是否等价”?
  2. 根据规约判断是否行为等价,为了使用一种实现代替另一种实现成为可能,并且知道什么时候可以接受这种替代,我们需要一个规约来准确地说明客户所依赖的东西。

2.4规约的结构:前置条件和后置条件

(1)Precondition:前置条件,关键词requires,对客户端的约束,在使用方法时必须满足的条件。

(2)Postcondition:后置条件,关键词effects,对开发者的约束,方法结束时必须满足的条件。

(3)Exceptional behavior:异常行为,如果违反了前提条件,做什么?

契约:如果前置条件满足了,后置条件必须满足

如果方法被调用时,前置条件不成立,那么实现就不受后置条件的约束,它可以自由地做任何事情,包括不终止、抛出异常、返回任意的结果、进行任意的修改,等等。

当我们的前提条件被违反时,客户端就有一个错误。

我们可以通过快速地失败来使这个错误更容易被发现和修复,尽管我们没有义务这样做。

(4)java中的规约:

静态类型声明是一种规约,可据此进行静态类型检查static checking;

方法前的注释也是一种规约,但需人工判定其是否满足;

参数由@param子句描述,结果由@return和@throws子句描述;

在可能的情况下,把前置条件放到@param中,把后置条件放到@return和@throws中。

  1. 规约可能谈到的内容:

只讨论方法的参数和返回值,不讨论方法的类的局部变量或私有域;

程序的内部实现对规约的读者来说理应是不可见的;

在Java中,方法的源代码对你的规范的读者来说往往是不可用的。Javadoc工具从你的代码中提取规范的注释,并将它们渲染成HTML。

  1. 对于会改变参数(mutable)的方法的规约

除非在后置条件里声明过,否则方法内部不应该改变输入参数;

应尽量遵循此规则,尽量不设计mutating的spec,否则就容易引发bugs;

程序员之间应达成的默契:除非spec必须如此,否则不应修改输入参数;

尽量避免使用mutable的对象,可变对象会让简单的规约变得复杂;

避免使用可变的全局变量!由于需要对这样的全局变量进行推敲,因此更难理解具有可变数据结构的程序,并很难对其正确性充满信心;

可变对象降低了程序的可改变性;

第3节 设计规约

3.1规约分类

  1. 规约的强度:

S2强度≥S1,情况如下:

S2的前置条件弱于或等于S1的

S2的后置条件强于或等于S1的

这样就可以用S2替代S1

spec变强:更放松的前置条件+更严格的后置条件(对他人宽容,对自己严格)

如果S3既不比S1强也不比S1弱,那么这些规约可能会重叠(比如存在只满足S1、只满足S3、同时满足S1和S3的实现),也可能是不相交的。在这些情况中,S1和S3的强度是不可比的。

越强的规约,意味着implementor的自由度和责任越重,而client的责任越轻。

  1. 确定性的与欠定的规格

Deterministic:确定性:当出现满足前提条件的状态时,结果是完全确定的。只有一个返回值和一个最终状态是可能的。没有任何有效的输入会有一个以上的有效输出。

确定的规约:给定一个满足precondition的输入,其输出是唯一的、明确的

Under-deterministic:欠定的规约,同一个输入可以有多个输出,

Nondeterministic:非确定的规约,同一个输入,多次执行时得到的输出可能不同。

为了避免混淆,我们将不确定的规约也称为欠定的。欠定的规约通常有确定的实现。

  1. 声明式和操作式

Operational操作式规约:操作规范给出了方法所执行的一系列步骤;伪代码描述是操作性的。

Declarative声明式规约:没有内部实现的描述,只有“初-终”状态,声明性规范不提供中间步骤的细节。相反,他们只是给出最终结果的属性,以及它与初始状态的关系。

声明式规约更有价值,它们通常更短,更容易理解,而且最重要的是,不会无意中暴露出客户可能依赖的实现细节。

3.2设计好的规约:

  1. 规约应该是内聚的(coherent):Spec描述的功能应单一、简单、易理解
  2. 信息丰富的:但不能让客户端产生理解的歧义
  3. 规约需要足够强:在一般情况下,规约应该给客户足够强大的保证--它需要满足客户的基本要求。太弱的spec,client不放心、不敢用 (因为没有给出足够的承诺)。开发者应尽可能考虑各种特殊情况,在post-condition给出处理措施。
  4. 规约也需要足够弱。
  5. 规约里应该使用抽象类型,在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度。在Java中,这通常意味着使用一个接口类型,如Map或Reader,而不是像HashMap或FileReader这样的具体实现类型。
  6. 前置条件:不写Precondition,就要在代码内部check;若代价太大,在规约里加入precondition,把责任交给client,惯用做法是:不限定太强的precondition,而是在postcondition中抛出异常:输入不合法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值