软件构造--Chapter5总结

设计规约

Functions & methods in programming languages

Java以类为基础,类中包含属性、方法,而方法包含各种参数。检查参数的类型是否匹配、返回值类型是否匹配,是在静态检查中的。
换言之,方法也同样是程序的基础。可以被独立开发、测试、复用使用方法的客户端,无需了解方法内部如何实现,这也就是抽象。
常见的规范的方法的书写如下:
在这里插入图片描述

Specification:Programming for communication

Documenting in programming

在声明变量的时候常常需要书写关于变量的假设,例如变量的类型。这样的假设有两种原因要写:编写者容易记不住;非编写者不容易看懂。
另外,在声明变量的时候以关键字final修饰,这就定义了设计决策为“不可改变”。
编写程序的时候有两个目标:与计算机进行交流,通过在代码中蕴含“设计决策”,提供给编译器信息,实现与计算机交流;与人交流,通过注释展示“设计决策”,实现与人交流。

Specification and Contract( of a method )

规约可以帮助分派协作者的任务、一定程度上了解程序的对错、与客户端达成一致(明确双方责任、定义正确实现的含义)、在调用时双方都应该遵守规约。
规约可以有效消除来自双方误解产生的bug,也有利于定位错误,同时利于客户端按照自己需求调用函数。
以一个add()方法为例,其规约如下:
在这里插入图片描述
在这里插入图片描述
客户端不需要这个方法具体如何实现,只需要根据规约知道如何使用它。
规约更像是一个“防火墙”,客户端不需要知道实现,而实现者不需要知道如何被使用,完成解耦。
在这里插入图片描述
规约具体要做如下描述:
1.输入/输出的数据类型;
2.功能和正确性;
3.性能。
要注意的是,在规约中不能将实现暴露出来。

Behavioral equivalence

判断两个函数是否等价,需要根据规约进行判断。例如下面两个函数及规约:

static int finFirst(int[] arr,int val){
	for(int i=0;i<arr.length;i++){
		if(arr[i]==val)return i;
	}
	return arr.length;
}

static int findLast(int[] arr,int val){
	for(int i=arr.length-1;i>=0;i++){
		if(arr[i]==val)return i;
	}
	return -1;
static int find(int[] arr,int val)
	requires:val occures exactly in arr
	effects:returns index i such that arr[i]=val

尽管在val不在arr数组的情况下两者返回不同,但是它们都符合规约,因此它们是等价的。
单纯的看实现代码不足以判定实现是否“行为等价”,需要根据代码的spec判定,因此在代码之前需要规定spec。

Specification structure:pre-condition and post-condition

一个方法的规约要包含如下部分:
Precondition,以关键字requires修饰;Postcondition,以关键字effects修饰;Exceptional behavior,如果违反前提条件怎么办。
其中,前置条件是对客户端的约束,使用时必须满足;后置条件是对开发者的约束,方法结束时必须满足的条件;契约,如果前置条件满足,后置条件必须满足。而前置条件不满足时,方法可以做任何事。
当前置条件被违反时,说明客户端有bug,可以通过快速失败使bug更容易被找到、修复。
Java中静态类型声明是一种规约,可据此进行静态类型检查。而方法前的注释也是一种规约,但这个需要人工检查。
例如

static int find(int[] arr,int val)
	requires:val occures exactly in arr
	effects:returns index i such that arr[i]=val

静态类型声明的版本如下:

/**
 * Find a value in an arry.
 * @param arr array to search, requires that val occurs exactly once in arr
 * @param val value to search for
 * @return index i such thar arr[i] = val
 * /
static int find(int[] arr,int val)

方法规约可以提及参数和返回值的值,但是不能提及局部变量和私有域。
如果后置条件未声明,则方法内部不应该改变输入参数;要尽量遵守规约,不涉及可变的规约;运用可变对象会使规约复杂化。
尽量要让其“不可变”,在规约中就进行限定。

Testing and verifying specifications

黑盒测试,在程序中方法实现前即可实现。根据程序中方法的规约进行设计测试用例。

Designing specifications

Classifying specifications

判断两个规约的强弱,规约2强于规约1:(规约1和规约2分别简称1和2)
如果2的前置条件不强于1的前置条件(更少的要求);
如果2的后置条件不弱于1的后置条件(更多的承诺)。
更强的规约可以替换较弱的规约。
例如下面三个规约,从上到下强度依次递增:

static int find1(int[] a,int val)
	requires:val occurs exactly once in a
	effects:returns index i such that a[i]=val

static int find2(int[] a,int val)
	requires:val occurs at least once in a
	effects:returns index i such that a[i]=val

static int find3(int[] a,int val)
	requires:val occurs at least once in a
	effects:returns lowest index i such that a[i]=val

越强的规约,意味着实现者自由度越少、责任越重,而客户端责任越轻。

Diagramming specifications

规约确定了一个范围,如果某个具体的实现满足规约,则在其范围内;否则就在范围外。
在这里插入图片描述
程序员可以在规约范围内自由实现函数,而客户端无需了解具体如何实现。
规约越强,则确定的范围就越小,实现的自由度更低。
在这里插入图片描述
当然也会出现规约强度无法比较的情况,二者的规约范围可能有一定交集。

Designing good specifications

良好的方法设计并不在于代码多好,而是针对spec设计实现如何,让开发者和客户端都更加舒服。
1.规约描述的功能应单一、简单、易理解(内聚);
2.规约结果应该是信息丰富的,不让客户端产生起义;
3.规约的强度应该足够高,太弱的规约无法令客户端放心使用;
4.规约的强度应该足够弱,太强的规约令开发者难以实现;
5.规约里使用抽象类型,可以给方法的实现和客户端更大的自由度;
6.前置条件和后置条件的考虑,常常不限定太强的前置条件,在后置条件在抛出异常给不合法输入。
衡量检查的标准是检查参数合法性的代价大小。
是否使用前置条件取决于check的代价、方法使用的范围。如果只在类的内部使用该方法,则可以使用前置条件,在使用该方法的各个位置进行check;如果在其他地方使用该方法,则可以不使用/放松前置条件,若client端不满足则方法抛出异常。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海质粒ABCC9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值