程序设计习惯养成计划---一、方法规约设计

Specification(spec)

1.引言

没有规约的方法都是垃圾(对,我写的都是垃圾。。。。噗玩笑ㄟ( ▔, ▔ )ㄏ)

虽说是玩笑,但事实就是如此,我们自我定义的函数不能只有自己知道他的用途,或者说不能只靠用户通过我们的命名去猜一个方法的参数返回值以及作用。

又如,一个大型程序设计过程中需要多人协作,那么如果在你的.java中的所有函数都是以只有命名的形式存在的话,对于与你协作的同事来说如何理你的方法声明和用途就是一件十分困难的事,也就别提什么调用之类的事情。

因此,我们在程序设计初期就需要规范自己,简明扼要的说就是需要给我们编写的函数划范围,那么如何为我们的函数划范围以至于用户能够理解并使用呢,这也就引出了我们的主题:规约(spec)。

2.规约结构

java程序设计中有一种约定俗称的结构,我们按照某种特定结构编写的spec能很容易的被理解并能自动生成类似jdk文档中的说明部分,例如:
!](https://img-blog.csdnimg.cn/20190318111723313.png)
在这里插入图片描述
怎么样,感受到规约的魅力了么,可以肯定的是你看过的jdk文档中关于方法的说明都是以这种形式自动生成的,怎么样,现在足够引起你的注意了么?

2.1前置条件

规约设计结构之一就是前置条件(Preconditions)

前置条件是针对用户(方法的使用者)而言,规定用户输入的参数的有效范围,规定方法调用必须满足的条件,一旦用户输入不合法,如果没有明确规定处理方法的时候(也在规约里),作为程序开发人员我们可以做任何事。

但是因为我们程序员道德高尚所以一般在方法主体部分做的第一件事就是检查输入合法性,一旦不合法,立即告知用户哪里出错。(建议)

2.2后置条件

规约设计结构之二就是后置条件(Postconditions)

后置条件针对程序设计人员,也就是我们程序员,规定方法调用结束必须满足的条件。例如,返回值的范围etc.

我们规定一旦前置条件满足,后置条件也必须满足!

2.3异常行为

规约设计结构之三就是异常行为

异常行为针对用户的异常行为,一旦用户输入违反了前置条件,异常行为就是规定发生的一些异常的处理方式,是一种对程序设计方式的规定,也是一种约定俗成,一旦与客户的签订协议就要求我们必须遵守约定。当然未说明的我们可以肆意妄为(不建议过火)

2.4具体设计

借一个小模块用于说明
在这里插入图片描述
看上述规约,
@param—>规定参数,一般安置前置条件,
@return—>规定返回值,一般安置后置条件,
@throws—>规定抛出异常,对违反前置条件的部分异常处理的说明。(最后这部分用的不多)
同时除了这些既定格式条件说明之外还可以在上面说明一下函数的作用之类的,毕竟用户需要知道方法的用途,详细一点没坏处

3.规约设计中的注意

3.1spec必须是确定的

这里举一个小例子引出一个行为等价性 (behavioral equilence)的概念,让我们看一下一下两段代码:

static int findFirst(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;
}

通过简单的阅读我们能很容易确定两段代码其实想要实现:查找数组arr中的指定元素val的index
单从代码角度而言,我们说这两段代码或许是不能等价的,因为一旦val并不在数据元素之内,两段代码的返回值前者是数组长度而后者却是-1;
那么如果我这样声明这两段代码的spec:

requires://前置条件
val occurs exactly once in arr
effects://后置条件
returns index i such that arr[i] = val

我们的规约中并没有提到如果查找不到的处理方法,换句话说用户的输入不满足“val in the array”这一前置条件却没有约定如何应对这种异常情况,因而现在比较两段代码会发现其实两段代码在这一个spec的约束下是等效的。

这就要求我们在设计规约时候必须要明确,一个确定的输出能给你程序的体验者带来极大的舒适感。

3.2spec的强度要适中

我们规定一个spec的强度是由前置条件和后置条件共同决定的!对于spec强度的规定,我们有这样一种判定法则:

spec的强度更强 = 更放松的前置条件 + 更严格的后置条件

由于spec的前置条件是对client的约束,那么很容易判断,当后置条件一定时,客户的输入范围更大,输入更宽松对客户的体验来说更好;同样当前置条件一定时,后置条件更明细,更确定时,用户的体验最好,但是这也给程序远的编程带来了很大的挑战性。

太弱的spec,明确性不高,甚至输出不明确;太强的spec,实施难度太大,甚至有可能没法完成。因此这就需要我们程序员在设定spec的时候尽可能的在强度上做一个折中。

3.3spec必须是清晰的

你设定的方法的spec不能模棱两可,不能给你的客户带来歧义,来看下面的例子
在这里插入图片描述
借一张MIT的例子,其中requires指向我们说的前置条件,也就相当于对输入参数的要求,而effects指向我们说的return。

我们仔细看这里的后置条件,站在客户的角度,我们能很轻松的知道,这个函数是为了在map里面添加一对key和value,然后返回给我们一个我们输入key对应的value。

但是,注意!它说没有则返回空,那么好了,如果我们获得了一个null,那么我怎么能够确定这是key之前绑定的value=null还是说我之前并没有向map中添加key呢??

或许你会思考这点模糊会影响什么呢,但是作为开发者你并不知道将来你的函数将会用在哪里,会怎么用,在不同的应用中会出什么样的bug,所以你的spec必须保证清晰!这不仅是对客户负责,也同样是对自己负责。

4.自我约束

我们说spec是对你程序的一些要求和约束,因为我们交付给客户的程序必须是有明确说明的。这里推荐你了解一下接口这一结构的定义
事实上对于客户来说,并不关心你方法的具体实现,你的程序只需要通过黑盒测试就够了,也就是要求我们的程序有正确的输入输出,接口中定义的方法只有spec没有实体,用于前期交付给客户再合适不过了,客户确定需求并签订协议之后spec就没有更改的可能了,从此,我们的程序完全基于spec的设定完成。

同时,虽然我前面说如果用户的输入违反了前置条件但是并没有约定这种异常的处理,在这种情况下我们程序员能对程序做任何事情,但是出于规范我们应该尽可能的避免你程序中的基于参数的操作对客户端造成的影响,例如:清空一个数组。
我们不能遵循“你违约在先,我肆意在后”的思想去设计我们方法的主体,同样我们更应该去采用不可变的对象(immutable)去设计我们的程序,以至于方法内部的实现对于参数主体不会造成很大的影响。

其次,方法功能的详细说明对于我们的程序来说是必须的,函数的功能必须尽可能的详细,以至于操作者(用户)能直接了当地获取信息。

总结

现在,可以为你所有编写过的方法加上spec了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值