编程语言中的函数和方法
一个函数或方法大体上有如下结构:
它有着如下的组成要素:
- 方法(函数)名。括 前的 。
- 返回值类型。方法(函数)名前的 。 表示“没有类型”。
- 参数列表。方法(函数)名后面括 中的部分。
- 形参(parameter)。参数列表中用逗 分隔的每个项。形参可以没有,也可以有一个或多个。
- 形参类型。形参的 。
- 形参名称。形参的 。
- 方法(函数)体。方法(函数)中用 包裹起来的部分。
- 返回值(return value)。 后面的表达式 就是返回值。
调用方法(函数),使用如下的语句:
其中 即为方法(函数)名, 为实参。参数类型是否匹配(即实参和形参是否匹配),以及返回值类型是否匹配,都在静态类型检查阶段完成。
一个较大的程序是由许多小的方法(函数)建立起来的。这些方法(函数)可以被独立地开发、测试和复用。用户不需要了解方法(函数)内部具体如何工作,只需要调用方法(函数),这就是一种抽象,或者说信息隐藏。对于方法(函数)来说,它的信息隐藏体现在方法(函数)体不能暴露给用户。用户只能看到如下的部分:
下面给出一个完整的方法的例子:
在这段代码中,方法的规约指的是下面的部分:
而方法的实现体(implementation)指的是:
规约:用于交流
方法(函数)的规约,就是告诉用户,这个方法(函数)是做什么的,以及怎样调用。通过规约,告诉用户方法(函数)有什么功能。
在编程中写文档
我们可以在 https://docs.oracle.com/en/java/javase/index.html 中查看 Java SE 的 API 文档。
把一个变量的类型写下来就记录了对它的一个假设:比如,这个变量总是一个整数。 Java 实际上会在编译时检查这个假设,来保证程序的任何地方都满足这个假设。
把一个变量声明为 也是记录文档的一种形式,它表明这个变量在它的初始赋值后永远都不会改变。Java 同样也会在静态类型检查时检查这一点。
为了交流的编程
我们为什么需要写下来我们的假设呢为在编程的过程中,充满了这些假设,而如果我们不把它们写下来,我们很可能记不住它们;此外,在之后需要阅读或者改变我们程序的其他人也不会知道这些假设,他们就不得不去猜了。说白了,就是:第一,自己记不住;第二,别人不懂。
我们在编写程序时,要在头脑中记住两个目标:
- 与计算机交流。首先要说服编译器你的程序是合理的——语法正确,类型正确。然后使你的程序逻辑正确,这样它就会在运行时给出正确的结果。这是代码中蕴含的“设计决策”,是给编译器看的。
- 和其他人交流。使你的程序易于理解,这样当其他人不得不去修正、改进或者是使它适应未来的需求时,他们有能力做到。这是注释形式的“设计决策”,是给自己和别人看的。
方法的规约和合同
规约(specification,spec)是团队合作的关键。没有规约,就不可能将实现一个方法的任务委派出去。规约就像是一个合同:实现者有责任按照合同的要求来实现方法,使用者也要依照合同的说明使用方法。规约表明了实现者和使用者双方的责任,规定了什么样的实现是正确的。但要注意的是,它只讲“能做什么”,不讲“怎么来做”。
规约对于实现者来说是好的,因为它给了实现者一种自由:可以修改具体的实现内容而不告诉用户。规约也可以提高代码的效率。
规约在实现者和用户之间扮演者“防火墙”的角色:
- 它将用户同实现的具体细节隔离开来。
- 它将实现者同用户的具体使用隔离开来。
- 这个防火墙的作用就是解耦(decoupling),它允许实现方法的代码和使用方法的代码各自独立地改变,只要这些改变满足规约的要求。
行为等价性
行为等价性(behavioral equivalence),是指两个实现之间可以相互替换。行为等价性的概念是站在用户的角度来看的。为了能够用一种实现来替代另一种,我们就需要用到规约了,因为它恰恰规定了用户应当依赖的东西。因此,就要根据规约来判断两个实现是否有行为等价性。如果都符合这个规约,那么就行为等价,否则就不等价。
单纯地看实现代码,并不足以判定不同的实现是否是“行为等价的”。需要根据代码的规约(实现者与用户之间形成的合同)来判定行为等价性。在编写代码之前,需要弄清楚规约如何协商形成、如何撰写。
规约的结构:前置条件和后置条件
一个方法的规约包含以下几个方面:
- 前置条件(precondition)。用关键字 指定。前置条件是对用户的约束,是在使用方法时必须满足的条件。
- 后置条件(postcondition)。用关键字 指定。后置条件是对实现者的约束,是方法结束时必须满足的条件。
- 异常行为(exceptional behavior)。当前置条件没有被满足时会做什么。
如果前置条件被满足了,那么后置条件就必须被满足。但是如果前置条件没有被满足,那么实现就不再受到后置条件的约束,这个方法就可以自由地做任何事情,比如永不终止、抛出异常、返回任意结果、做出任意修改等等。也就是说,“你违约在先,我自然就不遵守承诺”。
但是如果前置条件没有被满足,也建议实现者抛出一个异常。也就是说,前置条件没被满足,用户违约在先,你本可以乱搞事情。但是,这往往不也说明用户那边使用得有点 BUG 嘛,我们可以通过抛出异常这种让程序迅速出错的方法来帮助用户更容易找到 BUG 并修正它%——即使我们不是必须这么做。
Java 中的规约
在 Java 中,静态类型声明是一种规约,编译器可据此进行静态类型检查。方法前的注释也是一种规约,但需要人工判定其是否被满足。
形参用 子句描述,结果用 和 子句描述。一般来说,要尽可能地将前置条件放在 里,而把后置条件放在 和 里。
含可变参数的方法的规约
如果后置条件没有显式地表明输入可以被改变,那么我们就假设传入的参数默认是不可改变的。出乎意料的改变会引发可怕的错误。
程序员之间应当遵守如下的约定:
- 除非被显式表明允许改变输入参数,否则不允许改变输入参数。
- 不要有可变类型的输入。
可变的对象会让简单的规约变得非常复杂:可能有许多引用指向同一个可变对象,这些引用可能分布在程序的各个角落,它们往往依赖于这个对象保持不变的情况。我们也无法强迫实现者和客户不在其他地方保存任何该对象的其他引用(别名)。因此,要尽量避免使用可改变的对象。
包含了可变对象的规约依赖于引用了该对象的所有人的良好表现。但这样依赖于每个人的“道德”,是不靠谱的。所以,关键就在于在规约里限定“不可变”。
测试和验证规约
由于使用者不知道方法内部的实现,因此使用黑盒测试。通过黑盒测试,来检查程序是否以实现独立的方式遵循了规约。
测试用例不应该依赖于任何具体的实现行为。测试用例必须遵守规约,就像其他所有使用者一样。
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91508 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!