Mrwind 2019-06-27
1.编程语言中的功能/方法
2.规约:便于交流的编程,为什么需要规约
行为等同规约结构:前提条件和后条件测试和验证规约
3.设计规约分类规约图表规约质量规约
4.总结
方法:构建模块
大型项目由小型方法构建
方法可以单独开发,测试和重复使用
方法的用户不需要知道它是如何工作的 - 这被称为“抽象”
注意:调用方法时参数类型不匹配 - 静态检查
返回值类型是否匹配,也在静态类型检查阶段完成
(1)编程中的文档
Java API文档:一个例子
类层次结构和实现的接口列表。
直接子类,并为接口实现类。
类的描述
构建思想
方法摘要列出了我们可以调用的所有方法
每个方法和构造函数的详细描述
记录假设
向下写入变量的类型记录了一个关于它的假设:例如,此变量将始终引用一个整数。
声明一个变量final也是一种形式的文档,声明该变量在初始赋值后永远不会改变。
如何函数/方法的假设?
便于交流的编程
为什么我们需要写下我们的假设?
程序必须记住两个目标:
黑客与工程
黑客往往以肆无忌惮的乐观为标志
但软件工程不是黑客行为。 工程师是悲观主义者:
静态检查有助于此
(2)规约和契约(方法)
规约(或称为契约)
规约是团队合作的关键。 没有规约就不可能委托实施方法的责任。
规约作为一种契约:实施者负责满足契约,而使用该方法的客户可以依赖契约。
规约对双方都有要求:当规约有先决条件时,客户也有责任。
为什么规约?
现实:
优点:
规约对于方法的实现者来说是很好的,因为他们给了实现者自由地改变实现而不告诉客户。
规约也可以使码代码更快。
契约充当客户和实施者之间的防火墙。
对象与其用户之间的协议
该方法做了什么,而不是如何做
(3)行为等价性
要确定行为等同性,问题是我们是否可以用另一个实现替代另一个实现
等价的概念在客户眼中。
为了使一个实现替代另一个实现成为可能,并且知道何时可以接受,我们需要一个规约来说明客户端依赖于什么
注意:规约不应该谈论方法类的局部变量或方法类的私有字段。
(4)规约结构:前提条件和后置条件
一个方法的规约由几个子句组成:
先决条件是客户(即方法的调用者)的义务。 这是调用方法的状态。
后置条件是该方法实施者的义务。
如果前提条件适用于调用状态,则该方法必须遵守后置条件,方法是返回适当的值,抛出指定的异常,修改或不修改对象等等。
整体结构是一个合乎逻辑的含义:如果在调用方法时前提条件成立,则在方法完成时必须保持后置条件。
如果在调用方法时前提条件不成立,则实现不受后置条件的限制。
Java中的规约
Java的静态类型声明实际上是方法的前提条件和后置条件的一部分,该方法是编译器自动检查和执行的一部分。
静态检查
契约的其余部分必须在该方法之前的评论中进行描述,并且通常取决于人类对其进行检查并予以保证。
参数由@param子句描述,结果由@return和@throws子句描述。
将前提条件放在@param中,并将后置条件放入@return和@throws。
可变方法的规约
如果效应没有明确说明输入可以被突变,那么我们假设输入的突变是隐式地被禁止的。
几乎所有的程序员都会承担同样的事情。 惊喜突变导致可怕的错误。
惯例:
可变对象可以使简单的规约/合约非常复杂
可变对象降低了可变性
可变对象使简单的合约变得复杂
对同一个可变对象(对象的别名)的多次引用可能意味着程序中的多个地方 - 可能相当分散 - 依靠该对象保持一致。
按照规约说明,契约不能再在一个地方执行,例如, 一个类的客户和一个类的实施者之间。
涉及可变对象的契约现在取决于每个引用可变对象的每个人的良好行为。
作为这种非本地契约现象的一个症状,考虑Java集合类,这些类通常记录在客户端和实现者之间的非常明确的契约中。
对这样的全局属性进行推理的需要使得理解难度更大,并且对可变数据结构的程序的正确性有信心。
我们仍然必须这样做 - 为了性能和便利性 - 但是为了这样做,我们在bug安全方面付出了巨大的代价。
可变对象降低了可变性
可变对象使得客户端和实现者之间的契约更加复杂,并且减少了客户端和实现者改变的自由。
换句话说,使用允许更改的对象会使代码难以改变。
(5)*测试和验证规约
正式契约规约
Java建模语言(JML)
这是一个有优势的理论方法
缺点
文本说明 - Javadoc
实用方法
记录每个参数,返回值,每个异常(选中和未选中),该方法执行的操作,包括目的,副作用,任何线程安全问题,任何性能问题。
不要记录实施细节
语义正确性遵守契约
编译器确保类型正确(静态类型检查)
静态分析工具(如FindBugs)可以识别许多常见问题(错误模式)
但是,如何确保语义的正确性?
正式验证
使用数学方法证明正式规约的正确性
正式证明一个实现的所有可能的执行符合规约
手动努力; 部分自动化; 不能自动确定
测试
使用受控环境中的选定输入执行程序
目标
黑盒测试:以独立于实现的方式检查测试的程序是否遵循指定的规约。
(1)按规约分类
比较规约
它是如何确定性的。 该规约是否仅为给定输入定义了单个可能的输出,或允许实现者从一组合法输出中进行选择?
它是如何声明的。 规约是否只是表征输出的结果,还是明确说明如何计算输出?
它有多强大。 规约是否只有一小部分法律实施或一大套?
“什么使一些规约比其他规约更好?”
如何比较两种规约的行为来决定用新规约替换旧规约是否安全?
规约S2强于或等于规约S1如果
那么满足S2的实现也可以用来满足S1,在程序中用S2代替S1是安全的。
规则:
如果S3既不强于也不弱于S1,则规约可能会重叠(因此存在仅满足S1,仅S3,以及S1和S3的实现)或者可能不相交。
在这两种情况下,S1和S3都是无法比较的。
(2)图表规约
这个空间中的每个点代表一个方法实现。
规约在所有可能的实现的空间中定义了一个区域。
一个给定的实现要么按照规约行事,要满足前置条件 - 隐含 - 后置契约(它在区域内),或者不(在区域外)。
实现者可以自由地在规约中移动,更改代码而不用担心会破坏客户端。
这对于实现者能够提高其算法的性能,代码的清晰度或者在发现错误时改变他们的方法等而言是至关重要的。
客户不知道他们会得到哪些实现。
当S2比S1强时,它在此图中定义了一个较小的区域。
较弱的规约定义了一个更大的区域。
强化实施者的后置条件意味着他们自由度较低,对产出的要求更强。
弱化前提意味着:实现必须处理先前被规约排除的新输入。
(3)设计好的规约
规约的质量
什么是一个好方法? 设计方法意味着主要编写一个规约。
关于规约的形式:它显然应该简洁,清晰,结构良好,以便阅读。
然而,规约的内容很难规定。 没有一个可靠的规则,但有一些有用的指导方针。
规约应该是连贯的(内聚的)
该规约不应该有很多不同的情况。 冗长的参数列表,深层嵌套的if语句和布尔型标志都是麻烦的迹象。
除了可怕地使用全局变量和打印而不是返回之外,规约不是一致的 - 它执行两个不同的事情,计算单词并找出最长的单词。
调用的结果应该是信息丰富的
如果返回null,则无法确定密钥是否先前未绑定,或者实际上是否绑定为null。这不是一个很好的设计,因为返回值是无用的,除非您确定没有插入null。
规约应该足够强大
规约应给予客户在一般情况下足够强大的保证 - 它需要满足其基本要求。 - 在规定特殊情况时,我们必须格外小心,确保它们不会破坏本来是有用的方法。例如,对于一个不合理的论证抛出异常,但允许任意的突变是没有意义的,因为客户端将无法确定实际发生了什么样的突变。
规约也应该足够薄弱
这是一个不好的规约。
规约应该使用抽象类型
用抽象类型编写我们的规约为客户和实现者提供了更多的自由。
在Java中,这通常意味着使用接口类型,如Map或Reader,而不是像HashMap或FileReader这样的特定实现类型。
这强制客户端传入一个ArrayList,并强制实现返回一个ArrayList,即使可能存在他们希望使用的替代List实现。
先决条件还是后置条件?
是否使用前提条件,如果是,则在继续之前,方法代码是否应该尝试确保先决条件已满足?
对于程序员:
如果检查一个条件会使方法变得难以接受,那么通常需要一个先决条件。
对用户而言:
所以方法的用户不喜欢先决条件。
关键因素是检查的费用(编写和执行代码)以及方法的范围。
如果只在类本地调用,则可以通过仔细检查调用该方法的所有类来解决前提条件。
如果该方法是公开的,并且被其他开发人员使用,那么使用前提条件将不太明智。 相反,像Java API类一样,您应该抛出一个异常。
总结
规约作为程序实现者与其客户之间的关键防火墙。
它使得单独的开发成为可能:客户端可以自由地编写使用该过程的代码,而无需查看其源代码,并且实现者可以自由地编写实现该过程的代码而不知道它将如何使用。
减少错误保证安全
准备好改变
声明性规约在实践中是最有用的。
先决条件(削弱了规约)使客户的生活更加艰难,但明智地应用它们是软件设计师的重要工具,允许实施者做出必要的假设。
减少错误保证安全
容易理解
准备好改变