软件构造第三章2

第3章:抽象数据类型(ADT)和面向对象编程(OOP)3.2设计规范
3.2设计规范
大纲
1.编程语言中的功能/方法2.规范:通信编程为什么需要规范行为等效规范结构:前置条件和后置条件3.设计规范分类规范图表规范规范质量4总结
3.2设计规范
本讲座的目的
理解方法规范中的前提条件和后置条件,并能够编写正确的规范有哪些先决条件和后置条件,以及它们对于方法的实现者和客户端意味着什么。 理解未确定的规范,并能够识别和评估非确定性理解声明性与操作性规范,并能够编写声明性规范理解前提条件,后置条件和规范的强度;并且能够比较规格强度能够编写适当强度的连贯,有用的规范
软件构建
1编程语言中的函数和方法
3.2设计规范
方法
public static void threeLines(){
报表;
}
public static void main(String [] arguments){
System.out.println(“第1行”);
threeLines();
System.out.println(“第2行”);
}
3.2设计规范
参数
[…] NAME(类型名称,类型名称){
声明
}
致电:
NAME(arg1,arg2);
注意:调用方法时参数类型不匹配 - 静态检查
3.2设计规范
返回值
public static TYPE NAME(){
报表;
return EXPRESSION;
}
void表示“没有类型”
3.2设计规范
可变范围
class SquareChange {
public static void printSquare(int x){
System.out.println(“printSquare x =”+ x);
x = x * x;
System.out.println(“printSquare x =”+ x);
}
public static void main(String [] arguments){
int x = 5;
System.out.println(“main x =”+ x);
printSquare(X);
System.out.println(“main x =”+ x);
}
}
3.2设计规范
方法:构建块
大型程序是用小方法构建的方法可以单独开发,测试和重用方法的用户不需要知道它是如何工作的 - 这被称为“抽象”一个完整的方法
公共课Hailstone {/ ** 计算一个冰雹序列。 * @param n序列的起始编号。假设n> 0. * @return hailstone序列以n开头并以1结尾。 / public static List hailstoneSequence(int n){List list = new ArrayList (); while(n!= 1){list.add(n); if(n%2 == 0){n = n / 2; } else {n = 3 * n + 1; list.add(n);返回清单; }}
软件构建
2规范:通信编程
软件构建
(1)编程中的记录
3.2设计规范
Java API文档:一个例子
3.2设计规范
Java API文档:一个例子
3.2设计规范
Java API文档:一个例子
类层次结构和已实现接口的列表。 直接子类,以及实现类的接口。 类的描述构造函数摘要方法摘要列出了我们可以调用的所有方法每个方法和构造函数的详细描述 - 方法签名:我们看到返回类型,方法名称和参数。我们也看到例外。目前,这些通常意味着该方法可能遇到的错误。 - 完整描述。 - 参数:方法参数的描述。 - 以及该方法返回的描述。
3.2设计规范
记录假设
写下变量的类型会记录它的假设:例如,这个变量总是指一个整数。 - Java实际上在编译时检查了这个假设,并保证程序中没有你违反这个假设的地方。 声明变量final也是一种文档形式,声称变量在初始赋值后永远不会改变。 - Java也会静态地检查它。
3.2设计规范
通信编程
为什么我们需要写下我们的假设? - 因为编程充满了它们,如果我们不写下来,我们将不会记住它们,而其他需要阅读或更改我们的程序的人将不会知道它们。他们必须猜测。
编写程序时必须考虑两个目标: - 与计算机通信。首先说服编译器你的程序是合理的 - 语法正确和类型正确。然后获得正确的逻辑,以便它在运行时提供正确的结果。 - 与其他人沟通。使程序易于理解,以便当有人必须修复它,改进它或在将来适应它时,它们可以这样做。
3.2设计规范
黑客与工程
黑客通常以肆无忌惮的乐观态度为标志: - 糟糕:在测试任何代码之前编写大量代码 - 糟糕:保留所有细节,假设你会永远记住它们,而不是在代码中写下它们 - Bad :假设错误将不存在或者很容易找到和修复但软件工程不是黑客攻击。工程师是悲观主义者: - 好:一次写一点,随时测试(第7章中的测试优先编程)。 - 好:记录你的代码所依赖的假设 - 好:保护你的代码免受愚蠢 - 特别是你自己!静态检查有助于此。
软件构建
(2)规范和合同(方法)
3.2设计规范
规格(或称合同)
规格是团队合作的关键。没有规范就不可能委托实施方法的责任。 规范作为合同:实施者负责履行合同,使用该方法的客户可以依赖合同。 - 状态方法和调用者的职责 - 定义实现的正确含义与真正的法律合同一样,规范对双方都提出了要求:当规范有前提条件时,客户也有责任。 - 如果你按照这个时间表向我支付这笔金额… - 我将按照以下详细规范建立一个 - 一些合同有不良后果的补救措施为什么规格?
现实: - 程序中许多最棘手的错误是由于对两段代码之间的接口行为的误解而产生的。 - 尽管每个程序员都考虑到了规范,但并非所有程序员都将其记录下来。 - 因此,团队中的不同程序员会考虑不同的规范。 - 当程序失败时,很难确定错误的位置。 优点: - 代码中的精确规范允许您分配责任(代码片段,而不是人!),并且可以免除您对修复应该去的地方的困惑。 - 规范适用于方法的客户端,因为它们不需要读取代码。
3.2设计规范
规范的一个例子
Java类BigInteger的方法add()
3.2设计规范
规格(合同)
规范适用于方法的实现者,因为它们使实现者可以自由地更改实现而无需告知客户端。 规范也可以使代码更快。 合同充当客户端和实现者之间的防火墙。 - 它保护客户免受设备工作细节的影响。 - 它保护实施者免受设备使用细节的影响。 - 此防火墙导致解耦,允许单元代码和客户端代码独立更改,只要更改符合规范即可。
软件构建
(3)行为对等
3.2设计规范
行为等同
要确定行为等同性,问题是我们是否可以用另一个实现替代另一个实现。
等效的概念在客户的眼中。
3.2设计规范
行为等同
为了能够将一个实现替换为另一个实现,并知道何时可以接受,我们需要一个规范,准确说明客户端所依赖的内容。
注意:规范绝不应该讨论方法的局部变量或方法类的私有字段。
软件构建
(4)规格结构:前置条件和后置条件
3.2设计规范
规格结构
方法的规范由若干子句组成: - 前提条件,由关键字require表示 - 后置条件,由关键字效果指示 - 异常行为:如果违反前提条件它会做什么
前提条件是客户(即方法的调用者)的义务。这是调用该方法的状态。 后置条件是该方法实施者的义务。如果前提条件适用于调用状态,则该方法必须遵循后置条件,返回适当的值,抛出指定的异常,修改或不修改对象等。
3.2设计规范
规格结构
整体结构是一个逻辑含义:如果在调用方法时前置条件成立,则后置条件必须在方法完成时保持。
如果在调用方法时前置条件不成立,则实现不受后置条件的约束。它可以自由地做任何事情,包括不终止,抛出异常,返回任意结果,进行任意修改等。Java规范
Java的静态类型声明实际上是方法的前提条件和后置条件的一部分,方法是由编译器自动检查和强制执行的部分。 合同的其余部分必须在方法之前的注释中描述,并且通常取决于人类检查并保证。 参数由@param子句描述,结果由@return和@throws子句描述。 尽可能将前置条件放入@param,将后置条件放入@return和@throws。
3.2设计规范
Java规范
3.2设计规范
变异方法的规范
示例1:变异方法
示例2:变异方法
示例3:不改变其参数的方法
3.2设计规范
变异方法的规范
如果效果没有明确表示输入可以变异,那么我们假设隐含地禁止输入的变异。 几乎所有程序员都会采取同样的做法。惊喜突变导致可怕的错误。
公约: - 除非另有说明,否则不允许变异。 - 没有输入的突变
可变对象可以使简单的规范/合同变得非常复杂可变对象降低了可变性
3.2设计规范
可变对象降低了可变性
可变对象使客户端和实现者之间的契约变得更加复杂,并降低了客户端和实现者更改的自由度。 - 换句话说,使用允许更改的对象会使代码更难更改。 示例:在数据库中查找用户名并返回用户的9位数标识符的方法
使用此方法打印用户标识符的客户端:
3.2设计规范
可变对象降低了可变性
现在客户和实施者都分别决定进行更改。客户端担心用户的隐私,并决定隐藏ID的前5位数:实施者担心数据库的速度和负载,因此实施者引入了一个缓存,记住已查找的用户名:
会发生什么?
3.2设计规范
可变对象降低了可变性
共享可变对象会使合同复杂化。 谁应该责怪这里? - 客户是否有义务不修改它取回的物品? - 实施者是否有义务不坚持它返回的对象?
澄清规范的可能方法:
这个规格怎么样? - 这是终身合同!
3.2设计规范
可变对象降低了可变性
这个怎么样?
这个规范至少说阵列必须是新鲜的。 但是它是否会使实施者不再拥有该新阵列的别名?它是否会阻止实现者更改该阵列或将来重新使用它?
3.2设计规范
可变对象降低了可变性
这个怎么样?
不可变的String返回值提供了一个保证,即客户端和实现者永远不会以char数组的方式相互踩踏。 它不依赖程序员仔细阅读规范评论。 字符串是不可变的。不仅如此,这种方法(与前一种方法不同)为实施者提供了引入缓存的自由 - 性能改进。(5)测试和验证规范
3.2设计规范
Java建模语言(JML)
这是一种具有优势的理论方法 - 自动生成运行时检查 - 正式验证的基础 - 自动分析工具缺点 - 需要大量工作 - 大型不实用 - 行为的某些方面不适合正式规范
正式合同规范
3.2设计规范
文本规范 - Javadoc
实用方法记录每个参数,返回值,每个异常(已检查和未检查),方法的作用,包括目的,副作用,任何线程安全问题,任何性能问题不记录实现细节
3.2设计规范语义正确性遵守合同
编译器确保类型正确(类型检查) - 防止许多运行时错误,例如“找不到方法”和“无法将布尔添加到int”静态分析工具(例如,FindBugs)识别许多常见问题(错误模式) - - 覆盖equals而不覆盖hashCode第9章重构
3.2设计规范
测试
在受控环境中使用选定的输入执行程序目标 - 显示错误,以便修复它们(主要目标) - 评估质量 - 澄清规范,文档
“注意上面代码中的错误;我只是证明它是正确的,没有尝试过。“ - 唐纳德克努特,1977年
第7章健壮性
3.2设计规范
黑盒测试
黑盒测试:以与实现无关的方式检查测试程序是否遵循指定的规范。 示例规范:
测试用例:
软件构建
3设计规范
软件构建
(1)分类规格
3.2设计规范
比较规格
Howdeterministicit是。规范是否仅为给定输入定义单个可能的输出,还是允许实现者从一组合法输出中进行选择? 如何解决问题。规范是否只是表征输出应该是什么,还是明确说明如何计算输出? 这是Howstrong。规范是否有一小部分合法实现或大型集合?
“什么使某些规格比其他规格更好?”
3.2设计规范确定性与未确定的规范
确定性:当呈现满足前提条件的状态时,结果完全确定。 - 只能有一个返回值和一个最终状态。 - 没有有效输出,其中有多个有效输出。
3.2设计规范确定性与未确定的规范
不确定性:规范允许同一输入的多个有效输出。
不确定性:有时表现为单向,有时表现为另一种方式,即使在相同的程序中使用相同的输入进行调用(例如,取决于随机或时间)​​为了避免混淆,我们将引用不确定的指标作为欠定。 规范中的不确定性提供了实施者在实施时做出的选择。 - 欠定规范通常由完全确定性实现实现。声明与操作规范
操作规范提供了该方法执行的一系列步骤;伪代码描述是可操作的。 声明性规范没有提供中间步骤的详细信息。相反,他们只给出最终结果的属性,以及它与初始状态的关系。 声明性规范更可取。 - 它们通常更短,更容易理解,最重要的是,它们不会无意中暴露客户可能依赖的实现细节。 为何选择运营规范。存在? - 程序员使用spec注释来解释维护者的实现。 - 不要那样做。必要时,在方法体内使用注释,而不是在spec注释中。
3.2设计规范
声明规范。
标准:对代码的客户和维护者来说最清晰。
3.2设计规范
更强劲与更弱的规格
如何比较两个规范的行为,以确定用新规范替换旧规格是否安全? 如果 - S2的前提条件弱于或等于S1,则规范S2强于或等于规范S1 - 对于满足S1的前提条件的状态,S2的后置条件强于或等于S1。然后,满足S2的实现也可用于满足S1,并且在程序中用S2替换S1是安全的。 规则: - 削弱前提条件:减少对客户的要求永远不会让他们感到不安。 - 加强后置条件,这意味着做出更多承诺。
3.2设计规范
更强劲与更弱的规格
原始规格:
更强的规格:
更强大的规范:
3.2设计规范
更强劲与更弱的规格
这两个怎么样?
3.2设计规范
更强劲与更弱的规格
如果S3既不强也不弱于S1,那就是规格。可能重叠(这样存在只满足S1的实现,只满足S3,S1和S3)或者可能是不相交的。 在这两种情况下,S1和S3都是无与伦比的。
软件构建
(2)图表规范
3.2设计规范
图表规范
此空间中的每个点代表一个方法实现。
规范在所有可能实现的空间中定义区域。 给定的实现要么根据规范行事,要么满足前提条件 - 暗示 - 后置条件合约(它在区域内),要么不在(区域外)。
3.2设计规范
图表规范
当S2强于S1时,它在此图中定义了一个较小的区域;较弱的规范定义了一个更大的区域。
软件构建
(3)设计好规格
3.2设计规范
规格的质量
什么是一个好方法?设计方法意味着主要编写规范。 关于规范的形式:它显然应该简洁,清晰,结构合理,因此易于阅读。 但是,规范的内容难以规定。没有绝对可靠的规则,但有一些有用的指导方针。
3.2设计规范
规范应该是连贯的
规范不应该有很多不同的情况。长参数列表,深层嵌套的if语句和布尔标志都是麻烦的迹象。
除了可怕地使用全局变量和打印而不是返回之外,规范并不连贯 - 它做了两件不同的事情,计算单词并找到最长的单词。 如何改进: - 将这两个职责分成两个不同的方法将使它们更简单(易于理解),并在其他环境中更有用(准备好改变)。
3.2设计规范呼叫的结果应该是提供信息的
如果返回null,则无法判断密钥是否先前未绑定,或者是否实际上绑定为null。 这不是一个非常好的设计,因为除非你确定没有插入null,否则返回值是无用的。
3.2设计规范规范应该足够强大
规范应该在一般情况下为客户提供足够强大的保证 - 它需要满足他们的基本要求。在指定特殊情况时,我们必须格外小心,以确保它们不会破坏本来可能有用的方法。 例如,没有必要为一个坏的参数抛出异常,但允许任意突变,因为客户端将无法确定实际发生了什么突变。这是一个说明这个缺陷的规范(也以不恰当的操作方式编写):
如果抛出NullPointerException,客户端可以自行弄清楚list2的哪些元素实际上使它成为list1。规范也应该足够弱
这是一个糟糕的规范。 - 它缺乏重要的细节:文件是否为阅读或写作打开?它是否已经存在或是否已创建? - 它太强大了,因为它无法保证打开文件。它运行的过程可能缺少打开文件的权限,或者文件系统可能存在一些超出程序控制范围的问题。 相反,规范应该说一些更弱的东西:它试图打开一个文件,如果成功,该文件具有某些属性。
3.2设计规范规范应使用抽象类型
像List或Set这样的抽象概念像ArrayList或HashSet这样的特定实现。 使用抽象类型编写我们的规范为客户端和实现者提供了更多的自由。 在Java中,这通常意味着使用接口类型,如Map或Reader,而不是像HashMap或FileReader这样的特定实现类型。
3.2设计规范
前提条件还是后置条件?
是否使用前提条件,如果是,则方法代码是否应该在继续之前尝试确保满足前提条件? 对于程序员: - 前提条件的最常见用途是精确地要求一个属性,因为检查它的方法很难或很昂贵。
如果检查条件会使方法变得慢得令人无法接受,那么通常需要一个前提条件。
3.2设计规范
前提条件还是后置条件?
对于用户:一个非常重要的前提条件会给客户带来不便,因为他们必须确保他们不会在错误的状态下调用该方法(这违反了前提条件);如果他们这样做,没有可预测的方法从错误中恢复。所以方法的用户不喜欢前置条件。 - 因此,Java API类倾向于指定(作为后置条件)它们在参数不合适时抛出未经检查的异常。 - 这使得更容易在调用者代码中找到导致传递错误参数的错误或错误假设。 - 一般来说,最好快速失败,尽可能接近bug的站点,而不是让坏值通过远离其原始原因的程序传播。
软件构建
摘要
3.2设计规范
摘要
规范充当过程实现者与其客户端之间的关键防火墙。 它使单独的开发成为可能:客户端可以自由编写使用该过程的代码而无需查看其源代码,并且实现者可以自由编写实现该过程的代码,而无需知道如何使用它。
3.2设计规范
摘要
安全免受错误 - 良好的规范清楚地记录了客户端和实现者所依赖的相互假设。错误通常来自接口上的分歧,并且规范的存在减少了这一点。 - 在规范中使用机器检查的语言功能,例如静态类型和异常,而不仅仅是人类可读的注释,可以进一步减少错误。 易于理解 - 一个简短的简单规范比实现本身更容易理解,并且使其他人不必阅读代码。 随时准备改变 - 规范在代码的不同部分之间建立合同,允许这些部分独立更改,只要它们继续满足合同要求即可。
3.2设计规范
摘要
声明性规范在实践中最有用。 前提条件(削弱了规范)使客户的生活更加艰难,但明智地应用它们是软件设计人员的重要工具,允许实现者做出必要的假设。
3.2设计规范
摘要
免受错误的影响。 - 如果没有规格,即使对我们计划的任何部分进行的微小改动也可能是倾斜的多米诺骨牌。 - 结构良好,连贯一致的规范可以最大限度地减少误解,并最大限度地利用静态检查,谨慎推理,测试和代码审查来编写正确的代码。 易于理解 - 编写良好的声明性规范意味着客户端不必阅读或理解代码。 为变革做好准备 - 适当的弱规范为实施者提供了自由,适当强大的规范为客户提供了自由。 - 我们甚至可以自己改变规格,而不必重新审视他们所使用的每个地方,只要我们只是加强它们:削弱先决条件和加强后置条件。
软件构建
结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值