本文系笔者在学习软件构造课程期间所写,不保证通用性和正确性,仅供参考。
目录
一、前言
之前的代码中都不怎么重视注释,有注释也就是随便一两行解释一下下面的代码是干什么的。学习课程之后了解到软件构造中有很多约定好的注释格式,本文就把这些格式进行总结归纳,并在最后用一个例子来体现它们在具体应用中是怎么体现的。
本文所用的语言为课程要求的Java,IDE为IntelliJ IDEA。
二、Spec
Spec的全称为(Speculation) Specification,即代码规约,一般用在方法前,注明方法的各类信息,也是方法向用户提供信息的唯一途径。
2.1 写Spec的好处
- 增加代码可读性
- 对代码编写提出约束,在多人项目中方便合作
- 便于用户使用
- 作为文档注释生成Javadoc
我在spec里要求了你的数据必须以什么约束输入进来,那你要是不守规定出问题了就不关我的事啦x
2.2 Spec内容
Java中的spec如下所示:
/**
* 方法描述
* @param 参数 对参数的介绍和约束
* @return 对返回值的介绍
* @throws 描述方法可能抛出的异常
*/
@xxx 为spec中的属性,常用的即上面所见的三种,除此之外还有@author(注明方法作者)等。一些属性可以有多个,比如多个@param介绍不同的参数等。以@开头是为了让Javadoc识别并生成文档。
2.3 Spec生成
Java中注释分为三类:单行注释(//)、多行注释(/*)、文档注释(/**)。即Java会对/**格式的注释有额外的处理。
在IDEA中,默认只需在一个方法前打出/**并回车就可以自动识别方法的相关内容并生成格式化的spec。如下所示:
/**
int findIndex(String str) throws IllegalArgumentException {}
按下回车后:
/**
*
* @param str
* @return
* @throws IllegalArgumentException
*/
int findIndex(String str) throws IllegalArgumentException {}
值得一提的是,Java的spec支持HTML语法,在描述中插入HTML标志,可以使spec看起来更工整、美观。
2.4 Spec的前置/后置条件、强弱比较
Spec的前置条件是对客户端的约束,即在使用方法时必须满足的条件,比如@param;
而后置条件是对开发者的约束,即方法结束时必须满足的条件,比如@return,@throws。
称两个方法是等价的,如果它们虽然实现方式不同,但是可以应用相同的规约。
Spec也有强弱之分:如果一个spec的前置条件更弱,而后置条件更强,就意味着这个spec更强。
用通俗的话来说,就是这个spec更好用了。例如设一个函数findIndex(String str),它的功能是找到字符'a'在str中的位置:
原来的spec可能是:
@param str中必须含有'a'
@return 所有'a'中任意一个的位置
更强的spec可以是:
@param 任意字符串
@return 第一个'a'的位置,如果没有'a',返回-1
可见后者完全可以承担前者的工作,但它又可以实现一些其他的功能,所以说它更强。
还有一些情况不能比较两个spec的强弱,比如一个spec前置条件和后置条件都更弱/更强,或者二者的条件不完全一致(一个返回int,而另一个返回int[])。
2.5 关于Spec中的约束
我在spec里要求了你的数据必须以什么约束输入进来,那你要是不守规定出问题了就不关我的事啦x
确实是这样。但是结合2.4节就可以看到,约束过多的spec相应的就更弱,这样的spec对用户来说就更不好用了。所以虽然可以任意做出约束,但还是需要尽可能地提高spec的强度,以提高用户体验,提高方法竞争力。
还有一种情况,是出于程序自身的安全性考虑的。虽然约束代表你可以不用考虑约束之外的情况,但不代表用户就一定会乖乖照着约束来。如果约束之外的情况会导致程序本身内部泄漏等不安全的行为,这就不是在spec里可以禁止得了的了,而必须认真修复漏洞。
三、Abstract Function
接下来的三个注释都是以非文档注释形式进行的,也就是说这些是方法内部的注释,仅供研究源码使用。
Abstract Function,抽象函数,是一个由方法的内部域到外部表示的映射,它展示了方法内部的参数代表着什么。
3.1 Abstract Function内容
Abstraction Function可以写为如下形式:
// AF(某域) = 其表示的抽象实例
一般建议对类的所有内部域都写出它的AF(),这样可以对数据表示有一个充分的掌握。
作为一个映射,Abstraction Function只能保证是满射。比如说一个字符串,内部实现可能是char[],那么AF()就表示某个状态下的char[]对应一个什么样的字符串。每个可表示的字符串都会有一个对应的char[];不同的char[]可以对应同一个String;也可以有无法对应一个字符串的char[]。由此可见AF()是满射,但未必是单射。
四、Rep Invariant
Rep Invariant,全写为Representation Invariant,表示不变量。
上文说到AF()是由方法的内部域到外部表示的映射,而Rep Invariant则是表明怎样的内部域可以进行这样的映射。
RI()是一个从内部实现到boolean的映射,合法的值映射为true,反之为false。
4.1 Rep Invariant内容
Rep Invariant可以写为如下形式:
// Rep Invariant:
// str.length() > 0;
值得注意的是,如果某个域完全没有约束要求,那么RI应该填true。
4.2 checkRep()
虽然注释是这么写了,可怎么就能保证这些域就一定会在约束内呢?这就需要在类的内部具体实现一个函数:checkRep()。
checkRep()其实就是RI的实现,一般会放在类中每个方法的末尾,检查经过这么一个方法的操作之后,各变量是否还满足RI()。由于是测试用的函数,一般是用assert检测或者在不合规时抛出异常,而不是真的返回一个boolean。
五、Safety from Exposure
在2.5节中提到了内部泄漏的问题。这当然也是代码编写中的一个重要话题,所以就有了Safety from Exposure,自证清白的一种注释。
Safety from Exposure主要是阐明你做了什么工作来避免出现内部泄漏。
5.1 Safety from Exposure内容
Safety from Exposure可以写为如下形式:
// Safety from Exposure:
// All fields are private and ..., the methods...
一般会把上面的三种注释写在一起,放在内部域之下,所有方法之上,如下所示:
// Abstract Function:
// AF(xxx) = ...
// Rep Invariant:
// true
// Safety from Exposure:
// ...
5.2 避免泄露的tips
一些可能经常会写在Safety from Exposure里的,避免泄漏的基础的技巧:
- 将所有内部域设为private,尽可能设为final;
- 尝试选择不可变类型,如String而非StringBuilder;
- 方法返回可变的内部值时,考虑会不会把其地址泄漏出去:比如内部有个ArrayList list,则return new ArrayList (list)而非直接return list。
- 直接将整个类写为不可变的。举例而言,实现Pair<String, Integer>中改变Integer的方法,并非直接改变Integer的值而是构造出一个新的Pair而遗弃原来的Pair。这种思想需要考虑实际情况。
六、Testing Strategy
Java中有完备的JUnit用来对程序进行测试,那么为了提高测试代码的可读性,也需要在测试文件中写必要的注释,这就是Testing Strategy,测试策略。
6.1 Testing Strategy内容
Testing Strategy可写为如下形式:
// Testing Strategy:
// Method1():
// ......
// Method2():
// ......
一般写在测试方法之前。
6.2 进行测试的tips
测试要讲究策略,以尽可能发现程序中所有潜在的bug。下面是一些基础的测试策略,可以写在Testing Strategy里:
- 对所有输出进行测试,不仅包括return,还有异常的抛出是否符合预期。
- 考虑所有可能的输入情况,比如null、非法输入(要求int却输了char)等,并将各个参数的这些可能情况进行排列组合,穷尽可能。
- 考虑极限值。常见的极限值有0、1、大数(甚至数据溢出)等情况。
- 用到测试覆盖度的插件,观察已有的测试是否覆盖了程序中的所有代码(当然也不是必须全部覆盖)
七、结语
上述的五种注释便是常见的程序内注释了。当然注释可以很随意,比如在代码之间插入的解释注释,也是必要且未尝不可的;但加上这些约定俗成的注释,更有利于团队协作的效率,也很大程度避免了“跨月如读天书”的情况。不过,所有理论最后都是要根据实际情况进行调整的。实用主义,而不能太死板。
标签:演示,Spec,注释,实例,Safety,Invariant,spec,Exposure From: https://www.cnblogs.com/Senolytics/p/18120214